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