/* * Copyright (c) 1997, 2006, 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 call.gui; import java.awt.Component; import java.awt.IllegalComponentStateException; import java.awt.Point; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Locale; import javax.accessibility.Accessible; import javax.accessibility.AccessibleComponent; import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleText; import javax.accessibility.AccessibleValue; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JProgressBar; import javax.swing.ProgressMonitorInputStream; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.AttributeSet; /** * 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. * <p> * 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. * * <p> * * For further documentation and examples see <a href= * "http://java.sun.com/docs/books/tutorial/uiswing/components/progress.html" * >How to Monitor Progress</a>, a section in <em>The Java Tutorial.</em> * * @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(); } } private class ProgressOptionPane extends JOptionPane { private static final long serialVersionUID = -5721761952844102289L; ProgressOptionPane(Object messageList) { super(messageList, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, ProgressMonitor.this.cancelOption, null); } public int getMaxCharactersPerLineCount() { return 60; } // /////////////// // 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.setVisible(true); } } } } } /** * 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 <code>AccessibleContext</code> for the <code>ProgressMonitor</code> * * @since 1.5 */ protected AccessibleContext accessibleContext = null; private AccessibleContext accessibleJOptionPane = null; /** * Gets the <code>AccessibleContext</code> for the * <code>ProgressMonitor</code> * * @return the <code>AccessibleContext</code> for the * <code>ProgressMonitor</code> * @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; } /** * <code>AccessibleProgressMonitor</code> implements accessibility support * for the <code>ProgressMonitor</code> 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 <code>ChangeEvent</code> 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 <code>PropertyChangeEvent</code> 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 sublasses * that behave differently (e.g., AccessibleRole.CHECK_BOX for check * boxes and AccessibleRole.RADIO_BUTTON for radio buttons). * <p> * 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; } /** * 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 teh 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 }