/* *------------------------------------------------------------------------------ * Copyright (C) 2014 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.ui; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.SwingUtilities; /** * Scrollable menu. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @since 5.0 */ public class ScrollablePopupMenu extends JPopupMenu { /** The default number of visible rows.*/ public static final int ROWS = 30; /** The number of visible rows.*/ protected int maximumVisibleRows; /** The vertical scroll bar.*/ private JScrollBar verticalBar; /** Initializes the listeners for the menu.*/ private void initListeners() { addMouseWheelListener(new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent event) { JScrollBar bar = getVerticalScrollBar(); int amount; if (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { amount = event.getUnitsToScroll()*bar.getUnitIncrement(); } else { if (event.getWheelRotation() < 0) { amount = -bar.getBlockIncrement(); } else { amount = bar.getBlockIncrement(); } } bar.setValue(bar.getValue()+amount); event.consume(); } }); } /** Creates a new instance.*/ public ScrollablePopupMenu() { this(ROWS, SwingUtilities.RIGHT); } /** * Creates a new instance. * * @param location The location of the scrollbar. */ public ScrollablePopupMenu(int location) { this(ROWS, location); } /** * Creates a new instance. * * @param visibleRows The number of visible rows. * @param location The location of the scrollbar. */ public ScrollablePopupMenu(int visibleRows, int location) { super.add(getVerticalScrollBar()); if (visibleRows <= 0) visibleRows = ROWS; maximumVisibleRows = visibleRows; setLayout(new ScrollPopupMenuLayout(location)); initListeners(); } /** * Initializes or recycles the vertical scroll bar. * * @return See above. */ protected JScrollBar getVerticalScrollBar() { if (verticalBar == null) { verticalBar = new JScrollBar(JScrollBar.VERTICAL); verticalBar.addAdjustmentListener(new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent arg0) { doLayout(); repaint(); } }); verticalBar.setVisible(false); } return verticalBar; } /** * Returns the maximum number of visible rows. * * @return See above */ public int getMaximumVisibleRows() { return maximumVisibleRows; } /** * Sets the maximum number of rows. * * @param rows The value to set. */ public void setMaximumVisibleRows(int rows) { this.maximumVisibleRows = rows; } /** * Overridden so the scrollbar is not removed. */ public void removeAll() { super.removeAll(); super.add(getVerticalScrollBar()); } /** * Overridden to the paint the components. * @see JPopupMenu#paintChildren(Graphics)s */ public void paintChildren(Graphics g) { super.paintChildren(g); Insets insets = getInsets(); g.clipRect(insets.left, insets.top, getWidth(), getHeight()-insets.top-insets.bottom); } /** * Overridden to make the scrollbar visible. * @see JPopupMenu#addImpl(Component, Object, int) */ protected void addImpl(Component comp, Object constraints, int index) { super.addImpl(comp, constraints, index); if (maximumVisibleRows < getComponentCount()-1) { getVerticalScrollBar().setVisible(true); } } /** * Overridden to size the components. * @see JPopupMenu#show(Component invoker, int x, int y) */ public void show(Component invoker, int x, int y) { JScrollBar scrollBar = getVerticalScrollBar(); if (scrollBar.isVisible()) { int extent = 0; int max = 0; int i = 0; int unit = -1; int width = 0; for (Component comp : getComponents()) { if (!(comp instanceof JScrollBar)) { Dimension preferredSize = comp.getPreferredSize(); width = Math.max(width, preferredSize.width); if (unit < 0) { unit = preferredSize.height; } if (i++ < maximumVisibleRows) { extent += preferredSize.height; } max += preferredSize.height; } } Insets insets = getInsets(); int widthMargin = insets.left+insets.right; int heightMargin = insets.top+insets.bottom; scrollBar.setUnitIncrement(unit); scrollBar.setBlockIncrement(extent); scrollBar.setValues(0, heightMargin+extent, 0, heightMargin+max); width += scrollBar.getPreferredSize().width+widthMargin; int height = heightMargin + extent; setPopupSize(new Dimension(width, height)); } super.show(invoker, x, y); } /** * Inner class used to layout the components. */ protected static class ScrollPopupMenuLayout implements LayoutManager { /** The location of the scrollbar.*/ private int location = SwingUtilities.RIGHT; /** * Creates a default instance. * The scrollbar is displayed on the right. */ ScrollPopupMenuLayout() { this(SwingUtilities.RIGHT); } /** * Creates a new instance. * * @param location The location of the scrollbar. */ ScrollPopupMenuLayout(int location) { switch (location) { case SwingUtilities.RIGHT: case SwingUtilities.LEFT: this.location = location; break; default: location = SwingUtilities.RIGHT; } } /** * Required by the {@link LayoutManager} I/F but no-operation in * our case. * @see LayoutManager#addLayoutComponent(String, Component) */ public void addLayoutComponent(String name, Component comp) {} /** * Required by the {@link LayoutManager} I/F but no-operation in * our case. * @see LayoutManager#removeLayoutComponent(Component) */ public void removeLayoutComponent(Component comp) {} /** * Sets the preferred size. * @see LayoutManager#preferredLayoutSize(Container) */ public Dimension preferredLayoutSize(Container parent) { int visibleAmount = Integer.MAX_VALUE; Dimension dim = new Dimension(); for (Component comp :parent.getComponents()){ if (comp.isVisible()) { if (comp instanceof JScrollBar){ JScrollBar scrollBar = (JScrollBar) comp; visibleAmount = scrollBar.getVisibleAmount(); } else { Dimension pref = comp.getPreferredSize(); dim.width = Math.max(dim.width, pref.width); dim.height += pref.height; } } } Insets insets = parent.getInsets(); dim.height = Math.min(dim.height+insets.top+insets.bottom, visibleAmount); return dim; } /** * Sets the minimum size. * @see LayoutManager#minimumLayoutSize(Container) */ public Dimension minimumLayoutSize(Container parent) { int visibleAmount = Integer.MAX_VALUE; Dimension dim = new Dimension(); for (Component comp : parent.getComponents()) { if (comp.isVisible()){ if (comp instanceof JScrollBar) { JScrollBar scrollBar = (JScrollBar) comp; visibleAmount = scrollBar.getVisibleAmount(); } else { Dimension min = comp.getMinimumSize(); dim.width = Math.max(dim.width, min.width); dim.height += min.height; } } } Insets insets = parent.getInsets(); dim.height = Math.min(dim.height+insets.top+insets.bottom, visibleAmount); return dim; } /** * Lays out the components. * @see LayoutManager#layoutContainer(Container) */ public void layoutContainer(Container parent) { Insets insets = parent.getInsets(); int width = parent.getWidth()-insets.left-insets.right; int height = parent.getHeight()-insets.top-insets.bottom; int x = insets.left; int y = insets.top; int position = 0; for (Component comp : parent.getComponents()) { if (comp instanceof JScrollBar && comp.isVisible()) { JScrollBar scrollBar = (JScrollBar) comp; Dimension dim = scrollBar.getPreferredSize(); switch (location) { case SwingUtilities.RIGHT: scrollBar.setBounds(x+width-dim.width, y, dim.width, height); break; case SwingUtilities.LEFT: scrollBar.setBounds(x+width-dim.width, y, dim.width, height); } width -= dim.width; position = scrollBar.getValue(); } } y -= position; for (Component comp : parent.getComponents()) { if (!(comp instanceof JScrollBar) && comp.isVisible()) { Dimension pref = comp.getPreferredSize(); comp.setBounds(x, y, width, pref.height); y += pref.height; } } } } }