001: /*
002: * Copyright (c) Sun Microsystems.
003: *
004: * JGoodies Note: I've added this copyright to clarify
005: * that this code has been developed and published by Sun.
006: */
007:
008: package com.jgoodies.looks.common;
009:
010: import java.awt.AWTEvent;
011: import java.awt.Component;
012: import java.awt.Container;
013: import java.awt.FocusTraversalPolicy;
014: import java.awt.KeyboardFocusManager;
015: import java.awt.event.ActionEvent;
016: import java.awt.event.FocusEvent;
017: import java.awt.event.FocusListener;
018: import java.awt.event.MouseEvent;
019: import java.awt.event.MouseListener;
020: import java.text.AttributedCharacterIterator;
021: import java.text.CharacterIterator;
022: import java.text.DateFormat;
023: import java.text.Format;
024: import java.text.ParseException;
025: import java.util.Calendar;
026: import java.util.Map;
027:
028: import javax.swing.*;
029: import javax.swing.text.InternationalFormatter;
030:
031: /**
032: * A handler for spinner arrow button mouse and action events. When
033: * a left mouse pressed event occurs we look up the (enabled) spinner
034: * that's the source of the event and start the autorepeat timer. The
035: * timer fires action events until any button is released at which
036: * point the timer is stopped and the reference to the spinner cleared.
037: * The timer doesn't start until after a 300ms delay, so often the
038: * source of the initial (and final) action event is just the button
039: * logic for mouse released - which means that we're relying on the fact
040: * that our mouse listener runs after the buttons mouse listener.<p>
041: *
042: * Note that one instance of this handler is shared by all slider previous
043: * arrow buttons and likewise for all of the next buttons,
044: * so it doesn't have any state that persists beyond the limits
045: * of a single button pressed/released gesture.<p>
046: *
047: * Copied from javax.swing.BasicSpinnerUI
048: *
049: * @version $Revision: 1.4 $
050: *
051: * @see javax.swing.plaf.basic.BasicSpinnerUI
052: */
053: public final class ExtBasicArrowButtonHandler extends AbstractAction
054: implements MouseListener, FocusListener {
055:
056: private final javax.swing.Timer autoRepeatTimer;
057: private final boolean isNext;
058:
059: private JSpinner spinner;
060: private JButton arrowButton;
061:
062: public ExtBasicArrowButtonHandler(String name, boolean isNext) {
063: super (name);
064: this .isNext = isNext;
065: autoRepeatTimer = new javax.swing.Timer(60, this );
066: autoRepeatTimer.setInitialDelay(300);
067: }
068:
069: private JSpinner eventToSpinner(AWTEvent e) {
070: Object src = e.getSource();
071: while ((src instanceof Component) && !(src instanceof JSpinner)) {
072: src = ((Component) src).getParent();
073: }
074: return (src instanceof JSpinner) ? (JSpinner) src : null;
075: }
076:
077: public void actionPerformed(ActionEvent e) {
078: JSpinner spinner = this .spinner;
079:
080: if (!(e.getSource() instanceof javax.swing.Timer)) {
081: // Most likely resulting from being in ActionMap.
082: spinner = eventToSpinner(e);
083: if (e.getSource() instanceof JButton) {
084: arrowButton = (JButton) e.getSource();
085: }
086: } else {
087: if (arrowButton != null
088: && !arrowButton.getModel().isPressed()
089: && autoRepeatTimer.isRunning()) {
090: autoRepeatTimer.stop();
091: spinner = null;
092: arrowButton = null;
093: }
094: }
095: if (spinner != null) {
096: try {
097: int calendarField = getCalendarField(spinner);
098: spinner.commitEdit();
099: if (calendarField != -1) {
100: ((SpinnerDateModel) spinner.getModel())
101: .setCalendarField(calendarField);
102: }
103: Object value = (isNext) ? spinner.getNextValue()
104: : spinner.getPreviousValue();
105: if (value != null) {
106: spinner.setValue(value);
107: select(spinner);
108: }
109: } catch (IllegalArgumentException iae) {
110: UIManager.getLookAndFeel()
111: .provideErrorFeedback(spinner);
112: } catch (ParseException pe) {
113: UIManager.getLookAndFeel()
114: .provideErrorFeedback(spinner);
115: }
116: }
117: }
118:
119: /**
120: * If the spinner's editor is a DateEditor, this selects the field
121: * associated with the value that is being incremented.
122: */
123: private void select(JSpinner aSpinner) {
124: JComponent editor = aSpinner.getEditor();
125:
126: if (editor instanceof JSpinner.DateEditor) {
127: JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor;
128: JFormattedTextField ftf = dateEditor.getTextField();
129: Format format = dateEditor.getFormat();
130: Object value;
131:
132: if (format != null && (value = aSpinner.getValue()) != null) {
133: SpinnerDateModel model = dateEditor.getModel();
134: DateFormat.Field field = DateFormat.Field
135: .ofCalendarField(model.getCalendarField());
136:
137: if (field != null) {
138: try {
139: AttributedCharacterIterator iterator = format
140: .formatToCharacterIterator(value);
141: if (!select(ftf, iterator, field)
142: && field == DateFormat.Field.HOUR0) {
143: select(ftf, iterator,
144: DateFormat.Field.HOUR1);
145: }
146: } catch (IllegalArgumentException iae) {
147: // Should not happen
148: }
149: }
150: }
151: }
152: }
153:
154: /**
155: * Selects the passed in field, returning true if it is found, false otherwise.
156: */
157: private boolean select(JFormattedTextField ftf,
158: AttributedCharacterIterator iterator, DateFormat.Field field) {
159: int max = ftf.getDocument().getLength();
160:
161: iterator.first();
162: do {
163: Map attrs = iterator.getAttributes();
164:
165: if (attrs != null && attrs.containsKey(field)) {
166: int start = iterator.getRunStart(field);
167: int end = iterator.getRunLimit(field);
168:
169: if (start != -1 && end != -1 && start <= max
170: && end <= max) {
171: ftf.select(start, end);
172: }
173: return true;
174: }
175: } while (iterator.next() != CharacterIterator.DONE);
176: return false;
177: }
178:
179: /**
180: * Returns the calendarField under the start of the selection, or
181: * -1 if there is no valid calendar field under the selection (or
182: * the spinner isn't editing dates.
183: */
184: private int getCalendarField(JSpinner aSpinner) {
185: JComponent editor = aSpinner.getEditor();
186:
187: if (editor instanceof JSpinner.DateEditor) {
188: JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor;
189: JFormattedTextField ftf = dateEditor.getTextField();
190: int start = ftf.getSelectionStart();
191: JFormattedTextField.AbstractFormatter formatter = ftf
192: .getFormatter();
193:
194: if (formatter instanceof InternationalFormatter) {
195: Format.Field[] fields = ((InternationalFormatter) formatter)
196: .getFields(start);
197:
198: for (int counter = 0; counter < fields.length; counter++) {
199: if (fields[counter] instanceof DateFormat.Field) {
200: int calendarField;
201:
202: if (fields[counter] == DateFormat.Field.HOUR1) {
203: calendarField = Calendar.HOUR;
204: } else {
205: calendarField = ((DateFormat.Field) fields[counter])
206: .getCalendarField();
207: }
208: if (calendarField != -1) {
209: return calendarField;
210: }
211: }
212: }
213: }
214: }
215: return -1;
216: }
217:
218: public void mousePressed(MouseEvent e) {
219: if (SwingUtilities.isLeftMouseButton(e)
220: && e.getComponent().isEnabled()) {
221: spinner = eventToSpinner(e);
222: autoRepeatTimer.start();
223: focusSpinnerIfNecessary();
224: }
225: }
226:
227: public void mouseReleased(MouseEvent e) {
228: autoRepeatTimer.stop();
229: spinner = null;
230: arrowButton = null;
231: }
232:
233: public void mouseClicked(MouseEvent e) {
234: // Do nothing
235: }
236:
237: public void mouseEntered(MouseEvent e) {
238: if (spinner != null && !autoRepeatTimer.isRunning()) {
239: autoRepeatTimer.start();
240: }
241: }
242:
243: public void mouseExited(MouseEvent e) {
244: if (autoRepeatTimer.isRunning()) {
245: autoRepeatTimer.stop();
246: }
247: }
248:
249: /**
250: * Requests focus on a child of the spinner if the spinner doesn't
251: * have focus.
252: */
253: private void focusSpinnerIfNecessary() {
254: Component fo = KeyboardFocusManager
255: .getCurrentKeyboardFocusManager().getFocusOwner();
256: if (spinner.isRequestFocusEnabled()
257: && (fo == null || !SwingUtilities.isDescendingFrom(fo,
258: spinner))) {
259: Container root = spinner;
260:
261: if (!root.isFocusCycleRoot()) {
262: root = root.getFocusCycleRootAncestor();
263: }
264: if (root != null) {
265: FocusTraversalPolicy ftp = root
266: .getFocusTraversalPolicy();
267: Component child = ftp.getComponentAfter(root, spinner);
268:
269: if (child != null
270: && SwingUtilities.isDescendingFrom(child,
271: spinner)) {
272: child.requestFocus();
273: }
274: }
275: }
276: }
277:
278: public void focusGained(FocusEvent e) {
279: // Do nothing
280: }
281:
282: public void focusLost(FocusEvent e) {
283: if (autoRepeatTimer.isRunning()) {
284: autoRepeatTimer.stop();
285: }
286: spinner = null;
287: if (arrowButton != null) {
288: ButtonModel model = arrowButton.getModel();
289: model.setPressed(false);
290: model.setArmed(false);
291: arrowButton = null;
292: }
293: }
294:
295: }
|