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.HashMap;
036: import java.util.Map;
037:
038: import javax.swing.*;
039: import javax.swing.border.Border;
040: import javax.swing.plaf.ComponentUI;
041: import javax.swing.plaf.UIResource;
042: import javax.swing.plaf.basic.*;
043: import javax.swing.text.View;
044:
045: import org.jvnet.lafwidget.animation.*;
046: import org.jvnet.lafwidget.layout.TransitionLayout;
047: import org.jvnet.substance.painter.text.SubstanceTextPainter;
048: import org.jvnet.substance.theme.SubstanceTheme;
049: import org.jvnet.substance.utils.*;
050:
051: /**
052: * UI for radio buttons in <b>Substance </b> look and feel.
053: *
054: * @author Kirill Grouchnikov
055: */
056: public class SubstanceRadioButtonUI extends BasicRadioButtonUI {
057: // /**
058: // * Default radio button dimension.
059: // */
060: // private static final int DIMENSION = 11;
061:
062: /**
063: * Background delegate.
064: */
065: // private SubstanceFillBackgroundDelegate bgDelegate;
066: /**
067: * Property change listener. Listens on changes to
068: * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
069: */
070: protected PropertyChangeListener substancePropertyListener;
071:
072: /**
073: * Associated toggle button.
074: */
075: protected JToggleButton button;
076:
077: /**
078: * Icons for all component states
079: */
080: private static Map<String, Icon> icons = new HashMap<String, Icon>();
081:
082: /**
083: * Listener for fade animations.
084: */
085: protected FadeStateListener substanceFadeStateListener;
086:
087: /**
088: * Resets image maps (used when setting new theme).
089: *
090: * @see SubstanceLookAndFeel#setCurrentTheme(String)
091: * @see SubstanceLookAndFeel#setCurrentTheme(org.jvnet.substance.theme.SubstanceTheme)
092: */
093: public static synchronized void reset() {
094: SubstanceRadioButtonUI.icons.clear();
095: }
096:
097: /*
098: * (non-Javadoc)
099: *
100: * @see javax.swing.plaf.basic.BasicButtonUI#installListeners(javax.swing.AbstractButton)
101: */
102: @Override
103: protected void installListeners(final AbstractButton b) {
104: super .installListeners(b);
105:
106: substanceFadeStateListener = new FadeStateListener(b, b
107: .getModel(), SubstanceCoreUtilities.getFadeCallback(b,
108: false));
109: substanceFadeStateListener.registerListeners();
110:
111: substancePropertyListener = new PropertyChangeListener() {
112: public void propertyChange(PropertyChangeEvent evt) {
113: if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
114: .getPropertyName())) {
115: if (substanceFadeStateListener != null)
116: substanceFadeStateListener
117: .unregisterListeners();
118: substanceFadeStateListener = new FadeStateListener(
119: b, b.getModel(), SubstanceCoreUtilities
120: .getFadeCallback(b, false));
121: substanceFadeStateListener.registerListeners();
122: }
123: if ("opaque".equals(evt.getPropertyName())) {
124: if (!Boolean.TRUE
125: .equals(b
126: .getClientProperty(SubstanceButtonUI.LOCK_OPACITY))) {
127: b.putClientProperty(
128: SubstanceButtonUI.OPACITY_ORIGINAL, evt
129: .getNewValue());
130: // System.out
131: // .println("PCL: "
132: // + b.getText()
133: // + "->"
134: // + b
135: // .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
136: }
137: }
138: }
139: };
140: b.addPropertyChangeListener(substancePropertyListener);
141: }
142:
143: /*
144: * (non-Javadoc)
145: *
146: * @see javax.swing.plaf.basic.BasicRadioButtonUI#installDefaults(javax.swing.AbstractButton)
147: */
148: @Override
149: protected void installDefaults(AbstractButton b) {
150: super .installDefaults(b);
151: Border border = b.getBorder();
152: if (border == null || border instanceof UIResource) {
153: b.setBorder(SubstanceSizeUtils
154: .getRadioButtonBorder(SubstanceSizeUtils
155: .getComponentFontSize(b)));
156: }
157: }
158:
159: /*
160: * (non-Javadoc)
161: *
162: * @see javax.swing.plaf.basic.BasicRadioButtonUI#uninstallDefaults(javax.swing.AbstractButton)
163: */
164: @Override
165: protected void uninstallDefaults(AbstractButton b) {
166: super .uninstallDefaults(b);
167:
168: // b.setOpaque((Boolean) b
169: // .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
170: // b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, null);
171: }
172:
173: @Override
174: public void installUI(JComponent c) {
175: // c.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, null);
176: super .installUI(c);
177: }
178:
179: @Override
180: public void uninstallUI(JComponent c) {
181: // c.setOpaque((Boolean) c
182: // .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
183: // c.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, null);
184: super .uninstallUI(c);
185: }
186:
187: /*
188: * (non-Javadoc)
189: *
190: * @see javax.swing.plaf.basic.BasicButtonUI#uninstallListeners(javax.swing.AbstractButton)
191: */
192: @Override
193: protected void uninstallListeners(AbstractButton b) {
194: substanceFadeStateListener.unregisterListeners();
195: substanceFadeStateListener = null;
196:
197: b.removePropertyChangeListener(substancePropertyListener);
198: substancePropertyListener = null;
199:
200: super .uninstallListeners(b);
201: }
202:
203: /**
204: * Returns the icon that matches the current and previous states of the
205: * radio button.
206: *
207: * @param button
208: * Button (should be {@link JRadioButton}).
209: * @param currState
210: * Current state of the checkbox.
211: * @param prevState
212: * Previous state of the checkbox.
213: * @return Matching icon.
214: */
215: private static synchronized Icon getIcon(JToggleButton button,
216: ComponentState currState, ComponentState prevState) {
217: // check if fading
218: // FadeTracker fadeTracker = FadeTracker.getInstance();
219: float visibility = currState.isKindActive(FadeKind.SELECTION) ? 10
220: : 0;
221:
222: SubstanceTheme theme = SubstanceThemeUtilities.getTheme(button,
223: currState);
224: SubstanceTheme theme2 = SubstanceThemeUtilities.getTheme(
225: button, prevState);
226: float cyclePos = 0;
227:
228: FadeState fadeState = SubstanceFadeUtilities.getFadeState(
229: button, FadeKind.SELECTION, FadeKind.ROLLOVER,
230: FadeKind.PRESS);
231: if (fadeState != null) {
232: cyclePos = fadeState.getFadePosition();
233: if (fadeState.isFadingIn())
234: cyclePos = 10 - cyclePos;
235: if (fadeState.fadeKind == FadeKind.SELECTION) {
236: visibility = fadeState.getFadePosition();
237: }
238: }
239:
240: int checkMarkSize = // SubstanceSizeUtils
241: // .getRadioButtonMarkSize(SubstanceSizeUtils.getControlFontSize());
242: // if (!(button.getFont() instanceof UIResource))
243: // checkMarkSize =
244: SubstanceSizeUtils.getRadioButtonMarkSize(SubstanceSizeUtils
245: .getComponentFontSize(button));
246: String key = checkMarkSize + ":" + currState.name() + ":"
247: + currState.name() + ":" + theme.getDisplayName() + ":"
248: + theme2.getDisplayName() + ":" + cyclePos + ":"
249: + visibility;
250:
251: Icon result = SubstanceRadioButtonUI.icons.get(key);
252: if (result != null)
253: return result;
254: result = new ImageIcon(SubstanceImageCreator.getRadioButton(
255: button, checkMarkSize, currState, 0, theme, theme2,
256: cyclePos, visibility / 10.f));
257: SubstanceRadioButtonUI.icons.put(key, result);
258: return result;
259: }
260:
261: /*
262: * (non-Javadoc)
263: *
264: * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
265: */
266: public static ComponentUI createUI(JComponent b) {
267: return new SubstanceRadioButtonUI((JToggleButton) b);
268: }
269:
270: /**
271: * Simple constructor.
272: *
273: * @param button
274: * Associated radio button.
275: */
276: public SubstanceRadioButtonUI(JToggleButton button) {
277: // bgDelegate = new SubstanceFillBackgroundDelegate();
278: this .button = button;
279: button.setRolloverEnabled(true);
280: // button.setOpaque(false);
281: }
282:
283: /*
284: * (non-Javadoc)
285: *
286: * @see javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing.AbstractButton)
287: */
288: @Override
289: protected BasicButtonListener createButtonListener(AbstractButton b) {
290: return new RolloverButtonListener(b);
291: // return RolloverButtonListener.getListener(b);
292: }
293:
294: /*
295: * (non-Javadoc)
296: *
297: * @see javax.swing.plaf.basic.BasicRadioButtonUI#getDefaultIcon()
298: */
299: @Override
300: public Icon getDefaultIcon() {
301: ButtonModel model = button.getModel();
302: ComponentState currState = ComponentState.getState(model,
303: button);
304: ComponentState prevState = SubstanceCoreUtilities
305: .getPrevComponentState(button);
306: return SubstanceRadioButtonUI.getIcon(button, currState,
307: prevState);
308: }
309:
310: @Override
311: public void paint(Graphics g, JComponent c) {
312: AbstractButton b = (AbstractButton) c;
313:
314: boolean isOpaque = b.isOpaque();
315: b.putClientProperty(SubstanceButtonUI.LOCK_OPACITY,
316: Boolean.TRUE);
317: b.setOpaque(false);
318:
319: if (isOpaque || TransitionLayout.isOpaque(c)) {
320: SubstanceFillBackgroundDelegate.GLOBAL_INSTANCE.update(g,
321: c, false);
322: }
323:
324: b.setOpaque(isOpaque);
325:
326: b.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, null);
327:
328: SubstanceTextPainter textPainter = SubstanceLookAndFeel
329: .getCurrentTextPainter();
330: // if (!(b instanceof
331: // SubstanceDefaultTableCellRenderer.BooleanRenderer)) {
332: textPainter.init(b, null, true);
333: // }
334: if (textPainter.needsBackgroundImage()) {
335: boolean hasColorization = SubstanceCoreUtilities
336: .hasColorization(c);
337: textPainter.setBackgroundFill(b, hasColorization ? b
338: .getParent().getBackground() : b.getBackground(),
339: true, 0, 0);
340: }
341:
342: FontMetrics fm = g.getFontMetrics();
343:
344: Insets i = b.getInsets();
345:
346: Rectangle viewRect = new Rectangle();
347: Rectangle iconRect = new Rectangle();
348: final Rectangle textRect = new Rectangle();
349:
350: viewRect.x = i.left;
351: viewRect.y = i.top;
352: viewRect.width = b.getWidth() - (i.right + viewRect.x);
353: viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
354:
355: textRect.x = textRect.y = textRect.width = textRect.height = 0;
356: iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
357:
358: Font f = b.getFont();
359: g.setFont(f);
360:
361: Icon icon = SubstanceCoreUtilities.getIcon(b, this
362: .getDefaultIcon(), null, false);
363:
364: // layout the text and icon
365: String text = SwingUtilities.layoutCompoundLabel(c, fm, b
366: .getText(), icon, b.getVerticalAlignment(), b
367: .getHorizontalAlignment(), b.getVerticalTextPosition(),
368: b.getHorizontalTextPosition(), viewRect, iconRect,
369: textRect, b.getText() == null ? 0 : b.getIconTextGap());
370:
371: float textAlpha = 1.0f;
372: if (text != null && !text.equals("")) {
373: final View v = (View) b
374: .getClientProperty(BasicHTML.propertyKey);
375: if (v != null) {
376: if (textPainter.needsBackgroundImage()) {
377: textPainter
378: .attachCallback(new SubstanceTextPainter.BackgroundPaintingCallback() {
379: public void paintBackground(Graphics g) {
380: v.paint(g, textRect);
381: }
382: });
383: } else {
384: v.paint(g, textRect);
385: }
386: } else {
387: textAlpha = this .paintButtonText(g, b, textRect, text);
388: }
389: }
390: Graphics2D g2d = (Graphics2D) g.create();
391: g2d.setComposite(TransitionLayout.getAlphaComposite(button,
392: textAlpha, g));
393: textPainter.renderSurface(g2d);
394: g2d.dispose();
395:
396: // Paint the Icon
397: if (icon != null) {
398: icon.paintIcon(c, g, iconRect.x, iconRect.y);
399: }
400:
401: if (b.isFocusPainted()) {
402: if (b.hasFocus()
403: || FadeTracker.getInstance().isTracked(c,
404: FadeKind.FOCUS)) {
405: SubstanceCoreUtilities.paintFocus(g, button, button,
406: null, textRect, 1.0f, 1);
407: }
408: }
409: }
410:
411: // /*
412: // * (non-Javadoc)
413: // *
414: // * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
415: // * javax.swing.JComponent)
416: // */
417: // @Override
418: // public void update(Graphics g, JComponent c) {
419: // // failsafe for LAF change
420: // if (!(UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel))
421: // return;
422: // boolean isOpaque = c.isOpaque();
423: // c.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, Boolean.TRUE);
424: // c.setOpaque(false);
425: //
426: // if (isOpaque || TransitionLayout.isOpaque(c)) {
427: // bgDelegate.update(g, c);
428: // }
429: // super.paint(g, c);
430: //
431: // c.setOpaque(isOpaque);
432: //
433: // c.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, null);
434: //
435: // // Some ugly hack to allow fade-out of focus ring. The code
436: // // in BasicRadioButtonUI doesn't call paintFocus() at all
437: // // when the component is not focus owner.
438: // AbstractButton b = (AbstractButton) c;
439: // if (!b.isFocusPainted())
440: // return;
441: //
442: // FontMetrics fm = c.getFontMetrics(c.getFont());
443: //
444: // Insets i = c.getInsets();
445: // Dimension size = new Dimension();
446: // Rectangle viewRect = new Rectangle();
447: // Rectangle iconRect = new Rectangle();
448: // Rectangle textRect = new Rectangle();
449: //
450: // size = b.getSize(size);
451: // viewRect.x = i.left;
452: // viewRect.y = i.top;
453: // viewRect.width = size.width - (i.right + viewRect.x);
454: // viewRect.height = size.height - (i.bottom + viewRect.y);
455: // iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
456: // textRect.x = textRect.y = textRect.width = textRect.height = 0;
457: //
458: // Icon altIcon = b.getIcon();
459: //
460: // String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(),
461: // altIcon != null ? altIcon : getDefaultIcon(), b
462: // .getVerticalAlignment(), b.getHorizontalAlignment(), b
463: // .getVerticalTextPosition(), b
464: // .getHorizontalTextPosition(), viewRect, iconRect,
465: // textRect, b.getText() == null ? 0 : b.getIconTextGap());
466: //
467: // if ((text != null) && (textRect.width > 0) && (textRect.height > 0)) {
468: // if (!(b.hasFocus() && b.isFocusPainted())) {
469: // if (FadeTracker.getInstance().isTracked(c, FadeKind.FOCUS))
470: // this.paintFocus(g, textRect, size);
471: // }
472: // }
473: // }
474:
475: // /*
476: // * (non-Javadoc)
477: // *
478: // * @see
479: // javax.swing.plaf.basic.BasicRadioButtonUI#paintFocus(java.awt.Graphics,
480: // * java.awt.Rectangle, java.awt.Dimension)
481: // */
482: // @Override
483: // protected void paintFocus(Graphics g, Rectangle t, Dimension d) {
484: // // System.out.println(button.getText() + " -> focus");
485: // SubstanceCoreUtilities.paintFocus(g, button, button, null, t, 1.0f, 1);
486: // // FadeTracker fadeTracker = FadeTracker.getInstance();
487: // // FocusKind focusKind =
488: // // SubstanceCoreUtilities.getFocusKind(this.button);
489: // // if ((focusKind == FocusKind.NONE)
490: // // && (!fadeTracker.isTracked(this.button, FadeKind.FOCUS)))
491: // // return;
492: // // Graphics2D graphics = (Graphics2D) g.create();
493: // // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
494: // // RenderingHints.VALUE_ANTIALIAS_ON);
495: // //
496: // // float alpha = 1.0f;
497: // // if (fadeTracker.isTracked(this.button, FadeKind.FOCUS)) {
498: // // alpha = fadeTracker.getFade10(this.button, FadeKind.FOCUS) / 10.f;
499: // // }
500: // // // System.out.println(button.getText() + " -> " + alpha);
501: // // graphics.setComposite(TransitionLayout.getAlphaComposite(this.button,
502: // // alpha));
503: // //
504: // // Color color = SubstanceColorUtilities.getFocusColor(this.button);
505: // // graphics.setColor(color);
506: // // focusKind.paintFocus(this.button, graphics, t);
507: // // graphics.dispose();
508: // }
509:
510: /**
511: * Returns memory usage string.
512: *
513: * @return Memory usage string.
514: */
515: public static String getMemoryUsage() {
516: StringBuffer sb = new StringBuffer();
517: sb.append("SubstanceRadioButtonUI: \n");
518: sb
519: .append("\t" + SubstanceRadioButtonUI.icons.size()
520: + " icons");
521: return sb.toString();
522: }
523:
524: /**
525: * Paints the text.
526: *
527: * @param g
528: * Graphic context
529: * @param button
530: * Button
531: * @param textRect
532: * Text rectangle
533: * @param text
534: * Text to paint
535: * @return Text alpha channel.
536: */
537: protected float paintButtonText(Graphics g, AbstractButton button,
538: Rectangle textRect, String text) {
539: return SubstanceCoreUtilities.paintText(button, textRect, text,
540: button.getDisplayedMnemonicIndex());
541: }
542: }
|