1/*
2 * Copyright (c) 2005, 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 */
25package sun.swing;
26
27import javax.swing.*;
28import java.awt.*;
29import java.awt.event.ActionEvent;
30import java.awt.event.WindowAdapter;
31import java.awt.event.WindowEvent;
32import java.awt.print.PageFormat;
33import java.awt.print.Printable;
34import java.awt.print.PrinterException;
35import java.awt.print.PrinterJob;
36import java.text.MessageFormat;
37import java.util.concurrent.atomic.AtomicBoolean;
38import java.lang.reflect.InvocationTargetException;
39
40/**
41 * The {@code PrintingStatus} provides a dialog that displays progress
42 * of the printing job and provides a way to abort it
43 * <p>
44 * Methods of these class are thread safe, although most Swing methods
45 * are not. Please see
46 * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
47 * in Swing</A> for more information.
48 *
49 * @author Alexander Potochkin
50 * @since 1.6
51 */
52
53public class PrintingStatus {
54
55    private final PrinterJob job;
56    private final Component parent;
57    private JDialog abortDialog;
58
59    private JButton abortButton;
60    private JLabel statusLabel;
61    private MessageFormat statusFormat;
62    private final AtomicBoolean isAborted = new AtomicBoolean(false);
63
64    // the action that will abort printing
65    @SuppressWarnings("serial") // anonymous class
66    private final Action abortAction = new AbstractAction() {
67        public void actionPerformed(ActionEvent ae) {
68            if (!isAborted.get()) {
69                isAborted.set(true);
70
71                // update the status abortDialog to indicate aborting
72                abortButton.setEnabled(false);
73                abortDialog.setTitle(
74                    UIManager.getString("PrintingDialog.titleAbortingText"));
75                statusLabel.setText(
76                    UIManager.getString("PrintingDialog.contentAbortingText"));
77
78                // cancel the PrinterJob
79                job.cancel();
80            }
81        }
82    };
83
84    private final WindowAdapter closeListener = new WindowAdapter() {
85        public void windowClosing(WindowEvent we) {
86            abortAction.actionPerformed(null);
87        }
88    };
89
90    /**
91     * Creates PrintingStatus instance
92     *
93     * @param parent a <code>Component</code> object to be used
94     *               as parent component for PrintingStatus dialog
95     * @param job    a <code>PrinterJob</code> object to be cancelled
96     *               using this <code>PrintingStatus</code> dialog
97     * @return a <code>PrintingStatus</code> object
98     */
99    public static PrintingStatus
100            createPrintingStatus(Component parent, PrinterJob job) {
101        return new PrintingStatus(parent, job);
102    }
103
104    protected PrintingStatus(Component parent, PrinterJob job) {
105        this.job = job;
106        this.parent = parent;
107    }
108
109    private void init() {
110        // prepare the status JOptionPane
111        String progressTitle =
112            UIManager.getString("PrintingDialog.titleProgressText");
113
114        String dialogInitialContent =
115            UIManager.getString("PrintingDialog.contentInitialText");
116
117        // this one's a MessageFormat since it must include the page
118        // number in its text
119        statusFormat = new MessageFormat(
120            UIManager.getString("PrintingDialog.contentProgressText"));
121
122        String abortText =
123            UIManager.getString("PrintingDialog.abortButtonText");
124        String abortTooltip =
125            UIManager.getString("PrintingDialog.abortButtonToolTipText");
126        int abortMnemonic =
127            getInt("PrintingDialog.abortButtonMnemonic", -1);
128        int abortMnemonicIndex =
129            getInt("PrintingDialog.abortButtonDisplayedMnemonicIndex", -1);
130
131        abortButton = new JButton(abortText);
132        abortButton.addActionListener(abortAction);
133
134        abortButton.setToolTipText(abortTooltip);
135        if (abortMnemonic != -1) {
136            abortButton.setMnemonic(abortMnemonic);
137        }
138        if (abortMnemonicIndex != -1) {
139            abortButton.setDisplayedMnemonicIndex(abortMnemonicIndex);
140        }
141        statusLabel = new JLabel(dialogInitialContent);
142        JOptionPane abortPane = new JOptionPane(statusLabel,
143            JOptionPane.INFORMATION_MESSAGE,
144            JOptionPane.DEFAULT_OPTION,
145            null, new Object[]{abortButton},
146            abortButton);
147        abortPane.getActionMap().put("close", abortAction);
148
149        // The dialog should be centered over the viewport if the table is in one
150        if (parent != null && parent.getParent() instanceof JViewport) {
151            abortDialog =
152                    abortPane.createDialog(parent.getParent(), progressTitle);
153        } else {
154            abortDialog = abortPane.createDialog(parent, progressTitle);
155        }
156        // clicking the X button should not hide the dialog
157        abortDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
158        abortDialog.addWindowListener(closeListener);
159    }
160
161    /**
162     * Shows PrintingStatus dialog.
163     * if dialog is modal this method returns only
164     * after <code>dispose()</code> was called otherwise returns immediately
165     *
166     * @param isModal <code>true</code> this dialog should be modal;
167     *                <code>false</code> otherwise.
168     * @see #dispose
169     */
170    public void showModal(final boolean isModal) {
171        if (SwingUtilities.isEventDispatchThread()) {
172            showModalOnEDT(isModal);
173        } else {
174            try {
175                SwingUtilities.invokeAndWait(new Runnable() {
176                    public void run() {
177                        showModalOnEDT(isModal);
178                    }
179                });
180            } catch(InterruptedException e) {
181                throw new RuntimeException(e);
182            } catch(InvocationTargetException e) {
183                Throwable cause = e.getCause();
184                if (cause instanceof RuntimeException) {
185                   throw (RuntimeException) cause;
186                } else if (cause instanceof Error) {
187                   throw (Error) cause;
188                } else {
189                   throw new RuntimeException(cause);
190                }
191            }
192        }
193    }
194
195    /**
196     * The EDT part of the showModal method.
197     *
198     * This method is to be called on the EDT only.
199     */
200    private void showModalOnEDT(boolean isModal) {
201        assert SwingUtilities.isEventDispatchThread();
202        init();
203        abortDialog.setModal(isModal);
204        abortDialog.setVisible(true);
205    }
206
207    /**
208     * Disposes modal PrintingStatus dialog
209     *
210     * @see #showModal(boolean)
211     */
212    public void dispose() {
213        if (SwingUtilities.isEventDispatchThread()) {
214            disposeOnEDT();
215        } else {
216            SwingUtilities.invokeLater(new Runnable() {
217                public void run() {
218                    disposeOnEDT();
219                }
220            });
221        }
222    }
223
224    /**
225     * The EDT part of the dispose method.
226     *
227     * This method is to be called on the EDT only.
228     */
229    private void disposeOnEDT() {
230        assert SwingUtilities.isEventDispatchThread();
231        if (abortDialog != null) {
232            abortDialog.removeWindowListener(closeListener);
233            abortDialog.dispose();
234            abortDialog = null;
235        }
236    }
237
238    /**
239     * Returns whether the printng was aborted using this PrintingStatus
240     *
241     * @return whether the printng was aborted using this PrintingStatus
242     */
243    public boolean isAborted() {
244        return isAborted.get();
245    }
246
247    /**
248     * Returns printable which is used to track the current page being
249     * printed in this PrintingStatus
250     *
251     * @param printable to be used to create notification printable
252     * @return printable which is used to track the current page being
253     *         printed in this PrintingStatus
254     * @throws NullPointerException if <code>printable</code> is <code>null</code>
255     */
256    public Printable createNotificationPrintable(Printable printable) {
257        return new NotificationPrintable(printable);
258    }
259
260    private class NotificationPrintable implements Printable {
261        private final Printable printDelegatee;
262
263        public NotificationPrintable(Printable delegatee) {
264            if (delegatee == null) {
265                throw new NullPointerException("Printable is null");
266            }
267            this.printDelegatee = delegatee;
268        }
269
270        public int print(final Graphics graphics,
271                         final PageFormat pageFormat, final int pageIndex)
272                throws PrinterException {
273
274            final int retVal =
275                printDelegatee.print(graphics, pageFormat, pageIndex);
276            if (retVal != NO_SUCH_PAGE && !isAborted()) {
277                if (SwingUtilities.isEventDispatchThread()) {
278                    updateStatusOnEDT(pageIndex);
279                } else {
280                    SwingUtilities.invokeLater(new Runnable() {
281                        public void run() {
282                            updateStatusOnEDT(pageIndex);
283                        }
284                    });
285                }
286            }
287            return retVal;
288        }
289
290        /**
291         * The EDT part of the print method.
292         *
293         * This method is to be called on the EDT only.
294         */
295        private void updateStatusOnEDT(int pageIndex) {
296            assert SwingUtilities.isEventDispatchThread();
297            Object[] pageNumber = new Object[]{
298                pageIndex + 1};
299            statusLabel.setText(statusFormat.format(pageNumber));
300        }
301    }
302
303    /**
304     * Duplicated from UIManager to make it visible
305     */
306    static int getInt(Object key, int defaultValue) {
307        Object value = UIManager.get(key);
308        if (value instanceof Integer) {
309            return ((Integer) value).intValue();
310        }
311        if (value instanceof String) {
312            try {
313                return Integer.parseInt((String) value);
314            } catch(NumberFormatException nfe) {
315            }
316        }
317        return defaultValue;
318    }
319}
320