2
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.flamingo.api.common;
32
import java.awt.Dimension;
33
import java.awt.Rectangle;
34
import java.awt.event.*;
36
import javax.accessibility.AccessibleContext;
37
import javax.swing.ButtonModel;
38
import javax.swing.SwingConstants;
39
import javax.swing.event.*;
41
import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
42
import org.pushingpixels.flamingo.api.common.model.ActionButtonModel;
43
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
46
* Base class for command buttons.
48
* @author Kirill Grouchnikov
50
public abstract class AbstractCommandButton extends
51
RichToolTipManager.JTrackableComponent {
55
* @see #setIcon(ResizableIcon)
58
protected ResizableIcon icon;
61
* Associated disabled icon.
63
* @see #setDisabledIcon(ResizableIcon)
64
* @see #getDisabledIcon()
66
protected ResizableIcon disabledIcon;
71
* @see #setText(String)
77
* The button action model.
79
* @see #getActionModel()
80
* @see #setActionModel(ActionButtonModel)
82
protected ActionButtonModel actionModel;
85
* Additional text. This is shown for {@link CommandButtonDisplayState#TILE}
88
* @see #setExtraText(String)
89
* @see #getExtraText()
91
protected String extraText;
94
* Current display state of <code>this</code> button.
96
* @see #setDisplayState(CommandButtonDisplayState)
97
* @see #getDisplayState()
99
protected CommandButtonDisplayState displayState;
102
* The dimension of the icon of the associated command button in the
103
* {@link CommandButtonDisplayState#FIT_TO_ICON} state.
105
* @see #getCustomDimension()
106
* @see #updateCustomDimension(int)
108
protected int customDimension;
111
* Indication whether this button is flat.
113
* @see #setFlat(boolean)
116
protected boolean isFlat;
119
* Horizontal alignment of the content.
121
* @see #setHorizontalAlignment(int)
122
* @see #getHorizontalAlignment()
124
private int horizontalAlignment;
127
* Scale factor for horizontal gaps.
129
* @see #setHGapScaleFactor(double)
130
* @see #getHGapScaleFactor()
132
private double hgapScaleFactor;
135
* Scale factor for vertical gaps.
137
* @see #setVGapScaleFactor(double)
138
* @see #getVGapScaleFactor()
140
private double vgapScaleFactor;
143
* Rich tooltip for the action area.
145
* @see #setActionRichTooltip(RichTooltip)
146
* @see #getRichTooltip(MouseEvent)
148
private RichTooltip actionRichTooltip;
151
* Location order kind for buttons placed in command button strips or for
152
* buttons that need the visuals of segmented strips.
154
* @see #setLocationOrderKind(CommandButtonLocationOrderKind)
155
* @see #getLocationOrderKind()
157
private CommandButtonLocationOrderKind locationOrderKind;
160
* Action handler for the button.
162
protected ActionHandler actionHandler;
165
* Key tip for the action area.
167
* @see #setActionKeyTip(String)
168
* @see #getActionKeyTip()
170
protected String actionKeyTip;
173
* Enumerates the available values for the location order kind. This is used
174
* for buttons placed in command button strips or for buttons that need the
175
* visuals of segmented strips.
177
* @author Kirill Grouchnikov
179
public static enum CommandButtonLocationOrderKind {
181
* Indicates that this button is the only button in the strip.
186
* Indicates that this button is the first button in the strip.
191
* Indicates that this button is in the middle of the strip.
196
* Indicates that this button is the last button in the strip.
202
* Creates a new command button.
205
* Button title. May contain any number of words.
209
public AbstractCommandButton(String text, ResizableIcon icon) {
211
this.customDimension = -1;
212
this.displayState = CommandButtonDisplayState.FIT_TO_ICON;
213
this.horizontalAlignment = SwingConstants.CENTER;
214
this.actionHandler = new ActionHandler();
216
this.hgapScaleFactor = 1.0;
217
this.vgapScaleFactor = 1.0;
219
this.setOpaque(false);
223
* Sets the new UI delegate.
228
public void setUI(CommandButtonUI ui) {
233
* Returns the UI delegate for this button.
235
* @return The UI delegate for this button.
237
public CommandButtonUI getUI() {
238
return (CommandButtonUI) ui;
242
* Sets new display state for <code>this</code> button. Fires a
243
* <code>displayState</code> property change event.
247
* @see #getDisplayState()
249
public void setDisplayState(CommandButtonDisplayState state) {
250
CommandButtonDisplayState old = this.displayState;
251
this.displayState = state;
253
this.firePropertyChange("displayState", old, this.displayState);
257
* Returns the associated icon.
259
* @return The associated icon.
260
* @see #getDisabledIcon()
261
* @see #setIcon(ResizableIcon)
263
public ResizableIcon getIcon() {
268
* Sets new icon for this button. Fires an <code>icon</code> property change
272
* New default icon for this button.
273
* @see #setDisabledIcon(ResizableIcon)
276
public void setIcon(ResizableIcon defaultIcon) {
277
ResizableIcon oldValue = this.icon;
278
this.icon = defaultIcon;
280
firePropertyChange("icon", oldValue, defaultIcon);
281
if (defaultIcon != oldValue) {
282
if (defaultIcon == null || oldValue == null
283
|| defaultIcon.getIconWidth() != oldValue.getIconWidth()
284
|| defaultIcon.getIconHeight() != oldValue.getIconHeight()) {
292
* Sets the disabled icon for this button.
294
* @param disabledIcon
295
* Disabled icon for this button.
296
* @see #setIcon(ResizableIcon)
297
* @see #getDisabledIcon()
299
public void setDisabledIcon(ResizableIcon disabledIcon) {
300
this.disabledIcon = disabledIcon;
304
* Returns the associated disabled icon.
306
* @return The associated disabled icon.
307
* @see #setDisabledIcon(ResizableIcon)
310
public ResizableIcon getDisabledIcon() {
315
* Return the current display state of <code>this</code> button.
317
* @return The current display state of <code>this</code> button.
318
* @see #setDisplayState(CommandButtonDisplayState)
320
public CommandButtonDisplayState getDisplayState() {
325
* Returns the extra text of this button.
327
* @return Extra text of this button.
328
* @see #setExtraText(String)
330
public String getExtraText() {
331
return this.extraText;
335
* Sets the extra text for this button. Fires an <code>extraText</code>
336
* property change event.
339
* Extra text for this button.
340
* @see #getExtraText()
342
public void setExtraText(String extraText) {
343
String oldValue = this.extraText;
344
this.extraText = extraText;
345
firePropertyChange("extraText", oldValue, extraText);
347
if (accessibleContext != null) {
348
accessibleContext.firePropertyChange(
349
AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
350
oldValue, extraText);
352
if (extraText == null || oldValue == null
353
|| !extraText.equals(oldValue)) {
360
* Returns the text of this button.
362
* @return The text of this button.
363
* @see #setText(String)
365
public String getText() {
370
* Sets the new text for this button. Fires a <code>text</code> property
374
* The new text for this button.
377
public void setText(String text) {
378
String oldValue = this.text;
380
firePropertyChange("text", oldValue, text);
382
if (accessibleContext != null) {
383
accessibleContext.firePropertyChange(
384
AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
387
if (text == null || oldValue == null || !text.equals(oldValue)) {
394
* Updates the dimension of the icon of the associated command button in the
395
* {@link CommandButtonDisplayState#FIT_TO_ICON} state. Fires a
396
* <code>customDimension</code> property change event.
399
* New dimension of the icon of the associated command button in
400
* the {@link CommandButtonDisplayState#FIT_TO_ICON} state.
401
* @see #getCustomDimension()
403
public void updateCustomDimension(int dimension) {
404
if (this.customDimension != dimension) {
405
int old = this.customDimension;
406
this.customDimension = dimension;
407
this.firePropertyChange("customDimension", old,
408
this.customDimension);
413
* Returns the dimension of the icon of the associated command button in the
414
* {@link CommandButtonDisplayState#FIT_TO_ICON} state.
416
* @return The dimension of the icon of the associated command button in the
417
* {@link CommandButtonDisplayState#FIT_TO_ICON} state.
418
* @see #updateCustomDimension(int)
420
public int getCustomDimension() {
421
return this.customDimension;
425
* Returns indication whether this button has flat appearance.
427
* @return <code>true</code> if this button has flat appearance,
428
* <code>false</code> otherwise.
429
* @see #setFlat(boolean)
431
public boolean isFlat() {
436
* Sets the flat appearance of this button. Fires a <code>flat</code>
437
* property change event.
440
* If <code>true</code>, this button will have flat appearance,
441
* otherwise this button will not have flat appearance.
444
public void setFlat(boolean isFlat) {
445
boolean old = this.isFlat;
446
this.isFlat = isFlat;
447
if (old != this.isFlat) {
448
this.firePropertyChange("flat", old, this.isFlat);
457
* Returns the action model for this button.
459
* @return The action model for this button.
460
* @see #setActionModel(ActionButtonModel)
462
public ActionButtonModel getActionModel() {
463
return this.actionModel;
467
* Sets the new action model for this button. Fires an
468
* <code>actionModel</code> property change event.
471
* The new action model for this button.
472
* @see #getActionModel()
474
public void setActionModel(ActionButtonModel newModel) {
475
ButtonModel oldModel = getActionModel();
477
if (oldModel != null) {
478
oldModel.removeChangeListener(this.actionHandler);
479
oldModel.removeActionListener(this.actionHandler);
482
actionModel = newModel;
484
if (newModel != null) {
485
newModel.addChangeListener(this.actionHandler);
486
newModel.addActionListener(this.actionHandler);
489
firePropertyChange("actionModel", oldModel, newModel);
490
if (newModel != oldModel) {
497
* Adds the specified action listener to this button.
500
* Action listener to add.
501
* @see #removeActionListener(ActionListener)
503
public void addActionListener(ActionListener l) {
504
this.listenerList.add(ActionListener.class, l);
508
* Removes the specified action listener from this button.
511
* Action listener to remove.
512
* @see #addActionListener(ActionListener)
514
public void removeActionListener(ActionListener l) {
515
this.listenerList.remove(ActionListener.class, l);
519
* Adds the specified change listener to this button.
522
* Change listener to add.
523
* @see #removeChangeListener(ChangeListener)
525
public void addChangeListener(ChangeListener l) {
526
this.listenerList.add(ChangeListener.class, l);
530
* Removes the specified change listener from this button.
533
* Change listener to remove.
534
* @see #addChangeListener(ChangeListener)
536
public void removeChangeListener(ChangeListener l) {
537
this.listenerList.remove(ChangeListener.class, l);
543
* @see javax.swing.JComponent#setEnabled(boolean)
546
public void setEnabled(boolean b) {
547
if (!b && actionModel.isRollover()) {
548
actionModel.setRollover(false);
551
actionModel.setEnabled(b);
555
* Default action handler for this button.
557
* @author Kirill Grouchnikov
559
class ActionHandler implements ActionListener, ChangeListener {
561
public void stateChanged(ChangeEvent e) {
567
public void actionPerformed(ActionEvent event) {
568
fireActionPerformed(event);
573
* Notifies all listeners that have registered interest for notification on
574
* this event type. The event instance is lazily created.
576
* @see EventListenerList
578
protected void fireStateChanged() {
579
// Guaranteed to return a non-null array
580
Object[] listeners = listenerList.getListenerList();
581
// Process the listeners last to first, notifying
582
// those that are interested in this event
583
ChangeEvent ce = new ChangeEvent(this);
584
for (int i = listeners.length - 2; i >= 0; i -= 2) {
585
if (listeners[i] == ChangeListener.class) {
586
// Lazily create the event:
587
((ChangeListener) listeners[i + 1]).stateChanged(ce);
593
* Notifies all listeners that have registered interest for notification on
594
* this event type. The event instance is lazily created using the
595
* <code>event</code> parameter.
598
* the <code>ActionEvent</code> object
599
* @see EventListenerList
601
protected void fireActionPerformed(ActionEvent event) {
602
// Guaranteed to return a non-null array
603
Object[] listeners = listenerList.getListenerList();
604
ActionEvent e = null;
605
// Process the listeners last to first, notifying
606
// those that are interested in this event
607
for (int i = listeners.length - 2; i >= 0; i -= 2) {
608
if (listeners[i] == ActionListener.class) {
609
// Lazily create the event:
611
String actionCommand = event.getActionCommand();
612
e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
613
actionCommand, event.getWhen(), event
616
((ActionListener) listeners[i + 1]).actionPerformed(e);
622
* Sets new horizontal alignment for the content of this button. Fires a
623
* <code>horizontalAlignment</code> property change event.
626
* New horizontal alignment for the content of this button.
627
* @see #getHorizontalAlignment()
629
public void setHorizontalAlignment(int alignment) {
630
if (alignment == this.horizontalAlignment)
632
int oldValue = this.horizontalAlignment;
633
this.horizontalAlignment = alignment;
634
firePropertyChange("horizontalAlignment", oldValue,
635
this.horizontalAlignment);
640
* Returns the horizontal alignment for the content of this button.
642
* @return The horizontal alignment for the content of this button.
643
* @see #setHorizontalAlignment(int)
645
public int getHorizontalAlignment() {
646
return this.horizontalAlignment;
650
* Sets new horizontal gap scale factor for the content of this button.
651
* Fires an <code>hgapScaleFactor</code> property change event.
653
* @param hgapScaleFactor
654
* New horizontal gap scale factor for the content of this
656
* @see #getHGapScaleFactor()
657
* @see #setVGapScaleFactor(double)
658
* @see #setGapScaleFactor(double)
660
public void setHGapScaleFactor(double hgapScaleFactor) {
661
if (hgapScaleFactor == this.hgapScaleFactor)
663
double oldValue = this.hgapScaleFactor;
664
this.hgapScaleFactor = hgapScaleFactor;
665
firePropertyChange("hgapScaleFactor", oldValue, this.hgapScaleFactor);
666
if (this.hgapScaleFactor != oldValue) {
673
* Sets new vertical gap scale factor for the content of this button. Fires
674
* a <code>vgapScaleFactor</code> property change event.
676
* @param vgapScaleFactor
677
* New vertical gap scale factor for the content of this button.
678
* @see #getVGapScaleFactor()
679
* @see #setHGapScaleFactor(double)
680
* @see #setGapScaleFactor(double)
682
public void setVGapScaleFactor(double vgapScaleFactor) {
683
if (vgapScaleFactor == this.vgapScaleFactor)
685
double oldValue = this.vgapScaleFactor;
686
this.vgapScaleFactor = vgapScaleFactor;
687
firePropertyChange("vgapScaleFactor", oldValue, this.vgapScaleFactor);
688
if (this.vgapScaleFactor != oldValue) {
695
* Sets new gap scale factor for the content of this button.
697
* @param gapScaleFactor
698
* New gap scale factor for the content of this button.
699
* @see #getHGapScaleFactor()
700
* @see #getVGapScaleFactor()
702
public void setGapScaleFactor(double gapScaleFactor) {
703
setHGapScaleFactor(gapScaleFactor);
704
setVGapScaleFactor(gapScaleFactor);
708
* Returns the horizontal gap scale factor for the content of this button.
710
* @return The horizontal gap scale factor for the content of this button.
711
* @see #setHGapScaleFactor(double)
712
* @see #setGapScaleFactor(double)
713
* @see #getVGapScaleFactor()
715
public double getHGapScaleFactor() {
716
return this.hgapScaleFactor;
720
* Returns the vertical gap scale factor for the content of this button.
722
* @return The vertical gap scale factor for the content of this button.
723
* @see #setVGapScaleFactor(double)
724
* @see #setGapScaleFactor(double)
725
* @see #getHGapScaleFactor()
727
public double getVGapScaleFactor() {
728
return this.vgapScaleFactor;
732
* Programmatically perform an action "click". This does the same thing as
733
* if the user had pressed and released the action area of the button.
735
public void doActionClick() {
736
Dimension size = getSize();
737
ButtonModel actionModel = this.getActionModel();
738
actionModel.setArmed(true);
739
actionModel.setPressed(true);
740
paintImmediately(new Rectangle(0, 0, size.width, size.height));
743
} catch (InterruptedException ie) {
745
actionModel.setPressed(false);
746
actionModel.setArmed(false);
749
boolean hasRichTooltips() {
750
return (this.actionRichTooltip != null);
754
* Sets the rich tooltip for the action area of this button.
757
* Rich tooltip for the action area of this button.
758
* @see #getRichTooltip(MouseEvent)
760
public void setActionRichTooltip(RichTooltip richTooltip) {
761
this.actionRichTooltip = richTooltip;
762
RichToolTipManager richToolTipManager = RichToolTipManager
764
if (this.hasRichTooltips()) {
765
richToolTipManager.registerComponent(this);
767
richToolTipManager.unregisterComponent(this);
774
* @seeorg.jvnet.flamingo.common.RichToolTipManager.JTrackableComponent#
775
* getRichTooltip(java.awt.event.MouseEvent)
778
public RichTooltip getRichTooltip(MouseEvent mouseEvent) {
779
return this.actionRichTooltip;
785
* @see javax.swing.JComponent#setToolTipText(java.lang.String)
788
public void setToolTipText(String text) {
789
throw new UnsupportedOperationException("Use rich tooltip APIs");
793
* Returns the location order kind for buttons placed in command button
794
* strips or for buttons that need the visuals of segmented strips.
796
* @return The location order kind for buttons placed in command button
797
* strips or for buttons that need the visuals of segmented strips.
798
* @see #setLocationOrderKind(CommandButtonLocationOrderKind)
800
public CommandButtonLocationOrderKind getLocationOrderKind() {
801
return this.locationOrderKind;
805
* Sets the location order kind for buttons placed in command button strips
806
* or for buttons that need the visuals of segmented strips. Fires a
807
* <code>locationOrderKind</code> property change event.
809
* @param locationOrderKind
810
* The location order kind for buttons placed in command button
811
* strips or for buttons that need the visuals of segmented
813
* @see #getLocationOrderKind()
815
public void setLocationOrderKind(
816
CommandButtonLocationOrderKind locationOrderKind) {
817
CommandButtonLocationOrderKind old = this.locationOrderKind;
818
if (old != locationOrderKind) {
819
this.locationOrderKind = locationOrderKind;
820
this.firePropertyChange("locationOrderKind", old,
821
this.locationOrderKind);
826
* Returns the key tip for the action area of this button.
828
* @return The key tip for the action area of this button.
829
* @see #setActionKeyTip(String)
831
public String getActionKeyTip() {
832
return this.actionKeyTip;
836
* Sets the key tip for the action area of this button. Fires an
837
* <code>actionKeyTip</code> property change event.
839
* @param actionKeyTip
840
* The key tip for the action area of this button.
841
* @see #getActionKeyTip()
843
public void setActionKeyTip(String actionKeyTip) {
844
String old = this.actionKeyTip;
845
this.actionKeyTip = actionKeyTip;
846
this.firePropertyChange("actionKeyTip", old, this.actionKeyTip);