001: /*
002: * Copyright (c) 2001-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.looks.plastic;
032:
033: import java.awt.Component;
034: import java.awt.Insets;
035: import java.awt.LayoutManager;
036:
037: import javax.swing.*;
038: import javax.swing.border.EmptyBorder;
039: import javax.swing.plaf.ComponentUI;
040: import javax.swing.plaf.basic.BasicSpinnerUI;
041:
042: import com.jgoodies.looks.common.ExtBasicArrowButtonHandler;
043: import com.jgoodies.looks.common.ExtBasicSpinnerLayout;
044:
045: /**
046: * The JGoodies Plastic Look&Feel implementation of <code>SpinnerUI</code>.
047: * Configures the default editor to adjust font baselines and component
048: * bounds. Also, changes the border of the buttons and the size of the arrows.
049: *
050: * @author Karsten Lentzsch
051: * @version $Revision: 1.4 $
052: */
053: public class PlasticSpinnerUI extends BasicSpinnerUI {
054:
055: public static ComponentUI createUI(JComponent b) {
056: return new PlasticSpinnerUI();
057: }
058:
059: /**
060: * The mouse/action listeners that are added to the spinner's
061: * arrow buttons. These listeners are shared by all
062: * spinner arrow buttons.
063: *
064: * @see #createNextButton
065: * @see #createPreviousButton
066: */
067: private static final ExtBasicArrowButtonHandler nextButtonHandler = new ExtBasicArrowButtonHandler(
068: "increment", true);
069: private static final ExtBasicArrowButtonHandler previousButtonHandler = new ExtBasicArrowButtonHandler(
070: "decrement", false);
071:
072: /**
073: * Create a component that will replace the spinner models value
074: * with the object returned by <code>spinner.getPreviousValue</code>.
075: * By default the <code>previousButton</code> is a JButton
076: * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
077: * ancestors model. If a previousButton isn't needed (in a subclass)
078: * then override this method to return null.
079: *
080: * @return a component that will replace the spinners model with the
081: * next value in the sequence, or null
082: * @see #installUI
083: * @see #createNextButton
084: */
085: protected Component createPreviousButton() {
086: return new SpinnerArrowButton(SwingConstants.SOUTH,
087: previousButtonHandler);
088: }
089:
090: /**
091: * Create a component that will replace the spinner models value
092: * with the object returned by <code>spinner.getNextValue</code>.
093: * By default the <code>nextButton</code> is a JButton
094: * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
095: * ancestors model. If a nextButton isn't needed (in a subclass)
096: * then override this method to return null.
097: *
098: * @return a component that will replace the spinners model with the
099: * next value in the sequence, or null
100: * @see #installUI
101: * @see #createPreviousButton
102: */
103: protected Component createNextButton() {
104: return new SpinnerArrowButton(SwingConstants.NORTH,
105: nextButtonHandler);
106: }
107:
108: /**
109: * Create a <code>LayoutManager</code> that manages the <code>editor</code>,
110: * <code>nextButton</code>, and <code>previousButton</code> children
111: * of the JSpinner. These three children must be added with a constraint
112: * that identifies their role: "Editor", "Next", and "Previous". The
113: * default layout manager can handle the absence of any of these children.
114: *
115: * @return a LayoutManager for the editor, next button, and previous
116: * button.
117: * @see #createNextButton
118: * @see #createPreviousButton
119: * @see #createEditor
120: */
121: protected LayoutManager createLayout() {
122: return new ExtBasicSpinnerLayout();
123: }
124:
125: /**
126: * This method is called by installUI to get the editor component
127: * of the <code>JSpinner</code>. By default it just returns
128: * <code>JSpinner.getEditor()</code>. Subclasses can override
129: * <code>createEditor</code> to return a component that contains
130: * the spinner's editor or null, if they're going to handle adding
131: * the editor to the <code>JSpinner</code> in an
132: * <code>installUI</code> override.
133: * <p>
134: * Typically this method would be overridden to wrap the editor
135: * with a container with a custom border, since one can't assume
136: * that the editors border can be set directly.
137: * <p>
138: * The <code>replaceEditor</code> method is called when the spinners
139: * editor is changed with <code>JSpinner.setEditor</code>. If you've
140: * overriden this method, then you'll probably want to override
141: * <code>replaceEditor</code> as well.
142: *
143: * @return the JSpinners editor JComponent, spinner.getEditor() by default
144: * @see #installUI
145: * @see #replaceEditor
146: * @see JSpinner#getEditor
147: */
148: protected JComponent createEditor() {
149: JComponent editor = spinner.getEditor();
150: configureEditorBorder(editor);
151: return editor;
152: }
153:
154: /**
155: * Called by the <code>PropertyChangeListener</code> when the
156: * <code>JSpinner</code> editor property changes. It's the responsibility
157: * of this method to remove the old editor and add the new one. By
158: * default this operation is just:
159: * <pre>
160: * spinner.remove(oldEditor);
161: * spinner.add(newEditor, "Editor");
162: * </pre>
163: * The implementation of <code>replaceEditor</code> should be coordinated
164: * with the <code>createEditor</code> method.
165: *
166: * @see #createEditor
167: * @see #createPropertyChangeListener
168: */
169: protected void replaceEditor(JComponent oldEditor,
170: JComponent newEditor) {
171: spinner.remove(oldEditor);
172: configureEditorBorder(newEditor);
173: spinner.add(newEditor, "Editor");
174: }
175:
176: /**
177: * Sets an empty border with the default text insets.
178: */
179: private void configureEditorBorder(JComponent editor) {
180: if ((editor instanceof JSpinner.DefaultEditor)) {
181: JSpinner.DefaultEditor defaultEditor = (JSpinner.DefaultEditor) editor;
182: JTextField editorField = defaultEditor.getTextField();
183: Insets insets = UIManager
184: .getInsets("Spinner.defaultEditorInsets");
185: editorField.setBorder(new EmptyBorder(insets));
186: } else if ((editor instanceof JPanel)
187: && (editor.getBorder() == null)
188: && (editor.getComponentCount() > 0)) {
189: JComponent editorField = (JComponent) editor
190: .getComponent(0);
191: Insets insets = UIManager
192: .getInsets("Spinner.defaultEditorInsets");
193: editorField.setBorder(new EmptyBorder(insets));
194: }
195: }
196:
197: /**
198: * It differs from its superclass in that it uses the same formula as JDK
199: * to calculate the arrow height.
200: */
201: private static final class SpinnerArrowButton extends
202: PlasticArrowButton {
203: private SpinnerArrowButton(int direction,
204: ExtBasicArrowButtonHandler handler) {
205: super (direction, UIManager.getInt("ScrollBar.width"), true);
206: addActionListener(handler);
207: addMouseListener(handler);
208: }
209:
210: protected int calculateArrowHeight(int height, int width) {
211: int arrowHeight = Math.min((height - 4) / 3,
212: (width - 4) / 3);
213: return Math.max(arrowHeight, 3);
214: }
215:
216: protected int calculateArrowOffset() {
217: return 1;
218: }
219:
220: protected boolean isPaintingNorthBottom() {
221: return true;
222: }
223:
224: }
225:
226: }
|