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.internal.ui.common.popup;
33
import java.awt.geom.AffineTransform;
36
import javax.swing.border.Border;
37
import javax.swing.border.EmptyBorder;
38
import javax.swing.event.ChangeEvent;
39
import javax.swing.event.ChangeListener;
40
import javax.swing.plaf.ComponentUI;
42
import org.pushingpixels.flamingo.api.common.*;
43
import org.pushingpixels.flamingo.api.common.popup.*;
44
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
45
import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
46
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonLayoutManagerMedium;
47
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
49
public class BasicCommandPopupMenuUI extends BasicPopupPanelUI {
51
* The associated popup menu
53
protected JCommandPopupMenu popupMenu;
55
protected ChangeListener popupMenuChangeListener;
57
protected PopupPanelManager.PopupListener popupListener;
59
protected ScrollableCommandButtonPanel commandButtonPanel;
61
protected JScrollablePanel<JPanel> menuItemsPanel;
63
public static final String FORCE_ICON = "flamingo.internal.commandButtonLayoutManagerMedium.forceIcon";
65
protected static final CommandButtonDisplayState POPUP_MENU = new CommandButtonDisplayState(
68
public CommandButtonLayoutManager createLayoutManager(
69
AbstractCommandButton commandButton) {
70
return new CommandButtonLayoutManagerMedium() {
72
protected float getIconTextGapFactor() {
80
* Popup panel that hosts groups of icons.
82
* @author Kirill Grouchnikov
84
protected static class ScrollableCommandButtonPanel extends JComponent {
86
* Maximum dimension of <code>this</code> popup gallery.
88
protected Dimension maxDimension;
91
* The internal panel that hosts the icon command buttons. Is hosted in
92
* the {@link #scroll}.
94
protected JCommandButtonPanel buttonPanel;
97
* The maximum number of visible button rows.
99
protected int maxVisibleButtonRows;
102
* Scroll panel that hosts {@link #buttonPanel}.
104
protected JScrollPane scroll;
107
* Creates new a icon popup panel.
110
* The internal panel that hosts icon command buttons.
111
* @param maxButtonColumns
112
* The maximum number of button columns.
113
* @param maxVisibleButtonRows
114
* The maximum number of visible button rows.
116
public ScrollableCommandButtonPanel(JCommandButtonPanel iconPanel,
117
int maxButtonColumns, int maxVisibleButtonRows) {
118
this.buttonPanel = iconPanel;
119
this.buttonPanel.setMaxButtonColumns(maxButtonColumns);
120
this.maxVisibleButtonRows = maxVisibleButtonRows;
122
int maxButtonWidth = 0;
123
int maxButtonHeight = 0;
124
int groupCount = iconPanel.getGroupCount();
125
for (int i = 0; i < groupCount; i++) {
126
for (AbstractCommandButton button : iconPanel
127
.getGroupButtons(i)) {
128
maxButtonWidth = Math.max(maxButtonWidth, button
129
.getPreferredSize().width);
130
maxButtonHeight = Math.max(maxButtonHeight, button
131
.getPreferredSize().height);
135
updateMaxDimension();
137
this.scroll = new JScrollPane(this.buttonPanel,
138
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
139
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
140
this.scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
141
this.buttonPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
142
this.scroll.setOpaque(false);
143
this.scroll.getViewport().setOpaque(false);
144
this.setLayout(new IconPopupLayout());
146
this.add(this.scroll);
148
this.setBorder(new Border() {
150
public Insets getBorderInsets(Component c) {
151
return new Insets(0, 0, 1, 0);
155
public boolean isBorderOpaque() {
160
public void paintBorder(Component c, Graphics g, int x, int y,
161
int width, int height) {
162
g.setColor(FlamingoUtilities.getBorderColor());
163
g.drawLine(x, y + height - 1, x + width, y + height - 1);
169
* Updates the max dimension of this panel. This method is for internal
172
public void updateMaxDimension() {
173
if (this.buttonPanel == null)
175
this.buttonPanel.setPreferredSize(null);
176
Dimension prefIconPanelDim = this.buttonPanel.getPreferredSize();
177
// fix for issue 13 - respect the gaps and insets
178
BasicCommandButtonPanelUI panelUI = (BasicCommandButtonPanelUI) buttonPanel
180
int titlePanelCount = buttonPanel.isToShowGroupLabels() ? 1 : 0;
181
this.maxDimension = new Dimension(prefIconPanelDim.width, panelUI
182
.getPreferredHeight(this.maxVisibleButtonRows,
184
this.setPreferredSize(null);
188
* Layout manager for <code>this</code> popup gallery.
190
* @author Kirill Grouchnikov
192
protected class IconPopupLayout implements LayoutManager {
196
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
197
* java.awt.Component)
200
public void addLayoutComponent(String name, Component comp) {
207
* java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
210
public void removeLayoutComponent(Component comp) {
216
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
219
public void layoutContainer(Container parent) {
220
Insets insets = parent.getInsets();
221
int left = insets.left;
222
int right = insets.right;
223
int top = insets.top;
224
int bottom = insets.bottom;
225
scroll.setBounds(left, top, parent.getWidth() - left - right,
226
parent.getHeight() - top - bottom);
232
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
235
public Dimension minimumLayoutSize(Container parent) {
236
return this.preferredLayoutSize(parent);
243
* java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
246
public Dimension preferredLayoutSize(Container parent) {
247
Insets insets = parent.getInsets();
248
int left = insets.left;
249
int right = insets.right;
250
int top = insets.top;
251
int bottom = insets.bottom;
252
Dimension controlPanelDim = buttonPanel.getPreferredSize();
253
if (controlPanelDim == null)
254
controlPanelDim = new Dimension(0, 0);
255
int w = Math.min(controlPanelDim.width, maxDimension.width)
257
int h = Math.min(controlPanelDim.height, maxDimension.height)
259
if (h == (maxDimension.height + top + bottom)) {
260
int scrollBarWidth = UIManager.getInt("ScrollBar.width");
261
if (scrollBarWidth == 0) {
263
scrollBarWidth = new JScrollBar(JScrollBar.VERTICAL)
264
.getPreferredSize().width;
269
return new Dimension(w, h);
277
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
279
public static ComponentUI createUI(JComponent c) {
280
return new BasicCommandPopupMenuUI();
287
* org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installUI(javax.swing.
291
public void installUI(JComponent c) {
292
this.popupMenu = (JCommandPopupMenu) c;
293
super.installUI(this.popupMenu);
295
this.popupMenu.setLayout(this.createLayoutManager());
301
* @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installComponents()
304
protected void installComponents() {
305
super.installComponents();
310
protected void syncComponents() {
311
if (this.popupMenu.hasCommandButtonPanel()) {
312
this.commandButtonPanel = createScrollableButtonPanel();
313
this.popupMenu.add(this.commandButtonPanel);
316
final JPanel menuPanel = this.createMenuPanel();
317
menuPanel.setLayout(new LayoutManager() {
319
public void addLayoutComponent(String name, Component comp) {
323
public void removeLayoutComponent(Component comp) {
327
public Dimension preferredLayoutSize(Container parent) {
330
for (int i = 0; i < parent.getComponentCount(); i++) {
331
Dimension pref = parent.getComponent(i).getPreferredSize();
332
height += pref.height;
333
width = Math.max(width, pref.width);
336
Insets ins = parent.getInsets();
337
return new Dimension(width + ins.left + ins.right, height
338
+ ins.top + ins.bottom);
342
public Dimension minimumLayoutSize(Container parent) {
343
return preferredLayoutSize(parent);
347
public void layoutContainer(Container parent) {
348
Insets ins = parent.getInsets();
351
for (int i = 0; i < parent.getComponentCount(); i++) {
352
Component comp = parent.getComponent(i);
353
Dimension pref = comp.getPreferredSize();
354
comp.setBounds(ins.left, topY, parent.getWidth() - ins.left
355
- ins.right, pref.height);
361
this.popupMenu.putClientProperty(BasicCommandPopupMenuUI.FORCE_ICON,
363
java.util.List<Component> menuComponents = this.popupMenu
364
.getMenuComponents();
365
if (menuComponents != null) {
366
for (Component menuComponent : menuComponents) {
367
menuPanel.add(menuComponent);
370
boolean atLeastOneButtonHasIcon = false;
371
for (Component menuComponent : menuComponents) {
372
if (menuComponent instanceof JCommandMenuButton) {
373
JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent;
374
if (menuButton.getIcon() != null) {
375
atLeastOneButtonHasIcon = true;
378
if (menuComponent instanceof JCommandToggleMenuButton) {
379
atLeastOneButtonHasIcon = true;
383
this.popupMenu.putClientProperty(
384
BasicCommandPopupMenuUI.FORCE_ICON,
385
atLeastOneButtonHasIcon ? Boolean.TRUE : null);
386
for (Component menuComponent : menuComponents) {
387
if (menuComponent instanceof JCommandMenuButton) {
388
JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent;
389
menuButton.putClientProperty(
390
BasicCommandPopupMenuUI.FORCE_ICON,
391
atLeastOneButtonHasIcon ? Boolean.TRUE : null);
392
menuButton.setDisplayState(POPUP_MENU);
394
if (menuComponent instanceof JCommandToggleMenuButton) {
395
JCommandToggleMenuButton menuButton = (JCommandToggleMenuButton) menuComponent;
396
menuButton.putClientProperty(
397
BasicCommandPopupMenuUI.FORCE_ICON, Boolean.TRUE);
398
menuButton.setDisplayState(POPUP_MENU);
403
this.menuItemsPanel = new JScrollablePanel<JPanel>(menuPanel,
404
JScrollablePanel.ScrollType.VERTICALLY);
405
final LayoutManager scrollableLm = this.menuItemsPanel.getLayout();
406
this.menuItemsPanel.setLayout(new LayoutManager() {
408
public void addLayoutComponent(String name, Component comp) {
409
scrollableLm.addLayoutComponent(name, comp);
413
public void removeLayoutComponent(Component comp) {
414
scrollableLm.removeLayoutComponent(comp);
418
public Dimension preferredLayoutSize(Container parent) {
419
Dimension result = menuPanel.getPreferredSize();
420
int maxMenuButtonCount = popupMenu.getMaxVisibleMenuButtons();
421
if ((maxMenuButtonCount < 0)
422
|| (maxMenuButtonCount >= menuPanel.getComponentCount())) {
425
// the assumption is that all menu buttons have the
427
int singleHeight = menuPanel.getComponent(0).getPreferredSize().height;
429
for (int i = 0; i < menuPanel.getComponentCount(); i++) {
430
width = Math.max(width, menuPanel.getComponent(i)
431
.getPreferredSize().width);
433
Insets ins = parent.getInsets();
434
// add two for scroller buttons
435
return new Dimension(width + ins.left + ins.right, singleHeight
436
* (maxMenuButtonCount + 2) + ins.top + ins.bottom);
440
public Dimension minimumLayoutSize(Container parent) {
441
return this.preferredLayoutSize(parent);
445
public void layoutContainer(Container parent) {
446
scrollableLm.layoutContainer(parent);
449
this.popupMenu.add(this.menuItemsPanel);
452
protected ScrollableCommandButtonPanel createScrollableButtonPanel() {
453
return new ScrollableCommandButtonPanel(this.popupMenu
454
.getMainButtonPanel(), this.popupMenu.getMaxButtonColumns(),
455
this.popupMenu.getMaxVisibleButtonRows());
461
* @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallComponents()
464
protected void uninstallComponents() {
465
this.popupMenu.removeAll();
466
super.uninstallComponents();
470
protected void installListeners() {
471
super.installListeners();
473
this.popupMenuChangeListener = new ChangeListener() {
475
public void stateChanged(ChangeEvent e) {
476
popupMenu.removeAll();
480
this.popupMenu.addChangeListener(this.popupMenuChangeListener);
482
this.popupListener = new PopupPanelManager.PopupListener() {
484
public void popupShown(PopupEvent event) {
488
public void popupHidden(PopupEvent event) {
489
if (event.getSource() instanceof JColorSelectorPopupMenu) {
490
((JColorSelectorPopupMenu) event.getSource())
491
.getColorSelectorCallback().onColorRollover(null);
495
PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
501
* @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallListeners()
504
protected void uninstallListeners() {
505
this.popupMenu.removeChangeListener(this.popupMenuChangeListener);
506
this.popupMenuChangeListener = null;
508
PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
509
this.popupListener = null;
511
super.uninstallListeners();
514
protected JPanel createMenuPanel() {
515
return new MenuPanel();
518
protected LayoutManager createLayoutManager() {
519
return new PopupMenuLayoutManager();
522
protected class PopupMenuLayoutManager implements LayoutManager {
524
public void addLayoutComponent(String name, Component comp) {
528
public void removeLayoutComponent(Component comp) {
532
public Dimension minimumLayoutSize(Container parent) {
537
public Dimension preferredLayoutSize(Container parent) {
541
if (commandButtonPanel != null) {
542
width = commandButtonPanel.getPreferredSize().width;
543
height = commandButtonPanel.getPreferredSize().height;
545
Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel
547
: menuItemsPanel.getView().getPreferredSize();
548
width = Math.max(menuItemsPref.width, width);
549
height += menuItemsPref.height;
551
Insets ins = parent.getInsets();
552
return new Dimension(width + ins.left + ins.right, height + ins.top
557
public void layoutContainer(Container parent) {
558
Insets ins = parent.getInsets();
560
int bottomY = parent.getHeight() - ins.bottom;
561
Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel
563
: menuItemsPanel.getView().getPreferredSize();
565
int menuHeight = Math.min(menuItemsPref.height, parent.getHeight());
566
menuItemsPanel.setBounds(ins.left, bottomY - menuHeight,
567
parent.getWidth() - ins.left - ins.right,
569
menuItemsPanel.doLayout();
570
bottomY -= menuHeight;
572
if (commandButtonPanel != null) {
573
commandButtonPanel.setBounds(ins.left, ins.top, parent
575
- ins.left - ins.right, bottomY - ins.top);
576
commandButtonPanel.invalidate();
577
commandButtonPanel.validate();
578
commandButtonPanel.doLayout();
583
protected static class MenuPanel extends JPanel {
585
public void paintComponent(Graphics g) {
586
super.paintComponent(g);
587
JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities
588
.getAncestorOfClass(JCommandPopupMenu.class, this);
589
if (Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) {
590
this.paintIconGutterBackground(g);
591
this.paintIconGutterSeparator(g);
595
protected int getSeparatorX() {
596
JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities
597
.getAncestorOfClass(JCommandPopupMenu.class, this);
598
if (!Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) {
601
java.util.List<Component> menuComponents = menu.getMenuComponents();
602
if (menuComponents != null) {
603
for (Component menuComponent : menuComponents) {
604
if (menuComponent instanceof JCommandMenuButton
605
|| menuComponent instanceof JCommandToggleMenuButton) {
606
AbstractCommandButton button = (AbstractCommandButton) menuComponent;
607
if (!Boolean.TRUE.equals(button
608
.getClientProperty(FORCE_ICON))) {
611
boolean ltr = button.getComponentOrientation()
613
CommandButtonLayoutManager.CommandButtonLayoutInfo layoutInfo = button
614
.getUI().getLayoutInfo();
616
int iconRight = layoutInfo.iconRect.x
617
+ layoutInfo.iconRect.width;
618
int textLeft = button.getWidth();
619
for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) {
620
textLeft = Math.min(textLeft, tli.textRect.x);
622
return (iconRight + textLeft) / 2;
624
int iconLeft = layoutInfo.iconRect.x;
626
for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) {
627
textRight = Math.max(textRight, tli.textRect.x
628
+ tli.textRect.width);
630
return (iconLeft + textRight) / 2;
635
throw new IllegalStateException(
636
"Menu marked to show icons but no menu buttons in it");
639
protected void paintIconGutterSeparator(Graphics g) {
640
CellRendererPane buttonRendererPane = new CellRendererPane();
641
JSeparator rendererSeparator = new JSeparator(JSeparator.VERTICAL);
643
buttonRendererPane.setBounds(0, 0, this.getWidth(), this
645
int sepX = this.getSeparatorX();
646
if (this.getComponentOrientation().isLeftToRight()) {
647
buttonRendererPane.paintComponent(g, rendererSeparator, this,
648
sepX, 2, 2, this.getHeight() - 4, true);
650
buttonRendererPane.paintComponent(g, rendererSeparator, this,
651
sepX, 2, 2, this.getHeight() - 4, true);
655
protected void paintIconGutterBackground(Graphics g) {
656
Graphics2D g2d = (Graphics2D) g.create();
657
g2d.setComposite(AlphaComposite.SrcOver.derive(0.7f));
659
int sepX = this.getSeparatorX();
660
if (this.getComponentOrientation().isLeftToRight()) {
661
g2d.clipRect(0, 0, sepX + 2, this.getHeight());
662
AffineTransform at = AffineTransform.getTranslateInstance(0,
664
at.rotate(-Math.PI / 2);
667
FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0, 0,
668
this.getHeight(), 50), false, false, false);
670
g2d.clipRect(this.getWidth() - sepX, 0, sepX + 2, this
672
AffineTransform at = AffineTransform.getTranslateInstance(0,
674
at.rotate(-Math.PI / 2);
677
FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0,
678
sepX, this.getHeight(), this.getWidth() - sepX), false,
b'\\ No newline at end of file'