001 /* BasicSpinnerUI.java --
002 Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Component;
042 import java.awt.Container;
043 import java.awt.Dimension;
044 import java.awt.Insets;
045 import java.awt.LayoutManager;
046 import java.awt.event.ActionEvent;
047 import java.awt.event.ActionListener;
048 import java.awt.event.MouseAdapter;
049 import java.awt.event.MouseEvent;
050 import java.beans.PropertyChangeEvent;
051 import java.beans.PropertyChangeListener;
052
053 import javax.swing.JButton;
054 import javax.swing.JComponent;
055 import javax.swing.JSpinner;
056 import javax.swing.LookAndFeel;
057 import javax.swing.Timer;
058 import javax.swing.plaf.ComponentUI;
059 import javax.swing.plaf.SpinnerUI;
060
061 /**
062 * A UI delegate for the {@link JSpinner} component.
063 *
064 * @author Ka-Hing Cheung
065 *
066 * @since 1.4
067 */
068 public class BasicSpinnerUI extends SpinnerUI
069 {
070 /**
071 * Creates a new <code>BasicSpinnerUI</code> for the specified
072 * <code>JComponent</code>
073 *
074 * @param c the component (ignored).
075 *
076 * @return A new instance of {@link BasicSpinnerUI}.
077 */
078 public static ComponentUI createUI(JComponent c)
079 {
080 return new BasicSpinnerUI();
081 }
082
083 /**
084 * Creates an editor component. Really, it just returns
085 * <code>JSpinner.getEditor()</code>
086 *
087 * @return a JComponent as an editor
088 *
089 * @see javax.swing.JSpinner#getEditor
090 */
091 protected JComponent createEditor()
092 {
093 return spinner.getEditor();
094 }
095
096 /**
097 * Creates a <code>LayoutManager</code> that layouts the sub components. The
098 * subcomponents are identifies by the constraint "Next", "Previous" and
099 * "Editor"
100 *
101 * @return a LayoutManager
102 *
103 * @see java.awt.LayoutManager
104 */
105 protected LayoutManager createLayout()
106 {
107 return new DefaultLayoutManager();
108 }
109
110 /**
111 * Creates the "Next" button
112 *
113 * @return the next button component
114 */
115 protected Component createNextButton()
116 {
117 JButton button = new BasicArrowButton(BasicArrowButton.NORTH);
118 return button;
119 }
120
121 /**
122 * Creates the "Previous" button
123 *
124 * @return the previous button component
125 */
126 protected Component createPreviousButton()
127 {
128 JButton button = new BasicArrowButton(BasicArrowButton.SOUTH);
129 return button;
130 }
131
132 /**
133 * Creates the <code>PropertyChangeListener</code> that will be attached by
134 * <code>installListeners</code>. It should watch for the "editor"
135 * property, when it's changed, replace the old editor with the new one,
136 * probably by calling <code>replaceEditor</code>
137 *
138 * @return a PropertyChangeListener
139 *
140 * @see #replaceEditor
141 */
142 protected PropertyChangeListener createPropertyChangeListener()
143 {
144 return new PropertyChangeListener()
145 {
146 public void propertyChange(PropertyChangeEvent event)
147 {
148 // FIXME: Add check for enabled property change. Need to
149 // disable the buttons.
150 if ("editor".equals(event.getPropertyName()))
151 BasicSpinnerUI.this.replaceEditor((JComponent) event.getOldValue(),
152 (JComponent) event.getNewValue());
153 // FIXME: Handle 'font' property change
154 }
155 };
156 }
157
158 /**
159 * Called by <code>installUI</code>. This should set various defaults
160 * obtained from <code>UIManager.getLookAndFeelDefaults</code>, as well as
161 * set the layout obtained from <code>createLayout</code>
162 *
163 * @see javax.swing.UIManager#getLookAndFeelDefaults
164 * @see #createLayout
165 * @see #installUI
166 */
167 protected void installDefaults()
168 {
169 LookAndFeel.installColorsAndFont(spinner, "Spinner.background",
170 "Spinner.foreground", "Spinner.font");
171 LookAndFeel.installBorder(spinner, "Spinner.border");
172 JComponent e = spinner.getEditor();
173 if (e instanceof JSpinner.DefaultEditor)
174 {
175 JSpinner.DefaultEditor de = (JSpinner.DefaultEditor) e;
176 de.getTextField().setBorder(null);
177 }
178 spinner.setLayout(createLayout());
179 spinner.setOpaque(true);
180 }
181
182 /*
183 * Called by <code>installUI</code>, which basically adds the
184 * <code>PropertyChangeListener</code> created by
185 * <code>createPropertyChangeListener</code>
186 *
187 * @see #createPropertyChangeListener
188 * @see #installUI
189 */
190 protected void installListeners()
191 {
192 spinner.addPropertyChangeListener(listener);
193 }
194
195 /*
196 * Install listeners to the next button so that it increments the model
197 */
198 protected void installNextButtonListeners(Component c)
199 {
200 c.addMouseListener(new MouseAdapter()
201 {
202 public void mousePressed(MouseEvent evt)
203 {
204 if (! spinner.isEnabled())
205 return;
206 increment();
207 timer.setInitialDelay(500);
208 timer.start();
209 }
210
211 public void mouseReleased(MouseEvent evt)
212 {
213 timer.stop();
214 }
215
216 void increment()
217 {
218 Object next = BasicSpinnerUI.this.spinner.getNextValue();
219 if (next != null)
220 BasicSpinnerUI.this.spinner.getModel().setValue(next);
221 }
222
223 volatile boolean mouseDown;
224 Timer timer = new Timer(50,
225 new ActionListener()
226 {
227 public void actionPerformed(ActionEvent event)
228 {
229 increment();
230 }
231 });
232 });
233 }
234
235 /*
236 * Install listeners to the previous button so that it decrements the model
237 */
238 protected void installPreviousButtonListeners(Component c)
239 {
240 c.addMouseListener(new MouseAdapter()
241 {
242 public void mousePressed(MouseEvent evt)
243 {
244 if (! spinner.isEnabled())
245 return;
246 decrement();
247 timer.setInitialDelay(500);
248 timer.start();
249 }
250
251 public void mouseReleased(MouseEvent evt)
252 {
253 timer.stop();
254 }
255
256 void decrement()
257 {
258 Object prev = BasicSpinnerUI.this.spinner.getPreviousValue();
259 if (prev != null)
260 BasicSpinnerUI.this.spinner.getModel().setValue(prev);
261 }
262
263 volatile boolean mouseDown;
264 Timer timer = new Timer(50,
265 new ActionListener()
266 {
267 public void actionPerformed(ActionEvent event)
268 {
269 decrement();
270 }
271 });
272 });
273 }
274
275 /**
276 * Install this UI to the <code>JComponent</code>, which in reality, is a
277 * <code>JSpinner</code>. Calls <code>installDefaults</code>,
278 * <code>installListeners</code>, and also adds the buttons and editor.
279 *
280 * @param c DOCUMENT ME!
281 *
282 * @see #installDefaults
283 * @see #installListeners
284 * @see #createNextButton
285 * @see #createPreviousButton
286 * @see #createEditor
287 */
288 public void installUI(JComponent c)
289 {
290 super.installUI(c);
291
292 spinner = (JSpinner) c;
293
294 installDefaults();
295 installListeners();
296
297 Component next = createNextButton();
298 Component previous = createPreviousButton();
299
300 installNextButtonListeners(next);
301 installPreviousButtonListeners(previous);
302
303 c.add(createEditor(), "Editor");
304 c.add(next, "Next");
305 c.add(previous, "Previous");
306 }
307
308 /**
309 * Replace the old editor with the new one
310 *
311 * @param oldEditor the old editor
312 * @param newEditor the new one to replace with
313 */
314 protected void replaceEditor(JComponent oldEditor, JComponent newEditor)
315 {
316 spinner.remove(oldEditor);
317 spinner.add(newEditor);
318 }
319
320 /**
321 * The reverse of <code>installDefaults</code>. Called by
322 * <code>uninstallUI</code>
323 */
324 protected void uninstallDefaults()
325 {
326 spinner.setLayout(null);
327 }
328
329 /**
330 * The reverse of <code>installListeners</code>, called by
331 * <code>uninstallUI</code>
332 */
333 protected void uninstallListeners()
334 {
335 spinner.removePropertyChangeListener(listener);
336 }
337
338 /**
339 * Called when the current L&F is replaced with another one, should call
340 * <code>uninstallDefaults</code> and <code>uninstallListeners</code> as
341 * well as remove the next/previous buttons and the editor
342 *
343 * @param c DOCUMENT ME!
344 */
345 public void uninstallUI(JComponent c)
346 {
347 super.uninstallUI(c);
348
349 uninstallDefaults();
350 uninstallListeners();
351 c.removeAll();
352 }
353
354 /** The spinner for this UI */
355 protected JSpinner spinner;
356
357 /** DOCUMENT ME! */
358 private PropertyChangeListener listener = createPropertyChangeListener();
359
360 /**
361 * A layout manager for the {@link JSpinner} component. The spinner has
362 * three subcomponents: an editor, a 'next' button and a 'previous' button.
363 */
364 private class DefaultLayoutManager implements LayoutManager
365 {
366 /**
367 * Layout the spinners inner parts.
368 *
369 * @param parent The parent container
370 */
371 public void layoutContainer(Container parent)
372 {
373 synchronized (parent.getTreeLock())
374 {
375 Insets i = parent.getInsets();
376 boolean l2r = parent.getComponentOrientation().isLeftToRight();
377 /*
378 -------------- --------------
379 | | n | | n | |
380 | e | - | or | - | e |
381 | | p | | p | |
382 -------------- --------------
383 */
384 Dimension e = prefSize(editor);
385 Dimension n = prefSize(next);
386 Dimension p = prefSize(previous);
387 Dimension s = parent.getSize();
388
389 int x = l2r ? i.left : i.right;
390 int y = i.top;
391 int w = Math.max(p.width, n.width);
392 int h = (s.height - i.bottom) / 2;
393 int e_width = s.width - w - i.left - i.right;
394
395 if (l2r)
396 {
397 setBounds(editor, x, y, e_width, 2 * h);
398 x += e_width;
399 setBounds(next, x, y, w, h);
400 y += h;
401 setBounds(previous, x, y, w, h);
402 }
403 else
404 {
405 setBounds(next, x, y + (s.height - e.height) / 2, w, h);
406 y += h;
407 setBounds(previous, x, y + (s.height - e.height) / 2, w, h);
408 x += w;
409 y -= h;
410 setBounds(editor, x, y, e_width, e.height);
411 }
412 }
413 }
414
415 /**
416 * Calculates the minimum layout size.
417 *
418 * @param parent the parent.
419 *
420 * @return The minimum layout size.
421 */
422 public Dimension minimumLayoutSize(Container parent)
423 {
424 Dimension d = new Dimension();
425
426 if (editor != null)
427 {
428 Dimension tmp = editor.getMinimumSize();
429 d.width += tmp.width;
430 d.height = tmp.height;
431 }
432
433 int nextWidth = 0;
434 int previousWidth = 0;
435
436 if (next != null)
437 {
438 Dimension tmp = next.getMinimumSize();
439 nextWidth = tmp.width;
440 }
441 if (previous != null)
442 {
443 Dimension tmp = previous.getMinimumSize();
444 previousWidth = tmp.width;
445 }
446
447 d.width += Math.max(nextWidth, previousWidth);
448
449 return d;
450 }
451
452 /**
453 * Returns the preferred layout size of the container.
454 *
455 * @param parent DOCUMENT ME!
456 *
457 * @return DOCUMENT ME!
458 */
459 public Dimension preferredLayoutSize(Container parent)
460 {
461 Dimension d = new Dimension();
462
463 if (editor != null)
464 {
465 Dimension tmp = editor.getPreferredSize();
466 d.width += Math.max(tmp.width, 40);
467 d.height = tmp.height;
468 }
469
470 int nextWidth = 0;
471 int previousWidth = 0;
472
473 if (next != null)
474 {
475 Dimension tmp = next.getPreferredSize();
476 nextWidth = tmp.width;
477 }
478 if (previous != null)
479 {
480 Dimension tmp = previous.getPreferredSize();
481 previousWidth = tmp.width;
482 }
483
484 d.width += Math.max(nextWidth, previousWidth);
485 Insets insets = parent.getInsets();
486 d.width = d.width + insets.left + insets.right;
487 d.height = d.height + insets.top + insets.bottom;
488 return d;
489 }
490
491 /**
492 * DOCUMENT ME!
493 *
494 * @param child DOCUMENT ME!
495 */
496 public void removeLayoutComponent(Component child)
497 {
498 if (child == editor)
499 editor = null;
500 else if (child == next)
501 next = null;
502 else if (previous == child)
503 previous = null;
504 }
505
506 /**
507 * DOCUMENT ME!
508 *
509 * @param name DOCUMENT ME!
510 * @param child DOCUMENT ME!
511 */
512 public void addLayoutComponent(String name, Component child)
513 {
514 if ("Editor".equals(name))
515 editor = child;
516 else if ("Next".equals(name))
517 next = child;
518 else if ("Previous".equals(name))
519 previous = child;
520 }
521
522 /**
523 * DOCUMENT ME!
524 *
525 * @param c DOCUMENT ME!
526 *
527 * @return DOCUMENT ME!
528 */
529 private Dimension prefSize(Component c)
530 {
531 if (c == null)
532 return new Dimension();
533 else
534 return c.getPreferredSize();
535 }
536
537 /**
538 * Sets the bounds for the specified component.
539 *
540 * @param c the component.
541 * @param x the x-coordinate for the top-left of the component bounds.
542 * @param y the y-coordinate for the top-left of the component bounds.
543 * @param w the width of the bounds.
544 * @param h the height of the bounds.
545 */
546 private void setBounds(Component c, int x, int y, int w, int h)
547 {
548 if (c != null)
549 c.setBounds(x, y, w, h);
550 }
551
552 /** The editor component. */
553 private Component editor;
554
555 /** The next button. */
556 private Component next;
557
558 /** The previous button. */
559 private Component previous;
560 }
561 }