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