001: /*
002: * Copyright (c) 2005-2008 Substance Kirill Grouchnikov. 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 Substance Kirill Grouchnikov 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: package org.jvnet.substance;
031:
032: import java.awt.*;
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035: import java.util.HashSet;
036: import java.util.Set;
037:
038: import javax.swing.*;
039: import javax.swing.border.Border;
040: import javax.swing.border.EmptyBorder;
041: import javax.swing.plaf.ComponentUI;
042: import javax.swing.plaf.UIResource;
043: import javax.swing.plaf.basic.BasicSpinnerUI;
044:
045: import org.jvnet.lafwidget.animation.*;
046: import org.jvnet.substance.theme.SubstanceTheme;
047: import org.jvnet.substance.utils.*;
048: import org.jvnet.substance.utils.SubstanceConstants.Side;
049: import org.jvnet.substance.utils.icon.TransitionAwareIcon;
050:
051: /**
052: * UI for spinners in <b>Substance</b> look and feel.
053: *
054: * @author Kirill Grouchnikov
055: */
056: public class SubstanceSpinnerUI extends BasicSpinnerUI {
057: // /**
058: // * Focus listener.
059: // */
060: // protected FocusBorderListener substanceFocusListener;
061:
062: /**
063: * Tracks changes to editor, removing the border as necessary.
064: */
065: protected PropertyChangeListener substancePropertyChangeListener;
066:
067: /**
068: * Listener for fade animations.
069: */
070: protected FadeStateListener substanceFadeStateListener;
071:
072: // protected ChangeListener substanceChangeListener;
073:
074: /**
075: * The next (increment) button.
076: */
077: protected SubstanceSpinnerButton nextButton;
078:
079: /**
080: * The previous (decrement) button.
081: */
082: protected SubstanceSpinnerButton prevButton;
083:
084: /*
085: * (non-Javadoc)
086: *
087: * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
088: */
089: public static ComponentUI createUI(JComponent c) {
090: return new SubstanceSpinnerUI();
091: }
092:
093: /*
094: * (non-Javadoc)
095: *
096: * @see javax.swing.plaf.basic.BasicSpinnerUI#createNextButton()
097: */
098: @Override
099: protected Component createNextButton() {
100: this .nextButton = new SubstanceSpinnerButton(this .spinner,
101: SwingConstants.NORTH);
102: this .nextButton.setFont(this .spinner.getFont());
103: this .nextButton.setName("Spinner.nextButton");
104: // this.nextButton
105: // .setEnabled(this.spinner.getModel().getNextValue() != null);
106:
107: Icon icon = new TransitionAwareIcon(this .nextButton,
108: new TransitionAwareIcon.Delegate() {
109: public Icon getThemeIcon(SubstanceTheme theme) {
110: return SubstanceImageCreator.getArrowIcon(
111: SubstanceSizeUtils
112: .getComponentFontSize(spinner),
113: -2, -1, 0, SwingConstants.NORTH, theme);
114: }
115: });
116: //
117: // SubstanceImageCreator.getArrowIcon(SubstanceSizeUtils
118: // .getArrowIconWidth() - 2, SubstanceSizeUtils
119: // .getArrowIconHeight() - 1, SubstanceSizeUtils
120: // .getArrowStrokeWidth(), SwingConstants.NORTH,
121: // SubstanceCoreUtilities.getActiveTheme(this.spinner,
122: // true));
123: this .nextButton.setIcon(this .spinner, icon);
124:
125: int spinnerButtonSize = SubstanceSizeUtils
126: .getSpinnerButtonWidth(SubstanceSizeUtils
127: .getComponentFontSize(spinner));
128: this .nextButton.setPreferredSize(new Dimension(
129: spinnerButtonSize, spinnerButtonSize));
130: this .nextButton.setMinimumSize(new Dimension(5, 5));
131:
132: Set<Side> openSides = new HashSet<Side>();
133: openSides.add(Side.BOTTOM);
134: openSides.add(Side.TOP);
135: openSides.add(Side.RIGHT);
136: this .nextButton.putClientProperty(
137: SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY,
138: openSides);
139:
140: this .installNextButtonListeners(this .nextButton);
141: return this .nextButton;
142: }
143:
144: /*
145: * (non-Javadoc)
146: *
147: * @see javax.swing.plaf.basic.BasicSpinnerUI#createPreviousButton()
148: */
149: @Override
150: protected Component createPreviousButton() {
151: this .prevButton = new SubstanceSpinnerButton(this .spinner,
152: SwingConstants.SOUTH);
153: this .prevButton.setFont(this .spinner.getFont());
154: this .prevButton.setName("Spinner.previousButton");
155: // this.prevButton
156: // .setEnabled(this.spinner.getModel().getPreviousValue() != null);
157:
158: Icon icon = new TransitionAwareIcon(this .prevButton,
159: new TransitionAwareIcon.Delegate() {
160: public Icon getThemeIcon(SubstanceTheme theme) {
161: // System.out.println(spinner.getFont().getSize());
162: return SubstanceImageCreator.getArrowIcon(
163: SubstanceSizeUtils
164: .getComponentFontSize(spinner),
165: -2, -1, 0, SwingConstants.SOUTH, theme);
166: }
167: });
168: this .prevButton.setIcon(this .spinner, icon);
169:
170: int spinnerButtonSize = SubstanceSizeUtils
171: .getSpinnerButtonWidth(SubstanceSizeUtils
172: .getComponentFontSize(this .prevButton));
173: this .prevButton.setPreferredSize(new Dimension(
174: spinnerButtonSize, spinnerButtonSize));
175: this .prevButton.setMinimumSize(new Dimension(5, 5));
176:
177: Set<Side> openSides = new HashSet<Side>();
178: openSides.add(Side.TOP);
179: openSides.add(Side.BOTTOM);
180: openSides.add(Side.RIGHT);
181: this .prevButton.putClientProperty(
182: SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY,
183: openSides);
184:
185: this .installPreviousButtonListeners(this .prevButton);
186: return this .prevButton;
187: }
188:
189: /*
190: * (non-Javadoc)
191: *
192: * @see javax.swing.plaf.basic.BasicSpinnerUI#installDefaults()
193: */
194: @Override
195: protected void installDefaults() {
196: super .installDefaults();
197: JComponent editor = this .spinner.getEditor();
198: if ((editor != null)
199: && (editor instanceof JSpinner.DefaultEditor)) {
200: JTextField tf = ((JSpinner.DefaultEditor) editor)
201: .getTextField();
202: if (tf != null) {
203: int fontSize = SubstanceSizeUtils
204: .getComponentFontSize(this .spinner);
205: Insets ins = SubstanceSizeUtils
206: .getSpinnerTextBorderInsets(fontSize);
207: tf.setBorder(new EmptyBorder(ins.top, ins.left,
208: ins.bottom, ins.right));
209: tf.setFont(spinner.getFont());
210: }
211: }
212:
213: Border b = this .spinner.getBorder();
214: if (b == null || b instanceof UIResource) {
215: this .spinner.setBorder(// new
216: // BorderUIResource.CompoundBorderUIResource(
217: new SubstanceBorder(
218: SubstanceSizeUtils
219: .getSpinnerBorderInsets(SubstanceSizeUtils
220: .getComponentFontSize(this .spinner))));
221: // ,
222: // new BasicBorders.MarginBorder()));
223: }
224: }
225:
226: /*
227: * (non-Javadoc)
228: *
229: * @see javax.swing.plaf.basic.BasicSpinnerUI#installListeners()
230: */
231: @Override
232: protected void installListeners() {
233: super .installListeners();
234: // this.substanceFocusListener = new FocusBorderListener(this.spinner);
235: // this.spinner.addFocusListener(this.substanceFocusListener);
236:
237: this .substancePropertyChangeListener = new PropertyChangeListener() {
238: public void propertyChange(PropertyChangeEvent evt) {
239: if ("editor".equals(evt.getPropertyName())) {
240: SwingUtilities.invokeLater(new Runnable() {
241: public void run() {
242: if (substanceFadeStateListener != null) {
243: substanceFadeStateListener
244: .unregisterListeners();
245: substanceFadeStateListener = null;
246: }
247: if (spinner == null)
248: return;
249: JComponent editor = spinner.getEditor();
250: if ((editor != null)
251: && (editor instanceof JSpinner.DefaultEditor)) {
252: JTextField tf = ((JSpinner.DefaultEditor) editor)
253: .getTextField();
254: if (tf != null) {
255: Insets ins = SubstanceSizeUtils
256: .getSpinnerTextBorderInsets(SubstanceSizeUtils
257: .getComponentFontSize(spinner));
258: tf.setBorder(new EmptyBorder(
259: ins.top, ins.left,
260: ins.bottom, ins.right));
261: substanceFadeStateListener = new FadeStateListener(
262: tf, null,
263: new FadeTrackerAdapter() {
264: @Override
265: public void fadeEnded(
266: FadeKind fadeKind) {
267: if (spinner != null)
268: spinner
269: .repaint();
270: }
271:
272: @Override
273: public void fadePerformed(
274: FadeKind fadeKind,
275: float fadeCycle10) {
276: if (spinner != null)
277: spinner
278: .repaint();
279: }
280: });
281: substanceFadeStateListener
282: .registerListeners();
283: }
284: }
285: }
286: });
287: }
288:
289: if ("font".equals(evt.getPropertyName())) {
290: SwingUtilities.invokeLater(new Runnable() {
291: public void run() {
292: spinner.updateUI();
293: }
294: });
295: }
296:
297: if ("background".equals(evt.getPropertyName())) {
298: JComponent editor = spinner.getEditor();
299: if ((editor != null)
300: && (editor instanceof JSpinner.DefaultEditor)) {
301: JTextField tf = ((JSpinner.DefaultEditor) editor)
302: .getTextField();
303: if (tf != null) {
304: if (tf.getBackground() instanceof UIResource) {
305: // can replace
306: tf.setBackground(spinner
307: .getBackground());
308: }
309: }
310: }
311: }
312:
313: // // enhancement 266 - track model changes to
314: // // disable spinner buttons
315: // if ("model".equals(evt.getPropertyName())) {
316: // SpinnerModel old = (SpinnerModel) evt.getOldValue();
317: // if (old != null) {
318: // (old).removeChangeListener(substanceChangeListener);
319: // }
320: // spinner.getModel().addChangeListener(
321: // substanceChangeListener);
322: // }
323: }
324: };
325: this .spinner
326: .addPropertyChangeListener(this .substancePropertyChangeListener);
327:
328: JComponent editor = spinner.getEditor();
329: if ((editor != null)
330: && (editor instanceof JSpinner.DefaultEditor)) {
331: JTextField tf = ((JSpinner.DefaultEditor) editor)
332: .getTextField();
333: this .substanceFadeStateListener = new FadeStateListener(tf,
334: null, new FadeTrackerAdapter() {
335: @Override
336: public void fadeEnded(FadeKind fadeKind) {
337: if (spinner != null)
338: spinner.repaint();
339: }
340:
341: @Override
342: public void fadePerformed(FadeKind fadeKind,
343: float fadeCycle10) {
344: if (spinner != null)
345: spinner.repaint();
346: }
347: });
348: this .substanceFadeStateListener.registerListeners();
349: }
350:
351: // // enhancement 266 - track model state and disable
352: // // spinner buttons if necessary (limit value
353: // // reached).
354: // this.substanceChangeListener = new ChangeListener() {
355: // public void stateChanged(ChangeEvent e) {
356: // boolean hasPrev = (spinner.getModel().getPreviousValue() != null);
357: // boolean hasNext = (spinner.getModel().getNextValue() != null);
358: // prevButton.setEnabled(hasPrev);
359: // nextButton.setEnabled(hasNext);
360: // }
361: // };
362: // this.spinner.getModel().addChangeListener(this.substanceChangeListener);
363: }
364:
365: /*
366: * (non-Javadoc)
367: *
368: * @see javax.swing.plaf.basic.BasicSpinnerUI#uninstallListeners()
369: */
370: @Override
371: protected void uninstallListeners() {
372: // listener can be null is there is a custom editor installed
373: // on the spinner.
374: if (this .substanceFadeStateListener != null) {
375: this .substanceFadeStateListener.unregisterListeners();
376: this .substanceFadeStateListener = null;
377: }
378:
379: this .spinner
380: .removePropertyChangeListener(this .substancePropertyChangeListener);
381: this .substancePropertyChangeListener = null;
382:
383: super .uninstallListeners();
384: }
385:
386: /*
387: * (non-Javadoc)
388: *
389: * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
390: * javax.swing.JComponent)
391: */
392: @Override
393: public void paint(Graphics g, JComponent c) {
394: super .paint(g, c);
395: if (hasFocus(this .spinner)
396: || FadeTracker.getInstance().isTracked(this .spinner,
397: FadeKind.FOCUS)) {
398: this .paintFocus(g, this .spinner.getEditor().getBounds());
399: }
400: }
401:
402: /**
403: * Checks if a component or any of its children have focus.
404: *
405: * @param comp
406: * Component.
407: * @return <code>true</code> if the component of any of its children have
408: * focus, <code>false</code> otherwise.
409: */
410: private static boolean hasFocus(Component comp) {
411: if (comp.hasFocus())
412: return true;
413: if (comp instanceof Container) {
414: Container cont = (Container) comp;
415: for (int i = 0; i < cont.getComponentCount(); i++) {
416: Component child = cont.getComponent(i);
417: if (hasFocus(child))
418: return true;
419: }
420: }
421: return false;
422: }
423:
424: /**
425: * Paints the focus indication.
426: *
427: * @param g
428: * Graphics.
429: * @param bounds
430: * Bounds for text.
431: */
432: protected void paintFocus(Graphics g, Rectangle bounds) {
433: // JComponent editor = spinner.getEditor();
434: // if ((editor != null) && (editor instanceof JSpinner.DefaultEditor)) {
435: // JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
436: // if (tf != null) {
437: // SubstanceCoreUtilities.paintFocus(g, this.spinner, tf, null,
438: // bounds, 0.4f, 2 + SubstanceSizeUtils
439: // .getExtraPadding(SubstanceSizeUtils
440: // .getComponentFontSize(spinner)));
441: // }
442: // }
443: }
444:
445: /*
446: * (non-Javadoc)
447: *
448: * @see javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
449: */
450: @Override
451: public Dimension getPreferredSize(JComponent c) {
452: Dimension nextD = this .nextButton.getPreferredSize();
453: Dimension previousD = this .prevButton.getPreferredSize();
454: Dimension editorD = spinner.getEditor().getPreferredSize();
455:
456: Dimension size = new Dimension(editorD.width, editorD.height);
457: size.width += Math.max(nextD.width, previousD.width);
458: Insets insets = this .spinner.getInsets();
459: size.width += insets.left + insets.right;
460: size.height += insets.top + insets.bottom;
461: return size;
462: }
463:
464: /*
465: * (non-Javadoc)
466: *
467: * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
468: * javax.swing.JComponent)
469: */
470: @Override
471: public void update(Graphics g, JComponent c) {
472: SubstanceCoreUtilities.paintTextCompBackground(g, c);
473: this .paint(g, c);
474: }
475:
476: @Override
477: protected LayoutManager createLayout() {
478: return new SpinnerLayoutManager();
479: }
480:
481: protected class SpinnerLayoutManager implements LayoutManager {
482: public void addLayoutComponent(String name, Component comp) {
483: }
484:
485: public void removeLayoutComponent(Component comp) {
486: }
487:
488: public Dimension minimumLayoutSize(Container parent) {
489: return this .preferredLayoutSize(parent);
490: }
491:
492: public Dimension preferredLayoutSize(Container parent) {
493: Dimension nextD = nextButton.getPreferredSize();
494: Dimension previousD = prevButton.getPreferredSize();
495: Dimension editorD = spinner.getEditor().getPreferredSize();
496:
497: /*
498: * Force the editors height to be a multiple of 2
499: */
500: editorD.height = ((editorD.height + 1) / 2) * 2;
501:
502: Dimension size = new Dimension(editorD.width,
503: editorD.height);
504: size.width += Math.max(nextD.width, previousD.width);
505: Insets insets = parent.getInsets();
506: size.width += insets.left + insets.right;
507: size.height += insets.top + insets.bottom;
508: return size;
509: }
510:
511: public void layoutContainer(Container parent) {
512: int width = parent.getWidth();
513: int height = parent.getHeight();
514:
515: Insets insets = parent.getInsets();
516: Dimension nextD = nextButton.getPreferredSize();
517: Dimension previousD = prevButton.getPreferredSize();
518: int buttonsWidth = Math.max(nextD.width, previousD.width);
519: int editorHeight = height - (insets.top + insets.bottom);
520:
521: Insets buttonInsets = SubstanceSizeUtils
522: .getSpinnerArrowButtonInsets(SubstanceSizeUtils
523: .getComponentFontSize(spinner));
524: if (buttonInsets == null) {
525: buttonInsets = insets;
526: }
527:
528: /*
529: * Deal with the spinner's componentOrientation property.
530: */
531: int editorX, editorWidth, buttonsX;
532: if (parent.getComponentOrientation().isLeftToRight()) {
533: editorX = insets.left;
534: editorWidth = width - insets.left - buttonsWidth
535: - buttonInsets.right;
536: buttonsX = width - buttonsWidth - buttonInsets.right;
537: } else {
538: buttonsX = buttonInsets.left;
539: editorX = buttonsX + buttonsWidth;
540: editorWidth = width - buttonInsets.left - buttonsWidth
541: - insets.right;
542: }
543:
544: int nextY = buttonInsets.top;
545: int nextHeight = (height / 2) + (height % 2) - nextY;
546: int previousY = buttonInsets.top + nextHeight;
547: int previousHeight = height - previousY
548: - buttonInsets.bottom;
549:
550: spinner.getEditor().setBounds(editorX, insets.top,
551: editorWidth, editorHeight);
552: nextButton.setBounds(buttonsX, nextY, buttonsWidth,
553: nextHeight);
554: prevButton.setBounds(buttonsX, previousY, buttonsWidth,
555: previousHeight);
556: }
557:
558: }
559: }
|