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