1 /*
   2  * Copyright (c) 2000, 2017, 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 javax.print;
  27 
  28 import java.awt.Dialog;
  29 import java.awt.Frame;
  30 import java.awt.GraphicsConfiguration;
  31 import java.awt.GraphicsEnvironment;
  32 import java.awt.HeadlessException;
  33 import java.awt.Rectangle;
  34 import java.awt.Window;
  35 
  36 import javax.print.attribute.Attribute;
  37 import javax.print.attribute.AttributeSet;
  38 import javax.print.attribute.standard.DialogOwner;
  39 import javax.print.attribute.PrintRequestAttributeSet;
  40 import javax.print.attribute.standard.Destination;
  41 import javax.print.attribute.standard.Fidelity;
  42 
  43 import sun.print.ServiceDialog;
  44 import sun.print.SunAlternateMedia;
  45 
  46 /**
  47  * This class is a collection of UI convenience methods which provide a
  48  * graphical user dialog for browsing print services looked up through the Java
  49  * Print Service API.
  50  * <p>
  51  * The dialogs follow a standard pattern of acting as a continue/cancel option
  52  * for a user as well as allowing the user to select the print service to use
  53  * and specify choices such as paper size and number of copies.
  54  * <p>
  55  * The dialogs are designed to work with pluggable print services though the
  56  * public APIs of those print services.
  57  * <p>
  58  * If a print service provides any vendor extensions these may be made
  59  * accessible to the user through a vendor supplied tab panel {@code Component}.
  60  * Such a vendor extension is encouraged to use Swing! and to support its
  61  * accessibility APIs. The vendor extensions should return the settings as part
  62  * of the {@code AttributeSet}. Applications which want to preserve the user
  63  * settings should use those settings to specify the print job. Note that this
  64  * class is not referenced by any other part of the Java Print Service and may
  65  * not be included in profiles which cannot depend on the presence of the AWT
  66  * packages.
  67  */
  68 public class ServiceUI {
  69 
  70     /**
  71      * Presents a dialog to the user for selecting a print service (printer). It
  72      * is displayed at the location specified by the application and is modal.
  73      * If the specification is invalid or would make the dialog not visible it
  74      * will be displayed at a location determined by the implementation. The
  75      * dialog blocks its calling thread and is application modal.
  76      * <p>
  77      * The dialog may include a tab panel with custom UI lazily obtained from
  78      * the {@code PrintService}'s {@code ServiceUIFactory} when the
  79      * {@code PrintService} is browsed. The dialog will attempt to locate a
  80      * {@code MAIN_UIROLE} first as a {@code JComponent}, then as a
  81      * {@code Panel}. If there is no {@code ServiceUIFactory} or no matching
  82      * role the custom tab will be empty or not visible.
  83      * <p>
  84      * The dialog returns the print service selected by the user if the user
  85      * OK's the dialog and {@code null} if the user cancels the dialog.
  86      * <p>
  87      * An application must pass in an array of print services to browse. The
  88      * array must be {@code non-null} and non-empty. Typically an application
  89      * will pass in only {@code PrintServices} capable of printing a particular
  90      * document flavor.
  91      * <p>
  92      * An application may pass in a {@code PrintService} to be initially
  93      * displayed. A {@code non-null} parameter must be included in the array of
  94      * browsable services. If this parameter is {@code null} a service is chosen
  95      * by the implementation.
  96      * <p>
  97      * An application may optionally pass in the flavor to be printed. If this
  98      * is {@code non-null} choices presented to the user can be better validated
  99      * against those supported by the services. An application must pass in a
 100      * {@code PrintRequestAttributeSet} for returning user choices. On calling
 101      * the {@code PrintRequestAttributeSet} may be empty, or may contain
 102      * application-specified values.
 103      * <p>
 104      * These are used to set the initial settings for the initially displayed
 105      * print service. Values which are not supported by the print service are
 106      * ignored. As the user browses print services, attributes and values are
 107      * copied to the new display. If a user browses a print service which does
 108      * not support a particular attribute-value, the default for that service is
 109      * used as the new value to be copied.
 110      * <p>
 111      * If the user cancels the dialog, the returned attributes will not reflect
 112      * any changes made by the user.
 113      * <p>
 114      * A typical basic usage of this method may be:
 115      * <pre>{@code
 116      * PrintService[] services = PrintServiceLookup.lookupPrintServices(
 117      *                            DocFlavor.INPUT_STREAM.JPEG, null);
 118      * PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();
 119      * if (services.length > 0) {
 120      *    PrintService service =  ServiceUI.printDialog(null, 50, 50,
 121      *                                               services, services[0],
 122      *                                               null,
 123      *                                               attributes);
 124      *    if (service != null) {
 125      *     ... print ...
 126      *    }
 127      * }
 128      * }</pre>
 129      *
 130      * @param  gc used to select screen, {@code null} means primary or default
 131      *         screen
 132      * @param  x location of dialog including border in screen coordinates
 133      *         relative to the origin of {@code gc}
 134      * @param  y location of dialog including border in screen coordinates
 135      *         relative to the origin of {@code gc}
 136      * @param  services to be browsable, must be {@code non-null}
 137      * @param  defaultService initial {@code PrintService} to display
 138      * @param  flavor the flavor to be printed, or {@code null}
 139      * @param  attributes on input is the initial application supplied
 140      *         preferences. This cannot be {@code null} but may be empty. On
 141      *         output the attributes reflect changes made by the user.
 142      * @return print service selected by the user, or {@code null} if the user
 143      *         cancelled the dialog
 144      * @throws HeadlessException if {@code GraphicsEnvironment.isHeadless()}
 145      *         returns {@code true}
 146      * @throws IllegalArgumentException if services is {@code null} or empty, or
 147      *         attributes is {@code null}, or the initial {@code PrintService}
 148      *         is not in the list of browsable services
 149      */
 150     @SuppressWarnings("deprecation")
 151     public static PrintService printDialog(GraphicsConfiguration gc,
 152                                            int x, int y,
 153                                            PrintService[] services,
 154                                            PrintService defaultService,
 155                                            DocFlavor flavor,
 156                                            PrintRequestAttributeSet attributes)
 157         throws HeadlessException
 158     {
 159         int defaultIndex = -1;
 160 
 161         if (GraphicsEnvironment.isHeadless()) {
 162             throw new HeadlessException();
 163         } else if ((services == null) || (services.length == 0)) {
 164             throw new IllegalArgumentException("services must be non-null " +
 165                                                "and non-empty");
 166         } else if (attributes == null) {
 167             throw new IllegalArgumentException("attributes must be non-null");
 168         }
 169 
 170         if (defaultService != null) {
 171             for (int i = 0; i < services.length; i++) {
 172                 if (services[i].equals(defaultService)) {
 173                     defaultIndex = i;
 174                     break;
 175                 }
 176             }
 177 
 178             if (defaultIndex < 0) {
 179                 throw new IllegalArgumentException("services must contain " +
 180                                                    "defaultService");
 181             }
 182         } else {
 183             defaultIndex = 0;
 184         }
 185 
 186         DialogOwner dlgOwner = (DialogOwner)attributes.get(DialogOwner.class);
 187         Window owner = (dlgOwner != null) ? dlgOwner.getOwner() : null;
 188         boolean setOnTop = (dlgOwner != null) && (owner == null);
 189 
 190         Rectangle gcBounds = (gc == null) ?  GraphicsEnvironment.
 191             getLocalGraphicsEnvironment().getDefaultScreenDevice().
 192             getDefaultConfiguration().getBounds() : gc.getBounds();
 193 
 194         x += gcBounds.x;
 195         y += gcBounds.y;
 196         ServiceDialog dialog;
 197         if (owner instanceof Frame) {
 198             dialog = new ServiceDialog(gc,
 199                                        x,
 200                                        y,
 201                                        services, defaultIndex,
 202                                        flavor, attributes,
 203                                        (Frame)owner);
 204         } else {
 205             dialog = new ServiceDialog(gc,
 206                                        x,
 207                                        y,
 208                                        services, defaultIndex,
 209                                        flavor, attributes,
 210                                        (Dialog)owner);
 211         }
 212         if (setOnTop) {
 213             try {
 214                 dialog.setAlwaysOnTop(true);
 215             } catch (SecurityException e) {
 216             }
 217         }
 218         Rectangle dlgBounds = dialog.getBounds();
 219 
 220         // if portion of dialog is not within the gc boundary
 221         if (!gcBounds.contains(dlgBounds)) {
 222             // check if dialog exceed window bounds at left or bottom
 223             // Then position the dialog by moving it by the amount it exceeds
 224             // the window bounds
 225             // If it results in dialog moving beyond the window bounds at
 226             // top/left then position it at window top/left
 227             if (dlgBounds.x + dlgBounds.width > gcBounds.x + gcBounds.width) {
 228                 if ((gcBounds.x + gcBounds.width - dlgBounds.width) > gcBounds.x) {
 229                     x = (gcBounds.x + gcBounds.width) - dlgBounds.width;
 230                 } else {
 231                     x = gcBounds.x;
 232                 }
 233             }
 234             if (dlgBounds.y + dlgBounds.height > gcBounds.y + gcBounds.height) {
 235                 if ((gcBounds.y + gcBounds.height - dlgBounds.height) > gcBounds.y) {
 236                     y = (gcBounds.y + gcBounds.height) - dlgBounds.height;
 237                 } else {
 238                     y = gcBounds.y;
 239                 }
 240             }
 241             dialog.setBounds(x, y, dlgBounds.width, dlgBounds.height);
 242         }
 243         dialog.show();
 244 
 245         if (dialog.getStatus() == ServiceDialog.APPROVE) {
 246             PrintRequestAttributeSet newas = dialog.getAttributes();
 247             Class<?> dstCategory = Destination.class;
 248             Class<?> amCategory = SunAlternateMedia.class;
 249             Class<?> fdCategory = Fidelity.class;
 250 
 251             if (attributes.containsKey(dstCategory) &&
 252                 !newas.containsKey(dstCategory))
 253             {
 254                 attributes.remove(dstCategory);
 255             }
 256 
 257             if (attributes.containsKey(amCategory) &&
 258                 !newas.containsKey(amCategory))
 259             {
 260                 attributes.remove(amCategory);
 261             }
 262 
 263             attributes.addAll(newas);
 264 
 265             Fidelity fd = (Fidelity)attributes.get(fdCategory);
 266             if (fd != null) {
 267                 if (fd == Fidelity.FIDELITY_TRUE) {
 268                     removeUnsupportedAttributes(dialog.getPrintService(),
 269                                                 flavor, attributes);
 270                 }
 271             }
 272         }
 273 
 274         return dialog.getPrintService();
 275     }
 276 
 277     /**
 278      * POSSIBLE FUTURE API: This method may be used down the road if we
 279      * decide to allow developers to explicitly display a "page setup" dialog.
 280      * Currently we use that functionality internally for the AWT print model.
 281      */
 282     /*
 283     public static void pageDialog(GraphicsConfiguration gc,
 284                                   int x, int y,
 285                                   PrintService service,
 286                                   DocFlavor flavor,
 287                                   PrintRequestAttributeSet attributes)
 288         throws HeadlessException
 289     {
 290         if (GraphicsEnvironment.isHeadless()) {
 291             throw new HeadlessException();
 292         } else if (service == null) {
 293             throw new IllegalArgumentException("service must be non-null");
 294         } else if (attributes == null) {
 295             throw new IllegalArgumentException("attributes must be non-null");
 296         }
 297 
 298         ServiceDialog dialog = new ServiceDialog(gc, x, y, service,
 299                                                  flavor, attributes);
 300         dialog.show();
 301 
 302         if (dialog.getStatus() == ServiceDialog.APPROVE) {
 303             PrintRequestAttributeSet newas = dialog.getAttributes();
 304             Class amCategory = SunAlternateMedia.class;
 305 
 306             if (attributes.containsKey(amCategory) &&
 307                 !newas.containsKey(amCategory))
 308             {
 309                 attributes.remove(amCategory);
 310             }
 311 
 312             attributes.addAll(newas.values());
 313         }
 314 
 315         dialog.getOwner().dispose();
 316     }
 317     */
 318 
 319     /**
 320      * Removes any attributes from the given {@code AttributeSet} that are
 321      * unsupported by the given {@code PrintService/DocFlavor} combination.
 322      */
 323     private static void removeUnsupportedAttributes(PrintService ps,
 324                                                     DocFlavor flavor,
 325                                                     AttributeSet aset)
 326     {
 327         AttributeSet asUnsupported = ps.getUnsupportedAttributes(flavor,
 328                                                                  aset);
 329 
 330         if (asUnsupported != null) {
 331             Attribute[] usAttrs = asUnsupported.toArray();
 332 
 333             for (int i=0; i<usAttrs.length; i++) {
 334                 Class<? extends Attribute> category = usAttrs[i].getCategory();
 335 
 336                 if (ps.isAttributeCategorySupported(category)) {
 337                     Attribute attr =
 338                         (Attribute)ps.getDefaultAttributeValue(category);
 339 
 340                     if (attr != null) {
 341                         aset.add(attr);
 342                     } else {
 343                         aset.remove(category);
 344                     }
 345                 } else {
 346                     aset.remove(category);
 347                 }
 348             }
 349         }
 350     }
 351 }