/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.io.*; import java.util.*; import java.net.URL; import javax.swing.*; import javax.swing.event.*; import org.openide.explorer.*; import org.openide.nodes.*; import org.openide.util.WeakListener; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.util.HelpCtx; import org.openide.util.Utilities; import org.openide.util.actions.SystemAction; /** An explorer view that shows the context hierarchy in * a popup menu. Initially, it shows a left button which opens a popup * menu from the root context and a right button which opens a popup menu from the currently * explored context. * * @author Ian Formanek, Jaroslav Tulach */ public class MenuView extends JPanel { /** generated Serialized Version UID */ static final long serialVersionUID = -4970665063421766904L; /** The explorerManager that manages this view */ transient private ExplorerManager explorerManager; /** button to open root view */ private JButton root; /** button to open view from current node */ private JButton current; /** property change listener */ transient private Listener listener; /* This is the constructor implementation * recommended by ExplorerView class that only calls the inherited * constructor and leaves the initialization for method initialize(). * @see #initialize */ /** Construct a new menu view. */ public MenuView () { setLayout (new java.awt.FlowLayout()); root = new JButton(NbBundle.getBundle (MenuView.class).getString("MenuViewStartFromRoot")); add (root); current = new JButton(NbBundle.getBundle (MenuView.class).getString("MenuViewStartFromCurrent")); add (current); init (); } /** Initializes listeners */ private void init () { root.addMouseListener (listener = new Listener (true)); current.addMouseListener (new Listener (false)); } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject (); init (); } /* Initializes view. */ public void addNotify() { super.addNotify (); explorerManager = ExplorerManager.find (this); explorerManager.addPropertyChangeListener (listener); doChecks (); } /* Deinitializes view. */ public void removeNotify() { super.removeNotify (); explorerManager.removePropertyChangeListener (listener); explorerManager = null; } /** Does some checks */ private void doChecks () { current.setEnabled (explorerManager.getSelectedNodes ().length == 1); } /** Listener that opens the menu and listens to its actions */ private class Listener extends MouseAdapter implements NodeAcceptor, PropertyChangeListener { /** from root */ private boolean root; public Listener (boolean root) { this.root = root; } public void mousePressed (MouseEvent e) { if (e.getComponent ().isEnabled ()) { // open the popup menu Node context = null; if (!root) { Node[] sel = explorerManager.getSelectedNodes (); if (sel.length > 0) { context = sel[0]; } } if (context == null) { context = explorerManager.getRootContext(); } Menu menu = new Menu (context, listener); JPopupMenu popupMenu = menu.getPopupMenu (); java.awt.Point p = new java.awt.Point (e.getX (), e.getY ()); p.x = e.getX() - p.x; p.y = e.getY() - p.y; SwingUtilities.convertPointToScreen (p, e.getComponent ()); Dimension popupSize = popupMenu.getPreferredSize (); Rectangle screenBounds = Utilities.getUsableScreenBounds(getGraphicsConfiguration()); if (p.x + popupSize.width > screenBounds.x + screenBounds.width) p.x = screenBounds.x + screenBounds.width - popupSize.width; if (p.y + popupSize.height > screenBounds.y + screenBounds.height) p.y = screenBounds.y + screenBounds.height - popupSize.height; SwingUtilities.convertPointFromScreen (p, e.getComponent ()); popupMenu.show(e.getComponent (), p.x, p.y); } } /** Is the set of nodes acceptable? * @param nodes the nodes to consider * @return <CODE>true</CODE> if so */ public boolean acceptNodes(Node[] nodes) { // don't allow multiple selections if ((nodes == null) || (nodes.length != 1)) { return false; } Node n = nodes[0]; Node parent = n.getParentNode (); if (parent != null) { explorerManager.setExploredContext (parent, new Node[] { n }); } return true; } public void propertyChange (PropertyChangeEvent ev) { if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) { doChecks (); } } } /** Menu item representing a node (with children) in a menu hierarchy. * One can attach an acceptor to the menu that will be informed * each time a user selects an item whether * to close the menu or not. * The submenu content is taken as a blocking snapshot of the Node's * Children at the time of the submenu popup and does not reflect * subsequent changes to Node's Children until next popup. * This means that the Node's Children should properly implement * blocking getNodes(true) in order to show proper content in MenuView. */ public static class Menu extends org.openide.awt.JMenuPlus { static final long serialVersionUID =-1505289666675423991L; /** The node represented. */ protected Node node; /** Action listener to attach to all menu items. */ protected NodeAcceptor action; /** A boolean flag that notes the content was already created */ private boolean filled = false; /** Constructor that assigns the node a default action. * For example, open the Explorer or a property sheet. * @param node node to represent */ public Menu (Node node) { this (node, DEFAULT_LISTENER); } /** Constructor that permits specification of the action on the node. * * @param node node to represent * @param action action called when node is selected */ public Menu (Node node, NodeAcceptor action) { this (node, action, true); } /** @deprecated use {@link MenuView.Menu#MenuView.Menu(Node, NodeAcceptor)} */ public Menu (Node node, Acceptor action) { this (node, new AcceptorProxy(action), true); } /** @deprecated use {@link MenuView.Menu#MenuView.Menu(Node, NodeAcceptor, boolean)} */ public Menu (Node node, Acceptor action, boolean setName) { this (node, new AcceptorProxy(action), setName); } /** Constructor that permits specification of the action on the node, * and permits overriding the name and icon of the menu. * * @param node node to represent * @param action action called when node selected * @param setName <code>true</code> to automatically set the name and icon of the item */ public Menu (final Node node, NodeAcceptor action, boolean setName) { this.node = node; this.action = action; if (setName) { MenuItem.initialize (this, node); HelpCtx help = node.getHelpCtx (); if (help != null && ! help.equals (HelpCtx.DEFAULT_HELP) && help.getHelpID () != null) HelpCtx.setHelpIDString (this, help.getHelpID ()); } } /** Overriden to fill the submenu with the real content lazily */ public JPopupMenu getPopupMenu() { final JPopupMenu popup = super.getPopupMenu(); fillSubmenu(popup); return popup; } /** Little class that will reset our status on menu hide */ private class Helper implements PopupMenuListener { private JPopupMenu popup; Helper(JPopupMenu master) { popup = master; popup.addPopupMenuListener(this); } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { filled = false; // clear the status and stop listening popup.removePopupMenuListener(this); } public void popupMenuCanceled(PopupMenuEvent e) {} public void popupMenuWillBecomeVisible(PopupMenuEvent e) {} } private void fillSubmenu(JPopupMenu popup) { if (!filled) { filled = true; Helper h = new Helper(popup); Node[] nodes = node.getChildren().getNodes(true); // Fill in the popup. removeAll(); for(int i=0; i<nodes.length; i++) add(createMenuItem(nodes[i])); // also work with empty element if(getMenuComponentCount () == 0) add(createEmptyMenuItem()); } } /** Checks for {@link MouseEvent#isPopupTrigger right click} to ask the acceptor whether * to accept the selection. * @param e the mouse event * @param path used by the superclass * @param manager used by the superclass */ public void processMouseEvent(MouseEvent e, MenuElement[] path, MenuSelectionManager manager) { super.processMouseEvent (e, path, manager); if (e.isPopupTrigger () && action.acceptNodes (new Node[] { node })) { MenuSelectionManager.defaultManager ().clearSelectedPath (); } } /** Helper method. Creates empty menu item. */ private static JMenuItem createEmptyMenuItem() { JMenuItem empty = new JMenuItem( NbBundle.getMessage(MenuView.class, "EmptySubMenu") ); empty.setEnabled(false); return empty; } /** Create a menu element for a node. The default implementation creates * {@link MenuView.MenuItem}s for leafs and <code>Menu</code> for other nodes. * * @param n node to create element for * @return the created node */ protected JMenuItem createMenuItem (Node n) { return n.isLeaf () ? (JMenuItem) new MenuItem (n, action) : (JMenuItem) new Menu (n, action); } } // End of class Menu. /** Acceptor that can be passed to constructor of {@link MenuView.Menu}. * It permits determination of which nodes should be accepted upon a click. * * @deprecated This interface is almost the same as {@link NodeAcceptor} * so it is redundant and obsoleted. Use {@link NodeAcceptor} * interface instead. */ public static interface Acceptor { /** Test whether to accept the node or not. Can also perform some actions (such as opening the node, etc.). * @param n the node * @return true if the <code>menu</code> should close * @deprecated whole interface is obsoleted, use {@link NodeAcceptor#acceptNodes} instead. */ public boolean accept (Node n); } /** default listener that opens explorer */ static final NodeAcceptor DEFAULT_LISTENER = new NodeAcceptor () { public boolean acceptNodes (Node[] nodes) { // don't allow multiple selections if ((nodes == null) || (nodes.length != 1)) { return false; } Node n = nodes[0]; SystemAction sa = n.getDefaultAction(); if (sa != null) { // #16330. //sa.actionPerformed(new ActionEvent(n, 0, "")); // NOI18N TreeView.invokeAction(sa, new ActionEvent(n, 0, "")); // NOI18N return true; } return false; } }; /** Proxy that allows to use now deprecated MenuView.Acceptor interface * on places where NodeAcceptor is requested. * This class can be deleted together with MenuView.Acceptor deletion. */ private static final class AcceptorProxy implements NodeAcceptor { private Acceptor original; AcceptorProxy (Acceptor original) { this.original = original; } public boolean acceptNodes (Node[] nodes) { // don't allow multiple selections if ((nodes == null) || (nodes.length != 1)) { return false; } return original.accept(nodes[0]); } } // end of AcceptorProxy inner class /** Menu item that can represent one node in the tree. */ public static class MenuItem extends JMenuItem implements HelpCtx.Provider { /** generated Serialized Version UID */ static final long serialVersionUID = -918973978614344429L; /** The node represented. */ protected Node node; /** The action listener to attach to all menu items. */ protected NodeAcceptor action; /** Construct item for given node with the node's default action. * @param node the node to represent */ public MenuItem (Node node) { this (node, DEFAULT_LISTENER); } /** Construct item for given node, specifying an action. * @param node the node to represent * @param l the acceptor to decide whether to accept this node or not */ public MenuItem (Node node, NodeAcceptor l) { this (node, l, true); } /** @deprecated Use proper constructor with (@link NodeAcceptor). */ public MenuItem (Node node, Acceptor action) { this (node, new AcceptorProxy(action), true); } /** @deprecated Use proper constructor with (@link NodeAcceptor). */ public MenuItem (Node node, Acceptor action, boolean setName) { this (node, new AcceptorProxy(action), setName); } /** Construct item for given node, specifying the action and whether to create the icon and name automatically. * @param node the node to represent * @param l the acceptor to decide whether to accept this node or not * @param setName <code>false</code> if the name and icon should not be set */ public MenuItem (Node node, NodeAcceptor l, boolean setName) { super (); this.node = node; this.action = l; if (setName) { initialize (this, node); } // [pnejedly] HelpCtx is now provided through HelpCtx.Provider // HelpCtx help = node.getHelpCtx (); // if (help != null && ! help.equals (HelpCtx.DEFAULT_HELP) && help.getHelpID () != null) // HelpCtx.setHelpIDString (this, help.getHelpID ()); } /** * @return HelpCtx of the underlying node. * @since 3.38 */ public HelpCtx getHelpCtx() { return node.getHelpCtx (); } /** Inform the acceptor. * @param time see superclass */ public void doClick (int time) { action.acceptNodes (new Node[] { node }); } /** Initialize an item for a node. */ static void initialize (final JMenuItem item, final Node node) { final class NI implements Runnable, NodeListener, ItemListener { public void run () { item.setIcon (new ImageIcon(node.getIcon(java.beans.BeanInfo.ICON_COLOR_16x16))); item.setText (node.getDisplayName ()); /* item.setMargin(new java.awt.Insets(0, 0, 0, 0)); item.setHorizontalTextPosition(RIGHT); item.setHorizontalAlignment(LEFT); */ } public void childrenAdded(NodeMemberEvent ev) { } public void childrenRemoved(NodeMemberEvent ev) { } public void childrenReordered(NodeReorderEvent ev) { } public void nodeDestroyed (NodeEvent ev) { } /** Update a visualizer (change of name, icon, description, etc.) */ public void propertyChange (PropertyChangeEvent ev) { if (Node.PROP_ICON.equals (ev.getPropertyName())) { Mutex.EVENT.readAccess (this); return; } if (Node.PROP_DISPLAY_NAME.equals (ev.getPropertyName ())) { Mutex.EVENT.readAccess (this); return; } } public void itemStateChanged (ItemEvent ev) { } } NI ni = new NI (); // update this immediatelly ni.run (); // attach the listener to the menu item, to prevent it from garbage // collection until the menu item exists item.addItemListener(ni); // listen to changes in node, but weakly, to allow garbage collection // event the node exists node.addNodeListener (WeakListener.node (ni, node)); } } }