/* * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import java.io.*; import java.awt.BorderLayout; import java.awt.Frame; import java.awt.Dialog; import java.awt.Window; import java.awt.Component; import java.awt.Container; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.awt.event.WindowListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.IllegalComponentStateException; import java.awt.Point; import java.awt.Rectangle; import java.text.*; import java.util.Locale; import javax.accessibility.*; import javax.swing.event.*; import javax.swing.text.*; /** A class to monitor the progress of some operation. If it looks * like the operation will take a while, a progress dialog will be popped up. * When the ProgressMonitor is created it is given a numeric range and a * descriptive string. As the operation progresses, call the setProgress method * to indicate how far along the [min,max] range the operation is. * Initially, there is no ProgressDialog. After the first millisToDecideToPopup * milliseconds (default 500) the progress monitor will predict how long * the operation will take. If it is longer than millisToPopup (default 2000, * 2 seconds) a ProgressDialog will be popped up. *

* From time to time, when the Dialog box is visible, the progress bar will * be updated when setProgress is called. setProgress won't always update * the progress bar, it will only be done if the amount of progress is * visibly significant. * *

* * For further documentation and examples see * How to Monitor Progress, * a section in The Java Tutorial. * * @see ProgressMonitorInputStream * @author James Gosling * @author Lynn Monsanto (accessibility) */ public class ProgressMonitor implements Accessible { private ProgressMonitor root; private JDialog dialog; private JOptionPane pane; private JProgressBar myBar; private JLabel noteLabel; private Component parentComponent; private String note; private Object[] cancelOption = null; private Object message; private long T0; private int millisToDecideToPopup = 500; private int millisToPopup = 2000; private int min; private int max; /** * Constructs a graphic object that shows progress, typically by filling * in a rectangular bar as the process nears completion. * * @param parentComponent the parent component for the dialog box * @param message a descriptive message that will be shown * to the user to indicate what operation is being monitored. * This does not change as the operation progresses. * See the message parameters to methods in * {@link JOptionPane#message} * for the range of values. * @param note a short note describing the state of the * operation. As the operation progresses, you can call * setNote to change the note displayed. This is used, * for example, in operations that iterate through a * list of files to show the name of the file being processes. * If note is initially null, there will be no note line * in the dialog box and setNote will be ineffective * @param min the lower bound of the range * @param max the upper bound of the range * @see JDialog * @see JOptionPane */ public ProgressMonitor(Component parentComponent, Object message, String note, int min, int max) { this(parentComponent, message, note, min, max, null); } private ProgressMonitor(Component parentComponent, Object message, String note, int min, int max, ProgressMonitor group) { this.min = min; this.max = max; this.parentComponent = parentComponent; cancelOption = new Object[1]; cancelOption[0] = UIManager.getString("OptionPane.cancelButtonText"); this.message = message; this.note = note; if (group != null) { root = (group.root != null) ? group.root : group; T0 = root.T0; dialog = root.dialog; } else { T0 = System.currentTimeMillis(); } } @SuppressWarnings("serial") // Superclass is not serializable across versions private class ProgressOptionPane extends JOptionPane { ProgressOptionPane(Object messageList) { super(messageList, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, ProgressMonitor.this.cancelOption, null); } public int getMaxCharactersPerLineCount() { return 60; } // Equivalent to JOptionPane.createDialog, // but create a modeless dialog. // This is necessary because the Solaris implementation doesn't // support Dialog.setModal yet. public JDialog createDialog(Component parentComponent, String title) { final JDialog dialog; Window window = JOptionPane.getWindowForComponent(parentComponent); if (window instanceof Frame) { dialog = new JDialog((Frame)window, title, false); } else { dialog = new JDialog((Dialog)window, title, false); } if (window instanceof SwingUtilities.SharedOwnerFrame) { WindowListener ownerShutdownListener = SwingUtilities.getSharedOwnerFrameShutdownListener(); dialog.addWindowListener(ownerShutdownListener); } Container contentPane = dialog.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(this, BorderLayout.CENTER); dialog.pack(); dialog.setLocationRelativeTo(parentComponent); dialog.addWindowListener(new WindowAdapter() { boolean gotFocus = false; public void windowClosing(WindowEvent we) { setValue(cancelOption[0]); } public void windowActivated(WindowEvent we) { // Once window gets focus, set initial focus if (!gotFocus) { selectInitialValue(); gotFocus = true; } } }); addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if(dialog.isVisible() && event.getSource() == ProgressOptionPane.this && (event.getPropertyName().equals(VALUE_PROPERTY) || event.getPropertyName().equals(INPUT_VALUE_PROPERTY))){ dialog.setVisible(false); dialog.dispose(); } } }); return dialog; } ///////////////// // Accessibility support for ProgressOptionPane //////////////// /** * Gets the AccessibleContext for the ProgressOptionPane * * @return the AccessibleContext for the ProgressOptionPane * @since 1.5 */ public AccessibleContext getAccessibleContext() { return ProgressMonitor.this.getAccessibleContext(); } /* * Returns the AccessibleJOptionPane */ private AccessibleContext getAccessibleJOptionPane() { return super.getAccessibleContext(); } } /** * Indicate the progress of the operation being monitored. * If the specified value is >= the maximum, the progress * monitor is closed. * @param nv an int specifying the current value, between the * maximum and minimum specified for this component * @see #setMinimum * @see #setMaximum * @see #close */ public void setProgress(int nv) { if (nv >= max) { close(); } else { if (myBar != null) { myBar.setValue(nv); } else { long T = System.currentTimeMillis(); long dT = (int)(T-T0); if (dT >= millisToDecideToPopup) { int predictedCompletionTime; if (nv > min) { predictedCompletionTime = (int)(dT * (max - min) / (nv - min)); } else { predictedCompletionTime = millisToPopup; } if (predictedCompletionTime >= millisToPopup) { myBar = new JProgressBar(); myBar.setMinimum(min); myBar.setMaximum(max); myBar.setValue(nv); if (note != null) noteLabel = new JLabel(note); pane = new ProgressOptionPane(new Object[] {message, noteLabel, myBar}); dialog = pane.createDialog(parentComponent, UIManager.getString( "ProgressMonitor.progressText")); dialog.show(); } } } } } /** * Indicate that the operation is complete. This happens automatically * when the value set by setProgress is >= max, but it may be called * earlier if the operation ends early. */ public void close() { if (dialog != null) { dialog.setVisible(false); dialog.dispose(); dialog = null; pane = null; myBar = null; } } /** * Returns the minimum value -- the lower end of the progress value. * * @return an int representing the minimum value * @see #setMinimum */ public int getMinimum() { return min; } /** * Specifies the minimum value. * * @param m an int specifying the minimum value * @see #getMinimum */ public void setMinimum(int m) { if (myBar != null) { myBar.setMinimum(m); } min = m; } /** * Returns the maximum value -- the higher end of the progress value. * * @return an int representing the maximum value * @see #setMaximum */ public int getMaximum() { return max; } /** * Specifies the maximum value. * * @param m an int specifying the maximum value * @see #getMaximum */ public void setMaximum(int m) { if (myBar != null) { myBar.setMaximum(m); } max = m; } /** * Returns true if the user hits the Cancel button in the progress dialog. */ public boolean isCanceled() { if (pane == null) return false; Object v = pane.getValue(); return ((v != null) && (cancelOption.length == 1) && (v.equals(cancelOption[0]))); } /** * Specifies the amount of time to wait before deciding whether or * not to popup a progress monitor. * * @param millisToDecideToPopup an int specifying the time to wait, * in milliseconds * @see #getMillisToDecideToPopup */ public void setMillisToDecideToPopup(int millisToDecideToPopup) { this.millisToDecideToPopup = millisToDecideToPopup; } /** * Returns the amount of time this object waits before deciding whether * or not to popup a progress monitor. * * @see #setMillisToDecideToPopup */ public int getMillisToDecideToPopup() { return millisToDecideToPopup; } /** * Specifies the amount of time it will take for the popup to appear. * (If the predicted time remaining is less than this time, the popup * won't be displayed.) * * @param millisToPopup an int specifying the time in milliseconds * @see #getMillisToPopup */ public void setMillisToPopup(int millisToPopup) { this.millisToPopup = millisToPopup; } /** * Returns the amount of time it will take for the popup to appear. * * @see #setMillisToPopup */ public int getMillisToPopup() { return millisToPopup; } /** * Specifies the additional note that is displayed along with the * progress message. Used, for example, to show which file the * is currently being copied during a multiple-file copy. * * @param note a String specifying the note to display * @see #getNote */ public void setNote(String note) { this.note = note; if (noteLabel != null) { noteLabel.setText(note); } } /** * Specifies the additional note that is displayed along with the * progress message. * * @return a String specifying the note to display * @see #setNote */ public String getNote() { return note; } ///////////////// // Accessibility support //////////////// /** * The AccessibleContext for the ProgressMonitor * @since 1.5 */ protected AccessibleContext accessibleContext = null; private AccessibleContext accessibleJOptionPane = null; /** * Gets the AccessibleContext for the * ProgressMonitor * * @return the AccessibleContext for the * ProgressMonitor * @since 1.5 */ public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleProgressMonitor(); } if (pane != null && accessibleJOptionPane == null) { // Notify the AccessibleProgressMonitor that the // ProgressOptionPane was created. It is necessary // to poll for ProgressOptionPane creation because // the ProgressMonitor does not have a Component // to add a listener to until the ProgressOptionPane // is created. if (accessibleContext instanceof AccessibleProgressMonitor) { ((AccessibleProgressMonitor)accessibleContext).optionPaneCreated(); } } return accessibleContext; } /** * AccessibleProgressMonitor implements accessibility * support for the ProgressMonitor class. * @since 1.5 */ protected class AccessibleProgressMonitor extends AccessibleContext implements AccessibleText, ChangeListener, PropertyChangeListener { /* * The accessibility hierarchy for ProgressMonitor is a flattened * version of the ProgressOptionPane component hierarchy. * * The ProgressOptionPane component hierarchy is: * JDialog * ProgressOptionPane * JPanel * JPanel * JLabel * JLabel * JProgressBar * * The AccessibleProgessMonitor accessibility hierarchy is: * AccessibleJDialog * AccessibleProgressMonitor * AccessibleJLabel * AccessibleJLabel * AccessibleJProgressBar * * The abstraction presented to assitive technologies by * the AccessibleProgressMonitor is that a dialog contains a * progress monitor with three children: a message, a note * label and a progress bar. */ private Object oldModelValue; /** * AccessibleProgressMonitor constructor */ protected AccessibleProgressMonitor() { } /* * Initializes the AccessibleContext now that the ProgressOptionPane * has been created. Because the ProgressMonitor is not a Component * implementing the Accessible interface, an AccessibleContext * must be synthesized from the ProgressOptionPane and its children. * * For other AWT and Swing classes, the inner class that implements * accessibility for the class extends the inner class that implements * implements accessibility for the super class. AccessibleProgressMonitor * cannot extend AccessibleJOptionPane and must therefore delegate calls * to the AccessibleJOptionPane. */ private void optionPaneCreated() { accessibleJOptionPane = ((ProgressOptionPane)pane).getAccessibleJOptionPane(); // add a listener for progress bar ChangeEvents if (myBar != null) { myBar.addChangeListener(this); } // add a listener for note label PropertyChangeEvents if (noteLabel != null) { noteLabel.addPropertyChangeListener(this); } } /** * Invoked when the target of the listener has changed its state. * * @param e a ChangeEvent object. Must not be null. * @throws NullPointerException if the parameter is null. */ public void stateChanged(ChangeEvent e) { if (e == null) { return; } if (myBar != null) { // the progress bar value changed Object newModelValue = myBar.getValue(); firePropertyChange(ACCESSIBLE_VALUE_PROPERTY, oldModelValue, newModelValue); oldModelValue = newModelValue; } } /** * This method gets called when a bound property is changed. * * @param e A PropertyChangeEvent object describing * the event source and the property that has changed. Must not be null. * @throws NullPointerException if the parameter is null. */ public void propertyChange(PropertyChangeEvent e) { if (e.getSource() == noteLabel && e.getPropertyName() == "text") { // the note label text changed firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, 0); } } /* ===== Begin AccessileContext ===== */ /** * Gets the accessibleName property of this object. The accessibleName * property of an object is a localized String that designates the purpose * of the object. For example, the accessibleName property of a label * or button might be the text of the label or button itself. In the * case of an object that doesn't display its name, the accessibleName * should still be set. For example, in the case of a text field used * to enter the name of a city, the accessibleName for the en_US locale * could be 'city.' * * @return the localized name of the object; null if this * object does not have a name * * @see #setAccessibleName */ public String getAccessibleName() { if (accessibleName != null) { // defined in AccessibleContext return accessibleName; } else if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getAccessibleName(); } return null; } /** * Gets the accessibleDescription property of this object. The * accessibleDescription property of this object is a short localized * phrase describing the purpose of the object. For example, in the * case of a 'Cancel' button, the accessibleDescription could be * 'Ignore changes and close dialog box.' * * @return the localized description of the object; null if * this object does not have a description * * @see #setAccessibleDescription */ public String getAccessibleDescription() { if (accessibleDescription != null) { // defined in AccessibleContext return accessibleDescription; } else if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getAccessibleDescription(); } return null; } /** * Gets the role of this object. The role of the object is the generic * purpose or use of the class of this object. For example, the role * of a push button is AccessibleRole.PUSH_BUTTON. The roles in * AccessibleRole are provided so component developers can pick from * a set of predefined roles. This enables assistive technologies to * provide a consistent interface to various tweaked subclasses of * components (e.g., use AccessibleRole.PUSH_BUTTON for all components * that act like a push button) as well as distinguish between subclasses * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes * and AccessibleRole.RADIO_BUTTON for radio buttons). *

Note that the AccessibleRole class is also extensible, so * custom component developers can define their own AccessibleRole's * if the set of predefined roles is inadequate. * * @return an instance of AccessibleRole describing the role of the object * @see AccessibleRole */ public AccessibleRole getAccessibleRole() { return AccessibleRole.PROGRESS_MONITOR; } /** * Gets the state set of this object. The AccessibleStateSet of an object * is composed of a set of unique AccessibleStates. A change in the * AccessibleStateSet of an object will cause a PropertyChangeEvent to * be fired for the ACCESSIBLE_STATE_PROPERTY property. * * @return an instance of AccessibleStateSet containing the * current state set of the object * @see AccessibleStateSet * @see AccessibleState * @see #addPropertyChangeListener */ public AccessibleStateSet getAccessibleStateSet() { if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getAccessibleStateSet(); } return null; } /** * Gets the Accessible parent of this object. * * @return the Accessible parent of this object; null if this * object does not have an Accessible parent */ public Accessible getAccessibleParent() { return dialog; } /* * Returns the parent AccessibleContext */ private AccessibleContext getParentAccessibleContext() { if (dialog != null) { return dialog.getAccessibleContext(); } return null; } /** * Gets the 0-based index of this object in its accessible parent. * * @return the 0-based index of this object in its parent; -1 if this * object does not have an accessible parent. * * @see #getAccessibleParent * @see #getAccessibleChildrenCount * @see #getAccessibleChild */ public int getAccessibleIndexInParent() { if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getAccessibleIndexInParent(); } return -1; } /** * Returns the number of accessible children of the object. * * @return the number of accessible children of the object. */ public int getAccessibleChildrenCount() { // return the number of children in the JPanel containing // the message, note label and progress bar AccessibleContext ac = getPanelAccessibleContext(); if (ac != null) { return ac.getAccessibleChildrenCount(); } return 0; } /** * Returns the specified Accessible child of the object. The Accessible * children of an Accessible object are zero-based, so the first child * of an Accessible child is at index 0, the second child is at index 1, * and so on. * * @param i zero-based index of child * @return the Accessible child of the object * @see #getAccessibleChildrenCount */ public Accessible getAccessibleChild(int i) { // return a child in the JPanel containing the message, note label // and progress bar AccessibleContext ac = getPanelAccessibleContext(); if (ac != null) { return ac.getAccessibleChild(i); } return null; } /* * Returns the AccessibleContext for the JPanel containing the * message, note label and progress bar */ private AccessibleContext getPanelAccessibleContext() { if (myBar != null) { Component c = myBar.getParent(); if (c instanceof Accessible) { return c.getAccessibleContext(); } } return null; } /** * Gets the locale of the component. If the component does not have a * locale, then the locale of its parent is returned. * * @return this component's locale. If this component does not have * a locale, the locale of its parent is returned. * * @exception IllegalComponentStateException * If the Component does not have its own locale and has not yet been * added to a containment hierarchy such that the locale can be * determined from the containing parent. */ public Locale getLocale() throws IllegalComponentStateException { if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getLocale(); } return null; } /* ===== end AccessibleContext ===== */ /** * Gets the AccessibleComponent associated with this object that has a * graphical representation. * * @return AccessibleComponent if supported by object; else return null * @see AccessibleComponent */ public AccessibleComponent getAccessibleComponent() { if (accessibleJOptionPane != null) { // delegate to the AccessibleJOptionPane return accessibleJOptionPane.getAccessibleComponent(); } return null; } /** * Gets the AccessibleValue associated with this object that supports a * Numerical value. * * @return AccessibleValue if supported by object; else return null * @see AccessibleValue */ public AccessibleValue getAccessibleValue() { if (myBar != null) { // delegate to the AccessibleJProgressBar return myBar.getAccessibleContext().getAccessibleValue(); } return null; } /** * Gets the AccessibleText associated with this object presenting * text on the display. * * @return AccessibleText if supported by object; else return null * @see AccessibleText */ public AccessibleText getAccessibleText() { if (getNoteLabelAccessibleText() != null) { return this; } return null; } /* * Returns the note label AccessibleText */ private AccessibleText getNoteLabelAccessibleText() { if (noteLabel != null) { // AccessibleJLabel implements AccessibleText if the // JLabel contains HTML text return noteLabel.getAccessibleContext().getAccessibleText(); } return null; } /* ===== Begin AccessibleText impl ===== */ /** * Given a point in local coordinates, return the zero-based index * of the character under that Point. If the point is invalid, * this method returns -1. * * @param p the Point in local coordinates * @return the zero-based index of the character under Point p; if * Point is invalid return -1. */ public int getIndexAtPoint(Point p) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null && sameWindowAncestor(pane, noteLabel)) { // convert point from the option pane bounds // to the note label bounds. Point noteLabelPoint = SwingUtilities.convertPoint(pane, p, noteLabel); if (noteLabelPoint != null) { return at.getIndexAtPoint(noteLabelPoint); } } return -1; } /** * Determines the bounding box of the character at the given * index into the string. The bounds are returned in local * coordinates. If the index is invalid an empty rectangle is returned. * * @param i the index into the String * @return the screen coordinates of the character's bounding box, * if index is invalid return an empty rectangle. */ public Rectangle getCharacterBounds(int i) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null && sameWindowAncestor(pane, noteLabel)) { // return rectangle in the option pane bounds Rectangle noteLabelRect = at.getCharacterBounds(i); if (noteLabelRect != null) { return SwingUtilities.convertRectangle(noteLabel, noteLabelRect, pane); } } return null; } /* * Returns whether source and destination components have the * same window ancestor */ private boolean sameWindowAncestor(Component src, Component dest) { if (src == null || dest == null) { return false; } return SwingUtilities.getWindowAncestor(src) == SwingUtilities.getWindowAncestor(dest); } /** * Returns the number of characters (valid indicies) * * @return the number of characters */ public int getCharCount() { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getCharCount(); } return -1; } /** * Returns the zero-based offset of the caret. * * Note: That to the right of the caret will have the same index * value as the offset (the caret is between two characters). * @return the zero-based offset of the caret. */ public int getCaretPosition() { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getCaretPosition(); } return -1; } /** * Returns the String at a given index. * * @param part the CHARACTER, WORD, or SENTENCE to retrieve * @param index an index within the text * @return the letter, word, or sentence */ public String getAtIndex(int part, int index) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getAtIndex(part, index); } return null; } /** * Returns the String after a given index. * * @param part the CHARACTER, WORD, or SENTENCE to retrieve * @param index an index within the text * @return the letter, word, or sentence */ public String getAfterIndex(int part, int index) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getAfterIndex(part, index); } return null; } /** * Returns the String before a given index. * * @param part the CHARACTER, WORD, or SENTENCE to retrieve * @param index an index within the text * @return the letter, word, or sentence */ public String getBeforeIndex(int part, int index) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getBeforeIndex(part, index); } return null; } /** * Returns the AttributeSet for a given character at a given index * * @param i the zero-based index into the text * @return the AttributeSet of the character */ public AttributeSet getCharacterAttribute(int i) { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getCharacterAttribute(i); } return null; } /** * Returns the start offset within the selected text. * If there is no selection, but there is * a caret, the start and end offsets will be the same. * * @return the index into the text of the start of the selection */ public int getSelectionStart() { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getSelectionStart(); } return -1; } /** * Returns the end offset within the selected text. * If there is no selection, but there is * a caret, the start and end offsets will be the same. * * @return the index into the text of the end of the selection */ public int getSelectionEnd() { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getSelectionEnd(); } return -1; } /** * Returns the portion of the text that is selected. * * @return the String portion of the text that is selected */ public String getSelectedText() { AccessibleText at = getNoteLabelAccessibleText(); if (at != null) { // JLabel contains HTML text return at.getSelectedText(); } return null; } /* ===== End AccessibleText impl ===== */ } // inner class AccessibleProgressMonitor }