/* * 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.internal.ui.common.popup; import java.awt.*; import java.awt.geom.AffineTransform; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ComponentUI; import org.pushingpixels.flamingo.api.common.*; import org.pushingpixels.flamingo.api.common.popup.*; import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent; import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI; import org.pushingpixels.flamingo.internal.ui.common.CommandButtonLayoutManagerMedium; import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities; public class BasicCommandPopupMenuUI extends BasicPopupPanelUI { /** * The associated popup menu */ protected JCommandPopupMenu popupMenu; protected ChangeListener popupMenuChangeListener; protected PopupPanelManager.PopupListener popupListener; protected ScrollableCommandButtonPanel commandButtonPanel; protected JScrollablePanel<JPanel> menuItemsPanel; public static final String FORCE_ICON = "flamingo.internal.commandButtonLayoutManagerMedium.forceIcon"; protected static final CommandButtonDisplayState POPUP_MENU = new CommandButtonDisplayState( "Popup menu", 16) { @Override public CommandButtonLayoutManager createLayoutManager( AbstractCommandButton commandButton) { return new CommandButtonLayoutManagerMedium() { @Override protected float getIconTextGapFactor() { return 2.0f; }; }; } }; /** * Popup panel that hosts groups of icons. * * @author Kirill Grouchnikov */ protected static class ScrollableCommandButtonPanel extends JComponent { /** * Maximum dimension of <code>this</code> popup gallery. */ protected Dimension maxDimension; /** * The internal panel that hosts the icon command buttons. Is hosted in * the {@link #scroll}. */ protected JCommandButtonPanel buttonPanel; /** * The maximum number of visible button rows. */ protected int maxVisibleButtonRows; /** * Scroll panel that hosts {@link #buttonPanel}. */ protected JScrollPane scroll; /** * Creates new a icon popup panel. * * @param iconPanel * The internal panel that hosts icon command buttons. * @param maxButtonColumns * The maximum number of button columns. * @param maxVisibleButtonRows * The maximum number of visible button rows. */ public ScrollableCommandButtonPanel(JCommandButtonPanel iconPanel, int maxButtonColumns, int maxVisibleButtonRows) { this.buttonPanel = iconPanel; this.buttonPanel.setMaxButtonColumns(maxButtonColumns); this.maxVisibleButtonRows = maxVisibleButtonRows; int maxButtonWidth = 0; int maxButtonHeight = 0; int groupCount = iconPanel.getGroupCount(); for (int i = 0; i < groupCount; i++) { for (AbstractCommandButton button : iconPanel .getGroupButtons(i)) { maxButtonWidth = Math.max(maxButtonWidth, button .getPreferredSize().width); maxButtonHeight = Math.max(maxButtonHeight, button .getPreferredSize().height); } } updateMaxDimension(); this.scroll = new JScrollPane(this.buttonPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); this.scroll.setBorder(new EmptyBorder(0, 0, 0, 0)); this.buttonPanel.setBorder(new EmptyBorder(0, 0, 0, 0)); this.scroll.setOpaque(false); this.scroll.getViewport().setOpaque(false); this.setLayout(new IconPopupLayout()); this.add(this.scroll); this.setBorder(new Border() { @Override public Insets getBorderInsets(Component c) { return new Insets(0, 0, 1, 0); } @Override public boolean isBorderOpaque() { return true; } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { g.setColor(FlamingoUtilities.getBorderColor()); g.drawLine(x, y + height - 1, x + width, y + height - 1); } }); } /** * Updates the max dimension of this panel. This method is for internal * use only. */ public void updateMaxDimension() { if (this.buttonPanel == null) return; this.buttonPanel.setPreferredSize(null); Dimension prefIconPanelDim = this.buttonPanel.getPreferredSize(); // fix for issue 13 - respect the gaps and insets BasicCommandButtonPanelUI panelUI = (BasicCommandButtonPanelUI) buttonPanel .getUI(); int titlePanelCount = buttonPanel.isToShowGroupLabels() ? 1 : 0; this.maxDimension = new Dimension(prefIconPanelDim.width, panelUI .getPreferredHeight(this.maxVisibleButtonRows, titlePanelCount)); this.setPreferredSize(null); } /** * Layout manager for <code>this</code> popup gallery. * * @author Kirill Grouchnikov */ protected class IconPopupLayout implements LayoutManager { /* * (non-Javadoc) * * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, * java.awt.Component) */ @Override public void addLayoutComponent(String name, Component comp) { } /* * (non-Javadoc) * * @see * java.awt.LayoutManager#removeLayoutComponent(java.awt.Component) */ @Override public void removeLayoutComponent(Component comp) { } /* * (non-Javadoc) * * @see java.awt.LayoutManager#layoutContainer(java.awt.Container) */ @Override public void layoutContainer(Container parent) { Insets insets = parent.getInsets(); int left = insets.left; int right = insets.right; int top = insets.top; int bottom = insets.bottom; scroll.setBounds(left, top, parent.getWidth() - left - right, parent.getHeight() - top - bottom); } /* * (non-Javadoc) * * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container) */ @Override public Dimension minimumLayoutSize(Container parent) { return this.preferredLayoutSize(parent); } /* * (non-Javadoc) * * @see * java.awt.LayoutManager#preferredLayoutSize(java.awt.Container) */ @Override public Dimension preferredLayoutSize(Container parent) { Insets insets = parent.getInsets(); int left = insets.left; int right = insets.right; int top = insets.top; int bottom = insets.bottom; Dimension controlPanelDim = buttonPanel.getPreferredSize(); if (controlPanelDim == null) controlPanelDim = new Dimension(0, 0); int w = Math.min(controlPanelDim.width, maxDimension.width) + left + right; int h = Math.min(controlPanelDim.height, maxDimension.height) + top + bottom; if (h == (maxDimension.height + top + bottom)) { int scrollBarWidth = UIManager.getInt("ScrollBar.width"); if (scrollBarWidth == 0) { // Nimbus scrollBarWidth = new JScrollBar(JScrollBar.VERTICAL) .getPreferredSize().width; } w += scrollBarWidth; // h += 5; } return new Dimension(w, h); } } } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent) */ public static ComponentUI createUI(JComponent c) { return new BasicCommandPopupMenuUI(); } /* * (non-Javadoc) * * @see * org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installUI(javax.swing. * JComponent) */ @Override public void installUI(JComponent c) { this.popupMenu = (JCommandPopupMenu) c; super.installUI(this.popupMenu); this.popupMenu.setLayout(this.createLayoutManager()); } /* * (non-Javadoc) * * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installComponents() */ @Override protected void installComponents() { super.installComponents(); syncComponents(); } protected void syncComponents() { if (this.popupMenu.hasCommandButtonPanel()) { this.commandButtonPanel = createScrollableButtonPanel(); this.popupMenu.add(this.commandButtonPanel); } final JPanel menuPanel = this.createMenuPanel(); menuPanel.setLayout(new LayoutManager() { @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } @Override public Dimension preferredLayoutSize(Container parent) { int height = 0; int width = 0; for (int i = 0; i < parent.getComponentCount(); i++) { Dimension pref = parent.getComponent(i).getPreferredSize(); height += pref.height; width = Math.max(width, pref.width); } Insets ins = parent.getInsets(); return new Dimension(width + ins.left + ins.right, height + ins.top + ins.bottom); } @Override public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } @Override public void layoutContainer(Container parent) { Insets ins = parent.getInsets(); int topY = ins.top; for (int i = 0; i < parent.getComponentCount(); i++) { Component comp = parent.getComponent(i); Dimension pref = comp.getPreferredSize(); comp.setBounds(ins.left, topY, parent.getWidth() - ins.left - ins.right, pref.height); topY += pref.height; } } }); this.popupMenu.putClientProperty(BasicCommandPopupMenuUI.FORCE_ICON, null); java.util.List<Component> menuComponents = this.popupMenu .getMenuComponents(); if (menuComponents != null) { for (Component menuComponent : menuComponents) { menuPanel.add(menuComponent); } boolean atLeastOneButtonHasIcon = false; for (Component menuComponent : menuComponents) { if (menuComponent instanceof JCommandMenuButton) { JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent; if (menuButton.getIcon() != null) { atLeastOneButtonHasIcon = true; } } if (menuComponent instanceof JCommandToggleMenuButton) { atLeastOneButtonHasIcon = true; } } this.popupMenu.putClientProperty( BasicCommandPopupMenuUI.FORCE_ICON, atLeastOneButtonHasIcon ? Boolean.TRUE : null); for (Component menuComponent : menuComponents) { if (menuComponent instanceof JCommandMenuButton) { JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent; menuButton.putClientProperty( BasicCommandPopupMenuUI.FORCE_ICON, atLeastOneButtonHasIcon ? Boolean.TRUE : null); menuButton.setDisplayState(POPUP_MENU); } if (menuComponent instanceof JCommandToggleMenuButton) { JCommandToggleMenuButton menuButton = (JCommandToggleMenuButton) menuComponent; menuButton.putClientProperty( BasicCommandPopupMenuUI.FORCE_ICON, Boolean.TRUE); menuButton.setDisplayState(POPUP_MENU); } } } this.menuItemsPanel = new JScrollablePanel<JPanel>(menuPanel, JScrollablePanel.ScrollType.VERTICALLY); final LayoutManager scrollableLm = this.menuItemsPanel.getLayout(); this.menuItemsPanel.setLayout(new LayoutManager() { @Override public void addLayoutComponent(String name, Component comp) { scrollableLm.addLayoutComponent(name, comp); } @Override public void removeLayoutComponent(Component comp) { scrollableLm.removeLayoutComponent(comp); } @Override public Dimension preferredLayoutSize(Container parent) { Dimension result = menuPanel.getPreferredSize(); int maxMenuButtonCount = popupMenu.getMaxVisibleMenuButtons(); if ((maxMenuButtonCount < 0) || (maxMenuButtonCount >= menuPanel.getComponentCount())) { return result; } // the assumption is that all menu buttons have the // same height. int singleHeight = menuPanel.getComponent(0).getPreferredSize().height; int width = 0; for (int i = 0; i < menuPanel.getComponentCount(); i++) { width = Math.max(width, menuPanel.getComponent(i) .getPreferredSize().width); } Insets ins = parent.getInsets(); // add two for scroller buttons return new Dimension(width + ins.left + ins.right, singleHeight * (maxMenuButtonCount + 2) + ins.top + ins.bottom); } @Override public Dimension minimumLayoutSize(Container parent) { return this.preferredLayoutSize(parent); } @Override public void layoutContainer(Container parent) { scrollableLm.layoutContainer(parent); } }); this.popupMenu.add(this.menuItemsPanel); } protected ScrollableCommandButtonPanel createScrollableButtonPanel() { return new ScrollableCommandButtonPanel(this.popupMenu .getMainButtonPanel(), this.popupMenu.getMaxButtonColumns(), this.popupMenu.getMaxVisibleButtonRows()); } /* * (non-Javadoc) * * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallComponents() */ @Override protected void uninstallComponents() { this.popupMenu.removeAll(); super.uninstallComponents(); } @Override protected void installListeners() { super.installListeners(); this.popupMenuChangeListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { popupMenu.removeAll(); syncComponents(); } }; this.popupMenu.addChangeListener(this.popupMenuChangeListener); this.popupListener = new PopupPanelManager.PopupListener() { @Override public void popupShown(PopupEvent event) { } @Override public void popupHidden(PopupEvent event) { if (event.getSource() instanceof JColorSelectorPopupMenu) { ((JColorSelectorPopupMenu) event.getSource()) .getColorSelectorCallback().onColorRollover(null); } } }; PopupPanelManager.defaultManager().addPopupListener(this.popupListener); } /* * (non-Javadoc) * * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallListeners() */ @Override protected void uninstallListeners() { this.popupMenu.removeChangeListener(this.popupMenuChangeListener); this.popupMenuChangeListener = null; PopupPanelManager.defaultManager().addPopupListener(this.popupListener); this.popupListener = null; super.uninstallListeners(); } protected JPanel createMenuPanel() { return new MenuPanel(); } protected LayoutManager createLayoutManager() { return new PopupMenuLayoutManager(); } protected class PopupMenuLayoutManager implements LayoutManager { @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } @Override public Dimension minimumLayoutSize(Container parent) { return null; } @Override public Dimension preferredLayoutSize(Container parent) { int height = 0; int width = 0; if (commandButtonPanel != null) { width = commandButtonPanel.getPreferredSize().width; height = commandButtonPanel.getPreferredSize().height; } Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel .getPreferredSize() : menuItemsPanel.getView().getPreferredSize(); width = Math.max(menuItemsPref.width, width); height += menuItemsPref.height; Insets ins = parent.getInsets(); return new Dimension(width + ins.left + ins.right, height + ins.top + ins.bottom); } @Override public void layoutContainer(Container parent) { Insets ins = parent.getInsets(); int bottomY = parent.getHeight() - ins.bottom; Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel .getPreferredSize() : menuItemsPanel.getView().getPreferredSize(); menuItemsPanel.setBounds(ins.left, bottomY - menuItemsPref.height, parent.getWidth() - ins.left - ins.right, menuItemsPref.height); menuItemsPanel.doLayout(); bottomY -= menuItemsPref.height; if (commandButtonPanel != null) { commandButtonPanel.setBounds(ins.left, ins.top, parent .getWidth() - ins.left - ins.right, bottomY - ins.top); commandButtonPanel.invalidate(); commandButtonPanel.validate(); commandButtonPanel.doLayout(); } } } protected static class MenuPanel extends JPanel { @Override public void paintComponent(Graphics g) { super.paintComponent(g); JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities .getAncestorOfClass(JCommandPopupMenu.class, this); if (Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) { this.paintIconGutterBackground(g); this.paintIconGutterSeparator(g); } } protected int getSeparatorX() { JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities .getAncestorOfClass(JCommandPopupMenu.class, this); if (!Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) { return -1; } java.util.List<Component> menuComponents = menu.getMenuComponents(); if (menuComponents != null) { for (Component menuComponent : menuComponents) { if (menuComponent instanceof JCommandMenuButton || menuComponent instanceof JCommandToggleMenuButton) { AbstractCommandButton button = (AbstractCommandButton) menuComponent; if (!Boolean.TRUE.equals(button .getClientProperty(FORCE_ICON))) { continue; } boolean ltr = button.getComponentOrientation() .isLeftToRight(); CommandButtonLayoutManager.CommandButtonLayoutInfo layoutInfo = button .getUI().getLayoutInfo(); if (ltr) { int iconRight = layoutInfo.iconRect.x + layoutInfo.iconRect.width; int textLeft = button.getWidth(); for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) { textLeft = Math.min(textLeft, tli.textRect.x); } return (iconRight + textLeft) / 2; } else { int iconLeft = layoutInfo.iconRect.x; int textRight = 0; for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) { textRight = Math.max(textRight, tli.textRect.x + tli.textRect.width); } return (iconLeft + textRight) / 2; } } } } throw new IllegalStateException( "Menu marked to show icons but no menu buttons in it"); } protected void paintIconGutterSeparator(Graphics g) { CellRendererPane buttonRendererPane = new CellRendererPane(); JSeparator rendererSeparator = new JSeparator(JSeparator.VERTICAL); buttonRendererPane.setBounds(0, 0, this.getWidth(), this .getHeight()); int sepX = this.getSeparatorX(); if (this.getComponentOrientation().isLeftToRight()) { buttonRendererPane.paintComponent(g, rendererSeparator, this, sepX, 2, 2, this.getHeight() - 4, true); } else { buttonRendererPane.paintComponent(g, rendererSeparator, this, sepX, 2, 2, this.getHeight() - 4, true); } } protected void paintIconGutterBackground(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setComposite(AlphaComposite.SrcOver.derive(0.7f)); int sepX = this.getSeparatorX(); if (this.getComponentOrientation().isLeftToRight()) { g2d.clipRect(0, 0, sepX + 2, this.getHeight()); AffineTransform at = AffineTransform.getTranslateInstance(0, this.getHeight()); at.rotate(-Math.PI / 2); g2d.transform(at); FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0, 0, this.getHeight(), 50), false, false, false); } else { g2d.clipRect(this.getWidth() - sepX, 0, sepX + 2, this .getHeight()); AffineTransform at = AffineTransform.getTranslateInstance(0, this.getHeight()); at.rotate(-Math.PI / 2); g2d.transform(at); FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0, sepX, this.getHeight(), this.getWidth() - sepX), false, false, false); } g2d.dispose(); } } }