#255831: BrowserBuilder.showAndWait can be used multiple times with JavaFX presenter. Demonstrated by running the boot-fx tests in 'fork once' mode.
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
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]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
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.
43 package org.netbeans.html.boot.fx;
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;
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.
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;
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() {
95 if (!Platform.isFxApplicationThread()) {
97 Platform.runLater(this);
98 } catch (IllegalStateException ex) {
100 FXBrwsr.launch(FXBrwsr.class, callee);
101 } catch (Throwable t) {
104 FINISHED.countDown();
108 FXBrwsr brwsr = new FXBrwsr();
109 brwsr.start(new Stage(), callee);
111 FINISHED.countDown();
116 while (INSTANCE == null) {
118 FXBrwsr.class.wait();
119 } catch (InterruptedException ex) {
123 if (!Platform.isFxApplicationThread()) {
124 final WebView[] arr = {null};
125 final CountDownLatch waitForResult = new CountDownLatch(1);
126 Platform.runLater(new Runnable() {
129 arr[0] = INSTANCE.newView(url, onLoad);
130 waitForResult.countDown();
135 waitForResult.await();
137 } catch (InterruptedException ex) {
138 LOG.log(Level.INFO, null, ex);
143 return INSTANCE.newView(url, onLoad);
147 static synchronized Stage findStage() throws InterruptedException {
148 while (INSTANCE == null) {
149 FXBrwsr.class.wait();
151 return INSTANCE.stage;
157 public void start(Stage primaryStage) throws Exception {
158 start(primaryStage, this.getParameters().getRaw().get(0));
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);
167 this.stage = primaryStage;
168 synchronized (FXBrwsr.class) {
170 FXBrwsr.class.notifyAll();
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]);
177 if (Boolean.getBoolean("fxpresenter.headless")) {
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
190 if (cn.startsWith("net.java.html.")) { // NOI18N
193 if (cn.startsWith("java.")) { // NOI18N
196 if (cn.startsWith("javafx.")) { // NOI18N
199 if (cn.startsWith("com.sun.")) { // NOI18N
204 return "org.netbeans.html"; // NOI18N
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
216 x, y, width, height, null
219 if (!callee.equals("org.netbeans.html")) { // NOI18N
220 arr[4] = new EventHandler<WindowEvent>() {
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
235 private WebView newView(final URL url, final FXPresenter onLoad) {
236 final WebView view = new WebView();
237 view.setContextMenuEnabled(false);
241 newStage = new Stage();
242 newStage.initOwner(stage);
243 bp = new BorderPane();
244 newStage.setScene(new Scene(bp));
252 attachHandlers(view, newStage);
254 final Worker<Void> w = view.getEngine().getLoadWorker();
255 w.stateProperty().addListener(new ChangeListener<Worker.State>() {
256 private String previous;
259 public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State newState) {
260 if (newState.equals(Worker.State.SUCCEEDED)) {
262 FXConsole.register(view.getEngine());
266 if (newState.equals(Worker.State.FAILED)) {
267 throw new IllegalStateException("Failed to load " + url);
270 private boolean checkValid() {
271 final String crnt = view.getEngine().getLocation();
272 if (previous != null && !previous.equals(crnt)) {
273 w.stateProperty().removeListener(this);
281 class Title implements ChangeListener<String> {
283 private String title;
290 public void changed(ObservableValue<? extends String> ov, String t, String t1) {
291 title = view.getEngine().getTitle();
293 stage.setTitle(title);
297 final Title x = new Title();
298 view.getEngine().titleProperty().addListener(x);
299 x.changed(null, null, null);
303 private static void attachHandlers(final WebView view, final Stage owner) {
304 view.getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
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);
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();
326 view.getEngine().setConfirmHandler(new Callback<String, Boolean>() {
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);
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);
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();
359 view.getEngine().setPromptHandler(new Callback<PromptData, String>() {
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());
374 final Insets ins = new Insets(10);
375 final VBox box = new VBox();
376 box.setAlignment(Pos.CENTER);
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);
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;
398 static void waitFinished() {
403 } catch (InterruptedException ex) {
404 LOG.log(Level.INFO, null, ex);
409 private static final class CloseDialogHandler implements EventHandler<ActionEvent> {
410 private final Stage dialogStage;
411 private final boolean[] res;
413 public CloseDialogHandler(Stage dialogStage, boolean[] res) {
414 this.dialogStage = dialogStage;
419 public void handle(ActionEvent t) {