boot-fx/src/main/java/org/netbeans/html/boot/fx/FXBrwsr.java
author Jaroslav Tulach <jtulach@netbeans.org>
Fri, 02 Oct 2015 08:57:14 +0200
changeset 1006 a0f79e32d526
parent 953 cd7d2aca34be
child 1008 c535c36881af
permissions -rw-r--r--
Ability to run the JavaFX presenter in headless mode is useful for testing
jaroslav@288
     1
/**
jaroslav@358
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jaroslav@288
     3
 *
jaroslav@551
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jaroslav@288
     5
 *
jaroslav@358
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jaroslav@358
     7
 * Other names may be trademarks of their respective owners.
jaroslav@288
     8
 *
jaroslav@358
     9
 * The contents of this file are subject to the terms of either the GNU
jaroslav@358
    10
 * General Public License Version 2 only ("GPL") or the Common
jaroslav@358
    11
 * Development and Distribution License("CDDL") (collectively, the
jaroslav@358
    12
 * "License"). You may not use this file except in compliance with the
jaroslav@358
    13
 * License. You can obtain a copy of the License at
jaroslav@358
    14
 * http://www.netbeans.org/cddl-gplv2.html
jaroslav@358
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jaroslav@358
    16
 * specific language governing permissions and limitations under the
jaroslav@358
    17
 * License.  When distributing the software, include this License Header
jaroslav@358
    18
 * Notice in each file and include the License file at
jaroslav@358
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jaroslav@358
    20
 * particular file as subject to the "Classpath" exception as provided
jaroslav@358
    21
 * by Oracle in the GPL Version 2 section of the License file that
jaroslav@358
    22
 * accompanied this code. If applicable, add the following below the
jaroslav@358
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jaroslav@358
    24
 * your own identifying information:
jaroslav@358
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jaroslav@358
    26
 *
jaroslav@358
    27
 * Contributor(s):
jaroslav@358
    28
 *
jaroslav@358
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jaroslav@551
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jaroslav@358
    31
 *
jaroslav@358
    32
 * If you wish your version of this file to be governed by only the CDDL
jaroslav@358
    33
 * or only the GPL Version 2, indicate your decision by adding
jaroslav@358
    34
 * "[Contributor] elects to include this software in this distribution
jaroslav@358
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jaroslav@358
    36
 * single choice of license, a recipient has the option to distribute
jaroslav@358
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jaroslav@358
    38
 * to extend the choice of license to its licensees as provided above.
jaroslav@358
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jaroslav@358
    40
 * Version 2 license, then the option applies only if the new code is
jaroslav@358
    41
 * made subject to such option by the copyright holder.
jaroslav@288
    42
 */
jaroslav@362
    43
package org.netbeans.html.boot.fx;
jaroslav@288
    44
jaroslav@288
    45
import java.net.URL;
jtulach@731
    46
import java.util.ResourceBundle;
jaroslav@288
    47
import java.util.concurrent.CountDownLatch;
jaroslav@288
    48
import java.util.concurrent.Executors;
jaroslav@318
    49
import java.util.logging.Level;
jaroslav@318
    50
import java.util.logging.Logger;
jtulach@953
    51
import java.util.prefs.Preferences;
jaroslav@288
    52
import javafx.application.Application;
jaroslav@288
    53
import javafx.application.Platform;
jaroslav@288
    54
import javafx.beans.value.ChangeListener;
jaroslav@288
    55
import javafx.beans.value.ObservableValue;
jaroslav@288
    56
import javafx.concurrent.Worker;
jaroslav@288
    57
import javafx.event.ActionEvent;
jaroslav@288
    58
import javafx.event.EventHandler;
jaroslav@288
    59
import javafx.geometry.Insets;
jaroslav@288
    60
import javafx.geometry.Pos;
jtulach@941
    61
import javafx.geometry.Rectangle2D;
jaroslav@288
    62
import javafx.scene.Scene;
jaroslav@288
    63
import javafx.scene.control.Button;
jtulach@733
    64
import javafx.scene.control.TextField;
jaroslav@288
    65
import javafx.scene.layout.BorderPane;
jtulach@732
    66
import javafx.scene.layout.HBox;
jaroslav@288
    67
import javafx.scene.layout.VBox;
jaroslav@288
    68
import javafx.scene.text.Text;
jtulach@733
    69
import javafx.scene.web.PromptData;
jaroslav@288
    70
import javafx.scene.web.WebEvent;
jaroslav@288
    71
import javafx.scene.web.WebView;
jaroslav@288
    72
import javafx.stage.Modality;
jtulach@941
    73
import javafx.stage.Screen;
jaroslav@288
    74
import javafx.stage.Stage;
jtulach@953
    75
import javafx.stage.Window;
jtulach@953
    76
import javafx.stage.WindowEvent;
jtulach@732
    77
import javafx.util.Callback;
jaroslav@288
    78
jaroslav@534
    79
/** This is an implementation class, to implement browser builder API. Just
jaroslav@534
    80
 * include this JAR on classpath and the browser builder API will find
jaroslav@288
    81
 * this implementation automatically.
jaroslav@288
    82
 */
jaroslav@288
    83
public class FXBrwsr extends Application {
jaroslav@318
    84
    private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName());
jaroslav@288
    85
    private static FXBrwsr INSTANCE;
jaroslav@288
    86
    private static final CountDownLatch FINISHED = new CountDownLatch(1);
jaroslav@288
    87
    private BorderPane root;
jaroslav@288
    88
jaroslav@288
    89
    public static synchronized WebView findWebView(final URL url, final FXPresenter onLoad) {
jaroslav@288
    90
        if (INSTANCE == null) {
jtulach@953
    91
            final String callee = findCalleeClassName();
jaroslav@288
    92
            Executors.newFixedThreadPool(1).submit(new Runnable() {
jaroslav@288
    93
                @Override
jaroslav@288
    94
                public void run() {
jaroslav@288
    95
                    try {
jtulach@953
    96
                        FXBrwsr.launch(FXBrwsr.class, callee);
jaroslav@288
    97
                    } catch (Throwable ex) {
jaroslav@288
    98
                        ex.printStackTrace();
jaroslav@288
    99
                    } finally {
jaroslav@288
   100
                        FINISHED.countDown();
jaroslav@288
   101
                    }
jaroslav@288
   102
                }
jaroslav@288
   103
            });
jaroslav@288
   104
        }
jaroslav@288
   105
        while (INSTANCE == null) {
jaroslav@288
   106
            try {
jaroslav@288
   107
                FXBrwsr.class.wait();
jaroslav@288
   108
            } catch (InterruptedException ex) {
jaroslav@288
   109
                // wait more
jaroslav@288
   110
            }
jaroslav@288
   111
        }
jaroslav@288
   112
        if (!Platform.isFxApplicationThread()) {
jaroslav@288
   113
            final WebView[] arr = {null};
jaroslav@288
   114
            final CountDownLatch waitForResult = new CountDownLatch(1);
jaroslav@288
   115
            Platform.runLater(new Runnable() {
jaroslav@288
   116
                @Override
jaroslav@288
   117
                public void run() {
jaroslav@288
   118
                    arr[0] = INSTANCE.newView(url, onLoad);
jaroslav@288
   119
                    waitForResult.countDown();
jaroslav@288
   120
                }
jaroslav@288
   121
            });
jaroslav@288
   122
            for (;;) {
jaroslav@288
   123
                try {
jaroslav@288
   124
                    waitForResult.await();
jaroslav@288
   125
                    break;
jaroslav@288
   126
                } catch (InterruptedException ex) {
jaroslav@318
   127
                    LOG.log(Level.INFO, null, ex);
jaroslav@288
   128
                }
jaroslav@288
   129
            }
jaroslav@288
   130
            return arr[0];
jaroslav@288
   131
        } else {
jaroslav@288
   132
            return INSTANCE.newView(url, onLoad);
jaroslav@288
   133
        }
jaroslav@288
   134
    }
jtulach@952
   135
jtulach@863
   136
    static synchronized Stage findStage() throws InterruptedException {
jtulach@863
   137
        while (INSTANCE == null) {
jtulach@863
   138
            FXBrwsr.class.wait();
jtulach@863
   139
        }
jtulach@863
   140
        return INSTANCE.stage;
jtulach@863
   141
    }
jtulach@952
   142
jtulach@730
   143
    private Stage stage;
jaroslav@288
   144
jaroslav@288
   145
    @Override
jaroslav@288
   146
    public void start(Stage primaryStage) throws Exception {
jtulach@863
   147
        BorderPane r = new BorderPane();
jtulach@953
   148
        Object[] arr = findInitialSize(this.getParameters().getRaw().get(0));
jtulach@953
   149
        Scene scene = new Scene(r, (Double)arr[2], (Double)arr[3]);
jtulach@863
   150
        primaryStage.setScene(scene);
jtulach@863
   151
        this.root = r;
jtulach@863
   152
        this.stage = primaryStage;
jaroslav@288
   153
        synchronized (FXBrwsr.class) {
jaroslav@288
   154
            INSTANCE = this;
jaroslav@288
   155
            FXBrwsr.class.notifyAll();
jaroslav@288
   156
        }
jtulach@953
   157
        primaryStage.setX((Double)arr[0]);
jtulach@953
   158
        primaryStage.setY((Double)arr[1]);
jtulach@953
   159
        if (arr[4] != null) {
jtulach@953
   160
            scene.getWindow().setOnCloseRequest((EventHandler<WindowEvent>) arr[4]);
jtulach@953
   161
        }
jtulach@1006
   162
        if (Boolean.getBoolean("fxpresenter.headless")) {
jtulach@1006
   163
            return;
jtulach@1006
   164
        }
jaroslav@288
   165
        primaryStage.show();
jaroslav@288
   166
    }
jaroslav@288
   167
jtulach@953
   168
    static String findCalleeClassName() {
jtulach@953
   169
        StackTraceElement[] frames = new Exception().getStackTrace();
jtulach@953
   170
        for (StackTraceElement e : frames) {
jtulach@953
   171
            String cn = e.getClassName();
jtulach@953
   172
            if (cn.startsWith("org.netbeans.html.")) { // NOI18N
jtulach@953
   173
                continue;
jtulach@953
   174
            }
jtulach@953
   175
            if (cn.startsWith("net.java.html.")) { // NOI18N
jtulach@953
   176
                continue;
jtulach@953
   177
            }
jtulach@953
   178
            if (cn.startsWith("java.")) { // NOI18N
jtulach@953
   179
                continue;
jtulach@953
   180
            }
jtulach@953
   181
            if (cn.startsWith("javafx.")) { // NOI18N
jtulach@953
   182
                continue;
jtulach@953
   183
            }
jtulach@953
   184
            if (cn.startsWith("com.sun.")) { // NOI18N
jtulach@953
   185
                continue;
jtulach@953
   186
            }
jtulach@953
   187
            return cn;
jtulach@953
   188
        }
jtulach@953
   189
        return "org.netbeans.html"; // NOI18N
jtulach@953
   190
    }
jtulach@953
   191
jtulach@953
   192
    private static Object[] findInitialSize(String callee) {
jtulach@953
   193
        final Preferences prefs = Preferences.userRoot().node(callee.replace('.', '/'));
jtulach@952
   194
        Rectangle2D screen = Screen.getPrimary().getBounds();
jtulach@953
   195
        double x = prefs.getDouble("x", screen.getWidth() * 0.05); // NOI18N
jtulach@953
   196
        double y = prefs.getDouble("y", screen.getHeight() * 0.05); // NOI18N
jtulach@953
   197
        double width = prefs.getDouble("width", screen.getWidth() * 0.9); // NOI18N
jtulach@953
   198
        double height = prefs.getDouble("height", screen.getHeight() * 0.9); // NOI18N
jtulach@953
   199
jtulach@953
   200
        Object[] arr = {
jtulach@953
   201
            x, y, width, height, null
jtulach@953
   202
        };
jtulach@953
   203
jtulach@953
   204
        if (!callee.equals("org.netbeans.html")) { // NOI18N
jtulach@953
   205
            arr[4] = new EventHandler<WindowEvent>() {
jtulach@953
   206
                @Override
jtulach@953
   207
                public void handle(WindowEvent event) {
jtulach@953
   208
                    Window window = (Window) event.getSource();
jtulach@953
   209
                    prefs.putDouble("x", window.getX()); // NOI18N
jtulach@953
   210
                    prefs.putDouble("y", window.getY()); // NOI18N
jtulach@953
   211
                    prefs.putDouble("width", window.getWidth()); // NOI18N
jtulach@953
   212
                    prefs.putDouble("height", window.getHeight()); // NOI18N
jtulach@953
   213
                }
jtulach@953
   214
            };
jtulach@953
   215
        }
jtulach@953
   216
jtulach@953
   217
        return arr;
jtulach@952
   218
    }
jtulach@952
   219
jaroslav@288
   220
    private WebView newView(final URL url, final FXPresenter onLoad) {
jaroslav@288
   221
        final WebView view = new WebView();
jaroslav@288
   222
        view.setContextMenuEnabled(false);
jaroslav@288
   223
        view.getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
jaroslav@288
   224
            @Override
jaroslav@288
   225
            public void handle(WebEvent<String> t) {
jaroslav@288
   226
                final Stage dialogStage = new Stage();
jaroslav@288
   227
                dialogStage.initModality(Modality.WINDOW_MODAL);
jtulach@730
   228
                dialogStage.initOwner(stage);
jtulach@731
   229
                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
jtulach@731
   230
                dialogStage.setTitle(r.getString("AlertTitle")); // NOI18N
jtulach@731
   231
                final Button button = new Button(r.getString("AlertCloseButton")); // NOI18N
jaroslav@288
   232
                final Text text = new Text(t.getData());
jaroslav@288
   233
                VBox box = new VBox();
jaroslav@288
   234
                box.setAlignment(Pos.CENTER);
jaroslav@288
   235
                box.setSpacing(10);
jaroslav@288
   236
                box.setPadding(new Insets(10));
jaroslav@288
   237
                box.getChildren().addAll(text, button);
jaroslav@288
   238
                dialogStage.setScene(new Scene(box));
jaroslav@288
   239
                button.setCancelButton(true);
jtulach@732
   240
                button.setOnAction(new CloseDialogHandler(dialogStage, null));
jaroslav@288
   241
                dialogStage.centerOnScreen();
jaroslav@288
   242
                dialogStage.showAndWait();
jaroslav@288
   243
            }
jaroslav@288
   244
        });
jtulach@732
   245
        view.getEngine().setConfirmHandler(new Callback<String, Boolean>() {
jtulach@732
   246
            @Override
jtulach@732
   247
            public Boolean call(String question) {
jtulach@732
   248
                final Stage dialogStage = new Stage();
jtulach@732
   249
                dialogStage.initModality(Modality.WINDOW_MODAL);
jtulach@732
   250
                dialogStage.initOwner(stage);
jtulach@732
   251
                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
jtulach@732
   252
                dialogStage.setTitle(r.getString("ConfirmTitle")); // NOI18N
jtulach@732
   253
                final Button ok = new Button(r.getString("ConfirmOKButton")); // NOI18N
jtulach@732
   254
                final Button cancel = new Button(r.getString("ConfirmCancelButton")); // NOI18N
jtulach@732
   255
                final Text text = new Text(question);
jtulach@732
   256
                final Insets ins = new Insets(10);
jtulach@732
   257
                final VBox box = new VBox();
jtulach@732
   258
                box.setAlignment(Pos.CENTER);
jtulach@732
   259
                box.setSpacing(10);
jtulach@732
   260
                box.setPadding(ins);
jtulach@741
   261
                final HBox buttons = new HBox(10);
jtulach@741
   262
                buttons.getChildren().addAll(ok, cancel);
jtulach@732
   263
                buttons.setAlignment(Pos.CENTER);
jtulach@732
   264
                buttons.setPadding(ins);
jtulach@732
   265
                box.getChildren().addAll(text, buttons);
jtulach@732
   266
                dialogStage.setScene(new Scene(box));
jtulach@732
   267
                ok.setCancelButton(false);
jtulach@952
   268
jtulach@732
   269
                final boolean[] res = new boolean[1];
jtulach@732
   270
                ok.setOnAction(new CloseDialogHandler(dialogStage, res));
jtulach@732
   271
                cancel.setCancelButton(true);
jtulach@732
   272
                cancel.setOnAction(new CloseDialogHandler(dialogStage, null));
jtulach@732
   273
                dialogStage.centerOnScreen();
jtulach@732
   274
                dialogStage.showAndWait();
jtulach@732
   275
                return res[0];
jtulach@732
   276
            }
jtulach@732
   277
        });
jtulach@733
   278
        view.getEngine().setPromptHandler(new Callback<PromptData, String>() {
jtulach@733
   279
            @Override
jtulach@733
   280
            public String call(PromptData prompt) {
jtulach@733
   281
                final Stage dialogStage = new Stage();
jtulach@733
   282
                dialogStage.initModality(Modality.WINDOW_MODAL);
jtulach@733
   283
                dialogStage.initOwner(stage);
jtulach@733
   284
                ResourceBundle r = ResourceBundle.getBundle("org/netbeans/html/boot/fx/Bundle"); // NOI18N
jtulach@733
   285
                dialogStage.setTitle(r.getString("PromptTitle")); // NOI18N
jtulach@733
   286
                final Button ok = new Button(r.getString("PromptOKButton")); // NOI18N
jtulach@733
   287
                final Button cancel = new Button(r.getString("PromptCancelButton")); // NOI18N
jtulach@733
   288
                final Text text = new Text(prompt.getMessage());
jtulach@733
   289
                final TextField line = new TextField();
jtulach@744
   290
                if (prompt.getDefaultValue() != null) {
jtulach@744
   291
                    line.setText(prompt.getDefaultValue());
jtulach@744
   292
                }
jtulach@733
   293
                final Insets ins = new Insets(10);
jtulach@733
   294
                final VBox box = new VBox();
jtulach@733
   295
                box.setAlignment(Pos.CENTER);
jtulach@733
   296
                box.setSpacing(10);
jtulach@733
   297
                box.setPadding(ins);
jtulach@741
   298
                final HBox buttons = new HBox(10);
jtulach@741
   299
                buttons.getChildren().addAll(ok, cancel);
jtulach@733
   300
                buttons.setAlignment(Pos.CENTER);
jtulach@733
   301
                buttons.setPadding(ins);
jtulach@733
   302
                box.getChildren().addAll(text, line, buttons);
jtulach@733
   303
                dialogStage.setScene(new Scene(box));
jtulach@733
   304
                ok.setCancelButton(false);
jtulach@952
   305
jtulach@733
   306
                final boolean[] res = new boolean[1];
jtulach@733
   307
                ok.setOnAction(new CloseDialogHandler(dialogStage, res));
jtulach@733
   308
                cancel.setCancelButton(true);
jtulach@733
   309
                cancel.setOnAction(new CloseDialogHandler(dialogStage, null));
jtulach@733
   310
                dialogStage.centerOnScreen();
jtulach@733
   311
                dialogStage.showAndWait();
jtulach@733
   312
                return res[0] ? line.getText() : null;
jtulach@733
   313
            }
jtulach@733
   314
        });
jaroslav@288
   315
        root.setCenter(view);
jaroslav@288
   316
        final Worker<Void> w = view.getEngine().getLoadWorker();
jaroslav@288
   317
        w.stateProperty().addListener(new ChangeListener<Worker.State>() {
jtulach@636
   318
            private String previous;
jtulach@952
   319
jaroslav@288
   320
            @Override
jaroslav@288
   321
            public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
jaroslav@288
   322
                if (newState.equals(Worker.State.SUCCEEDED)) {
jtulach@636
   323
                    if (checkValid()) {
jtulach@636
   324
                        FXConsole.register(view.getEngine());
jtulach@636
   325
                        onLoad.onPageLoad();
jtulach@636
   326
                    }
jaroslav@288
   327
                }
jaroslav@288
   328
                if (newState.equals(Worker.State.FAILED)) {
jaroslav@288
   329
                    throw new IllegalStateException("Failed to load " + url);
jaroslav@288
   330
                }
jaroslav@288
   331
            }
jtulach@636
   332
            private boolean checkValid() {
jtulach@636
   333
                final String crnt = view.getEngine().getLocation();
jtulach@636
   334
                if (previous != null && !previous.equals(crnt)) {
jtulach@636
   335
                    w.stateProperty().removeListener(this);
jtulach@636
   336
                    return false;
jtulach@636
   337
                }
jtulach@636
   338
                previous = crnt;
jtulach@636
   339
                return true;
jtulach@636
   340
            }
jtulach@952
   341
jaroslav@288
   342
        });
jtulach@863
   343
        class Title implements ChangeListener<String> {
jtulach@863
   344
jtulach@863
   345
            private String title;
jtulach@863
   346
jtulach@863
   347
            public Title() {
jtulach@863
   348
                super();
jtulach@863
   349
            }
jtulach@863
   350
jtulach@863
   351
            @Override
jtulach@863
   352
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
jtulach@863
   353
                title = view.getEngine().getTitle();
jtulach@863
   354
                if (title != null) {
jtulach@863
   355
                    stage.setTitle(title);
jtulach@863
   356
                }
jtulach@863
   357
            }
jtulach@863
   358
        }
jtulach@863
   359
        final Title x = new Title();
jtulach@863
   360
        view.getEngine().titleProperty().addListener(x);
jtulach@863
   361
        x.changed(null, null, null);
jaroslav@288
   362
        return view;
jaroslav@288
   363
    }
jaroslav@288
   364
jaroslav@288
   365
    static void waitFinished() {
jaroslav@288
   366
        for (;;) {
jaroslav@288
   367
            try {
jaroslav@288
   368
                FINISHED.await();
jaroslav@288
   369
                break;
jaroslav@288
   370
            } catch (InterruptedException ex) {
jaroslav@318
   371
                LOG.log(Level.INFO, null, ex);
jaroslav@288
   372
            }
jaroslav@288
   373
        }
jaroslav@288
   374
    }
jtulach@952
   375
jtulach@732
   376
    private static final class CloseDialogHandler implements EventHandler<ActionEvent> {
jtulach@732
   377
        private final Stage dialogStage;
jtulach@732
   378
        private final boolean[] res;
jtulach@732
   379
jtulach@732
   380
        public CloseDialogHandler(Stage dialogStage, boolean[] res) {
jtulach@732
   381
            this.dialogStage = dialogStage;
jtulach@732
   382
            this.res = res;
jtulach@732
   383
        }
jtulach@732
   384
jtulach@732
   385
        @Override
jtulach@732
   386
        public void handle(ActionEvent t) {
jtulach@732
   387
            dialogStage.close();
jtulach@732
   388
            if (res != null) {
jtulach@732
   389
                res[0] = true;
jtulach@732
   390
            }
jtulach@732
   391
        }
jtulach@732
   392
    }
jtulach@952
   393
jaroslav@288
   394
}