package org.freehep.swing; import java.awt.Component; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Iterator; import java.util.LinkedList; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.Timer; import org.freehep.swing.layout.StackedLayout; /** * This defines a swing-like element which provides a popup toolbar. This is * intended to provide a submenu-type functionality to toolbars. The * component has a single selected element. If the visible button is clicked, * the action associated with this button is run. However, if the button is * pressed and held, a popup toolbar with all options is shown. The user then * may select the desired option which will then become the selected button. * The action associated with this is also run. * * @author Charles Loomis */ public class JSubToolBar extends JLayeredPane { JPopupMenu popup; JToolBar subToolBar; JComponent selectedComponent = null; MousePanel interceptor; LinkedList buttonList; public JSubToolBar() { // Make the button list. buttonList = new LinkedList(); // Set the layout manager for this component. setLayout(new StackedLayout()); // Add the panel which will intercept mouse events. interceptor = new MousePanel(); add(interceptor, DRAG_LAYER); // Make the toolbar which will be used to hold actions. subToolBar = new JToolBar(); subToolBar.setFloatable(false); subToolBar.setBorderPainted(false); subToolBar.setMargin(new Insets(1,1,1,1)); // Make the popup which will contain the sub-toolbar. popup = new JPopupMenu(); popup.add(subToolBar); popup.setBorderPainted(true); // Set a small default size. This will be updated with each // additional action which is added. Dimension d = new Dimension(10,10); setPreferredSize(d); } /** * This is used by subclasses to make a button from an action. */ protected JButton makeButtonFromAction(Action action) { // FIXME: there may be a better solution to this! JButton button = new JButton() { public Dimension getPreferredSize() { if (getIcon()!=null) { return new Dimension(24,24); } else { Dimension d = super.getPreferredSize(); d.height = 24; return d; } } public String getText() { return (getIcon()==null) ? super.getText() : null; } }; button.setAction(action); return button; } /** * Add an action to this component. Currently, only actions are supported * and thus this should be the only add method used. A button is made * from the action which is then added to the sub-toolbar. */ public void add(Action action) { JButton button = makeButtonFromAction(action); button.addActionListener(interceptor); subToolBar.add(button); // Keep track of all buttons which are added. buttonList.add(button); // Increase the default size if necessary. updatePreferredSize(button.getPreferredSize()); if (selectedComponent==null) setSelectedComponent(button); } /** * Set the delay (in ms) between a mouse press and the popup toolbar being * activated. This must be sufficiently long that clicks on the component * are recognized as such. */ public void setDelay(int msDelay) { interceptor.setDelay(msDelay); } /** * Get the delay (in ms) between a mouse press and the popup toolbar being * activated. */ public int getDelay() { return interceptor.getDelay(); } /** * Set which component is selected. This component must already have been * added to the toolbar. */ public void setSelectedComponent(JComponent component) { if (buttonList.contains(component)) { // Put the selected component into the visible area. if (selectedComponent!=null) remove(selectedComponent); selectedComponent = component; add(component,DEFAULT_LAYER); // Try to give this component the keyboard focus. component.requestFocus(); // Do the validation after the component has received the focus, // so that the component is correctly drawn. (There is a bug // here; the focus works correctly, but not always the // appearance.) validate(); // Re-add all of the buttons to the toolbar. This is necessary // because a component can be in only one visible container. // Therefore, the button will be lost if we don't put it back into // the toolbar. subToolBar.removeAll(); Iterator i = buttonList.iterator(); while (i.hasNext()) { Component c = (Component) i.next(); if (c!=selectedComponent) subToolBar.add(c); } } } /** * Update the preferred, minimum, and maximum sizes of this component. * This is the smallest rectangle which will contain all of the child * components. */ protected void updatePreferredSize(Dimension dim) { Dimension currentDim = getPreferredSize(); if (dim.width>currentDim.width) currentDim.width = dim.width; if (dim.height>currentDim.height) currentDim.height = dim.height; setPreferredSize(currentDim); setMaximumSize(currentDim); setMinimumSize(currentDim); } /** * Update the orientation of this sub-toolbar. The toolbar will be in the * perpendicular direction to the parent toolbar. */ private void updateOrientation() { // Look to see if we are inside of another toolbar. JToolBar parent = (JToolBar) SwingUtilities.getAncestorOfClass(JToolBar.class,this); int orientation = JToolBar.HORIZONTAL; // If so, change the orientation to be perpendicular to the // parent's orientation. if (parent!=null) { int parentOrientation = parent.getOrientation(); if (parentOrientation==JToolBar.HORIZONTAL) { orientation = JToolBar.VERTICAL; } } // Now actually update the orientation. subToolBar.setOrientation(orientation); } /** * This panel is used to intercept mouse events from the visible button in * the sub-toolbar. */ private class MousePanel extends JPanel implements MouseListener, ActionListener { private Timer timer; private int delay = 300; public MousePanel() { super(); setOpaque(false); timer = new Timer(delay,this); timer.setRepeats(false); addMouseListener(this); } /** * Set the delay (in ms) between a mouse press and the popup toolbar * being activated. This must be sufficiently long that clicks on the * component are recognized as such. */ public void setDelay(int msDelay) { delay = msDelay; } /** * Get the delay (in ms) between a mouse press and the popup toolbar * being activated. */ public int getDelay() { return delay; } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) { timer.stop(); } public void mousePressed(MouseEvent e) { timer.start(); } public void mouseReleased(MouseEvent e) { timer.stop(); } public void mouseClicked(MouseEvent e) { timer.stop(); if (!popup.isVisible() && selectedComponent!=null && selectedComponent instanceof AbstractButton) { ((AbstractButton)selectedComponent).doClick(); } } public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source instanceof Timer) { // This is a timer event, so the user must have pressed and // held the visible button. Bring up the popup toolbar. updateOrientation(); int orientation = subToolBar.getOrientation(); int x0 = 0; int y0 = 0; if (orientation==JToolBar.HORIZONTAL) { x0 = getWidth(); } else { y0 = getHeight(); } popup.show(this,x0,y0); // Check to see that the popup menu is visible on the // screen. If it isn't, then move the popup to a more // convenient position. int w = popup.getWidth(); int h = popup.getHeight(); Point point = new Point(x0+w,y0+h); SwingUtilities.convertPointToScreen(point,this); // Get information about the screen. GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gd.getDefaultConfiguration(); Rectangle screenBounds = gc.getBounds(); // If the popup isn't on the screen, then change the // direction. Unfortunately, there is no way to get the // correct width and height of the popup until it is // displayed. So, if it is incorrect, redisplay the popup. // The redisplay will also change the popup to a heavy-weight // component if necessary. if (!screenBounds.contains(point)) { if (subToolBar.getOrientation()==JToolBar.HORIZONTAL) { // Switch to the left direction. int x = -w; int y = 0; popup.setVisible(false); popup.show(this,x,y); } else { // Switch to the upward direction. int x = 0; int y = -h; popup.setVisible(false); popup.show(this,x,y); } } } else if (source instanceof JComponent) { // We have gotten an event from one of the buttons. Just // close the popup and set the selected component to the // chosen one. popup.setVisible(false); setSelectedComponent((JComponent) source); } } } }