/* $Id: ExplorerTree.java 18978 2011-01-24 18:25:30Z linus $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * thn ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2008 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.ui.explorer; import java.awt.Graphics; import java.awt.Image; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import org.argouml.application.helpers.ResourceLoaderWrapper; import org.argouml.kernel.Project; import org.argouml.kernel.ProjectManager; import org.argouml.kernel.ProjectSettings; import org.argouml.model.Model; import org.argouml.ui.DisplayTextTree; import org.argouml.ui.ProjectActions; import org.argouml.ui.targetmanager.TargetEvent; import org.argouml.ui.targetmanager.TargetListener; import org.argouml.ui.targetmanager.TargetManager; import org.tigris.gef.presentation.Fig; /** * This class is the JTree for the explorer. It provides: * <p> * * <pre> * - selection/target management * - mouse listener for the pop up * </pre> * * @author alexb * @since 0.15.2 */ public class ExplorerTree extends DisplayTextTree { /** * Background image only displayed in UML2 mode */ private Image bgImage = null; /** * Prevents target event cycles between this and the TargetManager. */ private boolean updatingSelection; /** * Prevents target event cycles between this and the TargetManager for tree * selection events. */ private boolean updatingSelectionViaTreeSelection; /** * Creates a new instance of ExplorerTree. */ public ExplorerTree() { super(); Project p = ProjectManager.getManager().getCurrentProject(); this.setModel(new ExplorerTreeModel(p, this)); if (Model.getFacade().getUmlVersion().charAt(0) == '2') { bgImage = ResourceLoaderWrapper.lookupIconResource( "uml2explorerbg").getImage(); } if (p != null) { ProjectSettings ps = p.getProjectSettings(); setShowStereotype(ps.getShowStereotypesValue()); } this.addMouseListener(new ExplorerMouseListener(this)); this.addTreeSelectionListener(new ExplorerTreeSelectionListener()); this.addTreeWillExpandListener(new ExplorerTreeWillExpandListener()); this.addTreeExpansionListener(new ExplorerTreeExpansionListener()); TargetManager.getInstance().addTargetListener( new ExplorerTargetListener()); } /** * Listens to mouse events coming from the *JTree*, on right click, brings * up the pop-up menu. */ class ExplorerMouseListener extends MouseAdapter { private JTree mLTree; /** * The constructor. * * @param newtree */ public ExplorerMouseListener(JTree newtree) { super(); mLTree = newtree; } /** * Brings up the pop-up menu. * * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ @Override public void mousePressed(MouseEvent me) { if (me.isPopupTrigger()) { me.consume(); showPopupMenu(me); } } /** * Brings up the pop-up menu. * * On Windows and Motif platforms, the user brings up a popup menu by * releasing the right mouse button while the cursor is over a component * that is popup-enabled. * * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ @Override public void mouseReleased(MouseEvent me) { if (me.isPopupTrigger()) { me.consume(); showPopupMenu(me); } } /** * Brings up the pop-up menu. * * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ @Override public void mouseClicked(MouseEvent me) { if (me.isPopupTrigger()) { me.consume(); showPopupMenu(me); } if (me.getClickCount() >= 2) { myDoubleClick(); } } /** * Double-clicking on an item attempts to show the item in a diagram. */ private void myDoubleClick() { Object target = TargetManager.getInstance().getTarget(); if (target != null) { List show = new ArrayList(); show.add(target); ProjectActions.jumpToDiagramShowing(show); } } /** * Builds a pop-up menu for extra functionality for the Tree. * * @param me The mouse event. */ public void showPopupMenu(MouseEvent me) { TreePath path = getPathForLocation(me.getX(), me.getY()); if (path == null) { return; } /* * We preserve the current (multiple) selection, if we are over part * of it ... */ if (!isPathSelected(path)) { /* ... otherwise we select the item below the mousepointer. */ getSelectionModel().setSelectionPath(path); } Object selectedItem = ((DefaultMutableTreeNode) path .getLastPathComponent()).getUserObject(); JPopupMenu popup = new ExplorerPopup(selectedItem, me); if (popup.getComponentCount() > 0) { popup.show(mLTree, me.getX(), me.getY()); } } } /* end class ExplorerMouseListener */ /** * Helps prepare state before a node is expanded. */ class ExplorerTreeWillExpandListener implements TreeWillExpandListener { /* * @see * javax.swing.event.TreeWillExpandListener#treeWillCollapse(javax.swing * .event.TreeExpansionEvent) */ public void treeWillCollapse(TreeExpansionEvent tee) { // unimplemented - we only care about expanding } /* * Updates stereotype setting, adds all children per treemodel 'build on * demand' design. * * @see * javax.swing.event.TreeWillExpandListener#treeWillExpand(javax.swing * .event.TreeExpansionEvent) */ public void treeWillExpand(TreeExpansionEvent tee) { // TODO: This should not need to know about ProjectSettings - tfm Project p = ProjectManager.getManager().getCurrentProject(); ProjectSettings ps = p.getProjectSettings(); setShowStereotype(ps.getShowStereotypesValue()); if (getModel() instanceof ExplorerTreeModel) { ((ExplorerTreeModel) getModel()).updateChildren(tee.getPath()); } } } /** * Helps react to tree expansion events. */ class ExplorerTreeExpansionListener implements TreeExpansionListener { /* * @see javax.swing.event.TreeExpansionListener#treeCollapsed( * javax.swing.event.TreeExpansionEvent) */ public void treeCollapsed(TreeExpansionEvent event) { // does nothing. } /* * @see javax.swing.event.TreeExpansionListener#treeExpanded( * javax.swing.event.TreeExpansionEvent) Updates the selection state. */ public void treeExpanded(TreeExpansionEvent event) { // need to update the selection state. setSelection(TargetManager.getInstance().getTargets().toArray()); } } /** * Refresh the selection of the tree nodes. This does not cause new events * to be fired to the TargetManager. */ public void refreshSelection() { Collection targets = TargetManager.getInstance().getTargets(); updatingSelectionViaTreeSelection = true; setSelection(targets.toArray()); updatingSelectionViaTreeSelection = false; } /** * Sets the selection state for a given set of targets. * * @param targets the targets */ private void setSelection(Object[] targets) { updatingSelectionViaTreeSelection = true; this.clearSelection(); addTargetsInternal(targets); updatingSelectionViaTreeSelection = false; } private void addTargetsInternal(Object[] addedTargets) { if (addedTargets.length < 1) { return; } Set targets = new HashSet(); for (Object t : addedTargets) { if (t instanceof Fig) { targets.add(((Fig) t).getOwner()); } else { targets.add(t); } // TODO: The following can be removed if selectAll gets fixed selectVisible(t); } // TODO: This doesn't perform well enough with large models to have // it enabled by default. If the performance can't be improved, // perhaps we can introduce a manual "find in explorer tree" action. // selectAll(targets); int[] selectedRows = getSelectionRows(); if (selectedRows != null && selectedRows.length > 0) { // TODO: This only works if the item is visible // (all its parents are expanded) // getExpandedDescendants, makeVisible makeVisible(getPathForRow(selectedRows[0])); scrollRowToVisible(selectedRows[0]); } } /** * Select any targets which are visible in the explorer pane */ private void selectVisible(Object target) { for (int j = 0; j < getRowCount(); j++) { Object rowItem = ((DefaultMutableTreeNode) getPathForRow(j) .getLastPathComponent()).getUserObject(); if (rowItem == target) { addSelectionRow(j); } } } /** * Search the entire tree and select all instances of targets found. */ private void selectAll(Set targets) { ExplorerTreeModel model = (ExplorerTreeModel) getModel(); ExplorerTreeNode root = (ExplorerTreeNode) model.getRoot(); selectChildren(model, root, targets); } /* * Perform recursive search of subtree rooted at 'node', selecting all nodes * which have a userObject matching one of our targets. */ private void selectChildren(ExplorerTreeModel model, ExplorerTreeNode node, Set targets) { if (targets.isEmpty()) { return; } Object nodeObject = node.getUserObject(); if (nodeObject != null) { for (Object t : targets) { if (t == nodeObject) { addSelectionPath(new TreePath(node.getPath())); // target may appear multiple places in the tree, so // we don't stop here (but it's expensive to search // the whole tree) - tfm - 20070904 // targets.remove(t); // break; } } } model.updateChildren(new TreePath(node.getPath())); Enumeration e = node.children(); while (e.hasMoreElements()) { selectChildren(model, (ExplorerTreeNode) e.nextElement(), targets); } } public void paint(Graphics g) { super.paint(g); if (bgImage != null) { g.drawImage(bgImage, 0, 40, null); } } /** * Manages selecting the item to show in Argo's other views based on the * highlighted row. */ class ExplorerTreeSelectionListener implements TreeSelectionListener { /** * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent) * * Change in explorer tree selection -> set target in target * manager. */ public void valueChanged(TreeSelectionEvent e) { if (!updatingSelectionViaTreeSelection) { updatingSelectionViaTreeSelection = true; // get the elements TreePath[] addedOrRemovedPaths = e.getPaths(); TreePath[] selectedPaths = getSelectionPaths(); List elementsAsList = new ArrayList(); for (int i = 0; selectedPaths != null && i < selectedPaths.length; i++) { Object element = ((DefaultMutableTreeNode) selectedPaths[i] .getLastPathComponent()).getUserObject(); elementsAsList.add(element); // scan the visible rows for duplicates of // this elem and select them int rows = getRowCount(); for (int row = 0; row < rows; row++) { Object rowItem = ((DefaultMutableTreeNode) getPathForRow( row).getLastPathComponent()).getUserObject(); if (rowItem == element && !(isRowSelected(row))) { addSelectionRow(row); } } } // check which targetmanager method to call boolean callSetTarget = true; List addedElements = new ArrayList(); for (int i = 0; i < addedOrRemovedPaths.length; i++) { Object element = ((DefaultMutableTreeNode) addedOrRemovedPaths[i] .getLastPathComponent()).getUserObject(); if (!e.isAddedPath(i)) { callSetTarget = false; break; } addedElements.add(element); } if (callSetTarget && addedElements.size() == elementsAsList.size() && elementsAsList.containsAll(addedElements)) { TargetManager.getInstance().setTargets(elementsAsList); } else { // we must call the correct method on targetmanager // for each added or removed target List removedTargets = new ArrayList(); List addedTargets = new ArrayList(); for (int i = 0; i < addedOrRemovedPaths.length; i++) { Object element = ((DefaultMutableTreeNode) addedOrRemovedPaths[i] .getLastPathComponent()).getUserObject(); if (e.isAddedPath(i)) { addedTargets.add(element); } else { removedTargets.add(element); } } // we can't remove the targets in one go, we have to // do it one by one. if (!removedTargets.isEmpty()) { Iterator it = removedTargets.iterator(); while (it.hasNext()) { TargetManager.getInstance().removeTarget(it.next()); } } if (!addedTargets.isEmpty()) { Iterator it = addedTargets.iterator(); while (it.hasNext()) { TargetManager.getInstance().addTarget(it.next()); } } } updatingSelectionViaTreeSelection = false; } } } class ExplorerTargetListener implements TargetListener { /** * Actions a change in targets received from the TargetManager. * * @param targets the targets */ private void setTargets(Object[] targets) { if (!updatingSelection) { updatingSelection = true; if (targets.length <= 0) { clearSelection(); } else { setSelection(targets); } updatingSelection = false; } } /* * @see org.argouml.ui.targetmanager.TargetListener#targetAdded( * org.argouml.ui.targetmanager.TargetEvent) */ public void targetAdded(TargetEvent e) { if (!updatingSelection) { updatingSelection = true; Object[] targets = e.getAddedTargets(); updatingSelectionViaTreeSelection = true; addTargetsInternal(targets); updatingSelectionViaTreeSelection = false; updatingSelection = false; } // setTargets(e.getNewTargets()); } /* * @see org.argouml.ui.targetmanager.TargetListener#targetRemoved( * org.argouml.ui.targetmanager.TargetEvent) */ public void targetRemoved(TargetEvent e) { if (!updatingSelection) { updatingSelection = true; Object[] targets = e.getRemovedTargets(); int rows = getRowCount(); for (int i = 0; i < targets.length; i++) { Object target = targets[i]; if (target instanceof Fig) { target = ((Fig) target).getOwner(); } for (int j = 0; j < rows; j++) { Object rowItem = ((DefaultMutableTreeNode) getPathForRow( j).getLastPathComponent()).getUserObject(); if (rowItem == target) { updatingSelectionViaTreeSelection = true; removeSelectionRow(j); updatingSelectionViaTreeSelection = false; } } } if (getSelectionCount() > 0) { scrollRowToVisible(getSelectionRows()[0]); } updatingSelection = false; } // setTargets(e.getNewTargets()); } /* * @see org.argouml.ui.targetmanager.TargetListener#targetSet( * org.argouml.ui.targetmanager.TargetEvent) */ public void targetSet(TargetEvent e) { setTargets(e.getNewTargets()); } } /** * The UID. */ private static final long serialVersionUID = 992867483644759920L; }