/* * @(#)JPopupButton.java * * Copyright (c) 2006-2008 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.gui; import javax.swing.event.PopupMenuEvent; import org.jhotdraw.gui.plaf.palette.PaletteMenuItemUI; import java.awt.*; import java.beans.*; import javax.swing.*; import java.awt.event.*; import javax.swing.event.PopupMenuListener; /** * JPopupButton provides a popup menu. * * @author Werner Randelshofer * @version $Id$ */ public class JPopupButton extends javax.swing.JButton { private static final long serialVersionUID = 1L; public static final String CLOSE_AUTOMATICALLY_PROPERTY = "closeAutomatically"; public static final String COLUMN_COUNT_PROPERTY = "columnCount"; public static final String ITEM_FONT_PROPERTY = "itemFont"; private JPopupMenu popupMenu; private int columnCount = 1; private Action action; private Rectangle actionArea; private Font itemFont; public static final Font ITEM_FONT = new Font("Dialog", Font.PLAIN, 10); private int popupAnchor = SwingConstants.SOUTH_WEST; /** The time when the popup became invisible. */ private long popupBecameInvisible; /** Whether the popup menu closes automatically, when another popup menu * is opened. */ private boolean isCloseAutomatically; private class Handler implements PropertyChangeListener, PopupMenuListener, AWTEventListener { // Property change listener @Override public void propertyChange(PropertyChangeEvent evt) { if ("enabled".equals(evt.getPropertyName())) { setEnabled(((Boolean) evt.getNewValue()).booleanValue()); } else { repaint(); } } // Popup menu listener @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { popupBecameInvisible = System.currentTimeMillis(); } @Override public void popupMenuCanceled(PopupMenuEvent e) { } // AWT event listener @Override public void eventDispatched(AWTEvent ev) { if (!(ev instanceof MouseEvent) || !(ev.getSource() instanceof Component)) { // We are interested in MouseEvents only return; } Component src = (Component) ev.getSource(); // Close popup only on mouse press on a component which has // the same window ancestor as our popup, but is not in the // popup layer of the window. if (ev.getID() == MouseEvent.MOUSE_PRESSED) { if (SwingUtilities.getWindowAncestor(src) == SwingUtilities.getWindowAncestor(JPopupButton.this)) { JLayeredPane srcLP = (JLayeredPane) SwingUtilities.getAncestorOfClass(JLayeredPane.class, src); Component srcLPChild = src; while (srcLPChild.getParent() != srcLP) { srcLPChild = srcLPChild.getParent(); } if (srcLP.getLayer(srcLPChild) < JLayeredPane.POPUP_LAYER) { popupMenu.setVisible(false); } } } else { } } }; private Handler handler = new Handler(); /** Creates new form JToolBarMenu */ public JPopupButton() { initComponents(); setFocusable(false); itemFont = ITEM_FONT; } /** Sets the font used for popup menu items. */ public void setItemFont(Font newValue) { Font oldValue = itemFont; itemFont = newValue; if (popupMenu != null) { updateItemFont(popupMenu); } firePropertyChange(ITEM_FONT_PROPERTY, oldValue, newValue); } /** Updates the font of the popup menu. */ private void updateItemFont(MenuElement menu) { menu.getComponent().setFont(itemFont); for (MenuElement child : menu.getSubElements()) { updateItemFont(child); } } /** Sets an action which is invoked when the user clicks on the * specified click area. * * @param action An action. * @param actionClickArea The click area. */ public void setAction(Action action, Rectangle actionClickArea) { if (this.action != null) { this.action.removePropertyChangeListener(handler); } this.action = action; this.actionArea = actionClickArea; if (action != null) { action.addPropertyChangeListener(handler); } } /** Returns the number of columns of the popup menu. */ public int getColumnCount() { return columnCount; } /** Sets the number of columns of the popup menu. */ public void setColumnCount(int newValue, boolean isVertical) { int oldValue = columnCount; columnCount = newValue; getPopupMenu().setLayout(new VerticalGridLayout(0, getColumnCount(), isVertical)); firePropertyChange(COLUMN_COUNT_PROPERTY, oldValue, newValue); } /** Adds an {@code Action} to the popup menu. * <p> * The {@code Action} is represented by a {@code JMenuItem}. */ public AbstractButton add(Action action) { JMenuItem item = getPopupMenu().add(action); if (getColumnCount() > 1) { item.setUI(new PaletteMenuItemUI()); } item.setFont(itemFont); return item; } /** Adds a sub-menu to the popup menu. */ public void add(JMenu submenu) { updateItemFont(submenu); } /** Adds a {@code JComponent} to the popup menu. * <p> * If the component can open popup menus of its own, for example * if contains combo boxes, then you should set {@link JComponentPopup} * as the popup menu before adding the component to this popup button. * This will prevent the popup menu from closing automatically. * <p> * Example: * <pre> * JPopupButton pb=new JPopupButton(); * pb.setPopupMenu(new JComponentPopup()); * pb.add(a component); * </pre> */ public void add(JComponent submenu) { getPopupMenu().add(submenu); } /** Adds a menu item to the popup menu. */ public void add(JMenuItem item) { getPopupMenu().add(item); item.setFont(itemFont); } /** Adds a separator to the popup menu. */ public void addSeparator() { getPopupMenu().addSeparator(); } /** Removes all items from the popup menu. */ @Override public void removeAll() { getPopupMenu().removeAll(); } public void setPopupMenu(JPopupMenu popupMenu) { if (this.popupMenu != null) { popupMenu.removePopupMenuListener(handler); } this.popupMenu = popupMenu; if (this.popupMenu != null) { popupMenu.addPopupMenuListener(handler); } } public JPopupMenu getPopupMenu() { if (popupMenu == null) { popupMenu = new JPopupMenu(); popupMenu.setLayout(new VerticalGridLayout(0, getColumnCount())); popupMenu.addPopupMenuListener(handler); popupMenu.setLightWeightPopupEnabled(false); } return popupMenu; } public void setPopupAlpha(float newValue) { float oldValue = getPopupAlpha(); getPopupMenu().putClientProperty("Quaqua.PopupMenu.windowAlpha", newValue); firePropertyChange("popupAlpha", oldValue, newValue); } public float getPopupAlpha() { Float value = (Float) getPopupMenu().getClientProperty("Quaqua.PopupMenu.windowAlpha"); return (value == null) ? 0.948f : value.floatValue(); } /** * Gets the popup anchor. * * @return SwingConstants.SOUTH_WEST or SOUTH_EAST. */ public int getPopupAnchor() { return popupAnchor; } /** * Sets the popup anchor. * <p> * <ul> * <li>SOUTH_WEST places the popup below the button and aligns it with its * left bound.</li> * <li>SOUTH_EAST places the popup below the button and aligns it with its * right bound.</li> * </ul> * * @param newValue SwingConstants.SOUTH_WEST or SOUTH_EAST. */ public void setPopupAnchor(int newValue) { popupAnchor = newValue; } protected void togglePopup(java.awt.event.MouseEvent evt) { if (popupMenu != null && popupMenu.isShowing() || popupBecameInvisible >= evt.getWhen()) { popupMenu.setVisible(false); } else { showPopup(evt); } } protected void showPopup(java.awt.event.MouseEvent evt) { // Add your handling code here: if (popupMenu != null && (actionArea == null || !actionArea.contains(evt.getX() - getInsets().left, evt.getY() - getInsets().top))) { int x, y; switch (popupAnchor) { case SOUTH_EAST: x = getWidth() - popupMenu.getPreferredSize().width; ; y = getHeight(); break; case SOUTH_WEST: default: x = 0; y = getHeight(); break; } if (getParent() instanceof JToolBar) { JToolBar toolbar = (JToolBar) getParent(); if (toolbar.getOrientation() == JToolBar.VERTICAL) { y = 0; if (toolbar.getX() > toolbar.getParent().getInsets().left) { x = -popupMenu.getPreferredSize().width; } else { x = getWidth(); } } else { if (toolbar.getY() > toolbar.getParent().getInsets().top) { y = -popupMenu.getPreferredSize().height; } } } popupMenu.show(this, x, y); popupMenu.repaint(); } } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { addMouseListener(new java.awt.event.MouseAdapter() { public void mousePressed(java.awt.event.MouseEvent evt) { handleMousePressed(evt); } public void mouseReleased(java.awt.event.MouseEvent evt) { performAction(evt); } }); }// </editor-fold>//GEN-END:initComponents private void performAction(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_performAction // Add your handling code here: if (actionArea != null && actionArea.contains(evt.getX() - getInsets().left, evt.getY() - getInsets().top)) { action.actionPerformed( new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null, evt.getWhen(), evt.getModifiers())); } }//GEN-LAST:event_performAction private void handleMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_handleMousePressed togglePopup(evt); }//GEN-LAST:event_handleMousePressed // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables }