Package org.openquark.gems.client.browser

Source Code of org.openquark.gems.client.browser.BrowserTree

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* BrowserTree.java
* Creation date: (10/29/00 11:16:35 AM)
* By: Luke Evans
*/
package org.openquark.gems.client.browser;

import java.awt.AlphaComposite;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Vector;

import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.MetaModule;
import org.openquark.gems.client.GemEntitySelection;
import org.openquark.gems.client.PickListKeyStrokeNavigator;
import org.openquark.gems.client.ToolTipHelpers;
import org.openquark.gems.client.navigator.NavAddress;
import org.openquark.gems.client.navigator.NavFrameOwner;
import org.openquark.gems.client.utilities.MouseClickDragAdapter;
import org.openquark.util.UnsafeCast;
import org.openquark.util.ui.UtilTree;


/**
* Extension of JTree adding drag source features.
* Also added are pop up menus which enable sorting capabilities.
* @author Luke Evans
*/
public class BrowserTree extends UtilTree {
   
    private static final long serialVersionUID = -283997771683405356L;

    /**
     * todoSN - A JDK 1.4 problem (see java bug #4485987, 4669873) periodically freezes
     *          the drag image and makes it unusable.  The problem seems to be related to the
     *          calls to paintImmediately() and drawImage() in the drop target version of dragOver()
     *          in the DnD handler (both here in the BrowserTree and in the TableTop).
     *          The drag image can be enabled/disabled by setting this flag so testing to see
     *          if the problem is fixed, supposedly for 1.4.1, should be trivial.
     *
     * A boolean to turn drag images on/off when dragging from the gem browser. Enabling the drag image
     * will disable the drag cursor, while disabling the drag image will enable the drag cursor.
     */
    private static final boolean DRAG_IMAGE_ENABLED = false;
   
    /** The display name given to modules with "" (no name) as their name. */
    public static final String DEFAULT_MODULE_NAME = "<default>";

    /** Whether the system supports drag images natively (otherwise we must render our own). */
    private static final boolean DRAG_IMAGE_SUPPORTED = DragSource.isDragImageSupported();
   
    /** The handler for drag and drop operations. */
    private final DragAndDropHandler dragAndDropHandler;

    /** Rendering hints - render at highest quality*/
    private static final RenderingHints RENDERING_HINTS_HIGH_QUALITY = new RenderingHints(null);
   
    /** Used by saveState() and restoreSavedState().
     *  The saved selected node and its siblings, in the order that they appear as children of the parent.
     *  If the selected node is not displayed after the model is reloaded this list will be used to choose
     *  the closest(/best) one which does. */
    private List<Object> savedChildrenList;
   
    static {
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        RENDERING_HINTS_HIGH_QUALITY.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }

    /** The BrowserTreeActions object associated with this tree. */
    private final BrowserTreeActions browserTreeActions = new BrowserTreeActions(this);

    /** The navigator owner that is used for editing/viewing metadata. */
    private NavFrameOwner navigatorOwner;

    /** The popup menu provider for this tree. */
    private PopupMenuProvider popupMenuProvider;

    /** Flag to indicate whether or not the Gems in the Tree are draggable.*/
    private boolean isDraggable;
   
    /** Flag to indicate whether or not type expressions should be displayed in the tree. */
    private boolean displayTypeExpr;
   
    /**
     * Whether the current module should be highlighted in a different colour.
     * Note: Should this flag be in the cell renderer instead?
     */
    private boolean highlightCurrentModule;

    /** A list to hold any LeafNodeTriggeredListeners that are interested in receiving events for this BrowserTree
     * Use a synchronized list, as the collection may be added-to by different threads..*/
    private final List<LeafNodeTriggeredListener> leafNodeTriggeredListeners = new Vector<LeafNodeTriggeredListener>();

   
    /**
     * Handler for drag and drop events.
     * Creation date: (03/15/2002 2:03:35 PM)
     * @author Edward Lam
     */
    private class DragAndDropHandler implements DragGestureListener, DragSourceListener, DropTargetListener {

        /** The image that shows up when dragging. */
        private BufferedImage dragImage = null;
               
        /** The offset of the mouse from the image origin while dragging. */
        private final Point mousePointOffset = new Point(0, 0);
       
        /** The bounds of the last drawn drag ghost. */
        private final Rectangle lastGhostRect = new Rectangle();
       
        /** The base image (a black arrow) used to build images for cursors. */
        private Image baseCursorImage = null;
       
        /** The drag and drop cursor to use when over a valid drop target. */
        private Cursor dndValidCursor = null;
       
        /**
         * Constructor for a drag-and-drop handler
         * Creation date: (03/15/2002 2:10:00 PM)
         */
        private DragAndDropHandler() {
            // create the drag gesture recognizer for this tree.
            DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(BrowserTree.this,
                    DnDConstants.ACTION_COPY_OR_MOVE, this);

            // Load the image to use as a base when building cursor images
            baseCursorImage = new javax.swing.ImageIcon(BrowserTree.class.getResource("/Resources/arrowMed.gif")).getImage();
        }

        /*
         * Methods implementing DragGestureListener ***********************************************************
         */
   
        /**
         * A <code>DragGestureRecognizer</code> has detected a platform-dependent drag initiating gesture and
         * is notifying this listener in order for it to initiate the action for the user.
         * <P>
         * @param dge the <code>DragGestureEvent</code> describing the gesture that has just occurred
         */
        public void dragGestureRecognized(DragGestureEvent dge) {
            // First, check to make sure that dragging is enabled. 
            if (!isDraggable) {
                // Do nothing.
                return;
            }
           
            // Check for valid selections to drag
            if (getSelectionCount() == 0) {
                // Nothing selected, no drag possible
                return;
            }
           
            // Dragging of an individual drawer is allowed; check if this is the case
            if (getSelectionCount() == 1) {

                TreePath[] selectedPaths = getSelectionPaths();
                TreeNode endpoint = (TreeNode)selectedPaths[0].getLastPathComponent();
               
                if (endpoint instanceof MaterialGemDrawer) {
                    // Get a cursor based on the selected tree nodes
                    dndValidCursor = buildValidDnDCursor(selectedPaths);
               
                    if (!DRAG_IMAGE_ENABLED) {
                        // Someone has disabled the drag image feature so start the drag without it.
                        dge.startDrag(null, null, null, new GemDrawerSelection(((MaterialGemDrawer)endpoint).getModuleName()), this);
                   
                    } else {
                        Point ptDragOrigin = dge.getDragOrigin();
                        TreePath path = getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
                        if (path == null) {
                            // not a drag condition
                            return;
                        }
                        Rectangle pathBounds = getPathBounds(path);
                        mousePointOffset.setLocation(ptDragOrigin.x - pathBounds.x, ptDragOrigin.y - pathBounds.y);
               
                        // Start the drag.  Prepare the drag image, in case the system can use it.
                        prepareDragImage(path);
                        dge.startDrag(null, dragImage, mousePointOffset, new GemDrawerSelection(((MaterialGemDrawer)endpoint).getModuleName()), this);
                    }
                   
                    return;
                }
            }
       
            // Make the list we'll build the list of gems in
            List<GemEntity> selectedGems = new ArrayList<GemEntity>();
       
            // Get all the selected paths
            TreePath[] paths = getSelectionPaths();
       
            // For each path, check it's a supercombinator and add to the string
            //   Note: (for now) we don't add sc's that aren't visible..
            for (final TreePath treePath : paths) {
                // Get the fully qualified name of the supercombinator represented by this path
                GemEntity gemEntity = getEntityFromPath(treePath);
       
                // If this is non-null, emit the name
                if (gemEntity != null && ((BrowserTreeModel)getModel()).isVisibleGem(gemEntity)) {           
                    // Add the supercombinator
                    selectedGems.add(gemEntity);
                }  
            }  
       
            // Did we get any gems to drag?
            if (!selectedGems.isEmpty()) {
                // Build a selection list comprising of all the gem names to be transfered
                GemEntitySelection scs = new GemEntitySelection(selectedGems);

                // Get a cursor based on the selected tree nodes
                dndValidCursor = buildValidDnDCursor(paths);

                // Make sure that the drag image has been enabled
                if (!DRAG_IMAGE_ENABLED) {
                    // Someone has disabled the drag image feature so start the drag without it.
                    dge.startDrag(null, null, null, scs, this);
                } else {
                    // Start the drag gesture
                    // figure out the offset of the mouse from the bounding rectangle of the cell being dragged
                    Point ptDragOrigin = dge.getDragOrigin();
                    TreePath path = getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
                    if (path == null || !Arrays.asList(paths).contains(path)) {
                        // not a drag condition
                        return;
                    }
                    Rectangle pathBounds = getPathBounds(path);
                    mousePointOffset.setLocation(ptDragOrigin.x - pathBounds.x, ptDragOrigin.y - pathBounds.y);
           
                    // prepare the image
                    prepareDragImage(path);
                   
                    // Start the drag.  Give the drag image, in case the system can use it.
                    dge.startDrag(null, dragImage, mousePointOffset, scs, this);
                }
            }
        }
       
        /**
         * Renders the mouse drag image for the specific tree path.
         */
        private void prepareDragImage(TreePath path) {
            // First calculate an image to drag (maybe later the gem drag ghost?)
           
            // Get the tree cell renderer
            Rectangle pathBounds = getPathBounds(path);
            JLabel rendererComponent = (JLabel) getCellRenderer().getTreeCellRendererComponent (
                BrowserTree.this,                                 // tree
                path.getLastPathComponent(),                    // value
                false,                                          // isSelected
                isExpanded(path),                               // isExpanded
                getModel().isLeaf(path.getLastPathComponent()), // isLeaf
                0,                                              // row
                false                                           // hasFocus
            );
       
            // The layout manager normally does this...
            rendererComponent.setSize((int)pathBounds.getWidth(), (int)pathBounds.getHeight());

            // Get a buffered image of the selection for dragging a ghost image
            dragImage = new BufferedImage(
                (int)pathBounds.getWidth(),
                (int)pathBounds.getHeight(),
                BufferedImage.TYPE_INT_ARGB_PRE
            );

            // Get a graphics context for this image, suggest antialiasing.
            Graphics2D g2d = dragImage.createGraphics();

            // Might as well render at the highest quality, if we're only going to do this once
            g2d.setRenderingHints(RENDERING_HINTS_HIGH_QUALITY);

            // Make the image ghostlike
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f));

            // Ask the cell renderer to paint itself into the BufferedImage
            rendererComponent.paint(g2d);

            // Finished with the graphics context now
            g2d.dispose();
        }
       
        /**
         * Builds a cursor for a drag and drop that can be used over a valid drop target.
         * Creation date: (06/12/2002 11:15:00 AM).
         * @param paths javax.swing.tree.TreePath[] the paths of the selected tree nodes.
         * @return Cursor
         */
        private Cursor buildValidDnDCursor(TreePath[] paths) {
           
            // Get the toolkit and scale the composite image to an appropriate size.
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            Dimension bestsize = toolkit.getBestCursorSize(1,1);

            if (bestsize.width <= 1) {
                // We don't appear to support custom cursors, so just use a cross hair
                return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
            }  
           
            // We're looking to see if more than one type of node was selected (constructor vs. sc) and
            // which came first.  The array holds the indices of the first occurrence of each type and the boolean
            // tracks which type came first.
            boolean isFirstNodeConstructor = false;
            int gemCount = 0;
            int[] nodes = {-1, -1};
           
            // Go thru the paths and see which nodes were dragged
            for (int i = 0; i < paths.length && nodes[1] == -1; i++) {

                // Get the entity represented by this path
                GemEntity gemEntity = getEntityFromPath(paths[i]);
       
                // If the sc is non-null see if it is a data constructor or just a regular sc.
                if (gemEntity != null) {
                    boolean isConstructor = gemEntity.isDataConstructor();
                    gemCount++;
                   
                    if (nodes[0] == -1) {
                        // We have found our first node so remember the details
                        nodes[0] = i;
                        isFirstNodeConstructor = isConstructor;
                       
                    } else if (isConstructor != isFirstNodeConstructor) {
                        // The first node has already been found and the second node is a different type.
                        nodes[1] = i;
                    }
                }  
            }  
           
            // Don't build a cursor if we have no gems to drag
            if (gemCount == 0) {
                return null;
            }
           
            // If gem count is greater than 1 but we only have one type, just display the same type twice
            if (gemCount > 1 && nodes[1] == -1) {
                nodes[1] = nodes[0];
            }
                    
            // Get the icons used in the labels and track the size of the composite image we need to build
            javax.swing.Icon[] icons = new javax.swing.Icon[2];
            int compWidth = baseCursorImage.getWidth(null);
            int compHeight = baseCursorImage.getHeight(null);
            for (int i = 0; i < nodes.length && nodes[i] >= 0; i++) {

                javax.swing.JLabel label = (javax.swing.JLabel)getCellRenderer().getTreeCellRendererComponent (
                        BrowserTree.this,                                            // tree
                        paths[nodes[i]].getLastPathComponent(),                    // value
                        false,                                                     // isSelected
                        isExpanded(paths[nodes[i]]),                               // isExpanded
                        getModel().isLeaf(paths[nodes[i]].getLastPathComponent()), // isLeaf
                        0,                                                         // row
                        false                                                      // hasFocus
                );
                icons[i] = label.getIcon();
               
                // Start tracking the size of the composite cursor image
                compWidth += icons[i].getIconWidth();
                compHeight += icons[i].getIconHeight();
            }
                       
            // Get a buffered image (and its graphics) to build a composite cursor image
            BufferedImage compImage = new BufferedImage(compWidth, compHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = (Graphics2D)compImage.getGraphics();
           
            // Paint the base image onto the composite graphics
            g2d.drawImage(baseCursorImage, 0, 0, null);

            // Paint the icons below and to the right of the base image.  The -2's are used to reduce the amount
            // of space between the bottom of the arrow and the top of the first tree node image. 
            int locX = baseCursorImage.getWidth(null)-2;
            int locY = baseCursorImage.getHeight(null)-2;
            for (int i = 0; i < icons.length && nodes[i] >=0; i++) {

                icons[i].paintIcon(null, g2d, locX, locY);
               
                // Shift the location so the next icon image is painted a little to the right.
                locX += 8;
            }
           
            g2d.dispose();

            // Scale the image correctly
            BufferedImage scaledCompImage = new BufferedImage(bestsize.width, bestsize.height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D scaledGraphics = scaledCompImage.createGraphics();
            scaledGraphics.drawImage(compImage, null, null);
            scaledGraphics.dispose();

            // Actually build a cursor from the scaled image
            return toolkit.createCustomCursor(scaledCompImage, new Point(0, 0), "dndValidCursor");
        }
   
        /*
         * Methods implementing DragSourceListener ************************************************************
         */
   
        /**
         * This method is invoked to signify that the Drag and Drop operation is complete.
         * The getDropSuccess() method of the <code>DragSourceDropEvent</code> can be used to
         * determine the termination state. The getDropAction() method returns the operation
         * that the <code>DropTarget</code> selected (via the DropTargetDropEvent acceptDrop() parameter)
         * to apply to the Drop operation. Once this method is complete, the current
         * <code>DragSourceContext</code> and associated resources become invalid.
         * <P>
         * @param dsde the <code>DragSourceDropEvent</code>
         */
        public void dragDropEnd(DragSourceDropEvent dsde) {
            // If necessary, erase the last ghost image

            if (!DRAG_IMAGE_SUPPORTED) {
                paintImmediately(lastGhostRect.getBounds());
            }
           
            // set image to null - we use this to find out when the operation ended
            dragImage = null;
        }
   
        /**
         * Called as the hotspot enters a platform dependent drop site.
         * This method is invoked when the following conditions are true:
         * <UL>
         * <LI>The logical cursor's hotspot initially intersects a GUI <code>Component</code>'s  visible geometry.
         * <LI>That <code>Component</code> has an active <code>DropTarget</code> associated with it.
         * <LI>The <code>DropTarget</code>'s registered <code>DropTargetListener</code> dragEnter() method
         * is invoked and returns successfully.
         * <LI>The registered <code>DropTargetListener</code> invokes the <code>DropTargetDragEvent</code>'s
         * acceptDrag() method to accept the drag based upon interrogation of the source's
         * potential drop action(s) and available data types (<code>DataFlavor</code>s).
         * </UL>
         *<P>
         *@param dsde the <code>DragSourceDragEvent</code>
         */
        public void dragEnter(DragSourceDragEvent dsde) {

            if (!DRAG_IMAGE_ENABLED) {
                // Set the cursor to the valid DnD cursor since we are entering a valid drop site.
                dsde.getDragSourceContext().setCursor(dndValidCursor);
            }
        }
   
        /**
         * Called as the hotspot exits a platform dependent drop site.
         * This method is invoked when the following conditions
         * are true:
         * <UL>
         * <LI>The cursor's logical hotspot no longer intersects the visible geometry of the <code>Component</code>
         * associated with the previous dragEnter() invocation.
         * </UL>
         * OR
         * <UL>
         * <LI>The <code>Component</code> that the logical cursor's hotspot intersected that resulted in the
         * previous dragEnter() invocation no longer has an active <code>DropTarget</code> or
         * <code>DropTargetListener</code> associated with it.
         * </UL>
         * OR
         * <UL>
         * <LI> The current <code>DropTarget</code>'s <code>DropTargetListener</code> has invoked rejectDrag()
         * since the last dragEnter() or dragOver() invocation.
         * </UL>
         * <P>
         * @param dse the <code>DragSourceEvent</code>
         */
        public void dragExit(DragSourceEvent dse) {

            if (!DRAG_IMAGE_ENABLED) {
                // Set the invalid cursor since we are leaving a valid drop site.
                dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
            }
        }
   
        /**
         * Called as the hotspot moves over a platform dependent drop site.
         * This method is invoked when the following conditions
         * are true:
         *<UL>
         *<LI>The cursor's logical hotspot has moved but still
         * intersects the visible geometry of the <code>Component</code>
         * associated with the previous dragEnter() invocation.
         * <LI>That <code>Component</code> still has a
         * <code>DropTarget</code> associated with it.
         * <LI>That <code>DropTarget</code> is still active.
         * <LI>The <code>DropTarget</code>'s registered
         * <code>DropTargetListener</code> dragOver() method
         * is invoked and returns successfully.
         * <LI>The <code>DropTarget</code> does not reject
         * the drag via rejectDrag()
         * </UL>
         * <P>
         * @param dsde the <code>DragSourceDragEvent</code>
         */
        public void dragOver(DragSourceDragEvent dsde) {
           
            if (!DRAG_IMAGE_ENABLED) {
                // Set the invalid cursor since we are leaving a valid drop site.
                dsde.getDragSourceContext().setCursor(dndValidCursor);
            }
        }
   
        /**
         * Called when the user has modified the drop gesture.
         * This method is invoked when the state of the input
         * device(s) that the user is interacting with changes.
         * Such devices are typically the mouse buttons or keyboard
         * modifiers that the user is interacting with.
         * <P>
         * @param dsde the <code>DragSourceDragEvent</code>
         */
        public void dropActionChanged(DragSourceDragEvent dsde) {
        }
   
        /*
         * Methods implementing DropTargetListener ************************************************************
         */
   
        /**
         * Called when a drag operation has encountered the <code>DropTarget</code>.
         * <P>
         * Creation date: (03/14/2002 5:59:00 PM)
         * @param dtde the <code>DropTargetDragEvent</code>
         */
        public void dragEnter(DropTargetDragEvent dtde){
        }
   
        /**
         * Called when a drag operation is ongoing on the <code>DropTarget</code>.
         * <P>
         * Creation date: (03/14/2002 5:59:00 PM)
         * @param dtde the <code>DropTargetDragEvent</code>
         */
        public void dragOver(DropTargetDragEvent dtde){
   
            // To draw the drag image:
            //   First, repaint the real estate the drag image last occupied.
            //    Note that simply calling repaint() won't work because it effectively delays the repainting,
            //    possibly until after you have drawn the new drag image, and therefore, erases all or part of it.
            //    You really must paint the area immediately, using the, you guessed it, paintImmediately() method.
            //
            //   Second, you draw the ghost image in its new location.
            //    Note that you draw the image the same distance away from the mouse pointer as when the node
            //    was first clicked.
            if (!DRAG_IMAGE_SUPPORTED && dragImage != null) {
                Graphics2D g2d = (Graphics2D)getGraphics();

                // Calculate new location
                Point mouseLocation = dtde.getLocation();
                int newX = mouseLocation.x - mousePointOffset.x;
                int newY = mouseLocation.y - mousePointOffset.y;
   
                // Update if the location changed
                if (newX != lastGhostRect.x || newY != lastGhostRect.y) {
                    // Erase the last ghost image and cue line
                    paintImmediately(lastGhostRect.getBounds());   
                    // Remember where you are about to draw the new ghost image
                    lastGhostRect.setBounds(newX, newY, dragImage.getWidth(), dragImage.getHeight());
                    // Draw the ghost image
                    g2d.drawImage(dragImage,
                            AffineTransform.getTranslateInstance(lastGhostRect.getX(), lastGhostRect.getY()), null);
                }
               
                g2d.dispose();
            }

        }
   
        /**
         * Called if the user has modified the current drop gesture.
         * <P>
         * Creation date: (03/14/2002 5:59:00 PM)
         * @param dtde the <code>DropTargetDragEvent</code>
         */
        public void dropActionChanged(DropTargetDragEvent dtde){
        }
   
        /**
         * The drag operation has departed the <code>DropTarget</code> without dropping.
         * <P>
         * Creation date: (03/14/2002 5:59:00 PM)
         * @param dte the <code>DropTargetEvent</code>
         */
        public void dragExit(DropTargetEvent dte){
            // If necessary, erase the last ghost image
            if (!DRAG_IMAGE_SUPPORTED) {
                paintImmediately(lastGhostRect.getBounds());
            }
        }
   
        /**
         * The drag operation has terminated with a drop on this <code>DropTarget</code>.
         * This method is responsible for undertaking the transfer of the data associated with the
         * gesture. The <code>DropTargetDropEvent</code> provides a means to obtain a <code>Transferable</code>
         * object that represents the data object(s) to be transfered.<P>
         * From this method, the <code>DropTargetListener</code> shall accept or reject the drop via the  
         * acceptDrop(int dropAction) or rejectDrop() methods of the <code>DropTargetDropEvent</code> parameter.
         * <P>
         * Subsequent to acceptDrop(), but not before, <code>DropTargetDropEvent</code>'s getTransferable()
         * method may be invoked, and data transfer may be performed via the returned <code>Transferable</code>'s
         * getTransferData() method.
         * <P>
         * At the completion of a drop, an implementation of this method is required to signal the success/failure
         * of the drop by passing an appropriate <code>boolean</code> to the <code>DropTargetDropEvent</code>'s
         * dropComplete(boolean success) method.
         * <P>
         * Note: The actual processing of the data transfer is not required to finish before this method returns. It may be
         * deferred until later.
         * <P>
         * Creation date: (03/14/2002 5:59:00 PM)
         * @param dtde the <code>DropTargetDropEvent</code>
         */
        public void drop(DropTargetDropEvent dtde){
            dtde.rejectDrop();
        }
   
    }

    /**
     * Listens for the user triggering the pop up menu mouse event.
     * Note: This listener automatically corrects for the strange
     * bug that right clicking on a JTree node does not select it.
     */
    private class PopupListener extends MouseAdapter

        @Override
        public void mousePressed(MouseEvent e) {
            // Correct for the RMB not selecting bug.
            if (javax.swing.SwingUtilities.isRightMouseButton(e)) {
                int selectedRow = getRowForLocation(e.getX(), e.getY());
                   
                // If there is actually a row there, then select it.
                // Else, do nothing.
                if (selectedRow != -1) {   
                    setSelectionRow(selectedRow);
                }
            }
               
            maybeShowPopup(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {      
            maybeShowPopup(e);
        }
       
        /**
         * Show the popup, if the given mouse event is the popup trigger.
         * @param e the mouse event.
         */
        private void maybeShowPopup(MouseEvent e) {

            if (e.isPopupTrigger() && popupMenuProvider != null) {

                // Get the popup menu for the current selection path.
                TreePath selectionPath = getSelectionPath();
                JPopupMenu browserTreePopupMenu = popupMenuProvider.getPopupMenu(selectionPath);

                // Show the popup menu if it has at least one item.
                if (browserTreePopupMenu.getSubElements().length > 0) {
                    browserTreePopupMenu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        }
    }

    /**
     * Interface to define a provider for a popup menu for this tree.
     * @author Edward Lam
     */
    public interface PopupMenuProvider {
        /**
         * Get the popup menu for a given selection path in this tree.
         * @param selectionPath the path on which the popup menu was invoked.
         * @return the popup menu for this tree.
         */
        JPopupMenu getPopupMenu(TreePath selectionPath);
    }
   
    /**
     * This listener fires a leafNodeTriggered event if the user selects leaf nodes and hits enter.
     * @author Edward Lam
     */
    private class KeyStrokeNavigator extends PickListKeyStrokeNavigator {

        /**
         * Constructor for the KeyStrokeNavigator class.
         * Creation date: (07/09/2002 8:33:00 AM).
         * @param component Component - the component that this listener is navigating.
         */
        public KeyStrokeNavigator(Component component) {
            super(component);
        }
       
        /**
         * Watch for the user to press ENTER.
         * Creation date: (04/16/2002 3:33:00 PM)
         */
        @Override
        public void keyPressed(KeyEvent evt) {
          
            if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
                // Get the currently selected paths
                TreePath[] selectedPaths = getSelectionPaths();
               
                // Loop thru the paths and build a list of objects for all the leaves
                if (selectedPaths != null) {
                   
                    List<Object> userObjects = new ArrayList<Object>();
                    for (final TreePath treePath : selectedPaths) {
                           
                        Object lastComponent = treePath.getLastPathComponent();
                        if (lastComponent instanceof BrowserTreeNode
                            && ((BrowserTreeNode)lastComponent).isLeaf()) {
                               
                            userObjects.add(((BrowserTreeNode)lastComponent).getUserObject());
                        }
                    }
                   
                    // Fire off an event for these nodes
                    fireLeafNodeTriggeredEvent(userObjects);
                }
            }
           
            // Hand the event off to the super version
            super.keyPressed(evt);
        }

        /**
         * Get the String text displayed in the nth row of the pick list.
         * @param index the index of the string to return
         * @return the text displayed in the nth row
         */
        @Override
        public String getNthRowString(int index){
           
            // get the node corresponding to this row
            TreePath tp = getPathForRow(index);
            BrowserTreeNode node = (BrowserTreeNode) tp.getLastPathComponent();

            // use the cell renderer to find out what text it would print out
            boolean isLeaf = (node instanceof GemTreeNode);
            TreeCellRenderer tcr = getCellRenderer();
            Component c = tcr.getTreeCellRendererComponent(BrowserTree.this, node, false, false, isLeaf, 0, false);

            // The returned component is an instance of JLabel.  Get the text which would be displayed, in lower case.
            String rowString = ((JLabel) c).getText();

            return rowString;
        }

        /**
         * Get the number of rows in the pick list.
         * Creation date: (11/09/2001 3:03:52 PM)
         * @return int the number of rows in the pick list
         */
        @Override
        public int getNumRows(){
            return getRowCount();
        }

        /**
         * Get the index of the row in the pick list which is currently selected.
         * Creation date: (11/09/2001 3:03:52 PM)
         * @return int the index of the currently selected row
         */
        @Override
        public int getSelectedRowIndex(){
            return getLeadSelectionRow();
        }

        /**
         * Move the selection in the pick list to a given row.
         * Creation date: (11/09/2001 3:03:52 PM)
         * @param index int the index of the row to set selected
         */
        @Override
        public void selectRow(int index){
            setSelectionRow(index);
            scrollRowToVisible(index);
        }
    }

    /**
     * This listener fires a leafNodeTriggered event if the user double-clicks on a leaf node.
     * @author Frank Worsley
     */   
    private class LeafNodeMouseListener extends MouseClickDragAdapter {
       
         @Override
        public boolean mouseReallyClicked(MouseEvent e) {

             boolean doubleClicked = super.mouseReallyClicked(e);

             TreePath[] selectedPaths = getSelectionPaths();
            
             // If there are no selected paths, then use the path that was clicked on.
             if (selectedPaths == null || selectedPaths.length == 0) {
                 selectedPaths = new TreePath[1];
                 selectedPaths[0] = getPathForLocation(e.getX(), e.getY());
             }
           
             if (doubleClicked && SwingUtilities.isLeftMouseButton(e)) {
               
                 List<Object> userObjects = new ArrayList<Object>();
                
                 for (final TreePath treePath : selectedPaths) {
                       
                     if (treePath == null) {
                         continue;
                     }
                    
                     Object lastComponent = treePath.getLastPathComponent();
                    
                     if (lastComponent instanceof BrowserTreeNode
                         && ((BrowserTreeNode) lastComponent).isLeaf()) {
                           
                         userObjects.add(((BrowserTreeNode) lastComponent).getUserObject());
                     }
                 }
                
                 if (userObjects.size() > 0) {
                    fireLeafNodeTriggeredEvent(userObjects);
                 }
             }
           
             return doubleClicked;
         }
     }

    /**
     * Default BrowserTree constructor.
     */
    public BrowserTree() {
       
        // Use an empty model until the Services get initialized       
        BrowserTreeModel browserTreeModel = new BrowserTreeModel();
        setModel(browserTreeModel);

        // We don't want to see the root node.
        setRootVisible(false);
        setShowsRootHandles(true);

        // Set the renderer for this tree
        setCellRenderer(new BrowserTreeCellRenderer());
   
        // Add pop up menu capabilities.
        addMouseListener(new PopupListener());
   
        // Add the ability to quickly get to a gem by typing in its name
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyStrokeNavigator(this));

        // Add a listener to fire leaf node events if the user double-clicks
        addMouseListener(new LeafNodeMouseListener());

        // Register the drag-and-drop handler
        dragAndDropHandler = new DragAndDropHandler();

        // Lie and say we are a drop target.
        // We do this so we can draw the drag image if that operation is not
        // supported natively on the platform. We never actually accept any drops.
        setDropTarget(new DropTarget(this, dragAndDropHandler));
       
        // Set the default popup menu provider
        popupMenuProvider = new PopupMenuProvider() {
            public JPopupMenu getPopupMenu(TreePath selectionPath) {
                return browserTreeActions.getDefaultBrowserTreePopup(selectionPath);
            }
        };

        isDraggable = true;
        highlightCurrentModule = true;
       
        // Add a listener that will update the metadata viewer when the selection changes.
        addTreeSelectionListener(new TreeSelectionListener() {
                   
            public void valueChanged(TreeSelectionEvent evt) {
               
                TreePath path = evt.getNewLeadSelectionPath();
               
                if (path == null) {
                    return;
                }
               
                BrowserTreeNode node = (BrowserTreeNode) path.getLastPathComponent();
               
                if (navigatorOwner != null) {
   
                    final NavAddress address;
                    if (node instanceof GemTreeNode) {
                        GemEntity gemEntity = (GemEntity) node.getUserObject();
                        address = NavAddress.getAddress(gemEntity);
                        navigatorOwner.displayMetadata(address, false);
                           
                    } else if (node instanceof GemDrawer) {
                        final GemDrawer gemDrawer = (GemDrawer) node;
                        final ModuleName moduleName = gemDrawer.getModuleName();
                        if (gemDrawer.isNamespaceNode()) {
                            address = NavAddress.getModuleNamespaceAddress(moduleName);
                            navigatorOwner.displayMetadata(address, false);
                        } else {
                            MetaModule metaModule = navigatorOwner.getPerspective().getMetaModule(moduleName);
                            if (metaModule != null) {
                                address = NavAddress.getAddress(metaModule);
                                navigatorOwner.displayMetadata(address, false);
                            }
                        }
                    }
                }
            }
        });
    }
   
    /**
     * Set the navigator owner to use for editing/displaying gem metadata.
     * @param navigatorOwner the navigator owner to use
     */
    public void setNavigatorOwner(NavFrameOwner navigatorOwner) {
        this.navigatorOwner = navigatorOwner;
    }
   
    /**
     * @return the navigator owner being used to display/edit metadata. This may be
     * null if no navigator owner has been setup.
     */
    public NavFrameOwner getNavigatorOwner() {
        return navigatorOwner;
    }

    /**
     * Expand all visible tree nodes at a given depth.
     * <p>
     * <strong>This should be run on the AWT event-handler thread.</strong>
     *
     * @param depth 0-based level of the nodes to expand.  Nodes with this depth will be expanded.
     */
    public void expandLevel(int depth) {

        for (Enumeration<BrowserTreeNode> nodeEnum = UnsafeCast.unsafeCast(((BrowserTreeNode)getModel().getRoot()).breadthFirstEnumeration()); nodeEnum.hasMoreElements(); ) {

            BrowserTreeNode node = nodeEnum.nextElement();

            int nodeDepth = node.getLevel();
            if (nodeDepth <= depth) {
                expandPath(new TreePath(node.getPath()));

            } else if (nodeDepth > depth) {
                return;     // breadth first, so all leftover elements will have depth >= the current depth
            }
        }
    }
   
    /**
     * If there is a node in the tree for the given entity, this will select that
     * node and scroll it into view. If there is no such node, then this does nothing.
     * <p>
     * <strong>This should be run on the AWT event-handler thread.</strong>
     *
     * @param newGemEntity the entity to select
     */
    public void selectGemNode(GemEntity newGemEntity) {
       
        BrowserTreeModel browserTreeModel = (BrowserTreeModel) getModel();
        BrowserTreeNode gemNode = browserTreeModel.getTreeNode(newGemEntity);
       
        if (gemNode != null) {
            TreePath nodePath = new TreePath(gemNode.getPath());
            scrollPathIntoView(nodePath);
            setSelectionPath(nodePath);
        }     
    }
   
    /**
     * If there is a drawer node with given name, this will select that
     * node and scroll it into view. If there is no such node, then this does nothing.
     * <p>
     * <strong>This should be run on the AWT event-handler thread.</strong>
     *
     * @param drawerName the name of the drawer to select
     */
    public void selectDrawerNode(ModuleName drawerName) {
       
        Enumeration<BrowserTreeNode> allNodes = UnsafeCast.unsafeCast(((BrowserTreeNode)getModel().getRoot()).breadthFirstEnumeration());
       
        while (allNodes.hasMoreElements()) {
           
            BrowserTreeNode node = allNodes.nextElement();
                       
            if (node instanceof GemDrawer && ((GemDrawer)node).getModuleName().equals(drawerName)) {
                TreePath nodePath = new TreePath(node.getPath());
                setSelectionPath(nodePath);
                scrollPathIntoView(nodePath);
                break;
            }
        }
    }
   
    /**
     * If there is a node with the given title, this will select that node
     * and scroll it into view. If there is no such node, then this does nothing.
     * If there are several nodes with the given title, the first node will be selected.
     * <p>
     * <strong>This should be run on the AWT event-handler thread.</strong>
     *
     * @param nodeTitle the title of the node to select
     */
    public void selectNode(String nodeTitle) {
       
        Enumeration<BrowserTreeNode> allNodes = UnsafeCast.unsafeCast(((BrowserTreeNode)getModel().getRoot()).breadthFirstEnumeration());
       
        while (allNodes.hasMoreElements()) {
           
            BrowserTreeNode node = allNodes.nextElement();
                       
            if (node.getDisplayedString().equals(nodeTitle)) {
                TreePath nodePath = new TreePath(node.getPath());
                setSelectionPath(nodePath);
                scrollPathIntoView(nodePath);
                break;
            }
        }
    }

    /**
     * {@inheritDoc}
     * Additionally saves the list of children that includes the selected node.
     */
    @Override
    public void saveState() {

        // Remember the currently expanded nodes.
        Enumeration<TreePath> expandedPaths = getExpandedDescendants(new TreePath(getModel().getRoot()));
        savedExpandedPaths = new HashSet<TreePath>();

        if (expandedPaths != null) {
            while (expandedPaths.hasMoreElements()) {
                TreePath path = expandedPaths.nextElement();
                savedExpandedPaths.add(path);
            }
        }

        // Remember the selected node and the current position of the selected node in respect to all its siblings
        savedSelectionPath = getSelectionPath();
        if (savedSelectionPath != null) {
            BrowserTreeNode parentNode = (BrowserTreeNode) savedSelectionPath.getParentPath().getLastPathComponent();
            Enumeration<TreeNode> enumChildren = UnsafeCast.unsafeCast(parentNode.children());
            savedChildrenList  = new ArrayList<Object>();
            while (enumChildren.hasMoreElements()) {
                savedChildrenList.add(enumChildren.nextElement());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void restoreSavedState() {

        for (final TreePath treePath : savedExpandedPaths) {
            this.expandPath(treePath);
        }

        if (savedSelectionPath != null) {

            TreeNode lastPathComponent = ((TreeNode) savedSelectionPath.getLastPathComponent());
            TreePath parentPath = savedSelectionPath.getParentPath();
            TreeNode parentNode = (TreeNode)parentPath.getLastPathComponent();
            TreePath newPath = parentPath;  // starts out as parent path, but can be changed below.
           
            // Check if the path contains a drawerNode and is still valid (ie. the module is not removed)
            boolean workingModuleExists = false;
           
            Object[] nodesOnPath = savedSelectionPath.getPath();
            int indexOfDrawerNodeOnPath = 0;
          
            for (int i = 0; i < savedSelectionPath.getPathCount(); i++) {
                if (nodesOnPath[i] instanceof GemDrawer) {
                    indexOfDrawerNodeOnPath = i;
                    TreeNode prevNodeOnPath = (TreeNode) nodesOnPath[i - 1];

                    for (int j = 0; j < prevNodeOnPath.getChildCount(); j++) {
                        if (prevNodeOnPath.getChildAt(j).equals(nodesOnPath[i])) {
                            workingModuleExists = true;
                            break;
                        }
                    }
                    break;
                }
            }

            // If there was a module on the path and it no longer exists, its parent will be selected
            if (indexOfDrawerNodeOnPath != 0 && !workingModuleExists) {
                TreeNode[] tempNewPath = new TreeNode[indexOfDrawerNodeOnPath];
                for (int i = 0; i < indexOfDrawerNodeOnPath; i++){
                    tempNewPath[i]= (TreeNode) nodesOnPath[i];
                }
                newPath = new TreePath(tempNewPath);
             
            // Leaf nodes are reconstructed therefore need to compare the user objects to find the correct path.
            } else if (lastPathComponent instanceof GemTreeNode) {
              
                GemEntity lastNodeObj = (GemEntity)((GemTreeNode) lastPathComponent).getUserObject();

                if (!((BrowserTreeModel)getModel()).getShowPublicGemsOnly() || lastNodeObj.getScope().isPublic()){

                    // Finding the new tree node for the gem entity
                    GemTreeNode child = getNodeFromEntity(parentNode, lastNodeObj);
                    if (child != null){  // for the case of a search node, the child can become null
                        newPath = new TreePath(child.getPath());
                    }

                } else { // If only showing public gems and the selected gem is not public, propagate selection to one of its siblings.
                    int prevChildIndex = savedChildrenList.indexOf(lastPathComponent);
                    int prevChildCount = savedChildrenList.size();

                    // First search downward from the index of the selected gem for the next public gem
                    for (int i = prevChildIndex; i < prevChildCount; i++) {
                        TreeNode oldSibling = (TreeNode) savedChildrenList.get(i);
                        if (oldSibling instanceof GemTreeNode) {
                            TreePath publicChildPath = getPublicGemTreeNodePath(parentNode, (GemTreeNode) oldSibling);
                            if (publicChildPath != null) {
                                newPath = publicChildPath;
                                break;
                            }
                        }
                    }
                    // If cannot find any public gems so far, go back to the index and search upward
                    if (newPath == parentPath) {
                        for (int i = prevChildIndex - 1; i >= 0; i--) {
                            TreeNode oldSibling = (TreeNode) savedChildrenList.get(i);
                            if (oldSibling instanceof GemTreeNode) {
                                TreePath publicChildPath = getPublicGemTreeNodePath(parentNode, (GemTreeNode)oldSibling);
                                if (publicChildPath != null) {
                                    newPath = publicChildPath;
                                    break;
                                }
                            }
                        }
                    }
                }

            } else {
                newPath = savedSelectionPath;
            }

            // If the last component on the new path is an empty category node, propagate the selection up the hierarchy
            TreeNode newLastPathComponent = (TreeNode)newPath.getLastPathComponent();
            if (newLastPathComponent instanceof GemCategoryNode) {
                while(newLastPathComponent.getChildCount() == 0 && newLastPathComponent instanceof GemCategoryNode) {
                    newPath = newPath.getParentPath();
                    newLastPathComponent = (TreeNode)newPath.getLastPathComponent();
                }
            }
           
            setSelectionPath(newPath);
            scrollPathIntoView(newPath);

        }
    }

    /**
     * Get the TreeNodePath to the child if the child's gem entity is public.
     * Otherwise return null.
     * @param parentNode the parent tree node.
     * @param oldGemTreeNode the gem tree node to look at (from the saved state)
     * @return the child if the child's gem entity is public.
     * Otherwise null.
     */
    private TreePath getPublicGemTreeNodePath(TreeNode parentNode, GemTreeNode oldGemTreeNode) {

        GemEntity childUserObject = (GemEntity)oldGemTreeNode.getUserObject();
                 
        if (childUserObject.getScope().isPublic()) {
            GemTreeNode sibling = getNodeFromEntity(parentNode, childUserObject);
            return new TreePath(sibling.getPath());
        }
        return null;
    }

    /**
     * To find the tree node that contains the gem entity
     *
     * @param parentNode the parent of the node of interest
     * @param gemEntity the entity that we wish to find
     * @return child the GemTreeNode that contains the entity
     */
    private GemTreeNode getNodeFromEntity(TreeNode parentNode, GemEntity gemEntity){
       
        // Need to compare the qualified name because it is consistent even after recompiling
        QualifiedName entityName = gemEntity.getName();
        for (int i = 0; i < parentNode.getChildCount(); i++) {
            GemTreeNode child = ((GemTreeNode) parentNode.getChildAt(i));
            QualifiedName childName = ((GemEntity)child.getUserObject()).getName();
            if (childName.equals(entityName)) {
                return child;
            }
        }
        return null;
    }


    /**
     * Obtain the entity for a Gem from a BrowserTree path.
     * @return the entity of the Gem or NULL if not a gem (eg. a drawer or workspace)
     * @param path the path to the Gem
     */
    private static GemEntity getEntityFromPath(TreePath path) {

        // First we can check if this is a Gem.  The endpoint must be a terminator node
        TreeNode endpoint = (TreeNode)path.getLastPathComponent();

        if (endpoint.getAllowsChildren()) {
            // No good, this is not a terminator
            return null;
        }
   
        return (GemEntity)((BrowserTreeNode)endpoint).getUserObject();
    }

    /**
     * Sets whether or not the gems in the BrowserTree can be involved with Drag and Drop.
     * Creation date: (06/04/01 12:01:00 PM)
     * @param allowed boolean
     */
    public void setEnabledDragAndDrop(boolean allowed) {
        isDraggable = allowed; 
    }
   
    /**
     * Sets whether or not type expressions are displayed next to gem names in the tree.
     * @param display
     */
    public void setDisplayTypeExpr(boolean display) {
        displayTypeExpr = display;
        updateUI();
    }
   
    /**
     * Gets whether or not type expressions are displayed next to gem names in the tree.
     * @return true if type expressions are displayed
     */
    public boolean getDisplayTypeExpr() {
        return displayTypeExpr;
    }
   
    /**
     * Sets whether the current module should be highlighted in the tree.
     * @param highlightCurrentModule
     */
    public void setHighlightCurrentModule(boolean highlightCurrentModule) {
        this.highlightCurrentModule = highlightCurrentModule;
    }
   
    /**
     * Return whether the current module should be highlighted in the tree.
     * @return whether the current module should be highlighted in the tree.
     */
    boolean getHighlightCurrentModule() {
        return highlightCurrentModule;
    }
   
    /**
     * Set the provider of popups to this tree.
     * @param newMenuProvider the new popup menu provider.
     */
    public void setPopupMenuProvider(PopupMenuProvider newMenuProvider) {
        this.popupMenuProvider = newMenuProvider;
    }
   
    /**
     * Return the BrowserTreeActions object associated with this tree
     * @return BrowserTreeActions
     */
    public BrowserTreeActions getBrowserTreeActions() {
        return browserTreeActions;
    }
   
    /**
     * Return the current drag image if the gem browser is the source of the drag.
     * Creation date: (03/15/01 3:48:00 PM)
     * @return BufferedImage the image to drag, or null if the browser is not the source of a drag.
     */
    public BufferedImage getDragImage() {
        return dragAndDropHandler.dragImage;
    }

    /**
     * Return the current drag offset if the gem browser is the source of the drag.
     * Creation date: (03/15/01 3:51:00 PM)
     * @return Point the current drag offset.
     */
    public Point getDragOffset() {
        return new Point(dragAndDropHandler.mousePointOffset);
    }

    /**
     * Add a LeafNodeTriggeredListener to the listener list.
     * Creation date: (04/17/2002 10:06:00 AM).
     * @param listener LeafNodeTriggeredListener - the listener to add
     */
    public void addLeafNodeTriggeredListener(LeafNodeTriggeredListener listener) {
      
        if (listener != null) {
            leafNodeTriggeredListeners.add(listener);
        }
    }
   
    /**
     * Notify the appropriate registered listeners that a node has been triggered.
     * Creation date: (04/17/2002 10:08:00 AM).
     * @param userObjects the objects of the nodes that were triggered
     */
    protected void fireLeafNodeTriggeredEvent(List<Object> userObjects) {
       
        // Create the LeafNodeTriggeredEvent
        LeafNodeTriggeredEvent event = new LeafNodeTriggeredEvent(this, userObjects);
       
        // Notify each listener
        for (int i = 0, listenerCount = leafNodeTriggeredListeners.size(); i < listenerCount; i++) {
           
            leafNodeTriggeredListeners.get(i).leafNodeTriggered(event);
        }
    }

    /**
     * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
     */
    @Override
    public String getToolTipText(MouseEvent mouseEvent) {
              
        Point mouseLocation = mouseEvent.getPoint();
        TreePath path = getPathForLocation(mouseLocation.x, mouseLocation.y);
       
        if (path != null) {
            BrowserTreeNode node = (BrowserTreeNode) path.getLastPathComponent();
            
            if (node instanceof GemTreeNode && node.isLeaf ()) {
                BrowserTreeModel browserTreeModel = (BrowserTreeModel) getModel();
                ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(browserTreeModel.getPerspective().getWorkingModuleTypeInfo());
                GemEntity gemEntity = (GemEntity)node.getUserObject()
                return ToolTipHelpers.getEntityToolTip(gemEntity, namingPolicy, this);
               
            } else if (node instanceof GemDrawer) {
               
                BrowserTreeModel browserTreeModel = (BrowserTreeModel)getModel();
                GemDrawer gemDrawer = (GemDrawer)node;
                ModuleName moduleName = gemDrawer.getModuleName();

                // Display visibility description in tooltip
                String drawerVisibility;
                if (!browserTreeModel.isVisibleModule(moduleName)) {
                    drawerVisibility = BrowserMessages.getString("GB_NotImported");
                   
                } else if (browserTreeModel.isWorkingModule(moduleName)) {
                    drawerVisibility = BrowserMessages.getString("GB_CurrentlyEdited");
                       
                } else {
                    drawerVisibility = BrowserMessages.getString("GB_Imported");
                }
               
                StringBuilder toolTipText = new StringBuilder("<html><b>" + node.getDisplayedString() + "</b>&nbsp;(" + drawerVisibility + ")<br>");
                toolTipText.append(node.getSecondaryToolTipText());       
                toolTipText.append("</html>");

                return toolTipText.toString();
               
            } else {
                return node.getToolTipText();
            }
        }
       
        return null;
    }
   
    /**
     * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
     */
    @Override
    public Point getToolTipLocation(MouseEvent e) {

        Point mouseLocation = e.getPoint();
        TreePath path = getPathForLocation(mouseLocation.x, mouseLocation.y);
       
        if (path != null) {

            Rectangle bounds = getPathBounds(path);
           
            if (bounds != null) {

                Rectangle visibleRect = getVisibleRect();
   
                // always display tooltip along the right side of the tree
                int x = visibleRect.x + visibleRect.width - 20;
                return new Point (x > 0 ? x : 0, bounds.y);
            }
        }
       
        return null;
    }
}
TOP

Related Classes of org.openquark.gems.client.browser.BrowserTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.