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;
37
import javax.swing.event.ChangeEvent;
38
import javax.swing.event.ChangeListener;
40
import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
41
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonPanelUI;
44
* Panel that hosts command buttons. Provides support for button groups, single
45
* selection mode (for toggle command buttons), same icon state / dimension and
46
* column-fill / row-fill layout.
49
* Under the default {@link LayoutKind#ROW_FILL}, the buttons are laid out in
50
* rows, never exceeding the available horizontal space. A vertical scroll bar
51
* will kick in once there is not enough vertical space to show all the buttons.
52
* The schematic below shows a row-fill command button panel:
56
* +-----------------------------+-+
58
* | +----+ +----+ +----+ +----+ | |
59
* | | 01 | | 02 | | 03 | | 04 | | |
60
* | +----+ +----+ +----+ +----+ | |
62
* | +----+ +----+ +----+ +----+ | |
63
* | | 05 | | 06 | | 07 | | 07 | | |
64
* | +----+ +----+ +----+ +----+ | |
66
* | +----+ +----+ +----+ +----+ | |
67
* | | 09 | | 10 | | 11 | | 12 | | |
68
* | +----+ +----+ +----+ +----+ | |
70
* | +----+ +----+ +----+ +----+ | |
71
* | | 13 | | 14 | | 15 | | 16 | | |
72
* +-----------------------------+-+
76
* Each row hosts four buttons, and the vertical scroll bar allows scrolling the
81
* Under the {@link LayoutKind#COLUMN_FILL}, the buttons are laid out in
82
* columns, never exceeding the available vertical space. A horizontal scroll
83
* bar will kick in once there is not enough horizontal space to show all the
84
* buttons. The schematic below shows a column-fill command button panel:
88
* +---------------------------------+
90
* | +----+ +----+ +----+ +----+ +---|
91
* | | 01 | | 04 | | 07 | | 10 | | 13|
92
* | +----+ +----+ +----+ +----+ +---|
94
* | +----+ +----+ +----+ +----+ +---|
95
* | | 02 | | 05 | | 08 | | 11 | | 14|
96
* | +----+ +----+ +----+ +----+ +---|
98
* | +----+ +----+ +----+ +----+ +---|
99
* | | 03 | | 06 | | 09 | | 12 | | 15|
100
* | +----+ +----+ +----+ +----+ +---|
102
* +---------------------------------+
103
* +---------------------------------+
107
* Each column hosts three buttons, and the horizontal scroll bar allows
108
* scrolling the content down.
111
* @author Kirill Grouchnikov
113
public class JCommandButtonPanel extends JPanel implements Scrollable {
117
public static final String uiClassID = "CommandButtonPanelUI";
120
* List of titles for all button groups.
122
* @see #getGroupCount()
123
* @see #getGroupTitleAt(int)
125
protected List<String> groupTitles;
128
* List of all button groups.
130
* @see #getGroupCount()
131
* @see #getGroupButtons(int)
133
protected List<List<AbstractCommandButton>> buttons;
136
* Maximum number of columns for this panel. Relevant only when the layout
137
* kind is {@link LayoutKind#ROW_FILL}.
139
* @see #getMaxButtonColumns()
140
* @see #setMaxButtonColumns(int)
142
protected int maxButtonColumns;
145
* Maximum number of rows for this panel. Relevant only when the layout kind
146
* is {@link LayoutKind#COLUMN_FILL}.
148
* @see #getMaxButtonRows()
149
* @see #setMaxButtonRows(int)
151
protected int maxButtonRows;
154
* Indicates the selection mode for the {@link JCommandToggleButton} in this
157
* @see #setSingleSelectionMode(boolean)
159
protected boolean isSingleSelectionMode;
162
* If <code>true</code>, the panel will show group labels.
164
* @see #setToShowGroupLabels(boolean)
165
* @see #isToShowGroupLabels()
167
protected boolean toShowGroupLabels;
170
* The button group for the single selection mode.
172
protected CommandToggleButtonGroup buttonGroup;
175
* Current icon dimension.
177
protected int currDimension;
180
* Current icon state.
182
protected CommandButtonDisplayState currState;
185
* Layout kind of this button panel.
187
* @see #getLayoutKind()
188
* @see #setLayoutKind(LayoutKind)
190
protected LayoutKind layoutKind;
193
* Enumerates the available layout kinds.
195
* @author Kirill Grouchnikov
197
public enum LayoutKind {
199
* The buttons are layed out in rows respecting the available width.
204
* The buttons are layed out in columns respecting the available height.
210
* Creates a new panel.
212
protected JCommandButtonPanel() {
213
this.buttons = new ArrayList<List<AbstractCommandButton>>();
214
this.groupTitles = new ArrayList<String>();
215
this.maxButtonColumns = -1;
216
this.maxButtonRows = -1;
217
this.isSingleSelectionMode = false;
218
this.toShowGroupLabels = true;
219
this.setLayoutKind(LayoutKind.ROW_FILL);
223
* Creates a new panel.
225
* @param startingDimension
226
* Initial dimension for buttons.
228
public JCommandButtonPanel(int startingDimension) {
230
this.currDimension = startingDimension;
231
this.currState = CommandButtonDisplayState.FIT_TO_ICON;
236
* Creates a new panel.
238
* @param startingState
239
* Initial state for buttons.
241
public JCommandButtonPanel(CommandButtonDisplayState startingState) {
243
this.currDimension = -1;
244
this.currState = startingState;
249
* Adds a new button group at the specified index.
251
* @param buttonGroupName
254
* Button group index.
255
* @see #addButtonGroup(String)
256
* @see #removeButtonGroup(String)
257
* @see #removeAllGroups()
259
public void addButtonGroup(String buttonGroupName, int groupIndex) {
260
this.groupTitles.add(groupIndex, buttonGroupName);
261
List<AbstractCommandButton> list = new ArrayList<AbstractCommandButton>();
262
this.buttons.add(groupIndex, list);
263
this.fireStateChanged();
267
* Adds a new button group after all the existing button groups.
269
* @param buttonGroupName
271
* @see #addButtonGroup(String, int)
272
* @see #removeButtonGroup(String)
273
* @see #removeAllGroups()
275
public void addButtonGroup(String buttonGroupName) {
276
this.addButtonGroup(buttonGroupName, this.groupTitles.size());
280
* Removes the specified button group.
282
* @param buttonGroupName
283
* Name of the button group to remove.
284
* @see #addButtonGroup(String)
285
* @see #addButtonGroup(String, int)
286
* @see #removeAllGroups()
288
public void removeButtonGroup(String buttonGroupName) {
289
int groupIndex = this.groupTitles.indexOf(buttonGroupName);
292
this.groupTitles.remove(groupIndex);
293
List<AbstractCommandButton> list = this.buttons.get(groupIndex);
295
for (AbstractCommandButton button : list) {
297
if (this.isSingleSelectionMode
298
&& (button instanceof JCommandToggleButton)) {
299
this.buttonGroup.remove((JCommandToggleButton) button);
303
this.buttons.remove(groupIndex);
304
this.fireStateChanged();
308
* Adds a new button to the specified button group.
310
* @param commandButton
312
* @return Returns the index of the button on the specified group, or -1 if
313
* no such group exists.
314
* @see #addButtonToGroup(String, AbstractCommandButton)
315
* @see #addButtonToGroup(String, int, AbstractCommandButton)
316
* @see #removeButtonFromGroup(String, int)
318
public int addButtonToLastGroup(AbstractCommandButton commandButton) {
319
if (this.groupTitles.size() == 0)
321
int groupIndex = this.groupTitles.size() - 1;
322
commandButton.setDisplayState(this.currState);
323
return this.addButtonToGroup(this.groupTitles.get(groupIndex),
324
this.buttons.get(groupIndex).size(), commandButton);
328
* Adds a new button to the specified button group.
330
* @param buttonGroupName
331
* Name of the button group.
332
* @param commandButton
334
* @return Returns the index of the button on the specified group, or -1 if
335
* no such group exists.
336
* @see #addButtonToGroup(String, int, AbstractCommandButton)
337
* @see #addButtonToLastGroup(AbstractCommandButton)
338
* @see #removeButtonFromGroup(String, int)
340
public int addButtonToGroup(String buttonGroupName,
341
AbstractCommandButton commandButton) {
342
int groupIndex = this.groupTitles.indexOf(buttonGroupName);
345
commandButton.setDisplayState(this.currState);
346
return this.addButtonToGroup(buttonGroupName, this.buttons.get(
347
groupIndex).size(), commandButton);
351
* Adds a new button to the specified button group.
353
* @param buttonGroupName
354
* Name of the button group.
355
* @param indexInGroup
356
* Index of the button in group.
357
* @param commandButton
359
* @return Returns the index of the button on the specified group, or -1 if
360
* no such group exists.
361
* @see #addButtonToGroup(String, int, AbstractCommandButton)
362
* @see #addButtonToLastGroup(AbstractCommandButton)
363
* @see #removeButtonFromGroup(String, int)
365
public int addButtonToGroup(String buttonGroupName, int indexInGroup,
366
AbstractCommandButton commandButton) {
367
int groupIndex = this.groupTitles.indexOf(buttonGroupName);
370
// commandButton.setState(ElementState.ORIG, true);
371
this.add(commandButton);
372
this.buttons.get(groupIndex).add(indexInGroup, commandButton);
373
if (this.isSingleSelectionMode
374
&& (commandButton instanceof JCommandToggleButton)) {
375
this.buttonGroup.add((JCommandToggleButton) commandButton);
377
this.fireStateChanged();
382
* Removes the button at the specified index from the specified button
385
* @param buttonGroupName
386
* Name of the button group.
387
* @param indexInGroup
388
* Index of the button to remove.
389
* @see #addButtonToGroup(String, AbstractCommandButton)
390
* @see #addButtonToGroup(String, int, AbstractCommandButton)
391
* @see #addButtonToLastGroup(AbstractCommandButton)
393
public void removeButtonFromGroup(String buttonGroupName, int indexInGroup) {
394
int groupIndex = this.groupTitles.indexOf(buttonGroupName);
398
AbstractCommandButton removed = this.buttons.get(groupIndex).remove(
400
this.remove(removed);
401
if (this.isSingleSelectionMode
402
&& (removed instanceof JCommandToggleButton)) {
403
this.buttonGroup.remove((JCommandToggleButton) removed);
405
this.fireStateChanged();
409
* Removes all the button groups and buttons from this panel.
411
* @see #addButtonGroup(String, int)
412
* @see #addButtonGroup(String)
413
* @see #removeButtonGroup(String)
414
* @see #removeButtonFromGroup(String, int)
416
public void removeAllGroups() {
417
for (List<AbstractCommandButton> ljcb : this.buttons) {
418
for (AbstractCommandButton jcb : ljcb) {
419
if (this.isSingleSelectionMode
420
&& (jcb instanceof JCommandToggleButton)) {
421
this.buttonGroup.remove((JCommandToggleButton) jcb);
426
this.buttons.clear();
427
this.groupTitles.clear();
428
this.fireStateChanged();
432
* Returns the number of button groups in this panel.
434
* @return Number of button groups in this panel.
436
public int getGroupCount() {
437
if (this.groupTitles == null)
439
return this.groupTitles.size();
443
* Returns the number of buttons in this panel.
445
* @return Number of buttons in this panel.
447
public int getButtonCount() {
449
for (List<AbstractCommandButton> ljcb : this.buttons) {
450
result += ljcb.size();
456
* Returns the title of the button group at the specified index.
459
* Button group index.
460
* @return Title of the button group at the specified index.
462
public String getGroupTitleAt(int index) {
463
return this.groupTitles.get(index);
469
* @see javax.swing.JPanel#updateUI()
472
public void updateUI() {
473
if (UIManager.get(getUIClassID()) != null) {
474
setUI((CommandButtonPanelUI) UIManager.getUI(this));
476
setUI(BasicCommandButtonPanelUI.createUI(this));
483
* @see javax.swing.JPanel#getUIClassID()
486
public String getUIClassID() {
491
* Sets the maximum button columns for this panel. When this panel is shown
492
* and the layout kind is {@link LayoutKind#ROW_FILL}, it will have no more
493
* than this number of buttons in each row. Fires a
494
* <code>maxButtonColumns</code> property change event.
496
* @param maxButtonColumns
497
* Maximum button columns for this panel.
498
* @see #getMaxButtonColumns()
499
* @see #setMaxButtonRows(int)
501
public void setMaxButtonColumns(int maxButtonColumns) {
502
if (maxButtonColumns != this.maxButtonColumns) {
503
int oldValue = this.maxButtonColumns;
504
this.maxButtonColumns = maxButtonColumns;
505
this.firePropertyChange("maxButtonColumns", oldValue,
506
this.maxButtonColumns);
511
* Returns the maximum button columns for this panel. The return value is
512
* relevant only when the layout kind is {@link LayoutKind#ROW_FILL}.
514
* @return Maximum button columns for this panel.
515
* @see #setMaxButtonColumns(int)
516
* @see #getMaxButtonRows()
518
public int getMaxButtonColumns() {
519
return this.maxButtonColumns;
523
* Sets the maximum button rows for this panel. When this panel is shown and
524
* the layout kind is {@link LayoutKind#COLUMN_FILL}, it will have no more
525
* than this number of buttons in each column. Fires a
526
* <code>maxButtonRows</code> property change event.
528
* @param maxButtonRows
529
* Maximum button rows for this panel.
530
* @see #getMaxButtonRows()
531
* @see #setMaxButtonColumns(int)
533
public void setMaxButtonRows(int maxButtonRows) {
534
if (maxButtonRows != this.maxButtonRows) {
535
int oldValue = this.maxButtonRows;
536
this.maxButtonRows = maxButtonRows;
537
this.firePropertyChange("maxButtonRows", oldValue,
543
* Returns the maximum button rows for this panel. The return value is
544
* relevant only when the layout kind is {@link LayoutKind#COLUMN_FILL}.
546
* @return Maximum button rows for this panel.
547
* @see #setMaxButtonRows(int)
548
* @see #getMaxButtonColumns()
550
public int getMaxButtonRows() {
551
return this.maxButtonRows;
555
* Returns the list of all buttons in the specified button group.
559
* @return Unmodifiable view on the list of all buttons in the specified
561
* @see #getGroupCount()
563
public List<AbstractCommandButton> getGroupButtons(int groupIndex) {
564
return Collections.unmodifiableList(this.buttons.get(groupIndex));
568
* Sets the selection mode for this panel. If <code>true</code> is passed as
569
* the parameter, all {@link JCommandToggleButton} in this panel are set to
570
* belong to the same button group.
572
* @param isSingleSelectionMode
573
* If <code>true</code>,all {@link JCommandToggleButton} in this
574
* panel are set to belong to the same button group.
575
* @see #getSelectedButton()
577
public void setSingleSelectionMode(boolean isSingleSelectionMode) {
578
if (this.isSingleSelectionMode == isSingleSelectionMode)
581
this.isSingleSelectionMode = isSingleSelectionMode;
582
if (this.isSingleSelectionMode) {
583
this.buttonGroup = new CommandToggleButtonGroup();
584
for (List<AbstractCommandButton> ljrb : this.buttons) {
585
for (AbstractCommandButton jrb : ljrb) {
586
if (jrb instanceof JCommandToggleButton) {
587
this.buttonGroup.add((JCommandToggleButton) jrb);
592
for (List<AbstractCommandButton> ljrb : this.buttons) {
593
for (AbstractCommandButton jrb : ljrb) {
594
if (jrb instanceof JCommandToggleButton) {
595
this.buttonGroup.remove((JCommandToggleButton) jrb);
599
this.buttonGroup = null;
604
* Sets indication whether button group labels should be shown. Fires a
605
* <code>toShowGroupLabels</code> property change event.
607
* @param toShowGroupLabels
608
* If <code>true</code>, this panel will show the labels of the
610
* @see #isToShowGroupLabels()
612
public void setToShowGroupLabels(boolean toShowGroupLabels) {
613
if ((layoutKind == LayoutKind.COLUMN_FILL) && toShowGroupLabels) {
614
throw new IllegalArgumentException(
615
"Column fill layout is not supported when group labels are shown");
617
if (this.toShowGroupLabels != toShowGroupLabels) {
618
boolean oldValue = this.toShowGroupLabels;
619
this.toShowGroupLabels = toShowGroupLabels;
620
this.firePropertyChange("toShowGroupLabels", oldValue,
621
this.toShowGroupLabels);
626
* Returns indication whether button group labels should be shown.
628
* @return If <code>true</code>, this panel shows the labels of the button
629
* groups, and <code>false</code> otherwise.
630
* @see #setToShowGroupLabels(boolean)
632
public boolean isToShowGroupLabels() {
633
return this.toShowGroupLabels;
637
* Sets the new dimension for the icons in this panel. The state for all the
638
* icons is set to {@link CommandButtonDisplayState#FIT_TO_ICON}.
641
* New dimension for the icons in this panel.
642
* @see #setIconState(CommandButtonDisplayState)
644
public void setIconDimension(int dimension) {
645
this.currDimension = dimension;
646
this.currState = CommandButtonDisplayState.FIT_TO_ICON;
647
for (List<AbstractCommandButton> buttonList : this.buttons) {
648
for (AbstractCommandButton button : buttonList) {
649
button.updateCustomDimension(dimension);
658
* Sets the new state for the icons in this panel. The dimension for all the
659
* icons is set to -1; this method should only be called with a state that
660
* has an associated default size (like
661
* {@link CommandButtonDisplayState#BIG},
662
* {@link CommandButtonDisplayState#TILE},
663
* {@link CommandButtonDisplayState#MEDIUM} and
664
* {@link CommandButtonDisplayState#SMALL}).
667
* New state for the icons in this panel.
668
* @see #setIconDimension(int)
670
public void setIconState(CommandButtonDisplayState state) {
671
this.currDimension = -1;
672
this.currState = state;
673
for (List<AbstractCommandButton> ljrb : this.buttons) {
674
for (AbstractCommandButton jrb : ljrb) {
675
jrb.setDisplayState(state);
686
* Returns the selected button of this panel. Only relevant for single
687
* selection mode (set by {@link #setSingleSelectionMode(boolean)}),
688
* returning <code>null</code> otherwise.
690
* @return The selected button of this panel.
691
* @see #setSingleSelectionMode(boolean)
693
public JCommandToggleButton getSelectedButton() {
694
if (this.isSingleSelectionMode) {
695
for (List<AbstractCommandButton> ljrb : this.buttons) {
696
for (AbstractCommandButton jrb : ljrb) {
697
if (jrb instanceof JCommandToggleButton) {
698
JCommandToggleButton jctb = (JCommandToggleButton) jrb;
699
if (jctb.getActionModel().isSelected())
709
* Returns the layout kind of this panel.
711
* @return Layout kind of this panel.
712
* @see #setLayoutKind(LayoutKind)
714
public LayoutKind getLayoutKind() {
719
* Sets the new layout kind for this panel. Fires a <code>layoutKind</code>
720
* property change event.
723
* New layout kind for this panel.
724
* @see #getLayoutKind()
726
public void setLayoutKind(LayoutKind layoutKind) {
727
if (layoutKind == null)
728
throw new IllegalArgumentException("Layout kind cannot be null");
729
if ((layoutKind == LayoutKind.COLUMN_FILL)
730
&& this.isToShowGroupLabels()) {
731
throw new IllegalArgumentException(
732
"Column fill layout is not supported when group labels are shown");
734
if (layoutKind != this.layoutKind) {
735
LayoutKind old = this.layoutKind;
736
this.layoutKind = layoutKind;
737
this.firePropertyChange("layoutKind", old, this.layoutKind);
742
* Adds the specified change listener to this button panel.
745
* Change listener to add.
746
* @see #removeChangeListener(ChangeListener)
748
public void addChangeListener(ChangeListener l) {
749
this.listenerList.add(ChangeListener.class, l);
753
* Removes the specified change listener from this button panel.
756
* Change listener to remove.
757
* @see #addChangeListener(ChangeListener)
759
public void removeChangeListener(ChangeListener l) {
760
this.listenerList.remove(ChangeListener.class, l);
764
* Notifies all registered listener that the state of this command button
767
protected void fireStateChanged() {
768
// Guaranteed to return a non-null array
769
Object[] listeners = listenerList.getListenerList();
770
// Process the listeners last to first, notifying
771
// those that are interested in this event
772
ChangeEvent event = new ChangeEvent(this);
773
for (int i = listeners.length - 2; i >= 0; i -= 2) {
774
if (listeners[i] == ChangeListener.class) {
775
((ChangeListener) listeners[i + 1]).stateChanged(event);
783
* @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
786
public Dimension getPreferredScrollableViewportSize() {
787
return this.getPreferredSize();
794
* javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle,
798
public int getScrollableBlockIncrement(Rectangle visibleRect,
799
int orientation, int direction) {
806
* @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
809
public boolean getScrollableTracksViewportHeight() {
810
return (this.layoutKind == LayoutKind.COLUMN_FILL);
816
* @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
819
public boolean getScrollableTracksViewportWidth() {
820
return (this.layoutKind == LayoutKind.ROW_FILL);
827
* javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle,
831
public int getScrollableUnitIncrement(Rectangle visibleRect,
832
int orientation, int direction) {