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.awt.image.BufferedImage;
034: import java.beans.PropertyChangeEvent;
035: import java.beans.PropertyChangeListener;
036:
037: import javax.swing.*;
038: import javax.swing.border.Border;
039: import javax.swing.plaf.ComponentUI;
040: import javax.swing.plaf.basic.*;
041: import javax.swing.text.View;
042:
043: import org.jvnet.lafwidget.animation.*;
044: import org.jvnet.lafwidget.layout.TransitionLayout;
045: import org.jvnet.substance.button.SubstanceButtonShaper;
046: import org.jvnet.substance.combo.SubstanceComboBoxButton;
047: import org.jvnet.substance.painter.text.SubstanceTextPainter;
048: import org.jvnet.substance.scroll.SubstanceScrollButton;
049: import org.jvnet.substance.theme.SubstanceTheme;
050: import org.jvnet.substance.utils.*;
051: import org.jvnet.substance.utils.icon.GlowingIcon;
052:
053: /**
054: * UI for buttons in <b>Substance</b> look and feel.
055: *
056: * @author Kirill Grouchnikov
057: */
058: public class SubstanceButtonUI extends BasicButtonUI {
059: /**
060: * Property used during the button shaper switch.
061: */
062: public static final String BORDER_COMPUTED = "substancelaf.buttonbordercomputed";
063:
064: /**
065: * Property used during the button shaper switch.
066: */
067: public static final String BORDER_COMPUTING = "substancelaf.buttonbordercomputing";
068:
069: /**
070: * Property used to store the original (pre-<b>Substance</b>) button
071: * border.
072: */
073: public static final String BORDER_ORIGINAL = "substancelaf.buttonborderoriginal";
074:
075: /**
076: * Property used to store the original button icon.
077: */
078: public static final String ICON_ORIGINAL = "substancelaf.buttoniconoriginal";
079:
080: /**
081: * Property used to store the original (pre-<b>Substance</b>) button
082: * opacity.
083: */
084: public static final String OPACITY_ORIGINAL = "substancelaf.buttonopacityoriginal";
085:
086: /**
087: * Property used to lock the original (pre-<b>Substance</b>) button
088: * opacity.
089: */
090: public static final String LOCK_OPACITY = "substancelaf.lockopacity";
091:
092: /**
093: * Internal property used to mark close buttons on title panes.
094: */
095: public static final String IS_TITLE_CLOSE_BUTTON = "substancelaf.internal.isTitleCloseButton";
096:
097: /**
098: * Painting delegate.
099: */
100: private ButtonBackgroundDelegate delegate;
101:
102: /**
103: * The rollover button listener.
104: */
105: private RolloverButtonListener substanceButtonListener;
106:
107: /**
108: * The matching glowing icon. Is used only when
109: * {@link FadeConfigurationManager#fadeAllowed(FadeKind, Component)} returns
110: * true on {@link FadeKind#ICON_GLOW}.
111: */
112: protected GlowingIcon glowingIcon;
113:
114: /**
115: * Property change listener. Listens on changes to the
116: * {@link SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY} property and
117: * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
118: */
119: protected PropertyChangeListener substancePropertyListener;
120:
121: /**
122: * Listener for fade animations.
123: */
124: protected FadeStateListener substanceFadeStateListener;
125:
126: /*
127: * (non-Javadoc)
128: *
129: * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
130: */
131: public static ComponentUI createUI(JComponent b) {
132: AbstractButton button = (AbstractButton) b;
133: button.setRolloverEnabled(true);
134: button.setOpaque(false);
135: return new SubstanceButtonUI();
136: }
137:
138: /**
139: * Simple constructor.
140: */
141: public SubstanceButtonUI() {
142: this .delegate = new ButtonBackgroundDelegate();
143: }
144:
145: /*
146: * (non-Javadoc)
147: *
148: * @see javax.swing.plaf.basic.BasicButtonUI#installDefaults(javax.swing.AbstractButton)
149: */
150: @Override
151: public void installDefaults(final AbstractButton b) {
152: super .installDefaults(b);
153:
154: if (b.getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL) == null)
155: b.putClientProperty(SubstanceButtonUI.BORDER_ORIGINAL, b
156: .getBorder());
157:
158: trackGlowingIcon(b);
159:
160: SubstanceButtonShaper shaper = SubstanceCoreUtilities
161: .getButtonShaper(b);
162:
163: if (b.getClientProperty(SubstanceButtonUI.BORDER_COMPUTED) == null) {
164: b.setBorder(shaper.getButtonBorder(b));
165: } else {
166: Border currBorder = b.getBorder();
167: if (!(currBorder instanceof SubstanceButtonBorder)) {
168: b.setBorder(shaper.getButtonBorder(b));
169: } else {
170: SubstanceButtonBorder sbCurrBorder = (SubstanceButtonBorder) currBorder;
171: if (shaper.getClass() != sbCurrBorder
172: .getButtonShaperClass())
173: b.setBorder(shaper.getButtonBorder(b));
174: }
175: }
176: b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, b
177: .isOpaque());
178: }
179:
180: /*
181: * (non-Javadoc)
182: *
183: * @see javax.swing.plaf.basic.BasicButtonUI#uninstallDefaults(javax.swing.AbstractButton)
184: */
185: @Override
186: public void uninstallDefaults(AbstractButton b) {
187: super .uninstallDefaults(b);
188:
189: b.setBorder((Border) b
190: .getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL));
191: b.setOpaque((Boolean) b
192: .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
193: Icon origIcon = (Icon) b
194: .getClientProperty(SubstanceButtonUI.ICON_ORIGINAL);
195: if (origIcon != null)
196: b.setIcon(origIcon);
197: b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, null);
198: }
199:
200: /*
201: * (non-Javadoc)
202: *
203: * @see javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing.AbstractButton)
204: */
205: @Override
206: protected BasicButtonListener createButtonListener(AbstractButton b) {
207: return null;
208: }
209:
210: /*
211: * (non-Javadoc)
212: *
213: * @see javax.swing.plaf.basic.BasicButtonUI#installListeners(javax.swing.AbstractButton)
214: */
215: @Override
216: protected void installListeners(final AbstractButton b) {
217: super .installListeners(b);
218:
219: this .substanceButtonListener = new RolloverButtonListener(b);
220: b.addMouseListener(this .substanceButtonListener);
221: b.addMouseMotionListener(this .substanceButtonListener);
222: b.addFocusListener(this .substanceButtonListener);
223: b.addPropertyChangeListener(this .substanceButtonListener);
224: b.addChangeListener(this .substanceButtonListener);
225:
226: this .substancePropertyListener = new PropertyChangeListener() {
227: public void propertyChange(PropertyChangeEvent evt) {
228: if (SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY
229: .equals(evt.getPropertyName())) {
230: SwingUtilities.invokeLater(new Runnable() {
231: public void run() {
232: SwingUtilities.updateComponentTreeUI(b);
233: }
234: });
235: }
236: if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
237: .getPropertyName())) {
238: if (substanceFadeStateListener != null)
239: substanceFadeStateListener
240: .unregisterListeners();
241: boolean toRepaintParent = (b instanceof SubstanceScrollButton)
242: || (b instanceof SubstanceSpinnerButton)
243: || (b instanceof SubstanceComboBoxButton);
244: substanceFadeStateListener = new FadeStateListener(
245: b, b.getModel(),
246: SubstanceCoreUtilities.getFadeCallback(b,
247: toRepaintParent));
248: substanceFadeStateListener
249: .registerListeners(toRepaintParent);
250: }
251: if (AbstractButton.ICON_CHANGED_PROPERTY.equals(evt
252: .getPropertyName())) {
253: trackGlowingIcon(b);
254: }
255: }
256: };
257: b.addPropertyChangeListener(this .substancePropertyListener);
258:
259: boolean toRepaintParent = (b instanceof SubstanceScrollButton)
260: || (b instanceof SubstanceSpinnerButton)
261: || (b instanceof SubstanceComboBoxButton);
262: this .substanceFadeStateListener = new FadeStateListener(b, b
263: .getModel(), SubstanceCoreUtilities.getFadeCallback(b,
264: toRepaintParent));
265: this .substanceFadeStateListener
266: .registerListeners(toRepaintParent);
267: }
268:
269: /*
270: * (non-Javadoc)
271: *
272: * @see javax.swing.plaf.basic.BasicButtonUI#uninstallListeners(javax.swing.AbstractButton)
273: */
274: @Override
275: protected void uninstallListeners(AbstractButton b) {
276: b.removeMouseListener(this .substanceButtonListener);
277: b.removeMouseMotionListener(this .substanceButtonListener);
278: b.removeFocusListener(this .substanceButtonListener);
279: b.removePropertyChangeListener(this .substanceButtonListener);
280: b.removeChangeListener(this .substanceButtonListener);
281: this .substanceButtonListener = null;
282:
283: b.removePropertyChangeListener(this .substancePropertyListener);
284: this .substancePropertyListener = null;
285:
286: this .substanceFadeStateListener.unregisterListeners();
287: this .substanceFadeStateListener = null;
288:
289: super .uninstallListeners(b);
290: }
291:
292: /*
293: * (non-Javadoc)
294: *
295: * @see javax.swing.plaf.basic.BasicButtonUI#paint(java.awt.Graphics,
296: * javax.swing.JComponent)
297: */
298: @Override
299: public void paint(Graphics g, JComponent c) {
300: final AbstractButton b = (AbstractButton) c;
301:
302: if (b instanceof JButton) {
303: JButton jb = (JButton) b;
304: if (PulseTracker.isPulsating(jb)) {
305: PulseTracker.update(jb);
306: } else {
307: }
308: }
309:
310: FontMetrics fm = g.getFontMetrics();
311:
312: Insets i = c.getInsets();
313:
314: Rectangle viewRect = new Rectangle();
315: Rectangle iconRect = new Rectangle();
316: final Rectangle textRect = new Rectangle();
317:
318: viewRect.x = i.left;
319: viewRect.y = i.top;
320: viewRect.width = b.getWidth() - (i.right + viewRect.x);
321: viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
322:
323: textRect.x = textRect.y = textRect.width = textRect.height = 0;
324: iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
325:
326: Font f = c.getFont();
327:
328: // layout the text and icon
329: String text = SwingUtilities.layoutCompoundLabel(c, fm, b
330: .getText(), b.getIcon(), b.getVerticalAlignment(), b
331: .getHorizontalAlignment(), b.getVerticalTextPosition(),
332: b.getHorizontalTextPosition(), viewRect, iconRect,
333: textRect, b.getText() == null ? 0 : b.getIconTextGap());
334:
335: // Special case for scroll buttons - these are painted
336: // on top of the "real" controls (scroll bars). In order
337: // to properly handle the translucent states under the native
338: // text painter, we need to paint these buttons to offscreen
339: // image
340: Graphics2D g2d = null;
341: BufferedImage offscreen = null;
342:
343: boolean isPartOfCompositeControl = SubstanceCoreUtilities
344: .isScrollButton(b)
345: || SubstanceCoreUtilities.isSpinnerButton(b);
346: if (isPartOfCompositeControl) {
347: offscreen = SubstanceCoreUtilities.getBlankImage(b
348: .getWidth(), b.getHeight());
349: g2d = offscreen.createGraphics();
350: SubstanceTheme buttonTheme = SubstanceThemeUtilities
351: .getTheme(b);
352: float themeAlpha = buttonTheme.getThemeAlpha(b,
353: ComponentState.getState(b.getModel(), b));
354:
355: g2d.setComposite(AlphaComposite.getInstance(
356: AlphaComposite.SRC_OVER, themeAlpha));
357: } else {
358: g2d = (Graphics2D) g.create();
359: }
360: boolean hasEmptyText = (text == null) || (text.length() == 0);
361:
362: final View v = (View) c
363: .getClientProperty(BasicHTML.propertyKey);
364: SubstanceTextPainter textPainter = SubstanceLookAndFeel
365: .getCurrentTextPainter();
366: textPainter.init(b, null, v != null);
367: g2d.setFont(f);
368:
369: if (!hasEmptyText && textPainter.needsBackgroundImage()) {
370: textPainter.setBackgroundFill(b, b.getParent()
371: .getBackground(), true, 0, 0);
372: textPainter
373: .attachCallback(new SubstanceTextPainter.BackgroundPaintingCallback() {
374: public void paintBackground(Graphics g) {
375: delegate.updateBackground(g, b);
376: };
377: });
378: } else {
379: this .delegate.updateBackground(g2d, b);
380: }
381: float textAlpha = 1.0f;
382: if (v != null) {
383: if (textPainter.needsBackgroundImage()) {
384: textPainter
385: .attachCallback(new SubstanceTextPainter.BackgroundPaintingCallback() {
386: public void paintBackground(Graphics g) {
387: v.paint(g, textRect);
388: }
389: });
390: } else {
391: v.paint(g2d, textRect);
392: }
393: } else {
394: textAlpha = this .paintButtonText(g, b, textRect, text);
395: }
396: g2d.setComposite(TransitionLayout.getAlphaComposite(b,
397: textAlpha, g));
398: textPainter.renderSurface(g2d);
399: g2d
400: .setComposite(TransitionLayout.getAlphaComposite(b,
401: 1.0f, g));
402:
403: // Paint the Icon
404: if (b.getIcon() != null) {
405: paintIcon(g2d, c, iconRect);
406: }
407:
408: if (b.isFocusPainted()) {
409: if (b.hasFocus()
410: || FadeTracker.getInstance().isTracked(c,
411: FadeKind.FOCUS)) {
412: this .paintFocus(g2d, b, viewRect, textRect, iconRect);
413: }
414: }
415:
416: if (isPartOfCompositeControl) {
417: g.drawImage(offscreen, 0, 0, null);
418: }
419: }
420:
421: /*
422: * (non-Javadoc)
423: *
424: * @see javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
425: */
426: @Override
427: public Dimension getPreferredSize(JComponent c) {
428: AbstractButton button = (AbstractButton) c;
429: SubstanceButtonShaper shaper = SubstanceCoreUtilities
430: .getButtonShaper(button);
431:
432: // fix for defect 263
433: Dimension super Pref = super .getPreferredSize(button);
434: if (super Pref == null)
435: return null;
436:
437: return shaper.getPreferredSize(button, super Pref);
438: }
439:
440: /*
441: * (non-Javadoc)
442: *
443: * @see javax.swing.plaf.basic.BasicButtonUI#paintFocus(java.awt.Graphics,
444: * javax.swing.AbstractButton, java.awt.Rectangle, java.awt.Rectangle,
445: * java.awt.Rectangle)
446: */
447: @Override
448: protected void paintFocus(Graphics g, AbstractButton b,
449: Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
450: if (!b.isFocusPainted())
451: return;
452:
453: SubstanceCoreUtilities.paintFocus(g, b, b, null, textRect,
454: 1.0f, 2 + SubstanceSizeUtils
455: .getExtraPadding(SubstanceSizeUtils
456: .getComponentFontSize(b)));
457: }
458:
459: /*
460: * (non-Javadoc)
461: *
462: * @see javax.swing.plaf.ComponentUI#contains(javax.swing.JComponent, int,
463: * int)
464: */
465: @Override
466: public boolean contains(JComponent c, int x, int y) {
467: return ButtonBackgroundDelegate.contains((JButton) c, x, y);
468: }
469:
470: /*
471: * (non-Javadoc)
472: *
473: * @see javax.swing.plaf.basic.BasicButtonUI#paintIcon(java.awt.Graphics,
474: * javax.swing.JComponent, java.awt.Rectangle)
475: */
476: @Override
477: protected void paintIcon(Graphics g, JComponent c,
478: Rectangle iconRect) {
479: Graphics2D graphics = (Graphics2D) g.create();
480: FadeTracker fadeTracker = FadeTracker.getInstance();
481: AbstractButton b = (AbstractButton) c;
482: Icon icon = SubstanceCoreUtilities.getIcon(b, null,
483: this .glowingIcon, false);
484:
485: graphics.setComposite(TransitionLayout.getAlphaComposite(b, g));
486: if (fadeTracker.isTracked(b, FadeKind.ROLLOVER)) {
487: ComponentState state = ComponentState.getState(
488: b.getModel(), b);
489: // System.out.println(state.name() + ":" + state.isRollover());
490: if (state.isKindActive(FadeKind.ROLLOVER)) {
491: // Came from default state
492: SubstanceCoreUtilities.getIcon(b, null,
493: this .glowingIcon, true).paintIcon(b, graphics,
494: iconRect.x, iconRect.y);
495: graphics.setComposite(TransitionLayout
496: .getAlphaComposite(b, fadeTracker.getFade10(b,
497: FadeKind.ROLLOVER) / 10.0f, g));
498: icon.paintIcon(b, graphics, iconRect.x, iconRect.y);
499: } else {
500: // if (state == ComponentState.DEFAULT) {
501: // Came from rollover state
502: icon.paintIcon(b, graphics, iconRect.x, iconRect.y);
503: graphics.setComposite(TransitionLayout
504: .getAlphaComposite(b, fadeTracker.getFade10(b,
505: FadeKind.ROLLOVER) / 10.0f, g));
506: b.getIcon().paintIcon(b, graphics, iconRect.x,
507: iconRect.y);
508: }
509: } else {
510: icon.paintIcon(b, graphics, iconRect.x, iconRect.y);
511: }
512: graphics.dispose();
513: }
514:
515: /**
516: * Paints the text.
517: *
518: * @param g
519: * Graphic context
520: * @param button
521: * Button
522: * @param textRect
523: * Text rectangle
524: * @param text
525: * Text to paint
526: * @return Text alpha channel.
527: */
528: protected float paintButtonText(Graphics g, AbstractButton button,
529: Rectangle textRect, String text) {
530: return SubstanceCoreUtilities.paintText(button, textRect, text,
531: button.getDisplayedMnemonicIndex());
532: }
533:
534: /**
535: * Tracks possible usage of glowing icon.
536: *
537: * @param b
538: * Button.
539: */
540: protected void trackGlowingIcon(AbstractButton b) {
541: Icon currIcon = b.getIcon();
542: if (currIcon instanceof GlowingIcon)
543: return;
544: if (currIcon == null)
545: return;
546: this .glowingIcon = new GlowingIcon(currIcon, b);
547: }
548:
549: /*
550: * (non-Javadoc)
551: *
552: * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
553: * javax.swing.JComponent)
554: */
555: @Override
556: public void update(Graphics g, JComponent c) {
557: this.paint(g, c);
558: }
559: }
|