--- old/modules/javafx.graphics/src/main/java/com/sun/prism/j2d/print/J2DPrinterJob.java 2017-03-10 11:56:40.253425448 -0800 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/j2d/print/J2DPrinterJob.java 2017-03-10 11:56:39.605425451 -0800 @@ -45,6 +45,7 @@ import javafx.stage.Window; import javax.print.PrintService; import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttribute; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.ResolutionSyntax; import javax.print.attribute.Size2DSyntax; @@ -76,9 +77,16 @@ import com.sun.javafx.print.PrinterJobImpl; import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.sg.prism.NGNode; +import com.sun.javafx.stage.WindowHelper; +import com.sun.javafx.tk.TKStage; import com.sun.javafx.tk.Toolkit; + import com.sun.prism.j2d.PrismPrintGraphics; +import java.lang.reflect.Constructor; +import java.security.AccessController; +import java.security.PrivilegedAction; + public class J2DPrinterJob implements PrinterJobImpl { javafx.print.PrinterJob fxPrinterJob; @@ -90,6 +98,25 @@ private PrintRequestAttributeSet printReqAttrSet; private volatile Object elo = null; + static private Class onTopClass = null; + PrintRequestAttribute getAlwaysOnTop(final long id) { + return AccessController.doPrivileged( + (PrivilegedAction) () -> { + + PrintRequestAttribute alwaysOnTop = null; + try { + if (onTopClass == null) { + onTopClass = Class.forName("sun.print.DialogOnTop"); + } + Constructor cons = + onTopClass.getConstructor(long.class); + alwaysOnTop = cons.newInstance(id); + } catch (Throwable t) { + } + return alwaysOnTop; + }); + } + public J2DPrinterJob(javafx.print.PrinterJob fxJob) { fxPrinterJob = fxJob; @@ -103,11 +130,21 @@ } printReqAttrSet = new HashPrintRequestAttributeSet(); printReqAttrSet.add(DialogTypeSelection.NATIVE); - j2dPageable = new J2DPageable(); pJob2D.setPageable(j2dPageable); } + private void setEnabledState(Window owner, boolean state) { + if (owner == null) { + return; + } + final TKStage stage = WindowHelper.getPeer(owner); + if (stage == null) { // just in case. + return; + } + Application.invokeAndWait(() -> stage.setEnabled(state)); + } + public boolean showPrintDialog(Window owner) { if (jobRunning || jobDone) { @@ -118,20 +155,37 @@ return true; } + if (onTopClass != null) { + printReqAttrSet.remove(onTopClass); + } + if (owner != null) { + long id = WindowHelper.getPeer(owner).getRawHandle(); + PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id); + if (alwaysOnTop != null) { + printReqAttrSet.add(alwaysOnTop); + } + } + boolean rv = false; syncSettingsToAttributes(); - if (!Toolkit.getToolkit().isFxUserThread()) { - rv = pJob2D.printDialog(printReqAttrSet); - } else { - // If we are on the event thread, we need to check whether we are - // allowed to call a nested event handler. - if (!Toolkit.getToolkit().canStartNestedEventLoop()) { - throw new IllegalStateException("Printing is not allowed during animation or layout processing"); + try { + setEnabledState(owner, false); + if (!Toolkit.getToolkit().isFxUserThread()) { + rv = pJob2D.printDialog(printReqAttrSet); + } else { + // If we are on the event thread, we need to check whether + // we are allowed to call a nested event handler. + if (!Toolkit.getToolkit().canStartNestedEventLoop()) { + throw new IllegalStateException( + "Printing is not allowed during animation or layout processing"); + } + rv = showPrintDialogWithNestedLoop(owner); } - rv = showPrintDialogWithNestedLoop(owner); - } - if (rv) { - updateSettingsFromDialog(); + if (rv) { + updateSettingsFromDialog(); + } + } finally { + setEnabledState(owner, true); } return rv; } @@ -171,18 +225,36 @@ if (GraphicsEnvironment.isHeadless()) { return true; } + + if (onTopClass != null) { + printReqAttrSet.remove(onTopClass); + } + if (owner != null) { + long id = WindowHelper.getPeer(owner).getRawHandle(); + PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id); + if (alwaysOnTop != null) { + printReqAttrSet.add(alwaysOnTop); + } + } + boolean rv = false; syncSettingsToAttributes(); - if (!Toolkit.getToolkit().isFxUserThread()) { - PageFormat pf = pJob2D.pageDialog(printReqAttrSet); - rv = pf != null; - } else { - // If we are on the event thread, we need to check whether we are - // allowed to call a nested event handler. - if (!Toolkit.getToolkit().canStartNestedEventLoop()) { - throw new IllegalStateException("Printing is not allowed during animation or layout processing"); + try { + setEnabledState(owner, false); + if (!Toolkit.getToolkit().isFxUserThread()) { + PageFormat pf = pJob2D.pageDialog(printReqAttrSet); + rv = pf != null; + } else { + // If we are on the event thread, we need to check whether + // we are allowed to call a nested event handler. + if (!Toolkit.getToolkit().canStartNestedEventLoop()) { + throw new IllegalStateException( + "Printing is not allowed during animation or layout processing"); + } + rv = showPageDialogFromNestedLoop(owner); } - rv = showPageDialogFromNestedLoop(owner); + } finally { + setEnabledState(owner, true); } if (rv) { updateSettingsFromDialog(); --- old/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java 2017-03-10 11:56:40.705425447 -0800 +++ new/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java 2017-03-10 11:56:40.533425447 -0800 @@ -215,6 +215,18 @@ public void setRTL(boolean b); + /** + * Whether mouse/keyboard events should be sent to this window. + * @param whether this stage should receive events/focus + */ + public void setEnabled(boolean enabled); + + /** + * Return a handle to the native platform window id. + * @return platform window id, or 0L if there is none. + */ + public long getRawHandle(); + public static final KeyCodeCombination defaultFullScreenExitKeycombo = new KeyCodeCombination(KeyCode.ESCAPE, ModifierValue.UP, --- old/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedStage.java 2017-03-10 11:56:41.153425445 -0800 +++ new/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedStage.java 2017-03-10 11:56:40.929425446 -0800 @@ -309,4 +309,15 @@ @Override public void setRTL(boolean b) { } + @Override + public void setEnabled(boolean enabled) { + } + + @Override + public long getRawHandle() { + /* Perhaps this could return the ID for the window in which this + * stage is embedded, but there is no current requirement for that. + */ + return 0L; + } } --- old/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java 2017-03-10 11:56:41.725425443 -0800 +++ new/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java 2017-03-10 11:56:41.409425444 -0800 @@ -877,7 +877,8 @@ } } - void setEnabled(boolean enabled) { + @Override + public void setEnabled(boolean enabled) { if ((owner != null) && (owner instanceof WindowStage)) { ((WindowStage) owner).setEnabled(enabled); } @@ -897,6 +898,11 @@ } } + @Override + public long getRawHandle() { + return platformWindow.getRawHandle(); + } + // Note: This method is required to workaround a glass issue mentioned in RT-12607 protected void requestToFront() { if (platformWindow != null) { --- old/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java 2017-03-10 11:56:42.433425440 -0800 +++ new/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java 2017-03-10 11:56:41.921425442 -0800 @@ -375,7 +375,7 @@ * return the "raw' pointer needed by subclasses to pass to native routines * @return the native pointer. */ - protected long getRawHandle() { + public long getRawHandle() { return ptr; } --- old/dependencies/java.desktop/module-info.java.extra 2017-03-10 11:56:43.069425438 -0800 +++ new/dependencies/java.desktop/module-info.java.extra 2017-03-10 11:56:42.769425439 -0800 @@ -30,3 +30,4 @@ exports sun.java2d to javafx.swing; exports sun.swing to javafx.swing; exports sun.font.lookup to javafx.graphics; +exports sun.print to javafx.graphics; --- old/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java 2017-03-10 11:56:43.577425436 -0800 +++ new/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java 2017-03-10 11:56:43.297425437 -0800 @@ -437,4 +437,13 @@ @Override public void setRTL(boolean b) { } + + @Override + public void setEnabled(boolean b) { + } + + @Override + public long getRawHandle() { + return 0L; + } } --- /dev/null 2016-11-01 09:38:44.965684815 -0700 +++ new/tests/manual/printing/PrintDialogModalityTest.java 2017-03-10 11:56:43.793425436 -0800 @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javafx.application.Application; +import javafx.event.ActionEvent; +import javafx.geometry.Pos; +import javafx.print.PrinterJob; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import javafx.stage.Window; +import javafx.scene.text.Text; + +public class PrintDialogModalityTest extends Application { + + static final String infoText = + "NOTE: if there are no printers installed this test is not valid " + + "since depending on O/S no dialog may be displayed.\n" + + "This tests that a print dialog can be made modal w.r.t " + + "a parent window. Cycle through in any order the different " + + "dialog options via pressing the buttons. For the modal cases " + + "when the dialog is displayed, the original window should be " + + "unresponsive to input, for example preventing you launching " + + "another dialog, and also should stay below the dialog. " + + "Depending on platform the dialog may stay above just the "+ + "parent, or all application or even all desktop windows.\n" + + "Non-modal dialogs will generally allow you to click on the "+ + "main window and raise it above the dialog. However " + + "depending on platform, even the non-modal cases may behave " + + "as if they are modal. Notably this is the case on MacOS as " + + "that is the behaviour enforced by the O/S"; + + @Override + public void start(Stage primaryStage) { + + VBox vbox; + + Text info = new Text(infoText); + info.setWrappingWidth(450); + final PrinterJob job = PrinterJob.createPrinterJob(); + if (job != null) { + + Button b1 = new Button("Modal Print"); + Button b2 = new Button("Modal Page Setup"); + Button b3 = new Button("Non-modal Print"); + Button b4 = new Button("Non-modal Page Setup"); + + b1.setOnAction((ActionEvent event) -> { + Window w = b1.getScene().getWindow(); + job.showPrintDialog(w); + }); + b2.setOnAction((ActionEvent event) -> { + Window w = b2.getScene().getWindow(); + job.showPageSetupDialog(w); + }); + b3.setOnAction((ActionEvent event) -> { + job.showPrintDialog(null); + }); + b4.setOnAction((ActionEvent event) -> { + job.showPageSetupDialog(null); + }); + HBox hbox1 = new HBox(2, b1, b2); + HBox hbox2 = new HBox(2, b3, b4); + hbox1.setAlignment(Pos.CENTER); + hbox2.setAlignment(Pos.CENTER); + vbox = new VBox(3, info, hbox1, hbox2); + } else { + Text noprinters = new Text("No printers found!"); + noprinters.setFill(Color.RED); + vbox = new VBox(2, info, noprinters); + } + vbox.setAlignment(Pos.TOP_CENTER); + Scene scene = new Scene(vbox, 500, 400); + primaryStage.setScene(scene); + primaryStage.show(); + } + + public static void main(String[] args) { + launch(args); + } +}