/* * 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.event.*; import java.awt.*; import java.awt.dnd.Autoscroll; import java.beans.*; import java.io.*; import java.awt.dnd.DnDConstants; import javax.swing.*; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.accessibility.*; import org.openide.ErrorManager; import org.openide.awt.MouseUtils; import org.openide.explorer.*; import org.openide.util.WeakListener; import org.openide.util.actions.ActionPerformer; import org.openide.util.actions.SystemAction; import org.openide.nodes.*; import org.openide.util.Utilities; import org.openide.util.actions.CallbackSystemAction; /** Explorer view to display items in a list. * @author Ian Formanek, Jan Jancura, Jaroslav Tulach */ public class ListView extends JScrollPane implements Externalizable { /** generated Serialized Version UID */ static final long serialVersionUID = -7540940974042262975L; /** Explorer manager to work with. Is not null only if the component is showing * in components hierarchy */ private transient ExplorerManager manager; /** The actual JList list */ transient protected JList list; /** model to use */ transient protected NodeListModel model; // // listeners // /** Listener to nearly everything */ transient Listener managerListener; /** weak variation of the listener for property change on the explorer manager */ transient PropertyChangeListener wlpc; /** weak variation of the listener for vetoable change on the explorer manager */ transient VetoableChangeListener wlvc; /** popup */ transient PopupSupport popupSupport; // // properties // /** if true, the icon view displays a popup on right mouse click, if false, the popup is not displayed */ private boolean popupAllowed = true; /** if true, the hierarchy traversal is allowed, if false, it is disabled */ private boolean traversalAllowed = true; /** action preformer */ private ActionListener defaultProcessor; // // Dnd // /** true if drag support is active */ transient boolean dragActive = false; /** true if drop support is active */ transient boolean dropActive = false; /** Drag support */ transient ListViewDragSupport dragSupport; /** Drop support */ transient ListViewDropSupport dropSupport; /** True, if the selection listener is attached. */ transient boolean listenerActive; // init ................................................................................. /** Default constructor. */ public ListView() { initializeList (); // activation of drop target if (DragDropUtilities.dragAndDropEnabled) { ExplorerDnDManager.getDefault ().addFutureDropTarget (this); // note: drag target is activated on focus gained } } /** Initializes the tree & model. */ private void initializeList () { // initilizes the JTree model = createModel (); list = createList (); list.setModel (model); setViewportView (list); { AbstractAction action = new GoUpAction (); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0); list.registerKeyboardAction(action, key, JComponent.WHEN_FOCUSED); } { AbstractAction action = new EnterAction (); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); list.registerKeyboardAction(action, key, JComponent.WHEN_FOCUSED); } managerListener = new Listener (); popupSupport = new PopupSupport(); list.getSelectionModel().setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); ToolTipManager.sharedInstance ().registerComponent (list); } /* * Write view's state to output stream. */ public void writeExternal (ObjectOutput out) throws IOException { out.writeObject (popupAllowed ? Boolean.TRUE : Boolean.FALSE); out.writeObject (traversalAllowed ? Boolean.TRUE : Boolean.FALSE); out.writeObject (new Integer (getSelectionMode ())); } /* * Reads view's state form output stream. */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { popupAllowed = ((Boolean)in.readObject ()).booleanValue (); traversalAllowed = ((Boolean)in.readObject ()).booleanValue (); setSelectionMode (((Integer)in.readObject ()).intValue ()); } // properties ........................................................................... /** Test whether display of a popup menu is enabled. * @return <code>true</code> if so */ public boolean isPopupAllowed () { return popupAllowed; } /** Enable/disable displaying popup menus on list view items. Default is enabled. * @param value <code>true</code> to enable */ public void setPopupAllowed (boolean value) { popupAllowed = value; } /** Test whether hierarchy traversal shortcuts are permitted. * @return <code>true</code> if so */ public boolean isTraversalAllowed () { return traversalAllowed; } /** Enable/disable hierarchy traversal using <code>CTRL+click</code> (down) and <code>Backspace</code> (up), default is enabled. * @param value <code>true</code> to enable */ public void setTraversalAllowed (boolean value) { traversalAllowed = value; } /** Get the current processor for default actions. * If not <code>null</code>, double-clicks or pressing Enter on * items in the view will not perform the default action on the selected node; rather the processor * will be notified about the event. * @return the current default-action processor, or <code>null</code> */ public ActionListener getDefaultProcessor () { return defaultProcessor; } /** Set a new processor for default actions. * @param value the new default-action processor, or <code>null</code> to restore use of the selected node's declared default action * @see #getDefaultProcessor */ public void setDefaultProcessor (ActionListener value) { defaultProcessor = value; } /** * Set whether single-item or multiple-item * selections are allowed. * @param selectionMode one of {@link ListSelectionModel#SINGLE_SELECTION}, {@link ListSelectionModel#SINGLE_INTERVAL_SELECTION}, or {@link ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} * @see ListSelectionModel#setSelectionMode * @beaninfo * description: The selection mode. * enum: SINGLE_SELECTION ListSelectionModel.SINGLE_SELECTION * SINGLE_INTERVAL_SELECTION ListSelectionModel.SINGLE_INTERVAL_SELECTION * MULTIPLE_INTERVAL_SELECTION ListSelectionModel.MULTIPLE_INTERVAL_SELECTION */ public void setSelectionMode(int selectionMode) { list.setSelectionMode(selectionMode); } /** Get the selection mode. * @return the mode * @see #setSelectionMode */ public int getSelectionMode() { return list.getSelectionMode(); } /********** Support for the Drag & Drop operations *********/ /** @return true if dragging from the view is enabled, false * otherwise.<br> * Drag support is disabled by default. */ public boolean isDragSource () { return dragActive; } /** Enables/disables dragging support. * @param state true enables dragging support, false disables it. */ public void setDragSource (boolean state) { if (state == dragActive) return; dragActive = state; // create drag support if needed if (dragActive && (dragSupport == null)) dragSupport = new ListViewDragSupport(this, list); // activate / deactivate support according to the state dragSupport.activate(dragActive); } /** @return true if dropping to the view is enabled, false * otherwise<br> * Drop support is disabled by default. */ public boolean isDropTarget () { return dropActive; } /** Enables/disables dropping support. * @param state true means drops into view are allowed, * false forbids any drops into this view. */ public void setDropTarget (boolean state) { if (state == dropActive) return; dropActive = state; // create drop support if needed if (dropActive && (dropSupport == null)) dropSupport = new ListViewDropSupport(this, list); // activate / deactivate support according to the state dropSupport.activate(dropActive); } /** @return Set of actions which are allowed when dragging from * asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions (copy, move, link) are allowed by default. */ public int getAllowedDragActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dragging * @param actions new drag actions, using DnDConstants.XXX */ public void setAllowedDragActions (int actions) { // PENDING } /** @return Set of actions which are allowed when dropping * into the asociated component. * Actions constants comes from DnDConstants.XXX constants. * All actions are allowed by default. */ public int getAllowedDropActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dropping. * @param actions new allowed drop actions, using DnDConstants.XXX */ public void setAllowedDropActions (int actions) { // PENDING } // // Methods to override // /** Creates the list that will display the data. */ protected JList createList () { JList list = new NbList (); list.setCellRenderer(NodeRenderer.sharedInstance ()); return list; } /** Allows subclasses to change the default model used for * the list. */ protected NodeListModel createModel () { return new NodeListModel (); } /** Called when the list changed selection and the explorer manager * should be updated. * @param nodes list of nodes that should be selected * @param em explorer manager * @exception PropertyVetoException if the manager does not allow the * selection */ protected void selectionChanged (Node[] nodes, ExplorerManager em) throws PropertyVetoException { em.setSelectedNodes (nodes); } /** Called when explorer manager is about to change the current selection. * The view can forbid the change if it is not able to display such * selection. * * @param nodes the nodes to select * @return false if the view is not able to change the selection */ protected boolean selectionAccept (Node[] nodes) { // if the selection is just the root context, confirm the selection if (nodes.length == 1 && manager.getRootContext().equals(nodes[0])) { return true; } Node cntx = manager.getExploredContext (); // we do not allow selection in other than the exploredContext for (int i = 0; i < nodes.length; i++) { VisualizerNode v = VisualizerNode.getVisualizer (null, nodes[i]); if (model.getIndex (v) == -1) { return false; } } return true; } /** Shows selection. * @param indexes indexes of objects to select */ protected void showSelection (int[] indexes) { list.setSelectedIndices (indexes); } // // Working methods // /* Initilizes the view. */ public void addNotify () { super.addNotify (); // run under mutex ExplorerManager em = ExplorerManager.find (this); if (em != manager) { if (manager != null) { manager.removeVetoableChangeListener (wlvc); manager.removePropertyChangeListener (wlpc); } manager = em; manager.addVetoableChangeListener(wlvc = WeakListener.vetoableChange (managerListener, manager)); manager.addPropertyChangeListener(wlpc = WeakListener.propertyChange (managerListener, manager)); model.setNode (manager.getExploredContext ()); updateSelection(); } else { // bugfix #23509, the listener were removed --> add it again if (!listenerActive && (manager != null)) { manager.addVetoableChangeListener(wlvc = WeakListener.vetoableChange (managerListener, manager)); manager.addPropertyChangeListener(wlpc = WeakListener.propertyChange (managerListener, manager)); } } if (!listenerActive) { listenerActive = true; list.getSelectionModel ().addListSelectionListener (managerListener); model.addListDataListener (managerListener); // bugfix #23974, model doesn't reflect an explorer context change // because any listener was not active model.setNode (manager.getExploredContext ()); list.addFocusListener (popupSupport); list.addMouseListener (popupSupport); } } /** Removes listeners. */ public void removeNotify () { super.removeNotify (); listenerActive = false; list.getSelectionModel ().removeListSelectionListener (managerListener); // bugfix #23509, remove useless listeners if (manager != null) { manager.removeVetoableChangeListener (wlvc); manager.removePropertyChangeListener (wlpc); } model.removeListDataListener (managerListener); list.removeFocusListener (popupSupport); list.removeMouseListener (popupSupport); } /* Requests focus for the list component. Overrides superclass method. */ public void requestFocus () { list.requestFocus(); } /** This method is called when user double-clicks on some object or * presses Enter key. * @param index Index of object in current explored context */ final void performObjectAt(int index, int modifiers) { if (index < 0 || index >= model.getSize ()) { return; } VisualizerNode v = (VisualizerNode)model.getElementAt (index); Node node = v.node; // if DefaultProcessor is set, the default action is notified to it overriding the default action on nodes if (defaultProcessor != null) { defaultProcessor.actionPerformed (new ActionEvent (node, 0, null, modifiers)); return; } // on double click - invoke default action, if there is any // (unless user holds CTRL key what means that we should always dive into the context) SystemAction sa = node.getDefaultAction (); if (sa != null && (modifiers & java.awt.event.InputEvent.CTRL_MASK) == 0) { TreeView.invokeAction (sa, new ActionEvent (node, ActionEvent.ACTION_PERFORMED, "")); // NOI18N } // otherwise dive into the context else if (traversalAllowed && (!node.isLeaf())) manager.setExploredContext (node, manager.getSelectedNodes()); } /** Called when selection has been changed. Make selection visible (at least partly). */ private void updateSelection() { Node[] sel = manager.getSelectedNodes (); int[] indices = new int[sel.length]; // bugfix #27094, make sure a selection is visible int firstVisible = list.getFirstVisibleIndex (); int lastVisible = list.getLastVisibleIndex (); boolean ensureVisible = indices.length > 0; for (int i = 0; i < sel.length; i++) { VisualizerNode v = VisualizerNode.getVisualizer (null, sel[i]); indices[i] = model.getIndex (v); ensureVisible = ensureVisible && (indices[i] < firstVisible || indices[i] > lastVisible); } // going to change list because of E.M.'s order -- temp disable the // listener if (listenerActive) list.getSelectionModel ().removeListSelectionListener(managerListener); try { showSelection (indices); if (ensureVisible) { list.ensureIndexIsVisible (indices[0]); } } finally { if (listenerActive) list.getSelectionModel ().addListSelectionListener(managerListener); } } // innerclasses ......................................................................... /** * Enhancement of standard JList. * Provides access to the Node's ToolTips, Accessibility and Autoscrolling. */ final class NbList extends JList implements Autoscroll { static final long serialVersionUID =-7571829536335024077L; /** The worker for the scrolling */ AutoscrollSupport support; NbList() { super(); if (System.getProperty("java.version").startsWith("1.4")) { // fix for #18292 (only for JDK 1.4) // default action map for JList defines these shortcuts // but we use our own mechanism for handling them // following lines disable default L&F handling (if it is // defined on Ctrl-c, Ctrl-v and Ctrl-x) getInputMap().put(KeyStroke.getKeyStroke("control C"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("control V"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("control X"), "none"); // NOI18N } } // ToolTips: /** * Overrides JComponent's getToolTipText method in order to allow * Node's tips to be used if they are usefull. * * @param event the MouseEvent that initiated the ToolTip display */ public String getToolTipText (MouseEvent event) { if (event != null) { Point p = event.getPoint (); int row = locationToIndex (p); if (row >= 0) { VisualizerNode v = (VisualizerNode)model.getElementAt (row); String tooltip = v.getShortDescription(); String displayName = v.getDisplayName (); if ((tooltip != null) && !tooltip.equals (displayName)) return tooltip; } } return null; } // Autoscroll: /** notify the Component to autoscroll */ public void autoscroll (Point cursorLoc) { getSupport().autoscroll(cursorLoc); } /** @return the Insets describing the autoscrolling region or border * relative to the geometry of the implementing Component. */ public Insets getAutoscrollInsets () { return getSupport().getAutoscrollInsets(); } /** Safe getter for autoscroll support. */ AutoscrollSupport getSupport() { if (support == null) support = new AutoscrollSupport( this, new Insets(15, 10, 15, 10)); return support; } // Accessibility: public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleExplorerList(); } return accessibleContext; } private class AccessibleExplorerList extends AccessibleJList { AccessibleExplorerList() {} public String getAccessibleName() { return ListView.this.getAccessibleContext().getAccessibleName(); } public String getAccessibleDescription() { return ListView.this.getAccessibleContext().getAccessibleDescription(); } } } void createPopup(int xpos, int ypos) { if (manager == null) { return; } if (!popupAllowed) { return; } Action[] actions = NodeOp.findActions(manager.getSelectedNodes()); JPopupMenu popup = Utilities.actionsToPopup(actions, this); if ((popup != null) && (popup.getSubElements().length > 0) && (TreeView.shouldPopupBeDisplayed(ListView.this))) { java.awt.Point p = getViewport().getViewPosition(); p.x = xpos - p.x; p.y = ypos - p.y; SwingUtilities.convertPointToScreen(p, ListView.this); Dimension popupSize = popup.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, ListView.this); popup.show(this, p.x, p.y); } } final class PopupSupport extends MouseUtils.PopupMouseAdapter implements ActionPerformer, Runnable, FocusListener { public void mouseClicked(MouseEvent e) { if (MouseUtils.isDoubleClick(e)) { int index = list.locationToIndex(e.getPoint()); performObjectAt(index, e.getModifiers()); } } protected void showPopup (MouseEvent e) { int i = list.locationToIndex (new Point (e.getX (), e.getY ())); if (!list.isSelectedIndex (i)) { list.setSelectedIndex (i); } createPopup(e.getX(), e.getY()); } public void performAction(SystemAction act) { SwingUtilities.invokeLater(this); } public void run() { boolean multisel = (list.getSelectionMode() != ListSelectionModel.SINGLE_SELECTION); int i = (multisel ? list.getLeadSelectionIndex() : list.getSelectedIndex()); if (i < 0) return; Point p = list.indexToLocation(i); if (p == null) return; createPopup(p.x, p.y); } CallbackSystemAction csa; public void focusGained(FocusEvent ev) { if (csa == null) { try { Class popup = Class.forName("org.openide.actions.PopupAction"); // NOI18N csa = (CallbackSystemAction) CallbackSystemAction.get(popup); } catch (ClassNotFoundException e) { Error err = new NoClassDefFoundError(); ErrorManager.getDefault ().annotate(err, e); throw err; } } csa.setActionPerformer(this); //ev.consume(); } public void focusLost(FocusEvent ev) { if (csa != null && (csa.getActionPerformer() instanceof PopupSupport)) { csa.setActionPerformer(null); } } } /** */ private final class Listener implements ListDataListener, ListSelectionListener, PropertyChangeListener, VetoableChangeListener { Listener() {} /** Implements <code>ListDataListener</code> interface. */ public void intervalAdded(ListDataEvent evt) { updateSelection(); } /** Implements <code>ListDataListener</code>. */ public void intervalRemoved(ListDataEvent evt) { updateSelection(); } /** Implemetns <code>ListDataListener</code>. */ public void contentsChanged(ListDataEvent evt) { updateSelection(); } public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { if (manager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { Node[] newNodes = (Node[])evt.getNewValue(); if (!selectionAccept (newNodes)) { throw new PropertyVetoException("", evt); // NOI18N } } } public void propertyChange(PropertyChangeEvent evt) { if (manager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { updateSelection(); return; } if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { model.setNode (manager.getExploredContext ()); //System.out.println("Children: " + java.util.Arrays.asList (list.getValues ())); // NOI18N return; } } public void valueChanged(ListSelectionEvent e) { int curSize = model.getSize(); int[] indices = list.getSelectedIndices(); // bugfix #24193, check if the nodes in selection are in the view's root context java.util.List ll = new java.util.ArrayList(indices.length); for (int i = 0; i < indices.length; i++) { if (indices[i] < curSize) { Node n = Visualizer.findNode( model.getElementAt(indices[i])); if (n == manager.getRootContext () || n.getParentNode() != null) { ll.add (n); } } else { // something went wrong? updateSelection(); return; } } Node[] nodes = (Node[])ll.toArray (new Node[ll.size ()]); // forwarding TO E.M., so we won't listen to its cries for a while manager.removePropertyChangeListener (wlpc); manager.removeVetoableChangeListener (wlvc); try { selectionChanged (nodes, manager); } catch (java.beans.PropertyVetoException ex) { // selection vetoed - restore previous selection updateSelection(); } finally { manager.addPropertyChangeListener (wlpc); manager.addVetoableChangeListener (wlvc); } } } // Backspace jumps to parent folder of explored context private final class GoUpAction extends AbstractAction { static final long serialVersionUID =1599999335583246715L; public GoUpAction () { super ("GoUpAction"); // NOI18N } public void actionPerformed(ActionEvent e) { if (traversalAllowed) { Node pan = manager.getExploredContext(); pan = pan.getParentNode(); if (pan != null) manager.setExploredContext(pan, manager.getSelectedNodes()); } } public boolean isEnabled() { return true; } } //Enter key performObjectAt selected index. private final class EnterAction extends AbstractAction { static final long serialVersionUID =-239805141416294016L; public EnterAction () { super ("Enter"); // NOI18N } public void actionPerformed(ActionEvent e) { int index = list.getSelectedIndex(); performObjectAt(index, e.getModifiers()); } public boolean isEnabled() { return true; } } }