001/* BasicSpinnerUI.java -- 002 Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.plaf.basic; 040 041import java.awt.Component; 042import java.awt.Container; 043import java.awt.Dimension; 044import java.awt.Insets; 045import java.awt.LayoutManager; 046import java.awt.event.ActionEvent; 047import java.awt.event.ActionListener; 048import java.awt.event.MouseAdapter; 049import java.awt.event.MouseEvent; 050import java.beans.PropertyChangeEvent; 051import java.beans.PropertyChangeListener; 052 053import javax.swing.JButton; 054import javax.swing.JComponent; 055import javax.swing.JSpinner; 056import javax.swing.LookAndFeel; 057import javax.swing.Timer; 058import javax.swing.plaf.ComponentUI; 059import 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 */ 068public 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}