/*
* 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));
}
}
}