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.
+ *
+ * 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 extends Attribute> 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 @@
*
*
* 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();
+ }
+ }
+}