1 /* BasicSpinnerUI.java -- 2 Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing.plaf.basic; 40 41 import java.awt.Component; 42 import java.awt.Container; 43 import java.awt.Dimension; 44 import java.awt.Insets; 45 import java.awt.LayoutManager; 46 import java.awt.event.ActionEvent; 47 import java.awt.event.ActionListener; 48 import java.awt.event.MouseAdapter; 49 import java.awt.event.MouseEvent; 50 import java.beans.PropertyChangeEvent; 51 import java.beans.PropertyChangeListener; 52 53 import javax.swing.JButton; 54 import javax.swing.JComponent; 55 import javax.swing.JSpinner; 56 import javax.swing.LookAndFeel; 57 import javax.swing.Timer; 58 import javax.swing.plaf.ComponentUI; 59 import javax.swing.plaf.SpinnerUI; 60 61 /** 62 * A UI delegate for the {@link JSpinner} component. 63 * 64 * @author Ka-Hing Cheung 65 * 66 * @since 1.4 67 */ 68 public class BasicSpinnerUI extends SpinnerUI 69 { 70 /** 71 * Creates a new <code>BasicSpinnerUI</code> for the specified 72 * <code>JComponent</code> 73 * 74 * @param c the component (ignored). 75 * 76 * @return A new instance of {@link BasicSpinnerUI}. 77 */ createUI(JComponent c)78 public static ComponentUI createUI(JComponent c) 79 { 80 return new BasicSpinnerUI(); 81 } 82 83 /** 84 * Creates an editor component. Really, it just returns 85 * <code>JSpinner.getEditor()</code> 86 * 87 * @return a JComponent as an editor 88 * 89 * @see javax.swing.JSpinner#getEditor 90 */ createEditor()91 protected JComponent createEditor() 92 { 93 return spinner.getEditor(); 94 } 95 96 /** 97 * Creates a <code>LayoutManager</code> that layouts the sub components. The 98 * subcomponents are identifies by the constraint "Next", "Previous" and 99 * "Editor" 100 * 101 * @return a LayoutManager 102 * 103 * @see java.awt.LayoutManager 104 */ createLayout()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 */ createNextButton()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 */ createPreviousButton()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 */ createPropertyChangeListener()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 } 562