/* * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of Flamingo Kirill Grouchnikov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pushingpixels.flamingo.api.common; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.*; import javax.accessibility.AccessibleContext; import javax.swing.ButtonModel; import javax.swing.SwingConstants; import javax.swing.event.*; import org.pushingpixels.flamingo.api.common.icon.ResizableIcon; import org.pushingpixels.flamingo.api.common.model.ActionButtonModel; import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI; /** * Base class for command buttons. * * @author Kirill Grouchnikov */ public abstract class AbstractCommandButton extends RichToolTipManager.JTrackableComponent { /** * Associated icon. * * @see #setIcon(ResizableIcon) * @see #getIcon() */ protected ResizableIcon icon; /** * Associated disabled icon. * * @see #setDisabledIcon(ResizableIcon) * @see #getDisabledIcon() */ protected ResizableIcon disabledIcon; /** * The button text. * * @see #setText(String) * @see #getText() */ private String text; /** * The button action model. * * @see #getActionModel() * @see #setActionModel(ActionButtonModel) */ protected ActionButtonModel actionModel; /** * Additional text. This is shown for {@link CommandButtonDisplayState#TILE} * . * * @see #setExtraText(String) * @see #getExtraText() */ protected String extraText; /** * Current display state of <code>this</code> button. * * @see #setDisplayState(CommandButtonDisplayState) * @see #getDisplayState() */ protected CommandButtonDisplayState displayState; /** * The dimension of the icon of the associated command button in the * {@link CommandButtonDisplayState#FIT_TO_ICON} state. * * @see #getCustomDimension() * @see #updateCustomDimension(int) */ protected int customDimension; /** * Indication whether this button is flat. * * @see #setFlat(boolean) * @see #isFlat() */ protected boolean isFlat; /** * Horizontal alignment of the content. * * @see #setHorizontalAlignment(int) * @see #getHorizontalAlignment() */ private int horizontalAlignment; /** * Scale factor for horizontal gaps. * * @see #setHGapScaleFactor(double) * @see #getHGapScaleFactor() */ private double hgapScaleFactor; /** * Scale factor for vertical gaps. * * @see #setVGapScaleFactor(double) * @see #getVGapScaleFactor() */ private double vgapScaleFactor; /** * Rich tooltip for the action area. * * @see #setActionRichTooltip(RichTooltip) * @see #getRichTooltip(MouseEvent) */ private RichTooltip actionRichTooltip; /** * Location order kind for buttons placed in command button strips or for * buttons that need the visuals of segmented strips. * * @see #setLocationOrderKind(CommandButtonLocationOrderKind) * @see #getLocationOrderKind() */ private CommandButtonLocationOrderKind locationOrderKind; /** * Action handler for the button. */ protected ActionHandler actionHandler; /** * Key tip for the action area. * * @see #setActionKeyTip(String) * @see #getActionKeyTip() */ protected String actionKeyTip; /** * Enumerates the available values for the location order kind. This is used * for buttons placed in command button strips or for buttons that need the * visuals of segmented strips. * * @author Kirill Grouchnikov */ public static enum CommandButtonLocationOrderKind { /** * Indicates that this button is the only button in the strip. */ ONLY, /** * Indicates that this button is the first button in the strip. */ FIRST, /** * Indicates that this button is in the middle of the strip. */ MIDDLE, /** * Indicates that this button is the last button in the strip. */ LAST } /** * Creates a new command button. * * @param text * Button title. May contain any number of words. * @param icon * Button icon. */ public AbstractCommandButton(String text, ResizableIcon icon) { this.icon = icon; this.customDimension = -1; this.displayState = CommandButtonDisplayState.FIT_TO_ICON; this.horizontalAlignment = SwingConstants.CENTER; this.actionHandler = new ActionHandler(); this.isFlat = true; this.hgapScaleFactor = 1.0; this.vgapScaleFactor = 1.0; this.setText(text); this.setOpaque(false); } /** * Sets the new UI delegate. * * @param ui * New UI delegate. */ public void setUI(CommandButtonUI ui) { super.setUI(ui); } /** * Returns the UI delegate for this button. * * @return The UI delegate for this button. */ public CommandButtonUI getUI() { return (CommandButtonUI) ui; } /** * Sets new display state for <code>this</code> button. Fires a * <code>displayState</code> property change event. * * @param state * New display state. * @see #getDisplayState() */ public void setDisplayState(CommandButtonDisplayState state) { CommandButtonDisplayState old = this.displayState; this.displayState = state; this.firePropertyChange("displayState", old, this.displayState); } /** * Returns the associated icon. * * @return The associated icon. * @see #getDisabledIcon() * @see #setIcon(ResizableIcon) */ public ResizableIcon getIcon() { return icon; } /** * Sets new icon for this button. Fires an <code>icon</code> property change * event. * * @param defaultIcon * New default icon for this button. * @see #setDisabledIcon(ResizableIcon) * @see #getIcon() */ public void setIcon(ResizableIcon defaultIcon) { ResizableIcon oldValue = this.icon; this.icon = defaultIcon; firePropertyChange("icon", oldValue, defaultIcon); if (defaultIcon != oldValue) { if (defaultIcon == null || oldValue == null || defaultIcon.getIconWidth() != oldValue.getIconWidth() || defaultIcon.getIconHeight() != oldValue.getIconHeight()) { revalidate(); } repaint(); } } /** * Sets the disabled icon for this button. * * @param disabledIcon * Disabled icon for this button. * @see #setIcon(ResizableIcon) * @see #getDisabledIcon() */ public void setDisabledIcon(ResizableIcon disabledIcon) { this.disabledIcon = disabledIcon; } /** * Returns the associated disabled icon. * * @return The associated disabled icon. * @see #setDisabledIcon(ResizableIcon) * @see #getIcon() */ public ResizableIcon getDisabledIcon() { return disabledIcon; } /** * Return the current display state of <code>this</code> button. * * @return The current display state of <code>this</code> button. * @see #setDisplayState(CommandButtonDisplayState) */ public CommandButtonDisplayState getDisplayState() { return displayState; } /** * Returns the extra text of this button. * * @return Extra text of this button. * @see #setExtraText(String) */ public String getExtraText() { return this.extraText; } /** * Sets the extra text for this button. Fires an <code>extraText</code> * property change event. * * @param extraText * Extra text for this button. * @see #getExtraText() */ public void setExtraText(String extraText) { String oldValue = this.extraText; this.extraText = extraText; firePropertyChange("extraText", oldValue, extraText); if (accessibleContext != null) { accessibleContext.firePropertyChange( AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, oldValue, extraText); } if (extraText == null || oldValue == null || !extraText.equals(oldValue)) { revalidate(); repaint(); } } /** * Returns the text of this button. * * @return The text of this button. * @see #setText(String) */ public String getText() { return this.text; } /** * Sets the new text for this button. Fires a <code>text</code> property * change event. * * @param text * The new text for this button. * @see #getText() */ public void setText(String text) { String oldValue = this.text; this.text = text; firePropertyChange("text", oldValue, text); if (accessibleContext != null) { accessibleContext.firePropertyChange( AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, oldValue, text); } if (text == null || oldValue == null || !text.equals(oldValue)) { revalidate(); repaint(); } } /** * Updates the dimension of the icon of the associated command button in the * {@link CommandButtonDisplayState#FIT_TO_ICON} state. Fires a * <code>customDimension</code> property change event. * * @param dimension * New dimension of the icon of the associated command button in * the {@link CommandButtonDisplayState#FIT_TO_ICON} state. * @see #getCustomDimension() */ public void updateCustomDimension(int dimension) { if (this.customDimension != dimension) { int old = this.customDimension; this.customDimension = dimension; this.firePropertyChange("customDimension", old, this.customDimension); } } /** * Returns the dimension of the icon of the associated command button in the * {@link CommandButtonDisplayState#FIT_TO_ICON} state. * * @return The dimension of the icon of the associated command button in the * {@link CommandButtonDisplayState#FIT_TO_ICON} state. * @see #updateCustomDimension(int) */ public int getCustomDimension() { return this.customDimension; } /** * Returns indication whether this button has flat appearance. * * @return <code>true</code> if this button has flat appearance, * <code>false</code> otherwise. * @see #setFlat(boolean) */ public boolean isFlat() { return this.isFlat; } /** * Sets the flat appearance of this button. Fires a <code>flat</code> * property change event. * * @param isFlat * If <code>true</code>, this button will have flat appearance, * otherwise this button will not have flat appearance. * @see #isFlat() */ public void setFlat(boolean isFlat) { boolean old = this.isFlat; this.isFlat = isFlat; if (old != this.isFlat) { this.firePropertyChange("flat", old, this.isFlat); } if (old != isFlat) { repaint(); } } /** * Returns the action model for this button. * * @return The action model for this button. * @see #setActionModel(ActionButtonModel) */ public ActionButtonModel getActionModel() { return this.actionModel; } /** * Sets the new action model for this button. Fires an * <code>actionModel</code> property change event. * * @param newModel * The new action model for this button. * @see #getActionModel() */ public void setActionModel(ActionButtonModel newModel) { ButtonModel oldModel = getActionModel(); if (oldModel != null) { oldModel.removeChangeListener(this.actionHandler); oldModel.removeActionListener(this.actionHandler); } actionModel = newModel; if (newModel != null) { newModel.addChangeListener(this.actionHandler); newModel.addActionListener(this.actionHandler); } firePropertyChange("actionModel", oldModel, newModel); if (newModel != oldModel) { revalidate(); repaint(); } } /** * Adds the specified action listener to this button. * * @param l * Action listener to add. * @see #removeActionListener(ActionListener) */ public void addActionListener(ActionListener l) { this.listenerList.add(ActionListener.class, l); } /** * Removes the specified action listener from this button. * * @param l * Action listener to remove. * @see #addActionListener(ActionListener) */ public void removeActionListener(ActionListener l) { this.listenerList.remove(ActionListener.class, l); } /** * Adds the specified change listener to this button. * * @param l * Change listener to add. * @see #removeChangeListener(ChangeListener) */ public void addChangeListener(ChangeListener l) { this.listenerList.add(ChangeListener.class, l); } /** * Removes the specified change listener from this button. * * @param l * Change listener to remove. * @see #addChangeListener(ChangeListener) */ public void removeChangeListener(ChangeListener l) { this.listenerList.remove(ChangeListener.class, l); } /* * (non-Javadoc) * * @see javax.swing.JComponent#setEnabled(boolean) */ @Override public void setEnabled(boolean b) { if (!b && actionModel.isRollover()) { actionModel.setRollover(false); } super.setEnabled(b); actionModel.setEnabled(b); } /** * Default action handler for this button. * * @author Kirill Grouchnikov */ class ActionHandler implements ActionListener, ChangeListener { @Override public void stateChanged(ChangeEvent e) { fireStateChanged(); repaint(); } @Override public void actionPerformed(ActionEvent event) { fireActionPerformed(event); } } /** * Notifies all listeners that have registered interest for notification on * this event type. The event instance is lazily created. * * @see EventListenerList */ protected void fireStateChanged() { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event ChangeEvent ce = new ChangeEvent(this); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { // Lazily create the event: ((ChangeListener) listeners[i + 1]).stateChanged(ce); } } } /** * Notifies all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * <code>event</code> parameter. * * @param event * the <code>ActionEvent</code> object * @see EventListenerList */ protected void fireActionPerformed(ActionEvent event) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); ActionEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ActionListener.class) { // Lazily create the event: if (e == null) { String actionCommand = event.getActionCommand(); e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, actionCommand, event.getWhen(), event .getModifiers()); } ((ActionListener) listeners[i + 1]).actionPerformed(e); } } } /** * Sets new horizontal alignment for the content of this button. Fires a * <code>horizontalAlignment</code> property change event. * * @param alignment * New horizontal alignment for the content of this button. * @see #getHorizontalAlignment() */ public void setHorizontalAlignment(int alignment) { if (alignment == this.horizontalAlignment) return; int oldValue = this.horizontalAlignment; this.horizontalAlignment = alignment; firePropertyChange("horizontalAlignment", oldValue, this.horizontalAlignment); repaint(); } /** * Returns the horizontal alignment for the content of this button. * * @return The horizontal alignment for the content of this button. * @see #setHorizontalAlignment(int) */ public int getHorizontalAlignment() { return this.horizontalAlignment; } /** * Sets new horizontal gap scale factor for the content of this button. * Fires an <code>hgapScaleFactor</code> property change event. * * @param hgapScaleFactor * New horizontal gap scale factor for the content of this * button. * @see #getHGapScaleFactor() * @see #setVGapScaleFactor(double) * @see #setGapScaleFactor(double) */ public void setHGapScaleFactor(double hgapScaleFactor) { if (hgapScaleFactor == this.hgapScaleFactor) return; double oldValue = this.hgapScaleFactor; this.hgapScaleFactor = hgapScaleFactor; firePropertyChange("hgapScaleFactor", oldValue, this.hgapScaleFactor); if (this.hgapScaleFactor != oldValue) { revalidate(); repaint(); } } /** * Sets new vertical gap scale factor for the content of this button. Fires * a <code>vgapScaleFactor</code> property change event. * * @param vgapScaleFactor * New vertical gap scale factor for the content of this button. * @see #getVGapScaleFactor() * @see #setHGapScaleFactor(double) * @see #setGapScaleFactor(double) */ public void setVGapScaleFactor(double vgapScaleFactor) { if (vgapScaleFactor == this.vgapScaleFactor) return; double oldValue = this.vgapScaleFactor; this.vgapScaleFactor = vgapScaleFactor; firePropertyChange("vgapScaleFactor", oldValue, this.vgapScaleFactor); if (this.vgapScaleFactor != oldValue) { revalidate(); repaint(); } } /** * Sets new gap scale factor for the content of this button. * * @param gapScaleFactor * New gap scale factor for the content of this button. * @see #getHGapScaleFactor() * @see #getVGapScaleFactor() */ public void setGapScaleFactor(double gapScaleFactor) { setHGapScaleFactor(gapScaleFactor); setVGapScaleFactor(gapScaleFactor); } /** * Returns the horizontal gap scale factor for the content of this button. * * @return The horizontal gap scale factor for the content of this button. * @see #setHGapScaleFactor(double) * @see #setGapScaleFactor(double) * @see #getVGapScaleFactor() */ public double getHGapScaleFactor() { return this.hgapScaleFactor; } /** * Returns the vertical gap scale factor for the content of this button. * * @return The vertical gap scale factor for the content of this button. * @see #setVGapScaleFactor(double) * @see #setGapScaleFactor(double) * @see #getHGapScaleFactor() */ public double getVGapScaleFactor() { return this.vgapScaleFactor; } /** * Programmatically perform an action "click". This does the same thing as * if the user had pressed and released the action area of the button. */ public void doActionClick() { Dimension size = getSize(); ButtonModel actionModel = this.getActionModel(); actionModel.setArmed(true); actionModel.setPressed(true); paintImmediately(new Rectangle(0, 0, size.width, size.height)); try { Thread.sleep(100); } catch (InterruptedException ie) { } actionModel.setPressed(false); actionModel.setArmed(false); } boolean hasRichTooltips() { return (this.actionRichTooltip != null); } /** * Sets the rich tooltip for the action area of this button. * * @param richTooltip * Rich tooltip for the action area of this button. * @see #getRichTooltip(MouseEvent) */ public void setActionRichTooltip(RichTooltip richTooltip) { this.actionRichTooltip = richTooltip; RichToolTipManager richToolTipManager = RichToolTipManager .sharedInstance(); if (this.hasRichTooltips()) { richToolTipManager.registerComponent(this); } else { richToolTipManager.unregisterComponent(this); } } /* * (non-Javadoc) * * @seeorg.jvnet.flamingo.common.RichToolTipManager.JTrackableComponent# * getRichTooltip(java.awt.event.MouseEvent) */ @Override public RichTooltip getRichTooltip(MouseEvent mouseEvent) { return this.actionRichTooltip; } /* * (non-Javadoc) * * @see javax.swing.JComponent#setToolTipText(java.lang.String) */ @Override public void setToolTipText(String text) { throw new UnsupportedOperationException("Use rich tooltip APIs"); } /** * Returns the location order kind for buttons placed in command button * strips or for buttons that need the visuals of segmented strips. * * @return The location order kind for buttons placed in command button * strips or for buttons that need the visuals of segmented strips. * @see #setLocationOrderKind(CommandButtonLocationOrderKind) */ public CommandButtonLocationOrderKind getLocationOrderKind() { return this.locationOrderKind; } /** * Sets the location order kind for buttons placed in command button strips * or for buttons that need the visuals of segmented strips. Fires a * <code>locationOrderKind</code> property change event. * * @param locationOrderKind * The location order kind for buttons placed in command button * strips or for buttons that need the visuals of segmented * strips. * @see #getLocationOrderKind() */ public void setLocationOrderKind( CommandButtonLocationOrderKind locationOrderKind) { CommandButtonLocationOrderKind old = this.locationOrderKind; if (old != locationOrderKind) { this.locationOrderKind = locationOrderKind; this.firePropertyChange("locationOrderKind", old, this.locationOrderKind); } } /** * Returns the key tip for the action area of this button. * * @return The key tip for the action area of this button. * @see #setActionKeyTip(String) */ public String getActionKeyTip() { return this.actionKeyTip; } /** * Sets the key tip for the action area of this button. Fires an * <code>actionKeyTip</code> property change event. * * @param actionKeyTip * The key tip for the action area of this button. * @see #getActionKeyTip() */ public void setActionKeyTip(String actionKeyTip) { String old = this.actionKeyTip; this.actionKeyTip = actionKeyTip; this.firePropertyChange("actionKeyTip", old, this.actionKeyTip); } }