diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 407af09810c..416a3ee002b 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -44,6 +44,7 @@ import javax.print.attribute.standard.Media; import javax.print.attribute.standard.MediaPrintableArea; import javax.print.attribute.standard.MediaSize; import javax.print.attribute.standard.MediaSizeName; +import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.PageRanges; import javax.print.attribute.standard.Sides; import javax.print.attribute.Attribute; @@ -70,6 +71,8 @@ public final class CPrinterJob extends RasterPrinterJob { private String tray = null; + private String outputBin = null; + // This is the NSPrintInfo for this PrinterJob. Protect multi thread // access to it. It is used by the pageDialog, jobDialog, and printLoop. // This way the state of these items is shared across these calls. @@ -191,6 +194,8 @@ public final class CPrinterJob extends RasterPrinterJob { tray = customTray.getChoiceName(); } + outputBin = getOutputBinValue(attributes.get(OutputBin.class)); + PageRanges pageRangesAttr = (PageRanges)attributes.get(PageRanges.class); if (isSupportedValue(pageRangesAttr, attributes)) { SunPageSelection rangeSelect = (SunPageSelection)attributes.get(SunPageSelection.class); @@ -658,6 +663,41 @@ public final class CPrinterJob extends RasterPrinterJob { return tray; } + private String getOutputBin() { + return outputBin; + } + + private void setOutputBin(String outputBinName) { + + OutputBin outputBin = toOutputBin(outputBinName); + if (outputBin != null) { + attributes.add(outputBin); + } + } + + private OutputBin toOutputBin(String outputBinName) { + + PrintService ps = getPrintService(); + if (ps == null) { + return null; + } + + OutputBin[] supportedBins = (OutputBin[]) ps.getSupportedAttributeValues(OutputBin.class, null, null); + if (supportedBins == null || supportedBins.length == 0) { + return null; + } + + for (OutputBin bin : supportedBins) { + if (bin instanceof CustomOutputBin customBin){ + if (customBin.getChoiceName().equals(outputBinName)) { + return customBin; + } + } + } + + return null; + } + private void setPrinterServiceFromNative(String printerName) { // This is called from the native side. PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m index 9cbd48bf843..f4794d40d31 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m @@ -383,6 +383,7 @@ static void nsPrintInfoToJavaPrinterJob(JNIEnv* env, NSPrintInfo* src, jobject d DECLARE_METHOD(jm_setPrintToFile, sjc_CPrinterJob, "setPrintToFile", "(Z)V"); DECLARE_METHOD(jm_setDestinationFile, sjc_CPrinterJob, "setDestinationFile", "(Ljava/lang/String;)V"); DECLARE_METHOD(jm_setSides, sjc_CPrinterJob, "setSides", "(I)V"); + DECLARE_METHOD(jm_setOutputBin, sjc_CPrinterJob, "setOutputBin", "(Ljava/lang/String;)V"); // get the selected printer's name, and set the appropriate PrintService on the Java side NSString *name = [[src printer] name]; @@ -449,6 +450,13 @@ static void nsPrintInfoToJavaPrinterJob(JNIEnv* env, NSPrintInfo* src, jobject d (*env)->CallVoidMethod(env, dstPrinterJob, jm_setSides, sides); // AWT_THREADING Safe (known object) CHECK_EXCEPTION(); } + + NSString* outputBin = [[src printSettings] objectForKey:@"OutputBin"]; + if (outputBin != nil) { + jstring outputBinName = NSStringToJavaString(env, outputBin); + (*env)->CallVoidMethod(env, dstPrinterJob, jm_setOutputBin, outputBinName); + CHECK_EXCEPTION(); + } } } @@ -468,6 +476,7 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj DECLARE_METHOD(jm_getPageFormat, sjc_CPrinterJob, "getPageFormatFromAttributes", "()Ljava/awt/print/PageFormat;"); DECLARE_METHOD(jm_getDestinationFile, sjc_CPrinterJob, "getDestinationFile", "()Ljava/lang/String;"); DECLARE_METHOD(jm_getSides, sjc_CPrinterJob, "getSides", "()I"); + DECLARE_METHOD(jm_getOutputBin, sjc_CPrinterJob, "getOutputBin", "()Ljava/lang/String;"); NSMutableDictionary* printingDictionary = [dst dictionary]; @@ -538,6 +547,15 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj [dst updateFromPMPrintSettings]; } } + + jobject outputBin = (*env)->CallObjectMethod(env, srcPrinterJob, jm_getOutputBin); + CHECK_EXCEPTION(); + if (outputBin != NULL) { + NSString *nsOutputBinStr = JavaStringToNSString(env, outputBin); + if (nsOutputBinStr != nil) { + [[dst printSettings] setObject:nsOutputBinStr forKey:@"OutputBin"]; + } + } } /* diff --git a/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java b/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java new file mode 100644 index 00000000000..3f817fe6c9b --- /dev/null +++ b/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, BELLSOFT. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.print.attribute.standard; + +import java.io.Serial; + +import javax.print.attribute.Attribute; +import javax.print.attribute.DocAttribute; +import javax.print.attribute.EnumSyntax; +import javax.print.attribute.PrintJobAttribute; +import javax.print.attribute.PrintRequestAttribute; + +import sun.print.CustomOutputBin; + +/** + * Class {@code OutputBin} is a printing attribute class, an enumeration, that + * specifies the output bin for the job. + *

+ * Class {@code OutputBin} declares keywords for standard output bin kind values. + *

+ * IPP Compatibility: This attribute is not an IPP 1.1 attribute; it is + * an attribute in the "output-bin" attribute extension + * ( + * PDF) of IPP 1.1. The category name returned by {@code getName()} is the + * IPP attribute name. The enumeration's integer value is the IPP enum value. + * The {@code toString()} method returns the IPP string representation of the + * attribute value. + */ +public sealed class OutputBin extends EnumSyntax implements PrintRequestAttribute, PrintJobAttribute permits CustomOutputBin { + + @Serial + private static final long serialVersionUID = -3718893309873137109L; + + /** + * The top output bin in the printer. + */ + public static final OutputBin TOP = new OutputBin(0); + + /** + * The middle output bin in the printer. + */ + public static final OutputBin MIDDLE = new OutputBin(1); + + /** + * The bottom output bin in the printer. + */ + public static final OutputBin BOTTOM = new OutputBin(2); + + /** + * The side output bin in the printer. + */ + public static final OutputBin SIDE = new OutputBin(3); + + /** + * The left output bin in the printer. + */ + public static final OutputBin LEFT = new OutputBin(4); + + /** + * The right output bin in the printer. + */ + public static final OutputBin RIGHT = new OutputBin(5); + + /** + * The center output bin in the printer. + */ + public static final OutputBin CENTER = new OutputBin(6); + + /** + * The rear output bin in the printer. + */ + public static final OutputBin REAR = new OutputBin(7); + + /** + * The face up output bin in the printer. + */ + public static final OutputBin FACE_UP = new OutputBin(8); + + /** + * The face down output bin in the printer. + */ + public static final OutputBin FACE_DOWN = new OutputBin(9); + + /** + * The large-capacity output bin in the printer. + */ + public static final OutputBin LARGE_CAPACITY = new OutputBin(10); + + /** + * Construct a new output bin enumeration value with the given integer + * value. + * + * @param value Integer value + */ + protected OutputBin(int value) { + super(value); + } + + /** + * The string table for class {@code OutputBin}. + */ + private static final String[] myStringTable = { + "top", + "middle", + "bottom", + "side", + "left", + "right", + "center", + "rear", + "face-up", + "face-down", + "large-capacity", + }; + + /** + * The enumeration value table for class {@code OutputBin}. + */ + private static final OutputBin[] myEnumValueTable = { + TOP, + MIDDLE, + BOTTOM, + SIDE, + LEFT, + RIGHT, + CENTER, + REAR, + FACE_UP, + FACE_DOWN, + LARGE_CAPACITY, + }; + + /** + * Returns the string table for class {@code OutputBin}. + */ + @Override + protected String[] getStringTable() { + return myStringTable.clone(); + } + + /** + * Returns the enumeration value table for class {@code OutputBin}. + */ + @Override + protected EnumSyntax[] getEnumValueTable() { + return (EnumSyntax[]) myEnumValueTable.clone(); + } + + /** + * Get the printing attribute class which is to be used as the "category" + * for this printing attribute value. + *

+ * For class {@code OutputBin} and any vendor-defined subclasses, the category + * is class {@code OutputBin} itself. + * + * @return printing attribute class (category), an instance of class + * {@link Class java.lang.Class} + */ + @Override + public final Class getCategory() { + return OutputBin.class; + } + + /** + * Get the name of the category of which this attribute value is an + * instance. + *

+ * For class {@code OutputBin} and any vendor-defined subclasses, the category + * name is {@code "output-bin"}. + * + * @return attribute category name + */ + @Override + public final String getName() { + return "output-bin"; + } +} diff --git a/src/java.desktop/share/classes/javax/print/attribute/standard/package-info.java b/src/java.desktop/share/classes/javax/print/attribute/standard/package-info.java index 31f4b17366c..08badfdb492 100644 --- a/src/java.desktop/share/classes/javax/print/attribute/standard/package-info.java +++ b/src/java.desktop/share/classes/javax/print/attribute/standard/package-info.java @@ -335,6 +335,13 @@ *   *   * + * OutputBin + *   + * X + * X + *   + *   + * * * DateTimeAtCompleted *   diff --git a/src/java.desktop/share/classes/sun/print/CustomOutputBin.java b/src/java.desktop/share/classes/sun/print/CustomOutputBin.java new file mode 100644 index 00000000000..71c63c91570 --- /dev/null +++ b/src/java.desktop/share/classes/sun/print/CustomOutputBin.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, BELLSOFT. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.print; + +import java.io.Serial; +import java.util.ArrayList; + +import javax.print.attribute.EnumSyntax; +import javax.print.attribute.standard.Media; +import javax.print.attribute.standard.OutputBin; + +public final class CustomOutputBin extends OutputBin { + private static ArrayList customStringTable = new ArrayList<>(); + private static ArrayList customEnumTable = new ArrayList<>(); + private String choiceName; + + private CustomOutputBin(int x) { + super(x); + } + + private static synchronized int nextValue(String name) { + customStringTable.add(name); + return (customStringTable.size()-1); + } + + private CustomOutputBin(String name, String choice) { + super(nextValue(name)); + choiceName = choice; + customEnumTable.add(this); + } + + /** + * Creates a custom output bin + */ + public static synchronized CustomOutputBin createOutputBin(String name, String choice) { + for (CustomOutputBin bin : customEnumTable) { + if (bin.getChoiceName().equals(choice) && bin.getCustomName().equals(name)) { + return bin; + } + } + return new CustomOutputBin(name, choice); + } + + private static final long serialVersionUID = 3018751086294120717L; + + /** + * Returns the command string for this media tray. + */ + public String getChoiceName() { + return choiceName; + } + + /** + * Returns the string table for super class MediaTray. + */ + public OutputBin[] getSuperEnumTable() { + return (OutputBin[])super.getEnumValueTable(); + } + + /** + * Returns the string table for class CustomOutputBin. + */ + @Override + protected String[] getStringTable() { + String[] nameTable = new String[customStringTable.size()]; + return customStringTable.toArray(nameTable); + } + + /** + * Returns a custom bin name + */ + public String getCustomName() { + return customStringTable.get(getValue() - getOffset()); + } + + /** + * Returns the enumeration value table for class CustomOutputBin. + */ + @Override + protected CustomOutputBin[] getEnumValueTable() { + CustomOutputBin[] enumTable = new CustomOutputBin[customEnumTable.size()]; + return customEnumTable.toArray(enumTable); + } +} diff --git a/src/java.desktop/share/classes/sun/print/PSPrinterJob.java b/src/java.desktop/share/classes/sun/print/PSPrinterJob.java index 8973467fb97..e64fb2cb0d5 100644 --- a/src/java.desktop/share/classes/sun/print/PSPrinterJob.java +++ b/src/java.desktop/share/classes/sun/print/PSPrinterJob.java @@ -61,6 +61,7 @@ import javax.print.attribute.standard.Copies; import javax.print.attribute.standard.Destination; import javax.print.attribute.standard.DialogTypeSelection; import javax.print.attribute.standard.JobName; +import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.Sides; import java.io.BufferedOutputStream; @@ -491,14 +492,19 @@ public class PSPrinterJob extends RasterPrinterJob { if (attributes == null) { return; // now always use attributes, so this shouldn't happen. } + mOptions = ""; Attribute attr = attributes.get(Media.class); if (attr instanceof CustomMediaTray) { CustomMediaTray customTray = (CustomMediaTray)attr; String choice = customTray.getChoiceName(); if (choice != null) { - mOptions = " InputSlot="+ choice; + mOptions += " InputSlot="+ choice; } } + String outputBin = getOutputBinValue(outputBinAttr); + if (outputBin != null) { + mOptions += " output-bin=" + outputBin; + } } /** @@ -1643,7 +1649,9 @@ public class PSPrinterJob extends RasterPrinterJob { execCmd[n++] = "-o job-sheets=standard"; } if ((pFlags & OPTIONS) != 0) { - execCmd[n++] = "-o" + options; + for (String option : options.trim().split(" ")) { + execCmd[n++] = "-o " + option; + } } } else { ncomps+=1; //add 1 arg for lp @@ -1666,7 +1674,9 @@ public class PSPrinterJob extends RasterPrinterJob { execCmd[n++] = "-o job-sheets=standard"; } if ((pFlags & OPTIONS) != 0) { - execCmd[n++] = "-o" + options; + for (String option : options.trim().split(" ")) { + execCmd[n++] = "-o " + option; + } } } execCmd[n++] = spoolFile; diff --git a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java index fc47c25806c..0dfa5e42d9a 100644 --- a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java +++ b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java @@ -81,6 +81,7 @@ import javax.print.attribute.standard.MediaPrintableArea; import javax.print.attribute.standard.MediaSize; import javax.print.attribute.standard.MediaSizeName; import javax.print.attribute.standard.OrientationRequested; +import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.PageRanges; import javax.print.attribute.standard.PrinterResolution; import javax.print.attribute.standard.PrinterState; @@ -279,6 +280,7 @@ public abstract class RasterPrinterJob extends PrinterJob { private PageRanges pageRangesAttr; protected PrinterResolution printerResAttr; protected Sides sidesAttr; + protected OutputBin outputBinAttr; protected String destinationAttr; protected boolean noJobSheet = false; protected int mDestType = RasterPrinterJob.FILE; @@ -1228,6 +1230,7 @@ public abstract class RasterPrinterJob extends PrinterJob { /* reset all values to defaults */ setCollated(false); sidesAttr = null; + outputBinAttr = null; printerResAttr = null; pageRangesAttr = null; copiesAttr = 0; @@ -1274,6 +1277,11 @@ public abstract class RasterPrinterJob extends PrinterJob { sidesAttr = Sides.ONE_SIDED; } + outputBinAttr = (OutputBin)attributes.get(OutputBin.class); + if (!isSupportedValue(outputBinAttr, attributes)) { + outputBinAttr = null; + } + printerResAttr = (PrinterResolution)attributes.get(PrinterResolution.class); if (service.isAttributeCategorySupported(PrinterResolution.class)) { if (!isSupportedValue(printerResAttr, attributes)) { @@ -2617,4 +2625,26 @@ public abstract class RasterPrinterJob extends PrinterJob { parentWindowID = DialogOwnerAccessor.getID(onTop); } } + + protected String getOutputBinValue(Attribute attr) { + if (attr instanceof CustomOutputBin customOutputBin) { + return customOutputBin.getChoiceName(); + } else if (attr instanceof OutputBin) { + PrintService ps = getPrintService(); + if (ps == null) { + return null; + } + String name = attr.toString(); + OutputBin[] outputBins = (OutputBin[]) ps + .getSupportedAttributeValues(OutputBin.class, null, null); + for (OutputBin outputBin : outputBins) { + String choice = ((CustomOutputBin) outputBin).getChoiceName(); + if (name.equalsIgnoreCase(choice) || name.replaceAll("-", "").equalsIgnoreCase(choice)) { + return choice; + } + } + return null; + } + return null; + } } diff --git a/src/java.desktop/share/classes/sun/print/ServiceDialog.java b/src/java.desktop/share/classes/sun/print/ServiceDialog.java index 7167ba1884f..6f2bdecb809 100644 --- a/src/java.desktop/share/classes/sun/print/ServiceDialog.java +++ b/src/java.desktop/share/classes/sun/print/ServiceDialog.java @@ -69,6 +69,7 @@ import java.awt.event.KeyEvent; import java.net.URISyntaxException; import java.lang.reflect.Field; import java.net.MalformedURLException; +import sun.awt.OSInfo; /** * A class which implements a cross-platform print dialog. @@ -2300,6 +2301,7 @@ public class ServiceDialog extends JDialog implements ActionListener { private QualityPanel pnlQuality; private JobAttributesPanel pnlJobAttributes; private SidesPanel pnlSides; + private OutputPanel pnlOutput; public AppearancePanel() { super(); @@ -2330,6 +2332,11 @@ public class ServiceDialog extends JDialog implements ActionListener { pnlJobAttributes = new JobAttributesPanel(); addToGB(pnlJobAttributes, this, gridbag, c); + if (OSInfo.getOSType() != OSInfo.OSType.WINDOWS) { + c.gridwidth = GridBagConstraints.REMAINDER; + pnlOutput = new OutputPanel(); + addToGB(pnlOutput, this, gridbag, c); + } } public void updateInfo() { @@ -2337,6 +2344,9 @@ public class ServiceDialog extends JDialog implements ActionListener { pnlQuality.updateInfo(); pnlSides.updateInfo(); pnlJobAttributes.updateInfo(); + if (pnlOutput != null) { + pnlOutput.updateInfo(); + } } } @@ -2818,8 +2828,106 @@ public class ServiceDialog extends JDialog implements ActionListener { } } + @SuppressWarnings("serial") // Superclass is not serializable across versions + private class OutputPanel extends JPanel implements ItemListener { + private final String strTitle = getMsg("border.output"); + private JLabel lblOutput; + private JComboBox cbOutput; + private Vector outputs = new Vector<>(); + public OutputPanel() { + super(); + + GridBagLayout gridbag = new GridBagLayout(); + GridBagConstraints c = new GridBagConstraints(); + + setLayout(gridbag); + setBorder(BorderFactory.createTitledBorder(strTitle)); + + cbOutput = new JComboBox<>(); + + c.fill = GridBagConstraints.BOTH; + c.insets = compInsets; + c.weighty = 1.0; + + c.weightx = 0.0; + lblOutput = new JLabel(getMsg("label.outputbins"), JLabel.TRAILING); + lblOutput.setDisplayedMnemonic(getMnemonic("label.outputbins")); + lblOutput.setLabelFor(cbOutput); + addToGB(lblOutput, this, gridbag, c); + c.weightx = 1.0; + c.gridwidth = GridBagConstraints.REMAINDER; + addToGB(cbOutput, this, gridbag, c); + } + + public void itemStateChanged(ItemEvent e) { + + Object source = e.getSource(); + if (e.getStateChange() == ItemEvent.SELECTED) { + if (source == cbOutput) { + int index = cbOutput.getSelectedIndex(); + if ((index >= 0) && (index < outputs.size())) { + asCurrent.add(outputs.get(index)); + } else if (index == cbOutput.getItemCount() - 1) { + asCurrent.remove(OutputBin.class); + } + } + } + } + + public void updateInfo() { + + Class obCategory = OutputBin.class; + + cbOutput.removeItemListener(this); + cbOutput.removeAllItems(); + + outputs.clear(); + + boolean outputEnabled = false; + + if (psCurrent.isAttributeCategorySupported(obCategory)) { + + Object values = + psCurrent.getSupportedAttributeValues(obCategory, + docFlavor, + asCurrent); + + if (values instanceof OutputBin[]) { + OutputBin[] outputBins = (OutputBin[])values; + + for (OutputBin outputBin: outputBins) { + outputs.add(outputBin); + cbOutput.addItem(outputBin.toString()); + } + + cbOutput.addItem(""); + cbOutput.setSelectedIndex(cbOutput.getItemCount() - 1); + + OutputBin current = (OutputBin) asCurrent.get(obCategory); + if (current != null) { + for (int i = 0; i < outputs.size(); i++) { + if (current.equals(outputs.get(i))) { + cbOutput.setSelectedIndex(i); + break; + } + } + } else if (outputBins.length == 1) { + cbOutput.setSelectedIndex(0); + } + + outputEnabled = outputBins.length > 1; + } + } + + cbOutput.setEnabled(outputEnabled); + lblOutput.setEnabled(outputEnabled); + if (outputEnabled) { + cbOutput.addItemListener(this); + } + } + } /** * A special widget that groups a JRadioButton with an associated icon, diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui.properties index 7b7027239b8..39fd3cd8d8e 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui.properties @@ -29,6 +29,7 @@ border.chromaticity=Color Appearance border.copies=Copies border.jobattributes=Job Attributes border.media=Media +border.output=Output border.orientation=Orientation border.printrange=Print Range border.printservice=Print Service @@ -62,6 +63,7 @@ label.pstype=Type: label.rangeto=To label.size=Si&ze: label.source=Sour&ce: +label.outputbins=Out&put trays: label.status=Status: label.username=&User Name: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_de.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_de.properties index 72e73ad4735..a50c78d060d 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_de.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_de.properties @@ -29,6 +29,7 @@ border.chromaticity=Farbdarstellung border.copies=Kopien border.jobattributes=Jobattribute border.media=Medien +border.output=Ausgabe border.orientation=Ausrichtung border.printrange=Druckbereich border.printservice=Druckservice @@ -62,6 +63,7 @@ label.pstype=Typ: label.rangeto=Bis label.size=G&röße: label.source=&Quelle: +label.outputbins=A&usgabefächer: label.status=Status: label.username=&Benutzername: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_es.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_es.properties index 0b229790165..170e7b0b91f 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_es.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_es.properties @@ -29,6 +29,7 @@ border.chromaticity=Apariencia del Color border.copies=Copias border.jobattributes=Atributos del Trabajo border.media=Soporte +border.output=Salida border.orientation=Orientación border.printrange=Rango de Impresión border.printservice=Servicio de Impresión @@ -62,6 +63,7 @@ label.pstype=Tipo: label.rangeto=A label.size=Tama&ño: label.source=Orig&en: +label.outputbins=Band&ejas de salida: label.status=Estado: label.username=&Usuario: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_fr.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_fr.properties index 3a77589e8b3..8222bf21e8d 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_fr.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_fr.properties @@ -29,6 +29,7 @@ border.chromaticity=Couleur border.copies=Copies border.jobattributes=Attributs de tâche border.media=Support +border.output=Sortir border.orientation=Orientation border.printrange=Plage d'impression border.printservice=Service d'impression @@ -62,6 +63,7 @@ label.pstype=Type : label.rangeto=A label.size=Tai&lle : label.source=Sour&ce : +label.outputbins=Bacs de s&ortie : label.status=Statut : label.username=Nom ut&ilisateur : label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_it.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_it.properties index 2f029b7bfc9..bac068e46ac 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_it.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_it.properties @@ -29,6 +29,7 @@ border.chromaticity=Aspetto colore border.copies=Copie border.jobattributes=Attributi job border.media=Supporti +border.output=Output border.orientation=Orientamento border.printrange=Intervallo di stampa border.printservice=Servizio di stampa @@ -62,6 +63,7 @@ label.pstype=Tipo: label.rangeto=A label.size=Di&mensioni: label.source=O&rigine: +label.outputbins=&Vassoi di uscita: label.status=Stato: label.username=Nome &utente: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_ja.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_ja.properties index 889df230161..ee8e591d15a 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_ja.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_ja.properties @@ -29,6 +29,7 @@ border.chromaticity=色の表現 border.copies=印刷部数 border.jobattributes=ジョブの属性 border.media=メディア +border.output=出力 border.orientation=用紙の向き border.printrange=印刷範囲 border.printservice=印刷サービス @@ -62,6 +63,7 @@ label.pstype=タイプ: label.rangeto=印刷範囲 label.size=サイズ(&Z): label.source=ソース(&C): +label.outputbins=出力トレイ(&P): label.status=状態: label.username=ユーザー名(&U): label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_ko.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_ko.properties index 61d38f52953..d737b987db1 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_ko.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_ko.properties @@ -29,6 +29,7 @@ border.chromaticity=색상 모양 border.copies=복사 border.jobattributes=작업 속성 border.media=매체 +border.output=출력물 border.orientation=방향 border.printrange=인쇄 범위 border.printservice=인쇄 서비스 @@ -62,6 +63,7 @@ label.pstype=유형: label.rangeto=종료 label.size=크기(&Z): label.source=소스(&C): +label.outputbins=출력 트레이(&P): label.status=상태: label.username=사용자 이름(&U): label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_pt_BR.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_pt_BR.properties index 0617aa2e005..5f157b9f5f7 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_pt_BR.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_pt_BR.properties @@ -29,6 +29,7 @@ border.chromaticity=Aparência da Cor border.copies=Cópias border.jobattributes=Atributos do Job border.media=Mídia +border.output=Saída border.orientation=Orientação border.printrange=Faixa de Impressão border.printservice=Serviço de Impressão @@ -62,6 +63,7 @@ label.pstype=Tipo: label.rangeto=Até label.size=Ta&manho: label.source=&Origem: +label.outputbins=Bande&jas de saída: label.status=Status: label.username=Nome do &Usuário: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_sv.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_sv.properties index eac40fa6744..8d84d54dc28 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_sv.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_sv.properties @@ -29,6 +29,7 @@ border.chromaticity=Färg border.copies=Antal exemplar border.jobattributes=Utskriftsattribut border.media=Media +border.output=Utmatning border.orientation=Orientering border.printrange=Utskriftsintervall border.printservice=Utskriftstjänst @@ -62,6 +63,7 @@ label.pstype=Typ: label.rangeto=Till label.size=Stor&lek: label.source=&Källa: +label.outputbins=Utma&tningsfack: label.status=Status: label.username=A&nvändarnamn: label.millimetres=(mm) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties index 598441e24d9..f4a1982f056 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties @@ -29,6 +29,7 @@ border.chromaticity=颜色外观 border.copies=份数 border.jobattributes=作业属性 border.media=介质 +border.output=出纸 border.orientation=方向 border.printrange=打印区域 border.printservice=打印服务 @@ -62,6 +63,7 @@ label.pstype=类型: label.rangeto=至 label.size=大小(&Z): label.source=来源(&C): +label.outputbins=出纸托盘(&P): label.status=状态: label.username=用户名(&U): label.millimetres=(毫米) diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_TW.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_TW.properties index 845122a0eb2..de600e74ec6 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_TW.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_TW.properties @@ -29,6 +29,7 @@ border.chromaticity=色彩外觀 border.copies=份數 border.jobattributes=工作屬性 border.media=媒體 +border.output=出紙 border.orientation=方向 border.printrange=列印範圍 border.printservice=列印服務 @@ -62,6 +63,7 @@ label.pstype=類型: label.rangeto=至 label.size=大小(&Z): label.source=來源(&C): +label.outputbins=输出纸盒(&P): label.status=狀態: label.username=使用者名稱(&U): label.millimetres=(mm) diff --git a/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java b/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java index 6c75949731d..4d2a4d616aa 100644 --- a/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java +++ b/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java @@ -34,11 +34,13 @@ import java.util.HashMap; import sun.print.IPPPrintService; import sun.print.CustomMediaSizeName; import sun.print.CustomMediaTray; +import sun.print.CustomOutputBin; import javax.print.attribute.standard.Media; import javax.print.attribute.standard.MediaSizeName; import javax.print.attribute.standard.MediaSize; import javax.print.attribute.standard.MediaTray; import javax.print.attribute.standard.MediaPrintableArea; +import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.PrinterResolution; import javax.print.attribute.Size2DSyntax; import javax.print.attribute.Attribute; @@ -60,6 +62,7 @@ public class CUPSPrinter { // CUPS does not support multi-threading. private static synchronized native String[] getMedia(String printer); private static synchronized native float[] getPageSizes(String printer); + private static synchronized native String[] getOutputBins(String printer); private static synchronized native void getResolutions(String printer, ArrayList resolutionList); //public static boolean useIPPMedia = false; will be used later @@ -68,10 +71,12 @@ public class CUPSPrinter { private MediaSizeName[] cupsMediaSNames; private CustomMediaSizeName[] cupsCustomMediaSNames; private MediaTray[] cupsMediaTrays; + private OutputBin[] cupsOutputBins; public int nPageSizes = 0; public int nTrays = 0; private String[] media; + private String[] outputBins; private float[] pageSizes; int[] resolutionsArray; private String printer; @@ -144,6 +149,8 @@ public class CUPSPrinter { for (int i=0; i < resolutionList.size(); i++) { resolutionsArray[i] = resolutionList.get(i); } + + outputBins = getOutputBins(printer); } } @@ -185,6 +192,14 @@ public class CUPSPrinter { return cupsMediaTrays; } + /** + * Returns array of OutputBins derived from PPD. + */ + OutputBin[] getOutputBins() { + initMedia(); + return cupsOutputBins; + } + /** * return the raw packed array of supported printer resolutions. */ @@ -261,6 +276,15 @@ public class CUPSPrinter { cupsMediaTrays[i] = mt; } + if (outputBins == null) { + cupsOutputBins = new OutputBin[0]; + } else { + int nBins = outputBins.length / 2; + cupsOutputBins = new OutputBin[nBins]; + for (int i = 0; i < nBins; i++) { + cupsOutputBins[i] = CustomOutputBin.createOutputBin(outputBins[i*2], outputBins[i*2+1]); + } + } } /** diff --git a/src/java.desktop/unix/classes/sun/print/IPPPrintService.java b/src/java.desktop/unix/classes/sun/print/IPPPrintService.java index 25123045f51..5a6437014dd 100644 --- a/src/java.desktop/unix/classes/sun/print/IPPPrintService.java +++ b/src/java.desktop/unix/classes/sun/print/IPPPrintService.java @@ -80,6 +80,7 @@ import javax.print.attribute.standard.MediaSizeName; import javax.print.attribute.standard.MediaTray; import javax.print.attribute.standard.NumberUp; import javax.print.attribute.standard.OrientationRequested; +import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.PDLOverrideSupported; import javax.print.attribute.standard.PageRanges; import javax.print.attribute.standard.PagesPerMinute; @@ -138,6 +139,7 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { private DocFlavor[] supportedDocFlavors; private Class[] supportedCats; private MediaTray[] mediaTrays; + private OutputBin[] outputBins; private MediaSizeName[] mediaSizeNames; private CustomMediaSizeName[] customMediaSizeNames; private int defaultMediaIndex; @@ -211,6 +213,7 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { new RequestingUserName("", Locale.getDefault()), //SheetCollate.UNCOLLATED, //CUPS has no sheet collate? Sides.ONE_SIDED, + OutputBin.TOP, }; @@ -440,6 +443,7 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { if ((urlConnection = getIPPConnection(myURL)) == null) { mediaSizeNames = new MediaSizeName[0]; mediaTrays = new MediaTray[0]; + outputBins = new OutputBin[0]; debug_println(debugPrefix+"initAttributes, NULL urlConnection "); init = true; return; @@ -460,6 +464,9 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { cps = new CUPSPrinter(printer); mediaSizeNames = cps.getMediaSizeNames(); mediaTrays = cps.getMediaTrays(); + outputBins = PrintServiceLookupProvider.isMac() + ? cps.getOutputBins() + : getSupportedOutputBins(); customMediaSizeNames = cps.getCustomMediaSizeNames(); defaultMediaIndex = cps.getDefaultMediaIndex(); rawResolutions = cps.getRawResolutions(); @@ -493,6 +500,11 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { mediaTrays = new MediaTray[trayList.size()]; mediaTrays = trayList.toArray(mediaTrays); } + + if (outputBins == null) { + outputBins = getSupportedOutputBins(); + } + urlConnection.disconnect(); init = true; @@ -827,6 +839,8 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { new PrinterResolution[supportedRes.length]; System.arraycopy(supportedRes, 0, arr, 0, supportedRes.length); return arr; + } else if (category == OutputBin.class) { + return Arrays.copyOf(outputBins, outputBins.length); } return null; @@ -1053,6 +1067,25 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { return new Media[0]; } + private OutputBin[] getSupportedOutputBins() { + if ((getAttMap != null) && getAttMap.containsKey("output-bin-supported")) { + + AttributeClass attribClass = getAttMap.get("output-bin-supported"); + + if (attribClass != null) { + String[] values = attribClass.getArrayOfStringValues(); + if (values == null || values.length == 0) { + return null; + } + OutputBin[] outputBinNames = new OutputBin[values.length]; + for (int i = 0; i < values.length; i++) { + outputBinNames[i] = CustomOutputBin.createOutputBin(values[i], values[i]); + } + return outputBinNames; + } + } + return null; + } public synchronized Class[] getSupportedAttributeCategories() { if (supportedCats != null) { @@ -1070,6 +1103,11 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { (PrintRequestAttribute)printReqAttribDefault[i]; if (getAttMap != null && getAttMap.containsKey(pra.getName()+"-supported")) { + + if (pra == OutputBin.TOP && (outputBins == null || outputBins.length == 0)) { + continue; + } + catList.add(pra.getCategory()); } } @@ -1148,6 +1186,11 @@ public class IPPPrintService implements PrintService, SunPrinterJobService { return true; } + if (category == OutputBin.class + && (outputBins == null || outputBins.length == 0)) { + return false; + } + for (int i=0;iGetStringUTFChars(env, printer, NULL); + if (name == NULL) { + (*env)->ExceptionClear(env); + JNU_ThrowOutOfMemoryError(env, "Could not create printer name"); + return NULL; + } + + // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file. + // unlink() must be caled to remove the file when finished using it. + filename = j2d_cupsGetPPD(name); + (*env)->ReleaseStringUTFChars(env, printer, name); + CHECK_NULL_RETURN(filename, NULL); + + cls = (*env)->FindClass(env, "java/lang/String"); + CHECK_NULL_RETURN(cls, NULL); + + if ((ppd = j2d_ppdOpenFile(filename)) == NULL) { + unlink(filename); + DPRINTF("CUPSfuncs::unable to open PPD %s\n", filename); + return NULL; + } + + outputBin = j2d_ppdFindOption(ppd, "OutputBin"); + if (outputBin != NULL) { + nBins = outputBin->num_choices; + } + + if (nBins > 0) { + nameArray = (*env)->NewObjectArray(env, nBins * 2, cls, NULL); + if (nameArray == NULL) { + unlink(filename); + j2d_ppdClose(ppd); + DPRINTF("CUPSfuncs::bad alloc new array\n", "") + if (!(*env)->ExceptionCheck(env)) { + JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError"); + } + return NULL; + } + + for (i = 0; outputBin!=NULL && ichoices)+i; + utf_str = JNU_NewStringPlatform(env, choice->text); + if (utf_str == NULL) { + unlink(filename); + j2d_ppdClose(ppd); + DPRINTF("CUPSfuncs::bad alloc new string text\n", "") + if (!(*env)->ExceptionCheck(env)) { + JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError"); + } + return NULL; + } + (*env)->SetObjectArrayElement(env, nameArray, i*2, utf_str); + (*env)->DeleteLocalRef(env, utf_str); + utf_str = JNU_NewStringPlatform(env, choice->choice); + if (utf_str == NULL) { + unlink(filename); + j2d_ppdClose(ppd); + DPRINTF("CUPSfuncs::bad alloc new string choice\n", "") + if (!(*env)->ExceptionCheck(env)) { + JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError"); + } + return NULL; + } + (*env)->SetObjectArrayElement(env, nameArray, i*2+1, utf_str); + (*env)->DeleteLocalRef(env, utf_str); + } + } + + j2d_ppdClose(ppd); + unlink(filename); + return nameArray; +} /* * Returns list of page sizes and imageable area. diff --git a/test/jdk/javax/print/attribute/CheckSupportedOutputBinsTest.java b/test/jdk/javax/print/attribute/CheckSupportedOutputBinsTest.java new file mode 100644 index 00000000000..67f370aeed3 --- /dev/null +++ b/test/jdk/javax/print/attribute/CheckSupportedOutputBinsTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, BELLSOFT. 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 java.util.Arrays; +import java.util.Set; + +import javax.print.PrintService; +import javax.print.PrintServiceLookup; +import javax.print.attribute.Attribute; +import javax.print.attribute.standard.OutputBin; + +/* + * @test + * @bug 8314070 + * @key printer + * @summary javax.print: Support IPP output-bin attribute extension + */ + +public class CheckSupportedOutputBinsTest { + + public static void main(String[] args) throws Exception { + + PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); + + if (services == null) { + System.out.printf("Skip the test as there are no available PrintServices.%n"); + return; + } + + System.out.printf("Print services: %d%n", services.length); + + for (PrintService service : services) { + checkSupportedOutputBins(service); + } + } + + private static void checkSupportedOutputBins(PrintService service) throws Exception { + + System.out.printf("Check printService: %s%n", service); + + boolean isOutputBinCategorySupported = service.isAttributeCategorySupported(OutputBin.class); + OutputBin defaultOutputBin = (OutputBin) service.getDefaultAttributeValue(OutputBin.class); + Set> supportedAttributeCategories = Set.of(service.getSupportedAttributeCategories()); + OutputBin[] supportedOutputBins = (OutputBin[]) service + .getSupportedAttributeValues(OutputBin.class, null, null); + + if (!isOutputBinCategorySupported) { + + if (supportedAttributeCategories.contains(OutputBin.class)) { + throw new Exception("OutputBin category is not supported" + + " and supported attribute categories contain OutputBin.class."); + } + + if (defaultOutputBin != null) { + throw new Exception("OutputBin category is not supported" + + " and the default output bin is not null."); + } + + if (supportedOutputBins != null && supportedOutputBins.length > 0) { + throw new Exception("OutputBin category is not supported" + + " and array of supported output bins is not null or its size is not zero."); + } + + return; + } + + if (!supportedAttributeCategories.contains(OutputBin.class)) { + throw new Exception("OutputBin category is supported" + + " and supported attribute categories do not contain OutputBin.class."); + } + + if (defaultOutputBin == null) { + throw new Exception("OutputBin category is supported" + + " and the default output bin is null."); + } + + if (supportedOutputBins == null || supportedOutputBins.length == 0) { + throw new Exception("OutputBin category is supported" + + " and PrintService.getSupportedAttributeValues() returns null or an array with zero elements."); + } + + if (!service.isAttributeValueSupported(defaultOutputBin, null, null)) { + throw new Exception("OutputBin category is supported" + + " and the default output bin " + defaultOutputBin + " is not supported"); + } + + for (OutputBin outputBin : supportedOutputBins) { + if (!service.isAttributeValueSupported(outputBin, null, null)) { + throw new Exception("OutputBin category is supported" + + " and the output bin " + outputBin + " from supported attribute values" + + " is not supported"); + } + } + } +} \ No newline at end of file diff --git a/test/jdk/javax/print/attribute/OutputBinAttributePrintDialogTest.java b/test/jdk/javax/print/attribute/OutputBinAttributePrintDialogTest.java new file mode 100644 index 00000000000..72bfcfe177b --- /dev/null +++ b/test/jdk/javax/print/attribute/OutputBinAttributePrintDialogTest.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, BELLSOFT. 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 javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.print.PrintService; +import javax.print.attribute.Attribute; +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.standard.DialogTypeSelection; +import javax.print.attribute.standard.MediaSizeName; +import javax.print.attribute.standard.OutputBin; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dialog; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/* + * @test + * @bug 8314070 + * @key printer + * @requires (os.family == "linux" | os.family == "mac") + * @summary javax.print: Support IPP output-bin attribute extension + * @run main/manual OutputBinAttributePrintDialogTest COMMON + * @run main/manual OutputBinAttributePrintDialogTest NATIVE + */ + +public class OutputBinAttributePrintDialogTest { + + private static final long TIMEOUT = 10 * 60_000; + private static volatile boolean testPassed = true; + private static volatile boolean testSkipped = false; + private static volatile boolean testFinished = false; + private static volatile boolean printJobCanceled = false; + private static volatile boolean timeout = false; + + private static volatile boolean isNativeDialog; + + private static volatile int testCount; + private static volatile int testTotalCount; + + public static void main(String[] args) throws Exception { + + if (args.length < 1) { + throw new RuntimeException("COMMON or NATIVE print dialog type argument is not provided!"); + } + + final DialogTypeSelection dialogTypeSelection = getDialogTypeSelection(args[0]); + isNativeDialog = (dialogTypeSelection == DialogTypeSelection.NATIVE); + + if (dialogTypeSelection == DialogTypeSelection.NATIVE) { + String os = System.getProperty("os.name").toLowerCase(); + if (os.startsWith("linux")) { + System.out.println("Skip the native print dialog type test on Linux as it is the same as the common."); + return; + } + } + + final OutputBin[] supportedOutputBins = getSupportedOutputBinttributes(); + if (supportedOutputBins == null) { + return; + } + + if (supportedOutputBins.length < 2) { + System.out.println("Skip the test as the number of supported output bins is less than 2."); + return; + } + + SwingUtilities.invokeLater(() -> { + testTotalCount = supportedOutputBins.length; + for (OutputBin outputBin : supportedOutputBins) { + if (testSkipped) { + break; + } + testPrint(dialogTypeSelection, outputBin, supportedOutputBins); + } + testFinished = true; + }); + + long time = System.currentTimeMillis() + TIMEOUT; + + while (System.currentTimeMillis() < time) { + if (!testPassed || testFinished) { + break; + } + Thread.sleep(500); + } + + timeout = true; + + closeDialogs(); + + if (testSkipped) { + System.out.printf("Test is skipped!%n"); + return; + } + + if (!testPassed) { + throw new Exception("Test failed!"); + } + + if (testCount != testTotalCount) { + throw new Exception( + "Timeout: " + testCount + " tests passed out from " + testTotalCount); + } + } + + private static void print(DialogTypeSelection dialogTypeSelection, OutputBin outputBin) throws PrinterException { + PrintRequestAttributeSet attr = new HashPrintRequestAttributeSet(); + attr.add(MediaSizeName.ISO_A4); + attr.add(dialogTypeSelection); + + for (Attribute attribute : attr.toArray()) { + System.out.printf("Used print request attribute: %s%n", attribute); + } + + PrinterJob job = PrinterJob.getPrinterJob(); + job.setJobName("Print to " + outputBin + " output bin through " + dialogTypeSelection + " print dialog"); + job.setPrintable(new OutputBinAttributePrintable(outputBin)); + + if (job.printDialog(attr)) { + job.print(); + } else if (isNativeDialog) { + printJobCanceled = true; + } else { + throw new RuntimeException(dialogTypeSelection + " print dialog for " + outputBin + " is canceled!"); + } + } + + private static class OutputBinAttributePrintable implements Printable { + + private final OutputBin outputBinAttr; + + public OutputBinAttributePrintable(OutputBin outputBinAttr) { + this.outputBinAttr = outputBinAttr; + } + + @Override + public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { + + if (pageIndex != 0) { + return NO_SUCH_PAGE; + } + + int x = (int) (pageFormat.getImageableX() + pageFormat.getImageableWidth() / 10); + int y = (int) (pageFormat.getImageableY() + pageFormat.getImageableHeight() / 5); + + Graphics2D g = (Graphics2D) graphics; + g.setColor(Color.BLACK); + g.drawString(getPageText(outputBinAttr), x, y); + return PAGE_EXISTS; + } + } + + private static String getPageText(OutputBin outputBin) { + return String.format("Output bin: %s", outputBin); + } + + private static OutputBin[] getSupportedOutputBinttributes() { + + PrinterJob printerJob = PrinterJob.getPrinterJob(); + PrintService service = printerJob.getPrintService(); + if (service == null) { + System.out.printf("No print service found."); + return null; + } + + if (!service.isAttributeCategorySupported(OutputBin.class)) { + System.out.printf("Skipping the test as OutputBin category is not supported for this printer."); + return null; + } + + Object obj = service.getSupportedAttributeValues(OutputBin.class, null, null); + + if (obj instanceof OutputBin[]) { + return (OutputBin[]) obj; + } + + throw new RuntimeException("OutputBin category is supported but no supported attribute values are returned."); + } + + private static void pass() { + testCount++; + } + + private static void skip() { + testSkipped = true; + } + + private static void fail(OutputBin outputBin) { + System.out.printf("Failed test: %s%n", getPageText(outputBin)); + testPassed = false; + } + + private static void runPrint(DialogTypeSelection dialogTypeSelection, OutputBin outputBin) { + try { + print(dialogTypeSelection, outputBin); + } catch (PrinterException e) { + e.printStackTrace(); + fail(outputBin); + closeDialogs(); + } + } + + private static void testPrint(DialogTypeSelection dialogTypeSelection, OutputBin outputBin, OutputBin[] supportedOutputBins) { + + System.out.printf("Test dialog: %s%n", dialogTypeSelection); + + String[] instructions = { + "Up to " + testTotalCount + " tests will run and it will test all output bins:", + Arrays.toString(supportedOutputBins), + "supported by the printer.", + "", + "The test is " + (testCount + 1) + " from " + testTotalCount + ".", + "", + "On-screen inspection is not possible for this printing-specific", + "test therefore its only output is a page printed to the printer", + outputBin + " output bin.", + "", + "To be able to run this test it is required to have a default", + "printer configured in your user environment.", + "", + " - Press 'Start Test' button.", + " The " + dialogTypeSelection + " print dialog should appear.", + String.join("\n", getPrintDialogInstructions(dialogTypeSelection, outputBin)), + "", + "Visual inspection of the printed pages is needed.", + "", + "A passing test will print the page with the text: '" + getPageText(outputBin) + "'", + "to the corresponding printer " + outputBin + " ouput bin.", + "", + "The test fails if the page is not printed in to the corresponding output bin.", + }; + + String title = String.format("Print %s dialog with %s output bin test: %d from %d", + dialogTypeSelection, outputBin, testCount + 1, testTotalCount); + final JDialog dialog = new JDialog((Frame) null, title, Dialog.ModalityType.DOCUMENT_MODAL); + JTextArea textArea = new JTextArea(String.join("\n", instructions)); + textArea.setEditable(false); + final JButton testButton = new JButton("Start Test"); + final JButton skipButton = new JButton("Skip Test"); + final JButton passButton = new JButton("PASS"); + skipButton.setEnabled(false); + passButton.setEnabled(false); + passButton.addActionListener((e) -> { + pass(); + dialog.dispose(); + }); + skipButton.addActionListener((e) -> { + skip(); + dialog.dispose(); + }); + final JButton failButton = new JButton("FAIL"); + failButton.setEnabled(false); + failButton.addActionListener((e) -> { + fail(outputBin); + dialog.dispose(); + }); + testButton.addActionListener((e) -> { + testButton.setEnabled(false); + runPrint(dialogTypeSelection, outputBin); + skipButton.setEnabled(true); + passButton.setEnabled(true); + failButton.setEnabled(true); + }); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(textArea, BorderLayout.CENTER); + JPanel buttonPanel = new JPanel(new FlowLayout()); + buttonPanel.add(testButton); + if (isNativeDialog) { + buttonPanel.add(skipButton); + } + buttonPanel.add(passButton); + buttonPanel.add(failButton); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + dialog.add(mainPanel); + dialog.pack(); + dialog.setVisible(true); + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.out.println("Dialog closing"); + fail(outputBin); + } + }); + } + + private static void closeDialogs() { + for (Window w : Dialog.getWindows()) { + w.dispose(); + } + } + + private static DialogTypeSelection getDialogTypeSelection(String dialogTypeSelection) { + switch (dialogTypeSelection) { + case "COMMON": + return DialogTypeSelection.COMMON; + case "NATIVE": + return DialogTypeSelection.NATIVE; + default: + throw new RuntimeException("Unknown dialog type selection: " + dialogTypeSelection); + } + } + + private static String[] getPrintDialogInstructions(DialogTypeSelection dialogTypeSelection, OutputBin outputBin) { + if (dialogTypeSelection == DialogTypeSelection.COMMON) { + return new String[]{ + " - Select 'Appearance' tab.", + " - Select '" + outputBin + "' output tray from 'Output trays' combo box.", + " - Press 'Print' button." + }; + } else if (dialogTypeSelection == DialogTypeSelection.NATIVE) { + return new String[]{ + " - Press 'Show Details' buttons if the details are hidded.", + " - Check that the native print dialog contains 'Finishing Options' in the drop-down list.", + " - If there is no 'Finishing Options' in the drop-down list then", + " - Press 'Cancel' button on the print dialog.", + " - Press 'Skip Test' button on the test dialog.", + " otherwise", + " - Select 'Finishing Options' from the drop-down list.", + " - Select '" + outputBin + "' Output Bin.", + " - Press 'Print' button." + }; + } + throw new RuntimeException("Unknown dialog type selection: " + dialogTypeSelection); + } +} diff --git a/test/jdk/javax/print/attribute/OutputBinAttributeTest.java b/test/jdk/javax/print/attribute/OutputBinAttributeTest.java new file mode 100644 index 00000000000..4d3ca71f5fb --- /dev/null +++ b/test/jdk/javax/print/attribute/OutputBinAttributeTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, BELLSOFT. 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 javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.print.PrintService; +import javax.print.attribute.Attribute; +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.standard.MediaSizeName; +import javax.print.attribute.standard.OutputBin; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dialog; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/* + * @test + * @bug 8314070 + * @key printer + * @requires (os.family == "linux" | os.family == "mac") + * @summary javax.print: Support IPP output-bin attribute extension + * @run main/manual OutputBinAttributeTest + */ + +public class OutputBinAttributeTest { + + private static final long TIMEOUT = 10 * 60_000; + private static volatile boolean testPassed = true; + private static volatile boolean testFinished = false; + private static volatile boolean timeout = false; + + private static volatile int testCount; + private static volatile int testTotalCount; + + public static void main(String[] args) throws Exception { + + SwingUtilities.invokeLater(() -> { + Set supportedOutputBins = getSupportedOutputBinttributes(); + if (supportedOutputBins != null) { + if (supportedOutputBins.size() > 1) { + testTotalCount = supportedOutputBins.size(); + for (OutputBin outputBin : supportedOutputBins) { + testPrint(outputBin, supportedOutputBins); + } + } else { + System.out.println("Skip the test as the number of supported output bins is less than 2."); + } + } + testFinished = true; + }); + + long time = System.currentTimeMillis() + TIMEOUT; + + while (System.currentTimeMillis() < time) { + if (!testPassed || testFinished) { + break; + } + Thread.sleep(500); + } + + timeout = true; + + closeDialogs(); + + if (!testPassed) { + throw new Exception("Test failed!"); + } + + if (testCount != testTotalCount) { + throw new Exception( + "Timeout: " + testCount + " tests passed out from " + testTotalCount); + } + } + + private static void print(OutputBin outputBin) throws PrinterException { + PrintRequestAttributeSet attr = new HashPrintRequestAttributeSet(); + attr.add(MediaSizeName.ISO_A4); + attr.add(outputBin); + + for (Attribute attribute : attr.toArray()) { + System.out.printf("Used print request attribute: %s%n", attribute); + } + + PrinterJob job = PrinterJob.getPrinterJob(); + job.setJobName("Print to " + outputBin + " output bin"); + job.setPrintable(new OutputBinAttributePrintable(outputBin)); + + job.print(attr); + } + + private static class OutputBinAttributePrintable implements Printable { + + private final OutputBin outputBinAttr; + + public OutputBinAttributePrintable(OutputBin outputBinAttr) { + this.outputBinAttr = outputBinAttr; + } + + @Override + public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { + + if (pageIndex != 0) { + return NO_SUCH_PAGE; + } + + int x = (int) (pageFormat.getImageableX() + pageFormat.getImageableWidth() / 10); + int y = (int) (pageFormat.getImageableY() + pageFormat.getImageableHeight() / 5); + + Graphics2D g = (Graphics2D) graphics; + g.setColor(Color.BLACK); + g.drawString(getPageText(outputBinAttr), x, y); + return PAGE_EXISTS; + } + } + + private static String getPageText(OutputBin outputBin) { + return String.format("Output bin: %s", outputBin); + } + + private static Set getSupportedOutputBinttributes() { + Set supportedOutputBins = new HashSet<>(); + + PrinterJob printerJob = PrinterJob.getPrinterJob(); + PrintService service = printerJob.getPrintService(); + if (service == null) { + System.out.printf("No print service found."); + return null; + } + + if (!service.isAttributeCategorySupported(OutputBin.class)) { + System.out.printf("Skipping the test as OutputBin category is not supported for this printer."); + return null; + } + + Object obj = service.getSupportedAttributeValues(OutputBin.class, null, null); + + if (obj instanceof OutputBin[]) { + Collections.addAll(supportedOutputBins, (OutputBin[]) obj); + return supportedOutputBins; + } + + throw new RuntimeException("OutputBin category is supported but no supported attribute values are returned."); + } + + private static void pass() { + testCount++; + } + + private static void fail(OutputBin outputBin) { + System.out.printf("Failed test: %s%n", getPageText(outputBin)); + testPassed = false; + } + + private static void runPrint(OutputBin outputBin) { + try { + print(outputBin); + } catch (PrinterException e) { + e.printStackTrace(); + fail(outputBin); + } + } + + private static void testPrint(OutputBin outputBin, Set supportedOutputBins) { + + String[] instructions = { + "Up to " + testTotalCount + " tests will run and it will test all output bins:", + supportedOutputBins.toString(), + "supported by the printer.", + "", + "The test is " + (testCount + 1) + " from " + testTotalCount + ".", + "", + "On-screen inspection is not possible for this printing-specific", + "test therefore its only output is a page printed to the printer", + outputBin + " output bin.", + "To be able to run this test it is required to have a default", + "printer configured in your user environment.", + "", + "Visual inspection of the printed pages is needed.", + "", + "A passing test will print the page with the text: '" + getPageText(outputBin) + "'", + "to the corresponding printer " + outputBin + " ouput bin.", + "", + "The test fails if the page is not printed in to the corresponding output bin.", + }; + + String title = String.format("Print %s output bin test: %d from %d", + outputBin, testCount + 1, testTotalCount); + final JDialog dialog = new JDialog((Frame) null, title, Dialog.ModalityType.DOCUMENT_MODAL); + JTextArea textArea = new JTextArea(String.join("\n", instructions)); + textArea.setEditable(false); + final JButton testButton = new JButton("Start Test"); + final JButton passButton = new JButton("PASS"); + passButton.setEnabled(false); + passButton.addActionListener((e) -> { + pass(); + dialog.dispose(); + }); + final JButton failButton = new JButton("FAIL"); + failButton.setEnabled(false); + failButton.addActionListener((e) -> { + fail(outputBin); + dialog.dispose(); + }); + testButton.addActionListener((e) -> { + testButton.setEnabled(false); + runPrint(outputBin); + passButton.setEnabled(true); + failButton.setEnabled(true); + }); + + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(textArea, BorderLayout.CENTER); + JPanel buttonPanel = new JPanel(new FlowLayout()); + buttonPanel.add(testButton); + buttonPanel.add(passButton); + buttonPanel.add(failButton); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + dialog.add(mainPanel); + dialog.pack(); + dialog.setVisible(true); + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.out.println("Dialog closing"); + fail(outputBin); + } + }); + } + + private static void closeDialogs() { + for (Window w : Dialog.getWindows()) { + w.dispose(); + } + } +}