/* * org.openmicroscopy.shoola.util.ui.ButtonMenu * *------------------------------------------------------------------------------ * Copyright (C) 2006 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; //Java imports import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.LayoutManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.AbstractButton; import javax.swing.DefaultButtonModel; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JPopupMenu; import javax.swing.border.Border; import javax.swing.plaf.basic.BasicArrowButton; //Third-party libraries //Application-internal dependencies /** * A drop-down button. * This button is actually composed by two sub-buttons: * <ul> * <li>An <i>arrow button</i> to trigger the display of a popup menu.</li> * <li>An <i>icon button</i> to forward mouse clicks on to an item in the * popup menu.</li> * </ul> * <p>Methods are provided to add/remove items to/from the popup menu. When an * {@link AbstractButton} is {@link #addToMenu(AbstractButton, boolean) added} * to the menu, it's possible to specify whether mouse clicks should be tracked. * The <i>icon button</i> remembers which was the last item, among those that * have been requested to be tracked, to be clicked and sets its tooltip to be * that item's text — that is, to the value returned by the item's * <code>getText</code> method. Any mouse click on the <i>icon button</i> will * then be forwarded to that menu item.</p> * <p>Note that the <i>icon button</i> doesn't necessarily track all menu items. * So it may happen that an item is clicked but not remembered. Moreover, if * the lastly remembered item happens to be * {@link #removeFromMenu(Component) removed} from the menu, the * <i>icon button</i> is reset. This means the button doesn't point to any * menu item and mouse clicks are just ignored.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public class ButtonMenu extends AbstractButton implements ActionListener { //TODO: provide an ad-hoc ButtonModel implementation. //We have two buttons but we want external classes to see this as a single //button. This is expecially true when we add the button to a JToolBar, //which modifies buttons appearence if its rollover property is set to true. /** Fixed width of the arrow button. */ private static final int ARROW_BUTTON_WIDTH = 18; /** Custom layout manager to display the arrow and icon buttons properly. */ private class DefaultLayoutManager implements LayoutManager { /** * Creates a new instance. * * @param parent The container the layout is for. */ public void layoutContainer(Container parent) { Dimension d = iconButton.getPreferredSize(); iconButton.setBounds(0, 0, d.width, d.height); arrowButton.setBounds(d.width, 0, ARROW_BUTTON_WIDTH, d.height); } /** * Implemented as specified by the I/F. * @see LayoutManager#minimumLayoutSize(Container) */ public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } /** * Implemented as specified by the I/F. * @see LayoutManager#preferredLayoutSize(Container) */ public Dimension preferredLayoutSize(Container parent) { return getPreferredSize(); } /** * Required by the I/F but no-op implementation in our case. * @see LayoutManager#removeLayoutComponent(Component) */ public void removeLayoutComponent(Component comp) {} /** * Required by the I/F but no-op implementation in our case. * @see LayoutManager#addLayoutComponent(String, Component) */ public void addLayoutComponent(String name, Component comp) {} } /** The arrow button to trigger the display of the drop-down menu. */ private JButton arrowButton; /** The icon button to trigger a click?? */ private JButton iconButton; /** The drop-down menu. */ private JPopupMenu menu; /** * Remembers the last tracked item that was clicked in the menu. * Not all items in the menu are necessarily tracked. In fact, items are * tracked only on request when they're * {@link #addToMenu(AbstractButton, boolean) added} to the menu. * If the lastly tracked item happens to be * {@link #removeFromMenu(Component) removed} from the menu, this field is * reset to <code>null</code>. */ private AbstractButton lastClickedItem; /** Builds and lays out the GUI. */ private void buildGUI() { setLayout(new DefaultLayoutManager()); setBorder(null); setMargin(null); add(iconButton); add(arrowButton); } /** Forwards the click to the {@link #lastClickedItem} if any. */ private void handleIconButtonClick() { if (lastClickedItem != null) lastClickedItem.doClick(); } /** Brings up the drop-down menu. */ private void handleArrowButtonClick() { //Avoid displaying an empty menu, kinda ugly. if (menu.getComponents().length == 0) return; Dimension d = iconButton.getPreferredSize(); menu.show(iconButton, 0, d.height); } /** * Sets {@link #lastClickedItem} to <code>src</code>. * * @param src The source of the event. */ private void handleMenuButtonClick(AbstractButton src) { lastClickedItem = src; iconButton.setToolTipText(UIUtilities.formatToolTipText(src.getText())); } /** * Overridden to make sure that no component other than the * {@link #iconButton} or {@link #arrowButton} can be added. * @see AbstractButton#addImpl(Component, Object, int) */ protected void addImpl(Component comp, Object constraints, int index) { if (comp == iconButton || comp == arrowButton) super.addImpl(comp, constraints, index); } //NOTE: we may want to override the remove methods from Container as well. /** * Creates a new instance. * * @param icon An icon for the <i>icon button</i>. Mustn't be * <code>null</code>. */ public ButtonMenu(Icon icon) { if (icon == null) throw new NullPointerException("No icon."); iconButton = new JButton(icon); iconButton.addActionListener(this); arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH); arrowButton.addActionListener(this); menu = new JPopupMenu(); setModel(new DefaultButtonModel()); //TODO: replace with ad-hoc model. buildGUI(); } /** * Adds the specified component to the drop-down menu. * * @param item The component to add. Normally a <code>JMenuItem</code> or * a <code>JSeparator</code>. */ public void addToMenu(Component item) { if (item != null) menu.add(item); } /** * Adds the specified button to the drop-down menu. * * @param btn The button to add. Normally a <code>JMenuItem</code>. * @param trackClick Specifies whether mouse clicks should be tracked. */ public void addToMenu(AbstractButton btn, boolean trackClick) { if (btn != null) menu.add(btn); if (trackClick) btn.addActionListener(this); } /** Appends a new separator at the end of the drop-down menu. */ public void addSeparator() { menu.addSeparator(); } /** * Removes the specified component from the drop-down menu. * * @param item The component to remove. */ public void removeFromMenu(Component item) { if (item == null) return; menu.remove(item); if (item instanceof AbstractButton) { ((AbstractButton) item).removeActionListener(this); //NOTE: addToMenu(AbstrctButton, boolean) might have registred with //this button. If so, we have to unsubscribe. If not, unsubscribing //will be harmless, but will avoid the pain of tracking the buttons //we actually registred with. if (item == lastClickedItem) { lastClickedItem = null; iconButton.setToolTipText(""); } } } /** Removes all components from the drop-down menu. */ public void clearMenu() { Component[] items = menu.getComponents(); for (int i = 0; i < items.length; ++i) removeFromMenu(items[i]); } /** * Demultiplexes the event to the right handler. * @see ActionListener#actionPerformed(ActionEvent) */ public void actionPerformed(ActionEvent ae) { Object src = ae.getSource(); if (src == iconButton) handleIconButtonClick(); else if (src == arrowButton) handleArrowButtonClick(); else handleMenuButtonClick((AbstractButton) src); //NOTE: we only register with AbstractButtons, so this is safe. } /** * Overridden to return the right size. * @see AbstractButton#getPreferredSize() */ public Dimension getPreferredSize() { Dimension d = iconButton.getPreferredSize(); return new Dimension(d.width+ARROW_BUTTON_WIDTH, d.height); } /** * Overridden to return the preferred size. * @see AbstractButton#getMaximumSize() */ public Dimension getMaximumSize() { return getPreferredSize(); } /** * Overridden to return the preferred size. * @see AbstractButton#getMinimumSize() */ public Dimension getMinimumSize() { return getPreferredSize(); } /** * Overridden to trigger the display of the drop-down menu. * @see AbstractButton#doClick() */ public void doClick() { arrowButton.doClick(); } /** * Overridden to trigger the display of the drop-down menu. * @see AbstractButton#doClick(int) */ public void doClick(int pressTime) { arrowButton.doClick(pressTime); } /** * Overridden to set the border of the two sub-buttons. * @see AbstractButton#setBorder(Border) */ public void setBorder(Border b) { iconButton.setBorder(b); arrowButton.setBorder(b); } /** * Overridden to set the rollover property of the two sub-buttons. * @see AbstractButton#setRolloverEnabled(boolean) */ public void setRolloverEnabled(boolean enable) { super.setRolloverEnabled(enable); iconButton.setRolloverEnabled(enable); arrowButton.setRolloverEnabled(enable); } }