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