1 /*
   2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.awt.BorderLayout;
  29 import java.awt.Color;
  30 import java.awt.Component;
  31 import java.awt.Container;
  32 import java.awt.Dialog;
  33 import java.awt.FlowLayout;
  34 import java.awt.Frame;
  35 import java.awt.GraphicsConfiguration;
  36 import java.awt.GridBagLayout;
  37 import java.awt.GridBagConstraints;
  38 import java.awt.GridLayout;
  39 import java.awt.Insets;
  40 import java.awt.Toolkit;
  41 import java.awt.Window;
  42 import java.awt.event.ActionEvent;
  43 import java.awt.event.ActionListener;
  44 import java.awt.event.FocusEvent;
  45 import java.awt.event.FocusListener;
  46 import java.awt.event.ItemEvent;
  47 import java.awt.event.ItemListener;
  48 import java.awt.event.WindowEvent;
  49 import java.awt.event.WindowAdapter;
  50 import java.awt.print.PrinterJob;
  51 import java.io.File;
  52 import java.io.FilePermission;
  53 import java.io.IOException;
  54 import java.net.URI;
  55 import java.net.URL;
  56 import java.text.DecimalFormat;
  57 import java.util.Locale;
  58 import java.util.ResourceBundle;
  59 import java.util.Vector;
  60 import javax.print.*;
  61 import javax.print.attribute.*;
  62 import javax.print.attribute.standard.*;
  63 import javax.swing.*;
  64 import javax.swing.border.Border;
  65 import javax.swing.border.EmptyBorder;
  66 import javax.swing.border.TitledBorder;
  67 import javax.swing.event.ChangeEvent;
  68 import javax.swing.event.ChangeListener;
  69 import javax.swing.event.DocumentEvent;
  70 import javax.swing.event.DocumentListener;
  71 import javax.swing.event.PopupMenuEvent;
  72 import javax.swing.event.PopupMenuListener;
  73 import javax.swing.text.NumberFormatter;
  74 import sun.print.SunPageSelection;
  75 import java.awt.event.KeyEvent;
  76 import java.net.URISyntaxException;
  77 import java.lang.reflect.Field;
  78 import java.net.MalformedURLException;
  79 
  80 /**
  81  * A class which implements a cross-platform print dialog.
  82  *
  83  * @author  Chris Campbell
  84  */
  85 @SuppressWarnings("serial") // Superclass is not serializable across versions
  86 public class ServiceDialog extends JDialog implements ActionListener {
  87 
  88     /**
  89      * Waiting print status (user response pending).
  90      */
  91     public static final int WAITING = 0;
  92 
  93     /**
  94      * Approve print status (user activated "Print" or "OK").
  95      */
  96     public static final int APPROVE = 1;
  97 
  98     /**
  99      * Cancel print status (user activated "Cancel");
 100      */
 101     public static final int CANCEL = 2;
 102 
 103     private static final String strBundle = "sun.print.resources.serviceui";
 104     private static final Insets panelInsets = new Insets(6, 6, 6, 6);
 105     private static final Insets compInsets = new Insets(3, 6, 3, 6);
 106 
 107     private static ResourceBundle messageRB;
 108     private JTabbedPane tpTabs;
 109     private JButton btnCancel, btnApprove;
 110     private PrintService[] services;
 111     private int defaultServiceIndex;
 112     private PrintRequestAttributeSet asOriginal;
 113     private HashPrintRequestAttributeSet asCurrent;
 114     private PrintService psCurrent;
 115     private DocFlavor docFlavor;
 116     private int status;
 117 
 118     private ValidatingFileChooser jfc;
 119 
 120     private GeneralPanel pnlGeneral;
 121     private PageSetupPanel pnlPageSetup;
 122     private AppearancePanel pnlAppearance;
 123 
 124     private boolean isAWT = false;
 125     static {
 126         initResource();
 127     }
 128 
 129 
 130     /**
 131      * Constructor for the "standard" print dialog (containing all relevant
 132      * tabs)
 133      */
 134     public ServiceDialog(GraphicsConfiguration gc,
 135                          int x, int y,
 136                          PrintService[] services,
 137                          int defaultServiceIndex,
 138                          DocFlavor flavor,
 139                          PrintRequestAttributeSet attributes,
 140                          Window window)
 141     {
 142         super(window, getMsg("dialog.printtitle"), Dialog.DEFAULT_MODALITY_TYPE, gc);
 143         initPrintDialog(x, y, services, defaultServiceIndex,
 144                         flavor, attributes);
 145     }
 146 
 147     /**
 148      * Initialize print dialog.
 149      */
 150     void initPrintDialog(int x, int y,
 151                          PrintService[] services,
 152                          int defaultServiceIndex,
 153                          DocFlavor flavor,
 154                          PrintRequestAttributeSet attributes)
 155     {
 156         this.services = services;
 157         this.defaultServiceIndex = defaultServiceIndex;
 158         this.asOriginal = attributes;
 159         this.asCurrent = new HashPrintRequestAttributeSet(attributes);
 160         this.psCurrent = services[defaultServiceIndex];
 161         this.docFlavor = flavor;
 162         SunPageSelection pages =
 163             (SunPageSelection)attributes.get(SunPageSelection.class);
 164         if (pages != null) {
 165             isAWT = true;
 166         }
 167 
 168         if (attributes.get(DialogOwner.class) != null) {
 169             DialogOwner owner = (DialogOwner)attributes.get(DialogOwner.class);
 170             /* When the ServiceDialog is constructed the caller of the
 171              * constructor checks for this attribute and if it specifies a
 172              * window then it will use that in the constructor instead of
 173              * inferring one from keyboard focus.
 174              * In this case the owner of the dialog is the same as that
 175              * specified in the attribute and we do not need to set the
 176              * on top property
 177              */
 178             if ((getOwner() == null) || (owner.getOwner() != getOwner())) {
 179                 try {
 180                     setAlwaysOnTop(true);
 181                 } catch (SecurityException e) {
 182                 }
 183             }
 184         }
 185         Container c = getContentPane();
 186         c.setLayout(new BorderLayout());
 187 
 188         tpTabs = new JTabbedPane();
 189         tpTabs.setBorder(new EmptyBorder(5, 5, 5, 5));
 190 
 191         String gkey = getMsg("tab.general");
 192         int gmnemonic = getVKMnemonic("tab.general");
 193         pnlGeneral = new GeneralPanel();
 194         tpTabs.add(gkey, pnlGeneral);
 195         tpTabs.setMnemonicAt(0, gmnemonic);
 196 
 197         String pkey = getMsg("tab.pagesetup");
 198         int pmnemonic = getVKMnemonic("tab.pagesetup");
 199         pnlPageSetup = new PageSetupPanel();
 200         tpTabs.add(pkey, pnlPageSetup);
 201         tpTabs.setMnemonicAt(1, pmnemonic);
 202 
 203         String akey = getMsg("tab.appearance");
 204         int amnemonic = getVKMnemonic("tab.appearance");
 205         pnlAppearance = new AppearancePanel();
 206         tpTabs.add(akey, pnlAppearance);
 207         tpTabs.setMnemonicAt(2, amnemonic);
 208 
 209         c.add(tpTabs, BorderLayout.CENTER);
 210 
 211         updatePanels();
 212 
 213         JPanel pnlSouth = new JPanel(new FlowLayout(FlowLayout.TRAILING));
 214         btnApprove = createExitButton("button.print", this);
 215         pnlSouth.add(btnApprove);
 216         getRootPane().setDefaultButton(btnApprove);
 217         btnCancel = createExitButton("button.cancel", this);
 218         handleEscKey(btnCancel);
 219         pnlSouth.add(btnCancel);
 220         c.add(pnlSouth, BorderLayout.SOUTH);
 221 
 222         addWindowListener(new WindowAdapter() {
 223             public void windowClosing(WindowEvent event) {
 224                 dispose(CANCEL);
 225             }
 226         });
 227 
 228         getAccessibleContext().setAccessibleDescription(getMsg("dialog.printtitle"));
 229         setResizable(false);
 230         setLocation(x, y);
 231         pack();
 232     }
 233 
 234    /**
 235      * Constructor for the solitary "page setup" dialog
 236      */
 237     public ServiceDialog(GraphicsConfiguration gc,
 238                          int x, int y,
 239                          PrintService ps,
 240                          DocFlavor flavor,
 241                          PrintRequestAttributeSet attributes,
 242                          Window window)
 243     {
 244         super(window, getMsg("dialog.pstitle"), Dialog.DEFAULT_MODALITY_TYPE, gc);
 245         initPageDialog(x, y, ps, flavor, attributes);
 246     }
 247 
 248     /**
 249      * Initialize "page setup" dialog
 250      */
 251     void initPageDialog(int x, int y,
 252                          PrintService ps,
 253                          DocFlavor flavor,
 254                          PrintRequestAttributeSet attributes)
 255     {
 256         this.psCurrent = ps;
 257         this.docFlavor = flavor;
 258         this.asOriginal = attributes;
 259         this.asCurrent = new HashPrintRequestAttributeSet(attributes);
 260 
 261         if (attributes.get(DialogOwner.class) != null) {
 262             /* See comments in same block in initPrintDialog */
 263             DialogOwner owner = (DialogOwner)attributes.get(DialogOwner.class);
 264             if ((getOwner() == null) || (owner.getOwner() != getOwner())) {
 265                 try {
 266                     setAlwaysOnTop(true);
 267                 } catch (SecurityException e) {
 268                 }
 269             }
 270         }
 271 
 272         Container c = getContentPane();
 273         c.setLayout(new BorderLayout());
 274 
 275         pnlPageSetup = new PageSetupPanel();
 276         c.add(pnlPageSetup, BorderLayout.CENTER);
 277 
 278         pnlPageSetup.updateInfo();
 279 
 280         JPanel pnlSouth = new JPanel(new FlowLayout(FlowLayout.TRAILING));
 281         btnApprove = createExitButton("button.ok", this);
 282         pnlSouth.add(btnApprove);
 283         getRootPane().setDefaultButton(btnApprove);
 284         btnCancel = createExitButton("button.cancel", this);
 285         handleEscKey(btnCancel);
 286         pnlSouth.add(btnCancel);
 287         c.add(pnlSouth, BorderLayout.SOUTH);
 288 
 289         addWindowListener(new WindowAdapter() {
 290             public void windowClosing(WindowEvent event) {
 291                 dispose(CANCEL);
 292             }
 293         });
 294 
 295         getAccessibleContext().setAccessibleDescription(getMsg("dialog.pstitle"));
 296         setResizable(false);
 297         setLocation(x, y);
 298         pack();
 299     }
 300 
 301     /**
 302      * Performs Cancel when Esc key is pressed.
 303      */
 304     private void handleEscKey(JButton btnCancel) {
 305         @SuppressWarnings("serial") // anonymous class
 306         Action cancelKeyAction = new AbstractAction() {
 307             public void actionPerformed(ActionEvent e) {
 308                 dispose(CANCEL);
 309             }
 310         };
 311         KeyStroke cancelKeyStroke =
 312             KeyStroke.getKeyStroke((char)KeyEvent.VK_ESCAPE, 0);
 313         InputMap inputMap =
 314             btnCancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
 315         ActionMap actionMap = btnCancel.getActionMap();
 316 
 317         if (inputMap != null && actionMap != null) {
 318             inputMap.put(cancelKeyStroke, "cancel");
 319             actionMap.put("cancel", cancelKeyAction);
 320         }
 321     }
 322 
 323 
 324     /**
 325      * Returns the current status of the dialog (whether the user has selected
 326      * the "Print" or "Cancel" button)
 327      */
 328     public int getStatus() {
 329         return status;
 330     }
 331 
 332     /**
 333      * Returns an AttributeSet based on whether or not the user cancelled the
 334      * dialog.  If the user selected "Print" we return their new selections,
 335      * otherwise we return the attributes that were passed in initially.
 336      */
 337     public PrintRequestAttributeSet getAttributes() {
 338         if (status == APPROVE) {
 339             return asCurrent;
 340         } else {
 341             return asOriginal;
 342         }
 343     }
 344 
 345     /**
 346      * Returns a PrintService based on whether or not the user cancelled the
 347      * dialog.  If the user selected "Print" we return the user's selection
 348      * for the PrintService, otherwise we return null.
 349      */
 350     public PrintService getPrintService() {
 351         if (status == APPROVE) {
 352             return psCurrent;
 353         } else {
 354             return null;
 355         }
 356     }
 357 
 358     /**
 359      * Sets the current status flag for the dialog and disposes it (thus
 360      * returning control of the parent frame back to the user)
 361      */
 362     public void dispose(int status) {
 363         this.status = status;
 364 
 365         super.dispose();
 366     }
 367 
 368     public void actionPerformed(ActionEvent e) {
 369         Object source = e.getSource();
 370         boolean approved = false;
 371 
 372         if (source == btnApprove) {
 373             approved = true;
 374 
 375             if (pnlGeneral != null) {
 376                 if (pnlGeneral.isPrintToFileRequested()) {
 377                     approved = showFileChooser();
 378                 } else {
 379                     asCurrent.remove(Destination.class);
 380                 }
 381             }
 382         }
 383 
 384         dispose(approved ? APPROVE : CANCEL);
 385     }
 386 
 387     /**
 388      * Displays a JFileChooser that allows the user to select the destination
 389      * for "Print To File"
 390      */
 391     private boolean showFileChooser() {
 392         Class<Destination> dstCategory = Destination.class;
 393 
 394         Destination dst = (Destination)asCurrent.get(dstCategory);
 395         if (dst == null) {
 396             dst = (Destination)asOriginal.get(dstCategory);
 397             if (dst == null) {
 398                 dst = (Destination)psCurrent.getDefaultAttributeValue(dstCategory);
 399                 // "dst" should not be null. The following code
 400                 // is only added to safeguard against a possible
 401                 // buggy implementation of a PrintService having a
 402                 // null default Destination.
 403                 if (dst == null) {
 404                     try {
 405                         dst = new Destination(new URI("file:out.prn"));
 406                     } catch (URISyntaxException e) {
 407                     }
 408                 }
 409             }
 410         }
 411 
 412         File fileDest;
 413         if (dst != null) {
 414             try {
 415                 fileDest = new File(dst.getURI());
 416             } catch (Exception e) {
 417                 // all manner of runtime exceptions possible
 418                 fileDest = new File("out.prn");
 419             }
 420         } else {
 421             fileDest = new File("out.prn");
 422         }
 423 
 424         ValidatingFileChooser jfc = new ValidatingFileChooser();
 425         jfc.setApproveButtonText(getMsg("button.ok"));
 426         jfc.setDialogTitle(getMsg("dialog.printtofile"));
 427         jfc.setDialogType(JFileChooser.SAVE_DIALOG);
 428         jfc.setSelectedFile(fileDest);
 429 
 430         int returnVal = jfc.showDialog(this, null);
 431         if (returnVal == JFileChooser.APPROVE_OPTION) {
 432             fileDest = jfc.getSelectedFile();
 433 
 434             try {
 435                 asCurrent.add(new Destination(fileDest.toURI()));
 436             } catch (Exception e) {
 437                 asCurrent.remove(dstCategory);
 438             }
 439         } else {
 440             asCurrent.remove(dstCategory);
 441         }
 442 
 443         return (returnVal == JFileChooser.APPROVE_OPTION);
 444     }
 445 
 446     /**
 447      * Updates each of the top level panels
 448      */
 449     private void updatePanels() {
 450         pnlGeneral.updateInfo();
 451         pnlPageSetup.updateInfo();
 452         pnlAppearance.updateInfo();
 453     }
 454 
 455     /**
 456      * Initialize ResourceBundle
 457      */
 458     public static void initResource() {
 459         java.security.AccessController.doPrivileged(
 460             new java.security.PrivilegedAction<Object>() {
 461                 public Object run() {
 462                     try {
 463                         messageRB = ResourceBundle.getBundle(strBundle);
 464                         return null;
 465                     } catch (java.util.MissingResourceException e) {
 466                         throw new Error("Fatal: Resource for ServiceUI " +
 467                                         "is missing");
 468                     }
 469                 }
 470             }
 471         );
 472     }
 473 
 474     /**
 475      * Returns message string from resource
 476      */
 477     public static String getMsg(String key) {
 478         try {
 479             return removeMnemonics(messageRB.getString(key));
 480         } catch (java.util.MissingResourceException e) {
 481             throw new Error("Fatal: Resource for ServiceUI is broken; " +
 482                             "there is no " + key + " key in resource");
 483         }
 484     }
 485 
 486     private static String removeMnemonics(String s) {
 487         int i = s.indexOf('&');
 488         int len = s.length();
 489         if (i < 0 || i == (len - 1)) {
 490             return s;
 491         }
 492         int j = s.indexOf('&', i+1);
 493         if (j == i+1) {
 494             if (j+1 == len) {
 495                 return s.substring(0, i+1);  // string ends with &&
 496             } else {
 497                 return s.substring(0, i+1) + removeMnemonics(s.substring(j+1));
 498             }
 499         }
 500         // ok first & not double &&
 501         if (i == 0) {
 502             return removeMnemonics(s.substring(1));
 503         } else {
 504             return (s.substring(0, i) + removeMnemonics(s.substring(i+1)));
 505         }
 506     }
 507 
 508 
 509     /**
 510      * Returns mnemonic character from resource
 511      */
 512     private static char getMnemonic(String key) {
 513         String str = messageRB.getString(key).replace("&&", "");
 514         int index = str.indexOf('&');
 515         if (0 <= index && index < str.length() - 1) {
 516             char c = str.charAt(index + 1);
 517             return Character.toUpperCase(c);
 518         } else {
 519             return (char)0;
 520         }
 521     }
 522 
 523     /**
 524      * Returns the mnemonic as a KeyEvent.VK constant from the resource.
 525      */
 526     static Class<?> _keyEventClazz = null;
 527     private static int getVKMnemonic(String key) {
 528         String s = String.valueOf(getMnemonic(key));
 529         if ( s == null || s.length() != 1) {
 530             return 0;
 531         }
 532         String vkString = "VK_" + s.toUpperCase();
 533 
 534         try {
 535             if (_keyEventClazz == null) {
 536                 _keyEventClazz= Class.forName("java.awt.event.KeyEvent",
 537                                  true, (ServiceDialog.class).getClassLoader());
 538             }
 539             Field field = _keyEventClazz.getDeclaredField(vkString);
 540             int value = field.getInt(null);
 541             return value;
 542         } catch (Exception e) {
 543         }
 544         return 0;
 545     }
 546 
 547     /**
 548      * Returns URL for image resource
 549      */
 550     private static URL getImageResource(final String key) {
 551         URL url = java.security.AccessController.doPrivileged(
 552                        new java.security.PrivilegedAction<URL>() {
 553                 public URL run() {
 554                     URL url = ServiceDialog.class.getResource(
 555                                                   "resources/" + key);
 556                     return url;
 557                 }
 558         });
 559 
 560         if (url == null) {
 561             throw new Error("Fatal: Resource for ServiceUI is broken; " +
 562                             "there is no " + key + " key in resource");
 563         }
 564 
 565         return url;
 566     }
 567 
 568     /**
 569      * Creates a new JButton and sets its text, mnemonic, and ActionListener
 570      */
 571     private static JButton createButton(String key, ActionListener al) {
 572         JButton btn = new JButton(getMsg(key));
 573         btn.setMnemonic(getMnemonic(key));
 574         btn.addActionListener(al);
 575 
 576         return btn;
 577     }
 578 
 579     /**
 580      * Creates a new JButton and sets its text, and ActionListener
 581      */
 582     private static JButton createExitButton(String key, ActionListener al) {
 583         String str = getMsg(key);
 584         JButton btn = new JButton(str);
 585         btn.addActionListener(al);
 586         btn.getAccessibleContext().setAccessibleDescription(str);
 587         return btn;
 588     }
 589 
 590     /**
 591      * Creates a new JCheckBox and sets its text, mnemonic, and ActionListener
 592      */
 593     private static JCheckBox createCheckBox(String key, ActionListener al) {
 594         JCheckBox cb = new JCheckBox(getMsg(key));
 595         cb.setMnemonic(getMnemonic(key));
 596         cb.addActionListener(al);
 597 
 598         return cb;
 599     }
 600 
 601     /**
 602      * Creates a new JRadioButton and sets its text, mnemonic,
 603      * and ActionListener
 604      */
 605     private static JRadioButton createRadioButton(String key,
 606                                                   ActionListener al)
 607     {
 608         JRadioButton rb = new JRadioButton(getMsg(key));
 609         rb.setMnemonic(getMnemonic(key));
 610         rb.addActionListener(al);
 611 
 612         return rb;
 613     }
 614 
 615   /**
 616    * Creates a  pop-up dialog for "no print service"
 617    */
 618     public static void showNoPrintService(GraphicsConfiguration gc)
 619     {
 620         Frame dlgFrame = new Frame(gc);
 621         JOptionPane.showMessageDialog(dlgFrame,
 622                                       getMsg("dialog.noprintermsg"));
 623         dlgFrame.dispose();
 624     }
 625 
 626     /**
 627      * Sets the constraints for the GridBagLayout and adds the Component
 628      * to the given Container
 629      */
 630     private static void addToGB(Component comp, Container cont,
 631                                 GridBagLayout gridbag,
 632                                 GridBagConstraints constraints)
 633     {
 634         gridbag.setConstraints(comp, constraints);
 635         cont.add(comp);
 636     }
 637 
 638     /**
 639      * Adds the AbstractButton to both the given ButtonGroup and Container
 640      */
 641     private static void addToBG(AbstractButton button, Container cont,
 642                                 ButtonGroup bg)
 643     {
 644         bg.add(button);
 645         cont.add(button);
 646     }
 647 
 648 
 649 
 650 
 651     /**
 652      * The "General" tab.  Includes the controls for PrintService,
 653      * PageRange, and Copies/Collate.
 654      */
 655     @SuppressWarnings("serial") // Superclass is not serializable across versions
 656     private class GeneralPanel extends JPanel {
 657 
 658         private PrintServicePanel pnlPrintService;
 659         private PrintRangePanel pnlPrintRange;
 660         private CopiesPanel pnlCopies;
 661 
 662         public GeneralPanel() {
 663             super();
 664 
 665             GridBagLayout gridbag = new GridBagLayout();
 666             GridBagConstraints c = new GridBagConstraints();
 667 
 668             setLayout(gridbag);
 669 
 670             c.fill = GridBagConstraints.BOTH;
 671             c.insets = panelInsets;
 672             c.weightx = 1.0;
 673             c.weighty = 1.0;
 674 
 675             c.gridwidth = GridBagConstraints.REMAINDER;
 676             pnlPrintService = new PrintServicePanel();
 677             addToGB(pnlPrintService, this, gridbag, c);
 678 
 679             c.gridwidth = GridBagConstraints.RELATIVE;
 680             pnlPrintRange = new PrintRangePanel();
 681             addToGB(pnlPrintRange, this, gridbag, c);
 682 
 683             c.gridwidth = GridBagConstraints.REMAINDER;
 684             pnlCopies = new CopiesPanel();
 685             addToGB(pnlCopies, this, gridbag, c);
 686         }
 687 
 688         public boolean isPrintToFileRequested() {
 689             return (pnlPrintService.isPrintToFileSelected());
 690         }
 691 
 692         public void updateInfo() {
 693             pnlPrintService.updateInfo();
 694             pnlPrintRange.updateInfo();
 695             pnlCopies.updateInfo();
 696         }
 697     }
 698 
 699     @SuppressWarnings("serial") // Superclass is not serializable across versions
 700     private class PrintServicePanel extends JPanel
 701         implements ActionListener, ItemListener, PopupMenuListener
 702     {
 703         private final String strTitle = getMsg("border.printservice");
 704         private FilePermission printToFilePermission;
 705         private JButton btnProperties;
 706         private JCheckBox cbPrintToFile;
 707         private JComboBox<String> cbName;
 708         private JLabel lblType, lblStatus, lblInfo;
 709         private ServiceUIFactory uiFactory;
 710         private boolean changedService = false;
 711         private boolean filePermission;
 712 
 713         public PrintServicePanel() {
 714             super();
 715 
 716             uiFactory = psCurrent.getServiceUIFactory();
 717 
 718             GridBagLayout gridbag = new GridBagLayout();
 719             GridBagConstraints c = new GridBagConstraints();
 720 
 721             setLayout(gridbag);
 722             setBorder(BorderFactory.createTitledBorder(strTitle));
 723 
 724             String[] psnames = new String[services.length];
 725             for (int i = 0; i < psnames.length; i++) {
 726                 psnames[i] = services[i].getName();
 727             }
 728             cbName = new JComboBox<>(psnames);
 729             cbName.setSelectedIndex(defaultServiceIndex);
 730             cbName.addItemListener(this);
 731             cbName.addPopupMenuListener(this);
 732 
 733             c.fill = GridBagConstraints.BOTH;
 734             c.insets = compInsets;
 735 
 736             c.weightx = 0.0;
 737             JLabel lblName = new JLabel(getMsg("label.psname"), JLabel.TRAILING);
 738             lblName.setDisplayedMnemonic(getMnemonic("label.psname"));
 739             lblName.setLabelFor(cbName);
 740             addToGB(lblName, this, gridbag, c);
 741             c.weightx = 1.0;
 742             c.gridwidth = GridBagConstraints.RELATIVE;
 743             addToGB(cbName, this, gridbag, c);
 744             c.weightx = 0.0;
 745             c.gridwidth = GridBagConstraints.REMAINDER;
 746             btnProperties = createButton("button.properties", this);
 747             addToGB(btnProperties, this, gridbag, c);
 748 
 749             c.weighty = 1.0;
 750             lblStatus = addLabel(getMsg("label.status"), gridbag, c);
 751             lblStatus.setLabelFor(null);
 752 
 753             lblType = addLabel(getMsg("label.pstype"), gridbag, c);
 754             lblType.setLabelFor(null);
 755 
 756             c.gridwidth = 1;
 757             addToGB(new JLabel(getMsg("label.info"), JLabel.TRAILING),
 758                     this, gridbag, c);
 759             c.gridwidth = GridBagConstraints.RELATIVE;
 760             lblInfo = new JLabel();
 761             lblInfo.setLabelFor(null);
 762 
 763             addToGB(lblInfo, this, gridbag, c);
 764 
 765             c.gridwidth = GridBagConstraints.REMAINDER;
 766             cbPrintToFile = createCheckBox("checkbox.printtofile", this);
 767             addToGB(cbPrintToFile, this, gridbag, c);
 768 
 769             filePermission = allowedToPrintToFile();
 770         }
 771 
 772         public boolean isPrintToFileSelected() {
 773             return cbPrintToFile.isSelected();
 774         }
 775 
 776         private JLabel addLabel(String text,
 777                                 GridBagLayout gridbag, GridBagConstraints c)
 778         {
 779             c.gridwidth = 1;
 780             addToGB(new JLabel(text, JLabel.TRAILING), this, gridbag, c);
 781 
 782             c.gridwidth = GridBagConstraints.REMAINDER;
 783             JLabel label = new JLabel();
 784             addToGB(label, this, gridbag, c);
 785 
 786             return label;
 787         }
 788 
 789         @SuppressWarnings("deprecation")
 790         public void actionPerformed(ActionEvent e) {
 791             Object source = e.getSource();
 792 
 793             if (source == btnProperties) {
 794                 if (uiFactory != null) {
 795                     JDialog dialog = (JDialog)uiFactory.getUI(
 796                                                ServiceUIFactory.MAIN_UIROLE,
 797                                                ServiceUIFactory.JDIALOG_UI);
 798 
 799                     if (dialog != null) {
 800                         dialog.show();
 801                     } else {
 802                         DocumentPropertiesUI docPropertiesUI = null;
 803                         try {
 804                             docPropertiesUI =
 805                                 (DocumentPropertiesUI)uiFactory.getUI
 806                                 (DocumentPropertiesUI.DOCUMENTPROPERTIES_ROLE,
 807                                  DocumentPropertiesUI.DOCPROPERTIESCLASSNAME);
 808                         } catch (Exception ex) {
 809                         }
 810                         if (docPropertiesUI != null) {
 811                             PrinterJobWrapper wrapper = (PrinterJobWrapper)
 812                                 asCurrent.get(PrinterJobWrapper.class);
 813                             if (wrapper == null) {
 814                                 return; // should not happen, defensive only.
 815                             }
 816                             PrinterJob job = wrapper.getPrinterJob();
 817                             if (job == null) {
 818                                 return;  // should not happen, defensive only.
 819                             }
 820                             PrintRequestAttributeSet newAttrs =
 821                                docPropertiesUI.showDocumentProperties
 822                                (job, ServiceDialog.this, psCurrent, asCurrent);
 823                             if (newAttrs != null) {
 824                                 asCurrent.addAll(newAttrs);
 825                                 updatePanels();
 826                             }
 827                         }
 828                     }
 829                 }
 830             }
 831         }
 832 
 833         public void itemStateChanged(ItemEvent e) {
 834             if (e.getStateChange() == ItemEvent.SELECTED) {
 835                 int index = cbName.getSelectedIndex();
 836 
 837                 if ((index >= 0) && (index < services.length)) {
 838                     if (!services[index].equals(psCurrent)) {
 839                         psCurrent = services[index];
 840                         uiFactory = psCurrent.getServiceUIFactory();
 841                         changedService = true;
 842 
 843                         Destination dest =
 844                             (Destination)asOriginal.get(Destination.class);
 845                         // to preserve the state of Print To File
 846                         if ((dest != null || isPrintToFileSelected())
 847                             && psCurrent.isAttributeCategorySupported(
 848                                                         Destination.class)) {
 849 
 850                             if (dest != null) {
 851                                 asCurrent.add(dest);
 852                             } else {
 853                                 dest = (Destination)psCurrent.
 854                                     getDefaultAttributeValue(Destination.class);
 855                                 // "dest" should not be null. The following code
 856                                 // is only added to safeguard against a possible
 857                                 // buggy implementation of a PrintService having a
 858                                 // null default Destination.
 859                                 if (dest == null) {
 860                                     try {
 861                                         dest =
 862                                             new Destination(new URI("file:out.prn"));
 863                                     } catch (URISyntaxException ue) {
 864                                     }
 865                                 }
 866 
 867                                 if (dest != null) {
 868                                     asCurrent.add(dest);
 869                                 }
 870                             }
 871                         } else {
 872                             asCurrent.remove(Destination.class);
 873                         }
 874                     }
 875                 }
 876             }
 877         }
 878 
 879         public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
 880             changedService = false;
 881         }
 882 
 883         public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
 884             if (changedService) {
 885                 changedService = false;
 886                 updatePanels();
 887             }
 888         }
 889 
 890         public void popupMenuCanceled(PopupMenuEvent e) {
 891         }
 892 
 893         /**
 894          * We disable the "Print To File" checkbox if this returns false
 895          */
 896         private boolean allowedToPrintToFile() {
 897             try {
 898                 throwPrintToFile();
 899                 return true;
 900             } catch (SecurityException e) {
 901                 return false;
 902             }
 903         }
 904 
 905         /**
 906          * Break this out as it may be useful when we allow API to
 907          * specify printing to a file. In that case its probably right
 908          * to throw a SecurityException if the permission is not granted.
 909          */
 910         private void throwPrintToFile() {
 911             SecurityManager security = System.getSecurityManager();
 912             if (security != null) {
 913                 if (printToFilePermission == null) {
 914                     printToFilePermission =
 915                         new FilePermission("<<ALL FILES>>", "read,write");
 916                 }
 917                 security.checkPermission(printToFilePermission);
 918             }
 919         }
 920 
 921         public void updateInfo() {
 922             Class<Destination> dstCategory = Destination.class;
 923             boolean dstSupported = false;
 924             boolean dstSelected = false;
 925             boolean dstAllowed = filePermission ?
 926                 allowedToPrintToFile() : false;
 927 
 928             // setup Destination (print-to-file) widgets
 929             Destination dst = (Destination)asCurrent.get(dstCategory);
 930             if (dst != null) {
 931                 try {
 932                      dst.getURI().toURL();
 933                      if (psCurrent.isAttributeValueSupported(dst, docFlavor,
 934                                                              asCurrent)) {
 935                          dstSupported = true;
 936                          dstSelected = true;
 937                      }
 938                  } catch (MalformedURLException ex) {
 939                      dstSupported = true;
 940                  }
 941             } else {
 942                 if (psCurrent.isAttributeCategorySupported(dstCategory)) {
 943                     dstSupported = true;
 944                 }
 945             }
 946             cbPrintToFile.setEnabled(dstSupported && dstAllowed);
 947             cbPrintToFile.setSelected(dstSelected && dstAllowed
 948                                       && dstSupported);
 949 
 950             // setup PrintService information widgets
 951             Attribute type = psCurrent.getAttribute(PrinterMakeAndModel.class);
 952             if (type != null) {
 953                 lblType.setText(type.toString());
 954             }
 955             Attribute status =
 956                 psCurrent.getAttribute(PrinterIsAcceptingJobs.class);
 957             if (status != null) {
 958                 lblStatus.setText(getMsg(status.toString()));
 959             }
 960             Attribute info = psCurrent.getAttribute(PrinterInfo.class);
 961             if (info != null) {
 962                 lblInfo.setText(info.toString());
 963             }
 964             btnProperties.setEnabled(uiFactory != null &&
 965                     (uiFactory.getUI(ServiceUIFactory.MAIN_UIROLE,
 966                                     ServiceUIFactory.JDIALOG_UI) != null));
 967         }
 968     }
 969 
 970     @SuppressWarnings("serial") // Superclass is not serializable across versions
 971     private class PrintRangePanel extends JPanel
 972         implements ActionListener, FocusListener
 973     {
 974         private final String strTitle = getMsg("border.printrange");
 975         private final PageRanges prAll = new PageRanges(1, Integer.MAX_VALUE);
 976         private JRadioButton rbAll, rbPages, rbSelect;
 977         private JFormattedTextField tfRangeFrom, tfRangeTo;
 978         private JLabel lblRangeTo;
 979         private boolean prSupported;
 980         private boolean prPgRngSupported;
 981 
 982         public PrintRangePanel() {
 983             super();
 984 
 985             GridBagLayout gridbag = new GridBagLayout();
 986             GridBagConstraints c = new GridBagConstraints();
 987 
 988             setLayout(gridbag);
 989             setBorder(BorderFactory.createTitledBorder(strTitle));
 990 
 991             c.fill = GridBagConstraints.BOTH;
 992             c.insets = compInsets;
 993             c.gridwidth = GridBagConstraints.REMAINDER;
 994 
 995             ButtonGroup bg = new ButtonGroup();
 996             JPanel pnlTop = new JPanel(new FlowLayout(FlowLayout.LEADING));
 997             rbAll = createRadioButton("radiobutton.rangeall", this);
 998             rbAll.setSelected(true);
 999             bg.add(rbAll);
1000             pnlTop.add(rbAll);
1001             addToGB(pnlTop, this, gridbag, c);
1002 
1003             // Selection never seemed to work so I'm commenting this part.
1004             /*
1005             if (isAWT) {
1006                 JPanel pnlMiddle  =
1007                     new JPanel(new FlowLayout(FlowLayout.LEADING));
1008                 rbSelect =
1009                     createRadioButton("radiobutton.selection", this);
1010                 bg.add(rbSelect);
1011                 pnlMiddle.add(rbSelect);
1012                 addToGB(pnlMiddle, this, gridbag, c);
1013             }
1014             */
1015 
1016             JPanel pnlBottom = new JPanel(new FlowLayout(FlowLayout.LEADING));
1017             rbPages = createRadioButton("radiobutton.rangepages", this);
1018             bg.add(rbPages);
1019             pnlBottom.add(rbPages);
1020             DecimalFormat format = new DecimalFormat("####0");
1021             format.setMinimumFractionDigits(0);
1022             format.setMaximumFractionDigits(0);
1023             format.setMinimumIntegerDigits(0);
1024             format.setMaximumIntegerDigits(5);
1025             format.setParseIntegerOnly(true);
1026             format.setDecimalSeparatorAlwaysShown(false);
1027             NumberFormatter nf = new NumberFormatter(format);
1028             nf.setMinimum(1);
1029             nf.setMaximum(Integer.MAX_VALUE);
1030             nf.setAllowsInvalid(true);
1031             nf.setCommitsOnValidEdit(true);
1032             tfRangeFrom = new JFormattedTextField(nf);
1033             tfRangeFrom.setColumns(4);
1034             tfRangeFrom.setEnabled(false);
1035             tfRangeFrom.addActionListener(this);
1036             tfRangeFrom.addFocusListener(this);
1037             tfRangeFrom.setFocusLostBehavior(
1038                 JFormattedTextField.PERSIST);
1039             tfRangeFrom.getAccessibleContext().setAccessibleName(
1040                                           getMsg("radiobutton.rangepages"));
1041             pnlBottom.add(tfRangeFrom);
1042             lblRangeTo = new JLabel(getMsg("label.rangeto"));
1043             lblRangeTo.setEnabled(false);
1044             pnlBottom.add(lblRangeTo);
1045             NumberFormatter nfto;
1046             try {
1047                 nfto = (NumberFormatter)nf.clone();
1048             } catch (CloneNotSupportedException e) {
1049                 nfto = new NumberFormatter();
1050             }
1051             tfRangeTo = new JFormattedTextField(nfto);
1052             tfRangeTo.setColumns(4);
1053             tfRangeTo.setEnabled(false);
1054             tfRangeTo.addFocusListener(this);
1055             tfRangeTo.getAccessibleContext().setAccessibleName(
1056                                           getMsg("label.rangeto"));
1057             pnlBottom.add(tfRangeTo);
1058             addToGB(pnlBottom, this, gridbag, c);
1059         }
1060 
1061         public void actionPerformed(ActionEvent e) {
1062             Object source = e.getSource();
1063             SunPageSelection select = SunPageSelection.ALL;
1064 
1065             setupRangeWidgets();
1066 
1067             if (source == rbAll) {
1068                 asCurrent.add(prAll);
1069             } else if (source == rbSelect) {
1070                 select = SunPageSelection.SELECTION;
1071             } else if (source == rbPages ||
1072                        source == tfRangeFrom ||
1073                        source == tfRangeTo) {
1074                 updateRangeAttribute();
1075                 select = SunPageSelection.RANGE;
1076             }
1077 
1078             if (isAWT) {
1079                 asCurrent.add(select);
1080             }
1081         }
1082 
1083         public void focusLost(FocusEvent e) {
1084             Object source = e.getSource();
1085 
1086             if ((source == tfRangeFrom) || (source == tfRangeTo)) {
1087                 updateRangeAttribute();
1088             }
1089         }
1090 
1091         public void focusGained(FocusEvent e) {}
1092 
1093         private void setupRangeWidgets() {
1094             boolean rangeEnabled = (rbPages.isSelected() && prPgRngSupported);
1095             tfRangeFrom.setEnabled(rangeEnabled);
1096             tfRangeTo.setEnabled(rangeEnabled);
1097             lblRangeTo.setEnabled(rangeEnabled);
1098         }
1099 
1100         private void updateRangeAttribute() {
1101             String strFrom = tfRangeFrom.getText();
1102             String strTo = tfRangeTo.getText();
1103 
1104             int min;
1105             int max;
1106 
1107             try {
1108                 min = Integer.parseInt(strFrom);
1109             } catch (NumberFormatException e) {
1110                 min = 1;
1111             }
1112 
1113             try {
1114                 max = Integer.parseInt(strTo);
1115             } catch (NumberFormatException e) {
1116                 max = min;
1117             }
1118 
1119             if (min < 1) {
1120                 min = 1;
1121                 tfRangeFrom.setValue(1);
1122             }
1123 
1124             if (max < min) {
1125                 max = min;
1126                 tfRangeTo.setValue(min);
1127             }
1128 
1129             PageRanges pr = new PageRanges(min, max);
1130             asCurrent.add(pr);
1131         }
1132 
1133         public void updateInfo() {
1134             Class<PageRanges> prCategory = PageRanges.class;
1135             prSupported = false;
1136 
1137             if (psCurrent.isAttributeCategorySupported(prCategory) ||
1138                    isAWT) {
1139                 prSupported = true;
1140                 prPgRngSupported = psCurrent.isAttributeValueSupported(prAll,
1141                                                                      docFlavor,
1142                                                                      asCurrent);
1143             }
1144 
1145             SunPageSelection select = SunPageSelection.ALL;
1146             int min = 1;
1147             int max = 1;
1148 
1149             PageRanges pr = (PageRanges)asCurrent.get(prCategory);
1150             if (pr != null) {
1151                 if (!pr.equals(prAll)) {
1152                     select = SunPageSelection.RANGE;
1153 
1154                     int[][] members = pr.getMembers();
1155                     if ((members.length > 0) &&
1156                         (members[0].length > 1)) {
1157                         min = members[0][0];
1158                         max = members[0][1];
1159                     }
1160                 }
1161             }
1162 
1163             if (isAWT) {
1164                 select = (SunPageSelection)asCurrent.get(
1165                                                 SunPageSelection.class);
1166             }
1167 
1168             if (select == SunPageSelection.ALL) {
1169                 rbAll.setSelected(true);
1170             } else if (select == SunPageSelection.SELECTION) {
1171                 // Comment this for now -  rbSelect is not initialized
1172                 // because Selection button is not added.
1173                 // See PrintRangePanel above.
1174 
1175                 //rbSelect.setSelected(true);
1176             } else { // RANGE
1177                 rbPages.setSelected(true);
1178             }
1179             tfRangeFrom.setValue(min);
1180             tfRangeTo.setValue(max);
1181             rbAll.setEnabled(prSupported);
1182             rbPages.setEnabled(prPgRngSupported);
1183             setupRangeWidgets();
1184         }
1185     }
1186 
1187     @SuppressWarnings("serial") // Superclass is not serializable across versions
1188     private class CopiesPanel extends JPanel
1189         implements ActionListener, ChangeListener
1190     {
1191         private final String strTitle = getMsg("border.copies");
1192         private SpinnerNumberModel snModel;
1193         private JSpinner spinCopies;
1194         private JLabel lblCopies;
1195         private JCheckBox cbCollate;
1196         private boolean scSupported;
1197 
1198         public CopiesPanel() {
1199             super();
1200 
1201             GridBagLayout gridbag = new GridBagLayout();
1202             GridBagConstraints c = new GridBagConstraints();
1203 
1204             setLayout(gridbag);
1205             setBorder(BorderFactory.createTitledBorder(strTitle));
1206 
1207             c.fill = GridBagConstraints.HORIZONTAL;
1208             c.insets = compInsets;
1209 
1210             lblCopies = new JLabel(getMsg("label.numcopies"), JLabel.TRAILING);
1211             lblCopies.setDisplayedMnemonic(getMnemonic("label.numcopies"));
1212             lblCopies.getAccessibleContext().setAccessibleName(
1213                                              getMsg("label.numcopies"));
1214             addToGB(lblCopies, this, gridbag, c);
1215 
1216             snModel = new SpinnerNumberModel(1, 1, 999, 1);
1217             spinCopies = new JSpinner(snModel);
1218             lblCopies.setLabelFor(spinCopies);
1219             // REMIND
1220             ((JSpinner.NumberEditor)spinCopies.getEditor()).getTextField().setColumns(3);
1221             spinCopies.addChangeListener(this);
1222             c.gridwidth = GridBagConstraints.REMAINDER;
1223             addToGB(spinCopies, this, gridbag, c);
1224 
1225             cbCollate = createCheckBox("checkbox.collate", this);
1226             cbCollate.setEnabled(false);
1227             addToGB(cbCollate, this, gridbag, c);
1228         }
1229 
1230         public void actionPerformed(ActionEvent e) {
1231             if (cbCollate.isSelected()) {
1232                 asCurrent.add(SheetCollate.COLLATED);
1233             } else {
1234                 asCurrent.add(SheetCollate.UNCOLLATED);
1235             }
1236         }
1237 
1238         public void stateChanged(ChangeEvent e) {
1239             updateCollateCB();
1240 
1241             asCurrent.add(new Copies(snModel.getNumber().intValue()));
1242         }
1243 
1244         private void updateCollateCB() {
1245             int num = snModel.getNumber().intValue();
1246             if (isAWT) {
1247                 cbCollate.setEnabled(true);
1248             } else {
1249                 cbCollate.setEnabled((num > 1) && scSupported);
1250             }
1251         }
1252 
1253         public void updateInfo() {
1254             Class<Copies> cpCategory = Copies.class;
1255             Class<SheetCollate> scCategory = SheetCollate.class;
1256             boolean cpSupported = false;
1257             scSupported = false;
1258 
1259             // setup Copies spinner
1260             if (psCurrent.isAttributeCategorySupported(cpCategory)) {
1261                 cpSupported = true;
1262             }
1263             CopiesSupported cs =
1264                 (CopiesSupported)psCurrent.getSupportedAttributeValues(
1265                                                        cpCategory, null, null);
1266             if (cs == null) {
1267                 cs = new CopiesSupported(1, 999);
1268             }
1269             Copies cp = (Copies)asCurrent.get(cpCategory);
1270             if (cp == null) {
1271                 cp = (Copies)psCurrent.getDefaultAttributeValue(cpCategory);
1272                 if (cp == null) {
1273                     cp = new Copies(1);
1274                 }
1275             }
1276             spinCopies.setEnabled(cpSupported);
1277             lblCopies.setEnabled(cpSupported);
1278 
1279             int[][] members = cs.getMembers();
1280             int min, max;
1281             if ((members.length > 0) && (members[0].length > 0)) {
1282                 min = members[0][0];
1283                 max = members[0][1];
1284             } else {
1285                 min = 1;
1286                 max = Integer.MAX_VALUE;
1287             }
1288             snModel.setMinimum(min);
1289             snModel.setMaximum(max);
1290 
1291             int value = cp.getValue();
1292             if ((value < min) || (value > max)) {
1293                 value = min;
1294             }
1295             snModel.setValue(value);
1296 
1297             // setup Collate checkbox
1298             if (psCurrent.isAttributeCategorySupported(scCategory)) {
1299                 scSupported = true;
1300             }
1301             SheetCollate sc = (SheetCollate)asCurrent.get(scCategory);
1302             if (sc == null) {
1303                 sc = (SheetCollate)psCurrent.getDefaultAttributeValue(scCategory);
1304                 if (sc == null) {
1305                     sc = SheetCollate.UNCOLLATED;
1306                 }
1307                 if (sc != null &&
1308                     !psCurrent.isAttributeValueSupported(sc, docFlavor, asCurrent)) {
1309                     scSupported = false;
1310                 }
1311             } else {
1312                 if (!psCurrent.isAttributeValueSupported(sc, docFlavor, asCurrent)) {
1313                     scSupported = false;
1314                 }
1315             }
1316             cbCollate.setSelected(sc == SheetCollate.COLLATED && scSupported);
1317             updateCollateCB();
1318         }
1319     }
1320 
1321 
1322 
1323 
1324     /**
1325      * The "Page Setup" tab.  Includes the controls for MediaSource/MediaTray,
1326      * OrientationRequested, and Sides.
1327      */
1328     @SuppressWarnings("serial") // Superclass is not serializable across versions
1329     private class PageSetupPanel extends JPanel {
1330 
1331         private MediaPanel pnlMedia;
1332         private OrientationPanel pnlOrientation;
1333         private MarginsPanel pnlMargins;
1334 
1335         public PageSetupPanel() {
1336             super();
1337 
1338             GridBagLayout gridbag = new GridBagLayout();
1339             GridBagConstraints c = new GridBagConstraints();
1340 
1341             setLayout(gridbag);
1342 
1343             c.fill = GridBagConstraints.BOTH;
1344             c.insets = panelInsets;
1345             c.weightx = 1.0;
1346             c.weighty = 1.0;
1347 
1348             c.gridwidth = GridBagConstraints.REMAINDER;
1349             pnlMedia = new MediaPanel();
1350             addToGB(pnlMedia, this, gridbag, c);
1351 
1352             pnlOrientation = new OrientationPanel();
1353             c.gridwidth = GridBagConstraints.RELATIVE;
1354             addToGB(pnlOrientation, this, gridbag, c);
1355 
1356             pnlMargins = new MarginsPanel();
1357             pnlOrientation.addOrientationListener(pnlMargins);
1358             pnlMedia.addMediaListener(pnlMargins);
1359             c.gridwidth = GridBagConstraints.REMAINDER;
1360             addToGB(pnlMargins, this, gridbag, c);
1361         }
1362 
1363         public void updateInfo() {
1364             pnlMedia.updateInfo();
1365             pnlOrientation.updateInfo();
1366             pnlMargins.updateInfo();
1367         }
1368     }
1369 
1370     @SuppressWarnings("serial") // Superclass is not serializable across versions
1371     private class MarginsPanel extends JPanel
1372                                implements ActionListener, FocusListener {
1373 
1374         private final String strTitle = getMsg("border.margins");
1375         private JFormattedTextField leftMargin, rightMargin,
1376                                     topMargin, bottomMargin;
1377         private JLabel lblLeft, lblRight, lblTop, lblBottom;
1378         private int units = MediaPrintableArea.MM;
1379         // storage for the last margin values calculated, -ve is uninitialised
1380         private float lmVal = -1f,rmVal = -1f, tmVal = -1f, bmVal = -1f;
1381         // storage for margins as objects mapped into orientation for display
1382         private Float lmObj,rmObj,tmObj,bmObj;
1383 
1384         public MarginsPanel() {
1385             super();
1386 
1387             GridBagLayout gridbag = new GridBagLayout();
1388             GridBagConstraints c = new GridBagConstraints();
1389             c.fill = GridBagConstraints.HORIZONTAL;
1390             c.weightx = 1.0;
1391             c.weighty = 0.0;
1392             c.insets = compInsets;
1393 
1394             setLayout(gridbag);
1395             setBorder(BorderFactory.createTitledBorder(strTitle));
1396 
1397             String unitsKey = "label.millimetres";
1398             String defaultCountry = Locale.getDefault().getCountry();
1399             if (defaultCountry != null &&
1400                 (defaultCountry.isEmpty() ||
1401                  defaultCountry.equals(Locale.US.getCountry()) ||
1402                  defaultCountry.equals(Locale.CANADA.getCountry()))) {
1403                 unitsKey = "label.inches";
1404                 units = MediaPrintableArea.INCH;
1405             }
1406             String unitsMsg = getMsg(unitsKey);
1407 
1408             DecimalFormat format;
1409             if (units == MediaPrintableArea.MM) {
1410                 format = new DecimalFormat("###.##");
1411                 format.setMaximumIntegerDigits(3);
1412             } else {
1413                 format = new DecimalFormat("##.##");
1414                 format.setMaximumIntegerDigits(2);
1415             }
1416 
1417             format.setMinimumFractionDigits(1);
1418             format.setMaximumFractionDigits(2);
1419             format.setMinimumIntegerDigits(1);
1420             format.setParseIntegerOnly(false);
1421             format.setDecimalSeparatorAlwaysShown(true);
1422             NumberFormatter nf = new NumberFormatter(format);
1423             nf.setMinimum(Float.valueOf(0.0f));
1424             nf.setMaximum(Float.valueOf(999.0f));
1425             nf.setAllowsInvalid(true);
1426             nf.setCommitsOnValidEdit(true);
1427 
1428             leftMargin = new JFormattedTextField(nf);
1429             leftMargin.addFocusListener(this);
1430             leftMargin.addActionListener(this);
1431             leftMargin.getAccessibleContext().setAccessibleName(
1432                                               getMsg("label.leftmargin"));
1433             rightMargin = new JFormattedTextField(nf);
1434             rightMargin.addFocusListener(this);
1435             rightMargin.addActionListener(this);
1436             rightMargin.getAccessibleContext().setAccessibleName(
1437                                               getMsg("label.rightmargin"));
1438             topMargin = new JFormattedTextField(nf);
1439             topMargin.addFocusListener(this);
1440             topMargin.addActionListener(this);
1441             topMargin.getAccessibleContext().setAccessibleName(
1442                                               getMsg("label.topmargin"));
1443 
1444             bottomMargin = new JFormattedTextField(nf);
1445             bottomMargin.addFocusListener(this);
1446             bottomMargin.addActionListener(this);
1447             bottomMargin.getAccessibleContext().setAccessibleName(
1448                                               getMsg("label.bottommargin"));
1449 
1450             c.gridwidth = GridBagConstraints.RELATIVE;
1451             lblLeft = new JLabel(getMsg("label.leftmargin") + " " + unitsMsg,
1452                                  JLabel.LEADING);
1453             lblLeft.setDisplayedMnemonic(getMnemonic("label.leftmargin"));
1454             lblLeft.setLabelFor(leftMargin);
1455             addToGB(lblLeft, this, gridbag, c);
1456 
1457             c.gridwidth = GridBagConstraints.REMAINDER;
1458             lblRight = new JLabel(getMsg("label.rightmargin") + " " + unitsMsg,
1459                                   JLabel.LEADING);
1460             lblRight.setDisplayedMnemonic(getMnemonic("label.rightmargin"));
1461             lblRight.setLabelFor(rightMargin);
1462             addToGB(lblRight, this, gridbag, c);
1463 
1464             c.gridwidth = GridBagConstraints.RELATIVE;
1465             addToGB(leftMargin, this, gridbag, c);
1466 
1467             c.gridwidth = GridBagConstraints.REMAINDER;
1468             addToGB(rightMargin, this, gridbag, c);
1469 
1470             // add an invisible spacing component.
1471             addToGB(new JPanel(), this, gridbag, c);
1472 
1473             c.gridwidth = GridBagConstraints.RELATIVE;
1474             lblTop = new JLabel(getMsg("label.topmargin") + " " + unitsMsg,
1475                                 JLabel.LEADING);
1476             lblTop.setDisplayedMnemonic(getMnemonic("label.topmargin"));
1477             lblTop.setLabelFor(topMargin);
1478             addToGB(lblTop, this, gridbag, c);
1479 
1480             c.gridwidth = GridBagConstraints.REMAINDER;
1481             lblBottom = new JLabel(getMsg("label.bottommargin") +
1482                                    " " + unitsMsg, JLabel.LEADING);
1483             lblBottom.setDisplayedMnemonic(getMnemonic("label.bottommargin"));
1484             lblBottom.setLabelFor(bottomMargin);
1485             addToGB(lblBottom, this, gridbag, c);
1486 
1487             c.gridwidth = GridBagConstraints.RELATIVE;
1488             addToGB(topMargin, this, gridbag, c);
1489 
1490             c.gridwidth = GridBagConstraints.REMAINDER;
1491             addToGB(bottomMargin, this, gridbag, c);
1492 
1493         }
1494 
1495         public void actionPerformed(ActionEvent e) {
1496             Object source = e.getSource();
1497             updateMargins(source);
1498         }
1499 
1500         public void focusLost(FocusEvent e) {
1501             Object source = e.getSource();
1502             updateMargins(source);
1503         }
1504 
1505         public void focusGained(FocusEvent e) {}
1506 
1507         /* Get the numbers, use to create a MPA.
1508          * If its valid, accept it and update the attribute set.
1509          * If its not valid, then reject it and call updateInfo()
1510          * to re-establish the previous entries.
1511          */
1512         public void updateMargins(Object source) {
1513             if (!(source instanceof JFormattedTextField)) {
1514                 return;
1515             } else {
1516                 JFormattedTextField tf = (JFormattedTextField)source;
1517                 Float val = (Float)tf.getValue();
1518                 if (val == null) {
1519                     return;
1520                 }
1521                 if (tf == leftMargin && val.equals(lmObj)) {
1522                     return;
1523                 }
1524                 if (tf == rightMargin && val.equals(rmObj)) {
1525                     return;
1526                 }
1527                 if (tf == topMargin && val.equals(tmObj)) {
1528                     return;
1529                 }
1530                 if (tf == bottomMargin && val.equals(bmObj)) {
1531                     return;
1532                 }
1533             }
1534 
1535             Float lmTmpObj = (Float)leftMargin.getValue();
1536             Float rmTmpObj = (Float)rightMargin.getValue();
1537             Float tmTmpObj = (Float)topMargin.getValue();
1538             Float bmTmpObj = (Float)bottomMargin.getValue();
1539 
1540             float lm = lmTmpObj.floatValue();
1541             float rm = rmTmpObj.floatValue();
1542             float tm = tmTmpObj.floatValue();
1543             float bm = bmTmpObj.floatValue();
1544 
1545             /* adjust for orientation */
1546             Class<OrientationRequested> orCategory = OrientationRequested.class;
1547             OrientationRequested or =
1548                 (OrientationRequested)asCurrent.get(orCategory);
1549 
1550             if (or == null) {
1551                 or = (OrientationRequested)
1552                      psCurrent.getDefaultAttributeValue(orCategory);
1553             }
1554 
1555             float tmp;
1556             if (or == OrientationRequested.REVERSE_PORTRAIT) {
1557                 tmp = lm; lm = rm; rm = tmp;
1558                 tmp = tm; tm = bm; bm = tmp;
1559             } else if (or == OrientationRequested.LANDSCAPE) {
1560                 tmp = lm;
1561                 lm = tm;
1562                 tm = rm;
1563                 rm = bm;
1564                 bm = tmp;
1565             } else if (or == OrientationRequested.REVERSE_LANDSCAPE) {
1566                 tmp = lm;
1567                 lm = bm;
1568                 bm = rm;
1569                 rm = tm;
1570                 tm = tmp;
1571             }
1572             MediaPrintableArea mpa;
1573             if ((mpa = validateMargins(lm, rm, tm, bm)) != null) {
1574                 asCurrent.add(mpa);
1575                 lmVal = lm;
1576                 rmVal = rm;
1577                 tmVal = tm;
1578                 bmVal = bm;
1579                 lmObj = lmTmpObj;
1580                 rmObj = rmTmpObj;
1581                 tmObj = tmTmpObj;
1582                 bmObj = bmTmpObj;
1583             } else {
1584                 if (lmObj == null || rmObj == null ||
1585                     tmObj == null || bmObj == null) {
1586                     return;
1587                 } else {
1588                     leftMargin.setValue(lmObj);
1589                     rightMargin.setValue(rmObj);
1590                     topMargin.setValue(tmObj);
1591                     bottomMargin.setValue(bmObj);
1592 
1593                 }
1594             }
1595         }
1596 
1597         /*
1598          * This method either accepts the values and creates a new
1599          * MediaPrintableArea, or does nothing.
1600          * It should not attempt to create a printable area from anything
1601          * other than the exact values passed in.
1602          * But REMIND/TBD: it would be user friendly to replace margins the
1603          * user entered but are out of bounds with the minimum.
1604          * At that point this method will need to take responsibility for
1605          * updating the "stored" values and the UI.
1606          */
1607         private MediaPrintableArea validateMargins(float lm, float rm,
1608                                                    float tm, float bm) {
1609 
1610             Class<MediaPrintableArea> mpaCategory = MediaPrintableArea.class;
1611             MediaPrintableArea mpa;
1612             MediaPrintableArea mpaMax = null;
1613             MediaSize mediaSize = null;
1614 
1615             Media media = (Media)asCurrent.get(Media.class);
1616             if (media == null || !(media instanceof MediaSizeName)) {
1617                 media = (Media)psCurrent.getDefaultAttributeValue(Media.class);
1618             }
1619             if (media != null && (media instanceof MediaSizeName)) {
1620                 MediaSizeName msn = (MediaSizeName)media;
1621                 mediaSize = MediaSize.getMediaSizeForName(msn);
1622             }
1623             if (mediaSize == null) {
1624                 mediaSize = new MediaSize(8.5f, 11f, Size2DSyntax.INCH);
1625             }
1626 
1627             if (media != null) {
1628                 PrintRequestAttributeSet tmpASet =
1629                     new HashPrintRequestAttributeSet(asCurrent);
1630                 tmpASet.add(media);
1631 
1632                 Object values =
1633                     psCurrent.getSupportedAttributeValues(mpaCategory,
1634                                                           docFlavor,
1635                                                           tmpASet);
1636                 if (values instanceof MediaPrintableArea[] &&
1637                     ((MediaPrintableArea[])values).length > 0) {
1638                     mpaMax = ((MediaPrintableArea[])values)[0];
1639 
1640                 }
1641             }
1642             if (mpaMax == null) {
1643                 mpaMax = new MediaPrintableArea(0f, 0f,
1644                                                 mediaSize.getX(units),
1645                                                 mediaSize.getY(units),
1646                                                 units);
1647             }
1648 
1649             float wid = mediaSize.getX(units);
1650             float hgt = mediaSize.getY(units);
1651             float pax = lm;
1652             float pay = tm;
1653             float par = rm;
1654             float pab = bm;
1655             float paw = wid - lm - rm;
1656             float pah = hgt - tm - bm;
1657 
1658             if (paw <= 0f || pah <= 0f || pax < 0f || pay < 0f ||
1659                 par <= 0f || pab <= 0f ||
1660                 pax < mpaMax.getX(units) || paw > mpaMax.getWidth(units) ||
1661                 pay < mpaMax.getY(units) || pah > mpaMax.getHeight(units)) {
1662                 return null;
1663             } else {
1664                 return new MediaPrintableArea(lm, tm, paw, pah, units);
1665             }
1666         }
1667 
1668         /* This is complex as a MediaPrintableArea is valid only within
1669          * a particular context of media size.
1670          * So we need a MediaSize as well as a MediaPrintableArea.
1671          * MediaSize can be obtained from MediaSizeName.
1672          * If the application specifies a MediaPrintableArea, we accept it
1673          * to the extent its valid for the Media they specify. If they
1674          * don't specify a Media, then the default is assumed.
1675          *
1676          * If an application doesn't define a MediaPrintableArea, we need to
1677          * create a suitable one, this is created using the specified (or
1678          * default) Media and default 1 inch margins. This is validated
1679          * against the paper in case this is too large for tiny media.
1680          */
1681         public void updateInfo() {
1682 
1683             if (isAWT) {
1684                 leftMargin.setEnabled(false);
1685                 rightMargin.setEnabled(false);
1686                 topMargin.setEnabled(false);
1687                 bottomMargin.setEnabled(false);
1688                 lblLeft.setEnabled(false);
1689                 lblRight.setEnabled(false);
1690                 lblTop.setEnabled(false);
1691                 lblBottom.setEnabled(false);
1692                 return;
1693             }
1694 
1695             Class<MediaPrintableArea> mpaCategory = MediaPrintableArea.class;
1696             MediaPrintableArea mpa =
1697                  (MediaPrintableArea)asCurrent.get(mpaCategory);
1698             MediaPrintableArea mpaMax = null;
1699             MediaSize mediaSize = null;
1700 
1701             Media media = (Media)asCurrent.get(Media.class);
1702             if (media == null || !(media instanceof MediaSizeName)) {
1703                 media = (Media)psCurrent.getDefaultAttributeValue(Media.class);
1704             }
1705             if (media != null && (media instanceof MediaSizeName)) {
1706                 MediaSizeName msn = (MediaSizeName)media;
1707                 mediaSize = MediaSize.getMediaSizeForName(msn);
1708             }
1709             if (mediaSize == null) {
1710                 mediaSize = new MediaSize(8.5f, 11f, Size2DSyntax.INCH);
1711             }
1712 
1713             if (media != null) {
1714                 PrintRequestAttributeSet tmpASet =
1715                     new HashPrintRequestAttributeSet(asCurrent);
1716                 tmpASet.add(media);
1717 
1718                 Object values =
1719                     psCurrent.getSupportedAttributeValues(mpaCategory,
1720                                                           docFlavor,
1721                                                           tmpASet);
1722                 if (values instanceof MediaPrintableArea[] &&
1723                     ((MediaPrintableArea[])values).length > 0) {
1724                     mpaMax = ((MediaPrintableArea[])values)[0];
1725 
1726                 } else if (values instanceof MediaPrintableArea) {
1727                     mpaMax = (MediaPrintableArea)values;
1728                 }
1729             }
1730             if (mpaMax == null) {
1731                 mpaMax = new MediaPrintableArea(0f, 0f,
1732                                                 mediaSize.getX(units),
1733                                                 mediaSize.getY(units),
1734                                                 units);
1735             }
1736 
1737             /*
1738              * At this point we now know as best we can :-
1739              * - the media size
1740              * - the maximum corresponding printable area
1741              * - the media printable area specified by the client, if any.
1742              * The next step is to create a default MPA if none was specified.
1743              * 1" margins are used unless they are disproportionately
1744              * large compared to the size of the media.
1745              */
1746 
1747             float wid = mediaSize.getX(MediaPrintableArea.INCH);
1748             float hgt = mediaSize.getY(MediaPrintableArea.INCH);
1749             float maxMarginRatio = 5f;
1750             float xMgn, yMgn;
1751             if (wid > maxMarginRatio) {
1752                 xMgn = 1f;
1753             } else {
1754                 xMgn = wid / maxMarginRatio;
1755             }
1756             if (hgt > maxMarginRatio) {
1757                 yMgn = 1f;
1758             } else {
1759                 yMgn = hgt / maxMarginRatio;
1760             }
1761 
1762             if (mpa == null) {
1763                 mpa = new MediaPrintableArea(xMgn, yMgn,
1764                                              wid-(2*xMgn), hgt-(2*yMgn),
1765                                              MediaPrintableArea.INCH);
1766                 asCurrent.add(mpa);
1767             }
1768             float pax = mpa.getX(units);
1769             float pay = mpa.getY(units);
1770             float paw = mpa.getWidth(units);
1771             float pah = mpa.getHeight(units);
1772             float paxMax = mpaMax.getX(units);
1773             float payMax = mpaMax.getY(units);
1774             float pawMax = mpaMax.getWidth(units);
1775             float pahMax = mpaMax.getHeight(units);
1776 
1777 
1778             boolean invalid = false;
1779 
1780             // If the paper is set to something which is too small to
1781             // accommodate a specified printable area, perhaps carried
1782             // over from a larger paper, the adjustment that needs to be
1783             // performed should seem the most natural from a user's viewpoint.
1784             // Since the user is specifying margins, then we are biased
1785             // towards keeping the margins as close to what is specified as
1786             // possible, shrinking or growing the printable area.
1787             // But the API uses printable area, so you need to know the
1788             // media size in which the margins were previously interpreted,
1789             // or at least have a record of the margins.
1790             // In the case that this is the creation of this UI we do not
1791             // have this record, so we are somewhat reliant on the client
1792             // to supply a reasonable default
1793             wid = mediaSize.getX(units);
1794             hgt = mediaSize.getY(units);
1795             if (lmVal >= 0f) {
1796                 invalid = true;
1797 
1798                 if (lmVal + rmVal > wid) {
1799                     // margins impossible, but maintain P.A if can
1800                     if (paw > pawMax) {
1801                         paw = pawMax;
1802                     }
1803                     // try to centre the printable area.
1804                     pax = (wid - paw)/2f;
1805                 } else {
1806                     pax = (lmVal >= paxMax) ? lmVal : paxMax;
1807                     paw = wid - pax - rmVal;
1808                 }
1809                 if (tmVal + bmVal > hgt) {
1810                     if (pah > pahMax) {
1811                         pah = pahMax;
1812                     }
1813                     pay = (hgt - pah)/2f;
1814                 } else {
1815                     pay = (tmVal >= payMax) ? tmVal : payMax;
1816                     pah = hgt - pay - bmVal;
1817                 }
1818             }
1819             if (pax < paxMax) {
1820                 invalid = true;
1821                 pax = paxMax;
1822             }
1823             if (pay < payMax) {
1824                 invalid = true;
1825                 pay = payMax;
1826             }
1827             if (paw > pawMax) {
1828                 invalid = true;
1829                 paw = pawMax;
1830             }
1831             if (pah > pahMax) {
1832                 invalid = true;
1833                 pah = pahMax;
1834             }
1835 
1836             if ((pax + paw > paxMax + pawMax) || (paw <= 0f)) {
1837                 invalid = true;
1838                 pax = paxMax;
1839                 paw = pawMax;
1840             }
1841             if ((pay + pah > payMax + pahMax) || (pah <= 0f)) {
1842                 invalid = true;
1843                 pay = payMax;
1844                 pah = pahMax;
1845             }
1846 
1847             if (invalid) {
1848                 mpa = new MediaPrintableArea(pax, pay, paw, pah, units);
1849                 asCurrent.add(mpa);
1850             }
1851 
1852             /* We now have a valid printable area.
1853              * Turn it into margins, using the mediaSize
1854              */
1855             lmVal = pax;
1856             tmVal = pay;
1857             rmVal = mediaSize.getX(units) - pax - paw;
1858             bmVal = mediaSize.getY(units) - pay - pah;
1859 
1860             lmObj = lmVal;
1861             rmObj = rmVal;
1862             tmObj = tmVal;
1863             bmObj = bmVal;
1864 
1865             /* Now we know the values to use, we need to assign them
1866              * to the fields appropriate for the orientation.
1867              * Note: if orientation changes this method must be called.
1868              */
1869             Class<OrientationRequested> orCategory = OrientationRequested.class;
1870             OrientationRequested or =
1871                 (OrientationRequested)asCurrent.get(orCategory);
1872 
1873             if (or == null) {
1874                 or = (OrientationRequested)
1875                      psCurrent.getDefaultAttributeValue(orCategory);
1876             }
1877 
1878             Float tmp;
1879 
1880             if (or == OrientationRequested.REVERSE_PORTRAIT) {
1881                 tmp = lmObj; lmObj = rmObj; rmObj = tmp;
1882                 tmp = tmObj; tmObj = bmObj; bmObj = tmp;
1883             }  else if (or == OrientationRequested.LANDSCAPE) {
1884                 tmp = lmObj;
1885                 lmObj = bmObj;
1886                 bmObj = rmObj;
1887                 rmObj = tmObj;
1888                 tmObj = tmp;
1889             }  else if (or == OrientationRequested.REVERSE_LANDSCAPE) {
1890                 tmp = lmObj;
1891                 lmObj = tmObj;
1892                 tmObj = rmObj;
1893                 rmObj = bmObj;
1894                 bmObj = tmp;
1895             }
1896 
1897             leftMargin.setValue(lmObj);
1898             rightMargin.setValue(rmObj);
1899             topMargin.setValue(tmObj);
1900             bottomMargin.setValue(bmObj);
1901         }
1902     }
1903 
1904     @SuppressWarnings("serial") // Superclass is not serializable across versions
1905     private class MediaPanel extends JPanel implements ItemListener {
1906 
1907         private final String strTitle = getMsg("border.media");
1908         private JLabel lblSize, lblSource;
1909         private JComboBox<Object> cbSize, cbSource;
1910         private Vector<MediaSizeName> sizes = new Vector<>();
1911         private Vector<MediaTray> sources = new Vector<>();
1912         private MarginsPanel pnlMargins = null;
1913 
1914         public MediaPanel() {
1915             super();
1916 
1917             GridBagLayout gridbag = new GridBagLayout();
1918             GridBagConstraints c = new GridBagConstraints();
1919 
1920             setLayout(gridbag);
1921             setBorder(BorderFactory.createTitledBorder(strTitle));
1922 
1923             cbSize = new JComboBox<>();
1924             cbSource = new JComboBox<>();
1925 
1926             c.fill = GridBagConstraints.BOTH;
1927             c.insets = compInsets;
1928             c.weighty = 1.0;
1929 
1930             c.weightx = 0.0;
1931             lblSize = new JLabel(getMsg("label.size"), JLabel.TRAILING);
1932             lblSize.setDisplayedMnemonic(getMnemonic("label.size"));
1933             lblSize.setLabelFor(cbSize);
1934             addToGB(lblSize, this, gridbag, c);
1935             c.weightx = 1.0;
1936             c.gridwidth = GridBagConstraints.REMAINDER;
1937             addToGB(cbSize, this, gridbag, c);
1938 
1939             c.weightx = 0.0;
1940             c.gridwidth = 1;
1941             lblSource = new JLabel(getMsg("label.source"), JLabel.TRAILING);
1942             lblSource.setDisplayedMnemonic(getMnemonic("label.source"));
1943             lblSource.setLabelFor(cbSource);
1944             addToGB(lblSource, this, gridbag, c);
1945             c.gridwidth = GridBagConstraints.REMAINDER;
1946             addToGB(cbSource, this, gridbag, c);
1947         }
1948 
1949         private String getMediaName(String key) {
1950             try {
1951                 // replace characters that would be invalid in
1952                 // a resource key with valid characters
1953                 String newkey = key.replace(' ', '-');
1954                 newkey = newkey.replace('#', 'n');
1955 
1956                 return messageRB.getString(newkey);
1957             } catch (java.util.MissingResourceException e) {
1958                 return key;
1959             }
1960         }
1961 
1962         public void itemStateChanged(ItemEvent e) {
1963             Object source = e.getSource();
1964 
1965             if (e.getStateChange() == ItemEvent.SELECTED) {
1966                 if (source == cbSize) {
1967                     int index = cbSize.getSelectedIndex();
1968 
1969                     if ((index >= 0) && (index < sizes.size())) {
1970                         if ((cbSource.getItemCount() > 1) &&
1971                             (cbSource.getSelectedIndex() >= 1))
1972                         {
1973                             int src = cbSource.getSelectedIndex() - 1;
1974                             MediaTray mt = sources.get(src);
1975                             asCurrent.add(new SunAlternateMedia(mt));
1976                         }
1977                         asCurrent.add(sizes.get(index));
1978                     }
1979                 } else if (source == cbSource) {
1980                     int index = cbSource.getSelectedIndex();
1981 
1982                     if ((index >= 1) && (index < (sources.size() + 1))) {
1983                        asCurrent.remove(SunAlternateMedia.class);
1984                        MediaTray newTray = sources.get(index - 1);
1985                        Media m = (Media)asCurrent.get(Media.class);
1986                        if (m == null || m instanceof MediaTray) {
1987                            asCurrent.add(newTray);
1988                        } else if (m instanceof MediaSizeName) {
1989                            MediaSizeName msn = (MediaSizeName)m;
1990                            Media def = (Media)psCurrent.getDefaultAttributeValue(Media.class);
1991                            if (def instanceof MediaSizeName && def.equals(msn)) {
1992                                asCurrent.add(newTray);
1993                            } else {
1994                                /* Non-default paper size, so need to store tray
1995                                 * as SunAlternateMedia
1996                                 */
1997                                asCurrent.add(new SunAlternateMedia(newTray));
1998                            }
1999                        }
2000                     } else if (index == 0) {
2001                         asCurrent.remove(SunAlternateMedia.class);
2002                         if (cbSize.getItemCount() > 0) {
2003                             int size = cbSize.getSelectedIndex();
2004                             asCurrent.add(sizes.get(size));
2005                         }
2006                     }
2007                 }
2008             // orientation affects display of margins.
2009                 if (pnlMargins != null) {
2010                     pnlMargins.updateInfo();
2011                 }
2012             }
2013         }
2014 
2015 
2016         /* this is ad hoc to keep things simple */
2017         public void addMediaListener(MarginsPanel pnl) {
2018             pnlMargins = pnl;
2019         }
2020         public void updateInfo() {
2021             Class<Media> mdCategory = Media.class;
2022             Class<SunAlternateMedia> amCategory = SunAlternateMedia.class;
2023             boolean mediaSupported = false;
2024 
2025             cbSize.removeItemListener(this);
2026             cbSize.removeAllItems();
2027             cbSource.removeItemListener(this);
2028             cbSource.removeAllItems();
2029             cbSource.addItem(getMediaName("auto-select"));
2030 
2031             sizes.clear();
2032             sources.clear();
2033 
2034             if (psCurrent.isAttributeCategorySupported(mdCategory)) {
2035                 mediaSupported = true;
2036 
2037                 Object values =
2038                     psCurrent.getSupportedAttributeValues(mdCategory,
2039                                                           docFlavor,
2040                                                           asCurrent);
2041 
2042                 if (values instanceof Media[]) {
2043                     Media[] media = (Media[])values;
2044 
2045                     for (int i = 0; i < media.length; i++) {
2046                         Media medium = media[i];
2047 
2048                         if (medium instanceof MediaSizeName) {
2049                             sizes.add((MediaSizeName)medium);
2050                             cbSize.addItem(getMediaName(medium.toString()));
2051                         } else if (medium instanceof MediaTray) {
2052                             sources.add((MediaTray)medium);
2053                             cbSource.addItem(getMediaName(medium.toString()));
2054                         }
2055                     }
2056                 }
2057             }
2058 
2059             boolean msSupported = (mediaSupported && (sizes.size() > 0));
2060             lblSize.setEnabled(msSupported);
2061             cbSize.setEnabled(msSupported);
2062 
2063             if (isAWT) {
2064                 cbSource.setEnabled(false);
2065                 lblSource.setEnabled(false);
2066             } else {
2067                 cbSource.setEnabled(mediaSupported);
2068             }
2069 
2070             if (mediaSupported) {
2071 
2072                 Media medium = (Media)asCurrent.get(mdCategory);
2073 
2074                // initialize size selection to default
2075                 Media defMedia = (Media)psCurrent.getDefaultAttributeValue(mdCategory);
2076                 if (defMedia instanceof MediaSizeName) {
2077                     cbSize.setSelectedIndex(sizes.size() > 0 ? sizes.indexOf(defMedia) : -1);
2078                 }
2079 
2080                 if (medium == null ||
2081                     !psCurrent.isAttributeValueSupported(medium,
2082                                                          docFlavor, asCurrent)) {
2083 
2084                     medium = defMedia;
2085 
2086                     if (medium == null) {
2087                         if (sizes.size() > 0) {
2088                             medium = (Media)sizes.get(0);
2089                         }
2090                     }
2091                     if (medium != null) {
2092                         asCurrent.add(medium);
2093                     }
2094                 }
2095                 if (medium != null) {
2096                     if (medium instanceof MediaSizeName) {
2097                         MediaSizeName ms = (MediaSizeName)medium;
2098                         cbSize.setSelectedIndex(sizes.indexOf(ms));
2099                     } else if (medium instanceof MediaTray) {
2100                         MediaTray mt = (MediaTray)medium;
2101                         cbSource.setSelectedIndex(sources.indexOf(mt) + 1);
2102                     }
2103                 } else {
2104                     cbSize.setSelectedIndex(sizes.size() > 0 ? 0 : -1);
2105                     cbSource.setSelectedIndex(0);
2106                 }
2107 
2108                 SunAlternateMedia alt = (SunAlternateMedia)asCurrent.get(amCategory);
2109                 if (alt != null) {
2110                     Media md = alt.getMedia();
2111                     if (md instanceof MediaTray) {
2112                         MediaTray mt = (MediaTray)md;
2113                         cbSource.setSelectedIndex(sources.indexOf(mt) + 1);
2114                     }
2115                 }
2116 
2117                 int selIndex = cbSize.getSelectedIndex();
2118                 if ((selIndex >= 0) && (selIndex < sizes.size())) {
2119                   asCurrent.add(sizes.get(selIndex));
2120                 }
2121 
2122                 selIndex = cbSource.getSelectedIndex();
2123                 if ((selIndex >= 1) && (selIndex < (sources.size()+1))) {
2124                     MediaTray mt = sources.get(selIndex-1);
2125                     if (medium instanceof MediaTray) {
2126                         asCurrent.add(mt);
2127                     } else {
2128                         asCurrent.add(new SunAlternateMedia(mt));
2129                     }
2130                 }
2131 
2132 
2133             }
2134             cbSize.addItemListener(this);
2135             cbSource.addItemListener(this);
2136         }
2137     }
2138 
2139     @SuppressWarnings("serial") // Superclass is not serializable across versions
2140     private class OrientationPanel extends JPanel
2141         implements ActionListener
2142     {
2143         private final String strTitle = getMsg("border.orientation");
2144         private IconRadioButton rbPortrait, rbLandscape,
2145                                 rbRevPortrait, rbRevLandscape;
2146         private MarginsPanel pnlMargins = null;
2147 
2148         public OrientationPanel() {
2149             super();
2150 
2151             GridBagLayout gridbag = new GridBagLayout();
2152             GridBagConstraints c = new GridBagConstraints();
2153 
2154             setLayout(gridbag);
2155             setBorder(BorderFactory.createTitledBorder(strTitle));
2156 
2157             c.fill = GridBagConstraints.BOTH;
2158             c.insets = compInsets;
2159             c.weighty = 1.0;
2160             c.gridwidth = GridBagConstraints.REMAINDER;
2161 
2162             ButtonGroup bg = new ButtonGroup();
2163             rbPortrait = new IconRadioButton("radiobutton.portrait",
2164                                              "orientPortrait.png", true,
2165                                              bg, this);
2166             rbPortrait.addActionListener(this);
2167             addToGB(rbPortrait, this, gridbag, c);
2168             rbLandscape = new IconRadioButton("radiobutton.landscape",
2169                                               "orientLandscape.png", false,
2170                                               bg, this);
2171             rbLandscape.addActionListener(this);
2172             addToGB(rbLandscape, this, gridbag, c);
2173             rbRevPortrait = new IconRadioButton("radiobutton.revportrait",
2174                                                 "orientRevPortrait.png", false,
2175                                                 bg, this);
2176             rbRevPortrait.addActionListener(this);
2177             addToGB(rbRevPortrait, this, gridbag, c);
2178             rbRevLandscape = new IconRadioButton("radiobutton.revlandscape",
2179                                                  "orientRevLandscape.png", false,
2180                                                  bg, this);
2181             rbRevLandscape.addActionListener(this);
2182             addToGB(rbRevLandscape, this, gridbag, c);
2183         }
2184 
2185         public void actionPerformed(ActionEvent e) {
2186             Object source = e.getSource();
2187 
2188             if (rbPortrait.isSameAs(source)) {
2189                 asCurrent.add(OrientationRequested.PORTRAIT);
2190             } else if (rbLandscape.isSameAs(source)) {
2191                 asCurrent.add(OrientationRequested.LANDSCAPE);
2192             } else if (rbRevPortrait.isSameAs(source)) {
2193                 asCurrent.add(OrientationRequested.REVERSE_PORTRAIT);
2194             } else if (rbRevLandscape.isSameAs(source)) {
2195                 asCurrent.add(OrientationRequested.REVERSE_LANDSCAPE);
2196             }
2197             // orientation affects display of margins.
2198             if (pnlMargins != null) {
2199                 pnlMargins.updateInfo();
2200             }
2201         }
2202 
2203         /* This is ad hoc to keep things simple */
2204         void addOrientationListener(MarginsPanel pnl) {
2205             pnlMargins = pnl;
2206         }
2207 
2208         public void updateInfo() {
2209             Class<OrientationRequested> orCategory = OrientationRequested.class;
2210             boolean pSupported = false;
2211             boolean lSupported = false;
2212             boolean rpSupported = false;
2213             boolean rlSupported = false;
2214 
2215             if (isAWT) {
2216                 pSupported = true;
2217                 lSupported = true;
2218             } else
2219             if (psCurrent.isAttributeCategorySupported(orCategory)) {
2220                 Object values =
2221                     psCurrent.getSupportedAttributeValues(orCategory,
2222                                                           docFlavor,
2223                                                           asCurrent);
2224 
2225                 if (values instanceof OrientationRequested[]) {
2226                     OrientationRequested[] ovalues =
2227                         (OrientationRequested[])values;
2228 
2229                     for (int i = 0; i < ovalues.length; i++) {
2230                         OrientationRequested value = ovalues[i];
2231 
2232                         if (value == OrientationRequested.PORTRAIT) {
2233                             pSupported = true;
2234                         } else if (value == OrientationRequested.LANDSCAPE) {
2235                             lSupported = true;
2236                         } else if (value == OrientationRequested.REVERSE_PORTRAIT) {
2237                             rpSupported = true;
2238                         } else if (value == OrientationRequested.REVERSE_LANDSCAPE) {
2239                             rlSupported = true;
2240                         }
2241                     }
2242                 }
2243             }
2244 
2245 
2246             rbPortrait.setEnabled(pSupported);
2247             rbLandscape.setEnabled(lSupported);
2248             rbRevPortrait.setEnabled(rpSupported);
2249             rbRevLandscape.setEnabled(rlSupported);
2250 
2251             OrientationRequested or = (OrientationRequested)asCurrent.get(orCategory);
2252             if (or == null ||
2253                 !psCurrent.isAttributeValueSupported(or, docFlavor, asCurrent)) {
2254 
2255                 or = (OrientationRequested)psCurrent.getDefaultAttributeValue(orCategory);
2256                 // need to validate if default is not supported
2257                 if ((or != null) &&
2258                    !psCurrent.isAttributeValueSupported(or, docFlavor, asCurrent)) {
2259                     or = null;
2260                     Object values =
2261                         psCurrent.getSupportedAttributeValues(orCategory,
2262                                                               docFlavor,
2263                                                               asCurrent);
2264                     if (values instanceof OrientationRequested[]) {
2265                         OrientationRequested[] orValues =
2266                                             (OrientationRequested[])values;
2267                         if (orValues.length > 1) {
2268                             // get the first in the list
2269                             or = orValues[0];
2270                         }
2271                     }
2272                 }
2273 
2274                 if (or == null) {
2275                     or = OrientationRequested.PORTRAIT;
2276                 }
2277                 asCurrent.add(or);
2278             }
2279 
2280             if (or == OrientationRequested.PORTRAIT) {
2281                 rbPortrait.setSelected(true);
2282             } else if (or == OrientationRequested.LANDSCAPE) {
2283                 rbLandscape.setSelected(true);
2284             } else if (or == OrientationRequested.REVERSE_PORTRAIT) {
2285                 rbRevPortrait.setSelected(true);
2286             } else { // if (or == OrientationRequested.REVERSE_LANDSCAPE)
2287                 rbRevLandscape.setSelected(true);
2288             }
2289         }
2290     }
2291 
2292 
2293 
2294     /**
2295      * The "Appearance" tab.  Includes the controls for Chromaticity,
2296      * PrintQuality, JobPriority, JobName, and other related job attributes.
2297      */
2298     @SuppressWarnings("serial") // Superclass is not serializable across versions
2299     private class AppearancePanel extends JPanel {
2300 
2301         private ChromaticityPanel pnlChromaticity;
2302         private QualityPanel pnlQuality;
2303         private JobAttributesPanel pnlJobAttributes;
2304         private SidesPanel pnlSides;
2305 
2306         public AppearancePanel() {
2307             super();
2308 
2309             GridBagLayout gridbag = new GridBagLayout();
2310             GridBagConstraints c = new GridBagConstraints();
2311 
2312             setLayout(gridbag);
2313 
2314             c.fill = GridBagConstraints.BOTH;
2315             c.insets = panelInsets;
2316             c.weightx = 1.0;
2317             c.weighty = 1.0;
2318 
2319             c.gridwidth = GridBagConstraints.RELATIVE;
2320             pnlChromaticity = new ChromaticityPanel();
2321             addToGB(pnlChromaticity, this, gridbag, c);
2322 
2323             c.gridwidth = GridBagConstraints.REMAINDER;
2324             pnlQuality = new QualityPanel();
2325             addToGB(pnlQuality, this, gridbag, c);
2326 
2327             c.gridwidth = 1;
2328             pnlSides = new SidesPanel();
2329             addToGB(pnlSides, this, gridbag, c);
2330 
2331             c.gridwidth = GridBagConstraints.REMAINDER;
2332             pnlJobAttributes = new JobAttributesPanel();
2333             addToGB(pnlJobAttributes, this, gridbag, c);
2334 
2335         }
2336 
2337         public void updateInfo() {
2338             pnlChromaticity.updateInfo();
2339             pnlQuality.updateInfo();
2340             pnlSides.updateInfo();
2341             pnlJobAttributes.updateInfo();
2342         }
2343     }
2344 
2345     @SuppressWarnings("serial") // Superclass is not serializable across versions
2346     private class ChromaticityPanel extends JPanel
2347         implements ActionListener
2348     {
2349         private final String strTitle = getMsg("border.chromaticity");
2350         private JRadioButton rbMonochrome, rbColor;
2351 
2352         public ChromaticityPanel() {
2353             super();
2354 
2355             GridBagLayout gridbag = new GridBagLayout();
2356             GridBagConstraints c = new GridBagConstraints();
2357 
2358             setLayout(gridbag);
2359             setBorder(BorderFactory.createTitledBorder(strTitle));
2360 
2361             c.fill = GridBagConstraints.BOTH;
2362             c.gridwidth = GridBagConstraints.REMAINDER;
2363             c.weighty = 1.0;
2364 
2365             ButtonGroup bg = new ButtonGroup();
2366             rbMonochrome = createRadioButton("radiobutton.monochrome", this);
2367             rbMonochrome.setSelected(true);
2368             bg.add(rbMonochrome);
2369             addToGB(rbMonochrome, this, gridbag, c);
2370             rbColor = createRadioButton("radiobutton.color", this);
2371             bg.add(rbColor);
2372             addToGB(rbColor, this, gridbag, c);
2373         }
2374 
2375         public void actionPerformed(ActionEvent e) {
2376             Object source = e.getSource();
2377 
2378             // REMIND: use isSameAs if we move to a IconRB in the future
2379             if (source == rbMonochrome) {
2380                 asCurrent.add(Chromaticity.MONOCHROME);
2381             } else if (source == rbColor) {
2382                 asCurrent.add(Chromaticity.COLOR);
2383             }
2384         }
2385 
2386         public void updateInfo() {
2387             Class<Chromaticity> chCategory = Chromaticity.class;
2388             boolean monoSupported = false;
2389             boolean colorSupported = false;
2390 
2391             if (isAWT) {
2392                 monoSupported = true;
2393                 colorSupported = true;
2394             } else
2395             if (psCurrent.isAttributeCategorySupported(chCategory)) {
2396                 Object values =
2397                     psCurrent.getSupportedAttributeValues(chCategory,
2398                                                           docFlavor,
2399                                                           asCurrent);
2400 
2401                 if (values instanceof Chromaticity[]) {
2402                     Chromaticity[] cvalues = (Chromaticity[])values;
2403 
2404                     for (int i = 0; i < cvalues.length; i++) {
2405                         Chromaticity value = cvalues[i];
2406 
2407                         if (value == Chromaticity.MONOCHROME) {
2408                             monoSupported = true;
2409                         } else if (value == Chromaticity.COLOR) {
2410                             colorSupported = true;
2411                         }
2412                     }
2413                 }
2414             }
2415 
2416 
2417             rbMonochrome.setEnabled(monoSupported);
2418             rbColor.setEnabled(colorSupported);
2419 
2420             Chromaticity ch = (Chromaticity)asCurrent.get(chCategory);
2421             if (ch == null) {
2422                 ch = (Chromaticity)psCurrent.getDefaultAttributeValue(chCategory);
2423                 if (ch == null) {
2424                     ch = Chromaticity.MONOCHROME;
2425                 }
2426             }
2427 
2428             if (ch == Chromaticity.MONOCHROME) {
2429                 rbMonochrome.setSelected(true);
2430             } else { // if (ch == Chromaticity.COLOR)
2431                 rbColor.setSelected(true);
2432             }
2433         }
2434     }
2435 
2436     @SuppressWarnings("serial") // Superclass is not serializable across versions
2437     private class QualityPanel extends JPanel
2438         implements ActionListener
2439     {
2440         private final String strTitle = getMsg("border.quality");
2441         private JRadioButton rbDraft, rbNormal, rbHigh;
2442 
2443         public QualityPanel() {
2444             super();
2445 
2446             GridBagLayout gridbag = new GridBagLayout();
2447             GridBagConstraints c = new GridBagConstraints();
2448 
2449             setLayout(gridbag);
2450             setBorder(BorderFactory.createTitledBorder(strTitle));
2451 
2452             c.fill = GridBagConstraints.BOTH;
2453             c.gridwidth = GridBagConstraints.REMAINDER;
2454             c.weighty = 1.0;
2455 
2456             ButtonGroup bg = new ButtonGroup();
2457             rbDraft = createRadioButton("radiobutton.draftq", this);
2458             bg.add(rbDraft);
2459             addToGB(rbDraft, this, gridbag, c);
2460             rbNormal = createRadioButton("radiobutton.normalq", this);
2461             rbNormal.setSelected(true);
2462             bg.add(rbNormal);
2463             addToGB(rbNormal, this, gridbag, c);
2464             rbHigh = createRadioButton("radiobutton.highq", this);
2465             bg.add(rbHigh);
2466             addToGB(rbHigh, this, gridbag, c);
2467         }
2468 
2469         public void actionPerformed(ActionEvent e) {
2470             Object source = e.getSource();
2471 
2472             if (source == rbDraft) {
2473                 asCurrent.add(PrintQuality.DRAFT);
2474             } else if (source == rbNormal) {
2475                 asCurrent.add(PrintQuality.NORMAL);
2476             } else if (source == rbHigh) {
2477                 asCurrent.add(PrintQuality.HIGH);
2478             }
2479         }
2480 
2481         public void updateInfo() {
2482             Class<PrintQuality> pqCategory = PrintQuality.class;
2483             boolean draftSupported = false;
2484             boolean normalSupported = false;
2485             boolean highSupported = false;
2486 
2487             if (isAWT) {
2488                 draftSupported = true;
2489                 normalSupported = true;
2490                 highSupported = true;
2491             } else
2492             if (psCurrent.isAttributeCategorySupported(pqCategory)) {
2493                 Object values =
2494                     psCurrent.getSupportedAttributeValues(pqCategory,
2495                                                           docFlavor,
2496                                                           asCurrent);
2497 
2498                 if (values instanceof PrintQuality[]) {
2499                     PrintQuality[] qvalues = (PrintQuality[])values;
2500 
2501                     for (int i = 0; i < qvalues.length; i++) {
2502                         PrintQuality value = qvalues[i];
2503 
2504                         if (value == PrintQuality.DRAFT) {
2505                             draftSupported = true;
2506                         } else if (value == PrintQuality.NORMAL) {
2507                             normalSupported = true;
2508                         } else if (value == PrintQuality.HIGH) {
2509                             highSupported = true;
2510                         }
2511                     }
2512                 }
2513             }
2514 
2515             rbDraft.setEnabled(draftSupported);
2516             rbNormal.setEnabled(normalSupported);
2517             rbHigh.setEnabled(highSupported);
2518 
2519             PrintQuality pq = (PrintQuality)asCurrent.get(pqCategory);
2520             if (pq == null) {
2521                 pq = (PrintQuality)psCurrent.getDefaultAttributeValue(pqCategory);
2522                 if (pq == null) {
2523                     pq = PrintQuality.NORMAL;
2524                 }
2525             }
2526 
2527             if (pq == PrintQuality.DRAFT) {
2528                 rbDraft.setSelected(true);
2529             } else if (pq == PrintQuality.NORMAL) {
2530                 rbNormal.setSelected(true);
2531             } else { // if (pq == PrintQuality.HIGH)
2532                 rbHigh.setSelected(true);
2533             }
2534         }
2535 
2536 
2537     }
2538 
2539     @SuppressWarnings("serial") // Superclass is not serializable across versions
2540     private class SidesPanel extends JPanel
2541         implements ActionListener
2542     {
2543         private final String strTitle = getMsg("border.sides");
2544         private IconRadioButton rbOneSide, rbTumble, rbDuplex;
2545 
2546         public SidesPanel() {
2547             super();
2548 
2549             GridBagLayout gridbag = new GridBagLayout();
2550             GridBagConstraints c = new GridBagConstraints();
2551 
2552             setLayout(gridbag);
2553             setBorder(BorderFactory.createTitledBorder(strTitle));
2554 
2555             c.fill = GridBagConstraints.BOTH;
2556             c.insets = compInsets;
2557             c.weighty = 1.0;
2558             c.gridwidth = GridBagConstraints.REMAINDER;
2559 
2560             ButtonGroup bg = new ButtonGroup();
2561             rbOneSide = new IconRadioButton("radiobutton.oneside",
2562                                             "oneside.png", true,
2563                                             bg, this);
2564             rbOneSide.addActionListener(this);
2565             addToGB(rbOneSide, this, gridbag, c);
2566             rbTumble = new IconRadioButton("radiobutton.tumble",
2567                                            "tumble.png", false,
2568                                            bg, this);
2569             rbTumble.addActionListener(this);
2570             addToGB(rbTumble, this, gridbag, c);
2571             rbDuplex = new IconRadioButton("radiobutton.duplex",
2572                                            "duplex.png", false,
2573                                            bg, this);
2574             rbDuplex.addActionListener(this);
2575             c.gridwidth = GridBagConstraints.REMAINDER;
2576             addToGB(rbDuplex, this, gridbag, c);
2577         }
2578 
2579         public void actionPerformed(ActionEvent e) {
2580             Object source = e.getSource();
2581 
2582             if (rbOneSide.isSameAs(source)) {
2583                 asCurrent.add(Sides.ONE_SIDED);
2584             } else if (rbTumble.isSameAs(source)) {
2585                 asCurrent.add(Sides.TUMBLE);
2586             } else if (rbDuplex.isSameAs(source)) {
2587                 asCurrent.add(Sides.DUPLEX);
2588             }
2589         }
2590 
2591         public void updateInfo() {
2592             Class<Sides> sdCategory = Sides.class;
2593             boolean osSupported = false;
2594             boolean tSupported = false;
2595             boolean dSupported = false;
2596 
2597             if (psCurrent.isAttributeCategorySupported(sdCategory)) {
2598                 Object values =
2599                     psCurrent.getSupportedAttributeValues(sdCategory,
2600                                                           docFlavor,
2601                                                           asCurrent);
2602 
2603                 if (values instanceof Sides[]) {
2604                     Sides[] svalues = (Sides[])values;
2605 
2606                     for (int i = 0; i < svalues.length; i++) {
2607                         Sides value = svalues[i];
2608 
2609                         if (value == Sides.ONE_SIDED) {
2610                             osSupported = true;
2611                         } else if (value == Sides.TUMBLE) {
2612                             tSupported = true;
2613                         } else if (value == Sides.DUPLEX) {
2614                             dSupported = true;
2615                         }
2616                     }
2617                 }
2618             }
2619             rbOneSide.setEnabled(osSupported);
2620             rbTumble.setEnabled(tSupported);
2621             rbDuplex.setEnabled(dSupported);
2622 
2623             Sides sd = (Sides)asCurrent.get(sdCategory);
2624             if (sd == null) {
2625                 sd = (Sides)psCurrent.getDefaultAttributeValue(sdCategory);
2626                 if (sd == null) {
2627                     sd = Sides.ONE_SIDED;
2628                 }
2629             }
2630 
2631             if (sd == Sides.ONE_SIDED) {
2632                 rbOneSide.setSelected(true);
2633             } else if (sd == Sides.TUMBLE) {
2634                 rbTumble.setSelected(true);
2635             } else { // if (sd == Sides.DUPLEX)
2636                 rbDuplex.setSelected(true);
2637             }
2638         }
2639     }
2640 
2641 
2642     @SuppressWarnings("serial") // Superclass is not serializable across versions
2643     private class JobAttributesPanel extends JPanel
2644         implements ActionListener, ChangeListener, FocusListener
2645     {
2646         private final String strTitle = getMsg("border.jobattributes");
2647         private JLabel lblPriority, lblJobName, lblUserName;
2648         private JSpinner spinPriority;
2649         private SpinnerNumberModel snModel;
2650         private JCheckBox cbJobSheets;
2651         private JTextField tfJobName, tfUserName;
2652 
2653         public JobAttributesPanel() {
2654             super();
2655 
2656             GridBagLayout gridbag = new GridBagLayout();
2657             GridBagConstraints c = new GridBagConstraints();
2658 
2659             setLayout(gridbag);
2660             setBorder(BorderFactory.createTitledBorder(strTitle));
2661 
2662             c.fill = GridBagConstraints.NONE;
2663             c.insets = compInsets;
2664             c.weighty = 1.0;
2665 
2666             cbJobSheets = createCheckBox("checkbox.jobsheets", this);
2667             c.anchor = GridBagConstraints.LINE_START;
2668             addToGB(cbJobSheets, this, gridbag, c);
2669 
2670             JPanel pnlTop = new JPanel();
2671             lblPriority = new JLabel(getMsg("label.priority"), JLabel.TRAILING);
2672             lblPriority.setDisplayedMnemonic(getMnemonic("label.priority"));
2673 
2674             pnlTop.add(lblPriority);
2675             snModel = new SpinnerNumberModel(1, 1, 100, 1);
2676             spinPriority = new JSpinner(snModel);
2677             lblPriority.setLabelFor(spinPriority);
2678             // REMIND
2679             ((JSpinner.NumberEditor)spinPriority.getEditor()).getTextField().setColumns(3);
2680             spinPriority.addChangeListener(this);
2681             pnlTop.add(spinPriority);
2682             c.anchor = GridBagConstraints.LINE_END;
2683             c.gridwidth = GridBagConstraints.REMAINDER;
2684             pnlTop.getAccessibleContext().setAccessibleName(
2685                                        getMsg("label.priority"));
2686             addToGB(pnlTop, this, gridbag, c);
2687 
2688             c.fill = GridBagConstraints.HORIZONTAL;
2689             c.anchor = GridBagConstraints.CENTER;
2690             c.weightx = 0.0;
2691             c.gridwidth = 1;
2692             char jmnemonic = getMnemonic("label.jobname");
2693             lblJobName = new JLabel(getMsg("label.jobname"), JLabel.TRAILING);
2694             lblJobName.setDisplayedMnemonic(jmnemonic);
2695             addToGB(lblJobName, this, gridbag, c);
2696             c.weightx = 1.0;
2697             c.gridwidth = GridBagConstraints.REMAINDER;
2698             tfJobName = new JTextField();
2699             lblJobName.setLabelFor(tfJobName);
2700             tfJobName.addFocusListener(this);
2701             tfJobName.setFocusAccelerator(jmnemonic);
2702             tfJobName.getAccessibleContext().setAccessibleName(
2703                                              getMsg("label.jobname"));
2704             addToGB(tfJobName, this, gridbag, c);
2705 
2706             c.weightx = 0.0;
2707             c.gridwidth = 1;
2708             char umnemonic = getMnemonic("label.username");
2709             lblUserName = new JLabel(getMsg("label.username"), JLabel.TRAILING);
2710             lblUserName.setDisplayedMnemonic(umnemonic);
2711             addToGB(lblUserName, this, gridbag, c);
2712             c.gridwidth = GridBagConstraints.REMAINDER;
2713             tfUserName = new JTextField();
2714             lblUserName.setLabelFor(tfUserName);
2715             tfUserName.addFocusListener(this);
2716             tfUserName.setFocusAccelerator(umnemonic);
2717             tfUserName.getAccessibleContext().setAccessibleName(
2718                                              getMsg("label.username"));
2719             addToGB(tfUserName, this, gridbag, c);
2720         }
2721 
2722         public void actionPerformed(ActionEvent e) {
2723             if (cbJobSheets.isSelected()) {
2724                 asCurrent.add(JobSheets.STANDARD);
2725             } else {
2726                 asCurrent.add(JobSheets.NONE);
2727             }
2728         }
2729 
2730         public void stateChanged(ChangeEvent e) {
2731             asCurrent.add(new JobPriority(snModel.getNumber().intValue()));
2732         }
2733 
2734         public void focusLost(FocusEvent e) {
2735             Object source = e.getSource();
2736 
2737             if (source == tfJobName) {
2738                 asCurrent.add(new JobName(tfJobName.getText(),
2739                                           Locale.getDefault()));
2740             } else if (source == tfUserName) {
2741                 asCurrent.add(new RequestingUserName(tfUserName.getText(),
2742                                                      Locale.getDefault()));
2743             }
2744         }
2745 
2746         public void focusGained(FocusEvent e) {}
2747 
2748         public void updateInfo() {
2749             Class<JobSheets>          jsCategory = JobSheets.class;
2750             Class<JobPriority>        jpCategory = JobPriority.class;
2751             Class<JobName>            jnCategory = JobName.class;
2752             Class<RequestingUserName> unCategory = RequestingUserName.class;
2753             boolean jsSupported = false;
2754             boolean jpSupported = false;
2755             boolean jnSupported = false;
2756             boolean unSupported = false;
2757 
2758             // setup JobSheets checkbox
2759             if (psCurrent.isAttributeCategorySupported(jsCategory)) {
2760                 jsSupported = true;
2761             }
2762             JobSheets js = (JobSheets)asCurrent.get(jsCategory);
2763             if (js == null) {
2764                 js = (JobSheets)psCurrent.getDefaultAttributeValue(jsCategory);
2765                 if (js == null) {
2766                     js = JobSheets.STANDARD;
2767                 }
2768             }
2769             cbJobSheets.setSelected(js != JobSheets.NONE && jsSupported);
2770             cbJobSheets.setEnabled(jsSupported);
2771 
2772             // setup JobPriority spinner
2773             if (!isAWT && psCurrent.isAttributeCategorySupported(jpCategory)) {
2774                 jpSupported = true;
2775             }
2776             JobPriority jp = (JobPriority)asCurrent.get(jpCategory);
2777             if (jp == null) {
2778                 jp = (JobPriority)psCurrent.getDefaultAttributeValue(jpCategory);
2779                 if (jp == null) {
2780                     jp = new JobPriority(1);
2781                 }
2782             }
2783             int value = jp.getValue();
2784             if ((value < 1) || (value > 100)) {
2785                 value = 1;
2786             }
2787             snModel.setValue(value);
2788             lblPriority.setEnabled(jpSupported);
2789             spinPriority.setEnabled(jpSupported);
2790 
2791             // setup JobName text field
2792             if (psCurrent.isAttributeCategorySupported(jnCategory)) {
2793                 jnSupported = true;
2794             }
2795             JobName jn = (JobName)asCurrent.get(jnCategory);
2796             if (jn == null) {
2797                 jn = (JobName)psCurrent.getDefaultAttributeValue(jnCategory);
2798                 if (jn == null) {
2799                     jn = new JobName("", Locale.getDefault());
2800                 }
2801             }
2802             tfJobName.setText(jn.getValue());
2803             tfJobName.setEnabled(jnSupported);
2804             lblJobName.setEnabled(jnSupported);
2805 
2806             // setup RequestingUserName text field
2807             if (!isAWT && psCurrent.isAttributeCategorySupported(unCategory)) {
2808                 unSupported = true;
2809             }
2810             RequestingUserName un = (RequestingUserName)asCurrent.get(unCategory);
2811             if (un == null) {
2812                 un = (RequestingUserName)psCurrent.getDefaultAttributeValue(unCategory);
2813                 if (un == null) {
2814                     un = new RequestingUserName("", Locale.getDefault());
2815                 }
2816             }
2817             tfUserName.setText(un.getValue());
2818             tfUserName.setEnabled(unSupported);
2819             lblUserName.setEnabled(unSupported);
2820         }
2821     }
2822 
2823 
2824 
2825 
2826     /**
2827      * A special widget that groups a JRadioButton with an associated icon,
2828      * placed to the left of the radio button.
2829      */
2830     @SuppressWarnings("serial") // Superclass is not serializable across versions
2831     private class IconRadioButton extends JPanel {
2832 
2833         private JRadioButton rb;
2834         private JLabel lbl;
2835 
2836         public IconRadioButton(String key, String img, boolean selected,
2837                                ButtonGroup bg, ActionListener al)
2838         {
2839             super(new FlowLayout(FlowLayout.LEADING));
2840             final URL imgURL = getImageResource(img);
2841             Icon icon = java.security.AccessController.doPrivileged(
2842                                  new java.security.PrivilegedAction<Icon>() {
2843                 public Icon run() {
2844                     Icon icon = new ImageIcon(imgURL);
2845                     return icon;
2846                 }
2847             });
2848             lbl = new JLabel(icon);
2849             add(lbl);
2850 
2851             rb = createRadioButton(key, al);
2852             rb.setSelected(selected);
2853             addToBG(rb, this, bg);
2854         }
2855 
2856         public void addActionListener(ActionListener al) {
2857             rb.addActionListener(al);
2858         }
2859 
2860         public boolean isSameAs(Object source) {
2861             return (rb == source);
2862         }
2863 
2864         public void setEnabled(boolean enabled) {
2865             rb.setEnabled(enabled);
2866             lbl.setEnabled(enabled);
2867         }
2868 
2869         public boolean isSelected() {
2870             return rb.isSelected();
2871         }
2872 
2873         public void setSelected(boolean selected) {
2874             rb.setSelected(selected);
2875         }
2876     }
2877 
2878     /**
2879      * Similar in functionality to the default JFileChooser, except this
2880      * chooser will pop up a "Do you want to overwrite..." dialog if the
2881      * user selects a file that already exists.
2882      */
2883     @SuppressWarnings("serial") // JDK implementation class
2884     private class ValidatingFileChooser extends JFileChooser {
2885         public void approveSelection() {
2886             File selected = getSelectedFile();
2887             boolean exists;
2888 
2889             try {
2890                 exists = selected.exists();
2891             } catch (SecurityException e) {
2892                 exists = false;
2893             }
2894 
2895             if (exists) {
2896                 int val;
2897                 val = JOptionPane.showConfirmDialog(this,
2898                                                     getMsg("dialog.overwrite"),
2899                                                     getMsg("dialog.owtitle"),
2900                                                     JOptionPane.YES_NO_OPTION);
2901                 if (val != JOptionPane.YES_OPTION) {
2902                     return;
2903                 }
2904             }
2905 
2906             try {
2907                 if (selected.createNewFile()) {
2908                     selected.delete();
2909                 }
2910             }  catch (IOException ioe) {
2911                 JOptionPane.showMessageDialog(this,
2912                                    getMsg("dialog.writeerror")+" "+selected,
2913                                    getMsg("dialog.owtitle"),
2914                                    JOptionPane.WARNING_MESSAGE);
2915                 return;
2916             } catch (SecurityException se) {
2917                 //There is already file read/write access so at this point
2918                 // only delete access is denied.  Just ignore it because in
2919                 // most cases the file created in createNewFile gets
2920                 // overwritten anyway.
2921             }
2922             File pFile = selected.getParentFile();
2923             if ((selected.exists() &&
2924                       (!selected.isFile() || !selected.canWrite())) ||
2925                      ((pFile != null) &&
2926                       (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) {
2927                 JOptionPane.showMessageDialog(this,
2928                                    getMsg("dialog.writeerror")+" "+selected,
2929                                    getMsg("dialog.owtitle"),
2930                                    JOptionPane.WARNING_MESSAGE);
2931                 return;
2932             }
2933 
2934             super.approveSelection();
2935         }
2936     }
2937 }