001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.util;
028:
029: import java.awt.Color;
030: import java.awt.Component;
031: import java.awt.Dimension;
032: import java.awt.Graphics;
033: import java.awt.Graphics2D;
034: import java.awt.GridBagConstraints;
035: import java.awt.GridBagLayout;
036: import java.awt.Polygon;
037: import java.awt.RenderingHints;
038: import java.awt.event.ActionEvent;
039: import java.awt.event.ActionListener;
040: import java.awt.event.FocusEvent;
041: import java.awt.event.FocusListener;
042: import java.awt.event.MouseEvent;
043: import java.awt.event.MouseListener;
044:
045: import javax.swing.Icon;
046: import javax.swing.JComponent;
047: import javax.swing.JPanel;
048: import javax.swing.JTextField;
049: import javax.swing.Timer;
050:
051: /**
052: * A control with arrows to cycle through a set of values and an entry
053: * field for direct entry of the value.
054: **/
055: public class Spinner extends JPanel {
056: /**
057: * A trivial Document that can hold numeric strings. Attempts to
058: * insert non-numeric information are thwarted. Additional accessor
059: * and mutator provide direct access to the numeric value.
060: **/
061:
062: /** An icon for the down arrow button. **/
063: static Icon downIcon;
064:
065: /** An icon for the up arrow button. **/
066: static Icon upIcon;
067:
068: static RenderingHints antialiasingHint = new RenderingHints(
069: RenderingHints.KEY_ANTIALIASING,
070: RenderingHints.VALUE_ANTIALIAS_ON);
071:
072: static class ArrowIcon implements Icon {
073: private static final int mid = 5;
074: private boolean isUp = true;
075:
076: public ArrowIcon(boolean isUp) {
077: this .isUp = isUp;
078: }
079:
080: public int getIconHeight() {
081: return mid + 1;
082: }
083:
084: public int getIconWidth() {
085: return mid * 2 + 1;
086: }
087:
088: public void paintIcon(Component c, Graphics g, int x, int y) {
089: Graphics2D g2d = ((Graphics2D) g);
090: Color base = g.getColor();
091: g2d.addRenderingHints(antialiasingHint);
092: g2d.setColor(Color.black);
093: g2d.translate(x + 0.5, y + 0.5);
094: if (isUp) {
095: int[] xp = { 0, mid, mid * 2 };
096: int[] yp = { mid, 0, mid };
097: g2d.fill(new Polygon(xp, yp, 3));
098: // for (int i = 0; i <= mid; i++) {
099: // g2d.drawLine(mid-i, i, mid+i, i);
100: // }
101: } else {
102: int[] xp = { 0, mid, mid * 2 };
103: int[] yp = { 0, mid, 0 };
104: g2d.fill(new Polygon(xp, yp, 3));
105: // for (int i = 0; i <= mid; i++) {
106: // g2d.drawLine(mid-i, mid-i, mid+i, mid-i);
107: // }
108: }
109: g2d.translate(-x - 0.5, -y - 0.5);
110: g.setColor(base);
111: }
112: }
113:
114: static Icon getUpIcon() {
115: return new ArrowIcon(true);
116: }
117:
118: static Icon getDownIcon() {
119: return new ArrowIcon(false);
120: }
121:
122: /**
123: * A button displaying an arrow to adjust the value of the spinner.
124: **/
125: private class MyBasicArrowButton extends JComponent implements
126: ActionListener, MouseListener {
127:
128: /** A timer to repeated increment the spinner while the mouse is pressed. **/
129: private Timer repeatTimer = new Timer(60, this );
130:
131: /** The basic increment -- always plus or minus one. **/
132: private int increment;
133:
134: /**
135: * Indicates that the button is current pressed. Used to alter the
136: * button's appearance.
137: **/
138: private boolean pressed = false;
139:
140: /**
141: * The number of times and increment has been performed for this
142: * press of the mouse. Used to accelerate the increment.
143: **/
144: private int nIncrements = 0;
145:
146: /** The correct icon for this button -- upIcon or downIcon **/
147: private Icon icon;
148:
149: /**
150: * Create a button to increment by the given amount.
151: * @param increment the amount to increment. Should be +/- one.
152: **/
153: public MyBasicArrowButton(int increment) {
154: this .increment = increment;
155: repeatTimer.setInitialDelay(300); // default InitialDelay?
156: this .addMouseListener(MyBasicArrowButton.this );
157: if (increment < 0) {
158: icon = getDownIcon();
159: } else {
160: icon = getUpIcon();
161: }
162: }
163:
164: /**
165: * Get the preferred size of this button. The width is built
166: * in. The height is always half the preferred height of the entry
167: * field.
168: * @return the preferred size of this button.
169: **/
170: public Dimension getPreferredSize() {
171: return new Dimension(icon.getIconWidth() + 6, entry
172: .getPreferredSize().height / 2);
173: }
174:
175: /**
176: * Handle a mouse press event. Increment the spinner and start the timer.
177: * @param e the mouse event is not used.
178: **/
179: public void mousePressed(MouseEvent e) {
180: doIncrement(increment);
181: nIncrements = 1;
182: repeatTimer.start();
183: pressed = true;
184: this .repaint();
185: }
186:
187: /**
188: * Handle a mouse release event. The timer is stopped.
189: * @param e the mouse event is not used.
190: **/
191: public void mouseReleased(MouseEvent e) {
192: repeatTimer.stop();
193: pressed = false;
194: this .repaint();
195: }
196:
197: /**
198: * Handle a mouse clicked event. Does nothing.
199: **/
200: public void mouseClicked(MouseEvent e) {
201: }
202:
203: /**
204: * Handle a mouse entered event. Does nothing.
205: **/
206: public void mouseEntered(MouseEvent e) {
207: }
208:
209: /**
210: * Handle a mouse exited event. Does nothing.
211: **/
212: public void mouseExited(MouseEvent e) {
213: }
214:
215: /**
216: * Handle the action event from the repeat timer. Increment the
217: * spinner by an amount that increases as time goes on.
218: **/
219: public void actionPerformed(ActionEvent e) {
220: if (nIncrements > 200) {
221: doIncrement(increment * 100);
222: } else if (nIncrements > 50) {
223: doIncrement(increment * 10);
224: } else {
225: doIncrement(increment);
226: }
227: nIncrements += 1;
228: }
229:
230: /**
231: * Paint this button. The button is a 3D rectangle with an arrow
232: * icon in it.
233: **/
234: public void paintComponent(Graphics g) {
235: Dimension size = this .getSize();
236: g.setColor(this .getBackground());
237: g.fill3DRect(0, 0, size.width, size.height, !pressed);
238: if (icon != null) {
239: icon.paintIcon(this , g, (size.width - icon
240: .getIconWidth()) / 2, (size.height - icon
241: .getIconHeight()) / 2);
242: }
243: }
244: }
245:
246: /**
247: * Increment the spinner by a given amount. The new value is limited
248: * by the range values.
249: * @param by the amount by which to increment the spinner.
250: **/
251: private void doIncrement(int by) {
252: setValue(Math
253: .min(maxValue, Math.max(minValue, getValue() + by)));
254: }
255:
256: /** The Document in the entry field. **/
257: private NumericDocument document = new NumericDocument();
258:
259: /** An entry field to display the spinner value and allow direct input. **/
260: private JTextField entry = new JTextField(document, "0", 6);
261:
262: /** The arrow button for increasing the spinner value. **/
263: private MyBasicArrowButton plusButton = new MyBasicArrowButton(1);
264:
265: /** The arrow button for decreasing the spinner value. **/
266: private MyBasicArrowButton minusButton = new MyBasicArrowButton(-1);
267:
268: /** The minimum value that is permitted in the spinner. **/
269: private int minValue;
270:
271: /** The maximum value that is permitted in the spinner. **/
272: private int maxValue;
273:
274: /**
275: * Create a new Spinner that allows values in a given range and
276: * having a specific initial value.
277: * @param min the minimum value allowed.
278: * @param max the maximum value allowed.
279: * @param value the initial value.
280: **/
281: public Spinner(int min, int max, int value) {
282: super (new GridBagLayout());
283: entry.setHorizontalAlignment(JTextField.RIGHT);
284: entry.addFocusListener(new FocusListener() {
285: public void focusLost(FocusEvent e) {
286: doIncrement(0);
287: }
288:
289: public void focusGained(FocusEvent e) {
290: entry.selectAll();
291: }
292: });
293: setRange(min, max);
294: setValue(value);
295: setColumns(Math.max(columnsNeeded(min), columnsNeeded(max)));
296: GridBagConstraints gbc = new GridBagConstraints();
297: gbc.gridx = 0;
298: gbc.gridy = 0;
299: gbc.gridheight = 2;
300: gbc.weightx = 1.0;
301: gbc.fill = GridBagConstraints.HORIZONTAL;
302: add(entry, gbc);
303: gbc.gridx = 1;
304: gbc.gridheight = 1;
305: gbc.fill = GridBagConstraints.NONE;
306: gbc.weightx = 0.0;
307: add(plusButton, gbc);
308: gbc.gridy = 1;
309: add(minusButton, gbc);
310: }
311:
312: /**
313: * Add an ActionListener to be notified if the value of this Spinner
314: * changes.
315: * @param l the listener to add.
316: **/
317: public synchronized void addActionListener(ActionListener l) {
318: listenerList.add(ActionListener.class, l);
319: }
320:
321: /**
322: * Remove an ActionListener.
323: * @param l the listener to remove.
324: **/
325: public synchronized void removeActionListener(ActionListener l) {
326: listenerList.remove(ActionListener.class, l);
327: }
328:
329: /**
330: * Send an ActionEvent to listeners.
331: **/
332: protected void fireActionPerformed() {
333: // Guaranteed to return a non-null array
334: Object[] listeners = listenerList.getListenerList();
335: ActionEvent e = new ActionEvent(this ,
336: ActionEvent.ACTION_PERFORMED, entry.getText());
337: // Process the listeners last to first, notifying
338: // those that are interested in this event
339: for (int i = listeners.length - 2; i >= 0; i -= 2) {
340: if (listeners[i] == ActionListener.class) {
341: ((ActionListener) listeners[i + 1]).actionPerformed(e);
342: }
343: }
344: }
345:
346: /**
347: * Get the current value of this Spinner.
348: * @return the current value of this Spinner.
349: **/
350: public int getValue() {
351: return document.getValue();
352: }
353:
354: /**
355: * Set the value of this spinner.
356: * @param newValue the new value.
357: * @exception IllegalArgumentException if the new value is out of range.
358: **/
359: public void setValue(int newValue) {
360: if (newValue < minValue || newValue > maxValue) {
361: throw new IllegalArgumentException("newValue out or range");
362: }
363: document.setValue(newValue);
364: fireActionPerformed();
365: }
366:
367: public void setColumns(int columns) {
368: entry.setColumns(columns);
369: }
370:
371: public int getColumns() {
372: return entry.getColumns();
373: }
374:
375: private int columnsNeeded(int val) {
376: if (val == Integer.MIN_VALUE)
377: return 11; // Can't negate this
378: int needed = 0;
379: if (val < 0) {
380: needed = 1;
381: val = -val;
382: }
383: while (val >= 1) {
384: needed++;
385: val /= 10;
386: }
387: return needed;
388: }
389:
390: /**
391: * Change the allowed range of values. The current value is forced
392: * into the specified range.
393: * @param newMin the new minimum value (inclusive)
394: * @param newMax the new maximum value (inclusive)
395: * @exception IllegalArgumentException if the newMin exceeds newMax
396: **/
397: public void setRange(int newMin, int newMax) {
398: if (newMin > newMax) {
399: throw new IllegalArgumentException("newMin > newMax");
400: }
401: minValue = newMin;
402: maxValue = newMax;
403: if (newMin > getValue()) {
404: setValue(newMin);
405: }
406: if (newMax < getValue()) {
407: setValue(newMax);
408: }
409: }
410: }
|