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