Package org.openquark.gems.client.explorer

Source Code of org.openquark.gems.client.explorer.ExplorerTree$ExplorerCellEditor

/*
* 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.
*/


/*
* ExplorerTree.java
* Creation date: Jan 17th 2003
* By: Ken Wong
*/

package org.openquark.gems.client.explorer;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.plaf.TreeUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.openquark.cal.metadata.FunctionMetadata;
import org.openquark.gems.client.CodeGem;
import org.openquark.gems.client.CollectorGem;
import org.openquark.gems.client.FunctionalAgentGem;
import org.openquark.gems.client.Gem;
import org.openquark.gems.client.RecordFieldSelectionGem;
import org.openquark.gems.client.ReflectorGem;
import org.openquark.gems.client.valueentry.ValueEditor;


/**
* This class represents the JTree JComponent that is used in the TableTopExplorer.
* @author Ken Wong
*/
public class ExplorerTree extends JTree implements Autoscroll{
   
    private static final long serialVersionUID = 1943630679610833258L;

    /**
     * The editor used to change the names of the code gems and collector gems
     * @author Ken Wong
     */
    private static class ExplorerCellEditor extends DefaultTreeCellEditor {
       
        /**
         * Default Constructor for this editor
         * @param tree the explorer tree the editor is for
         * @param renderer the cell renderer for the tree
         * @param field the cell editor for the field we should edit
         */
        ExplorerCellEditor(ExplorerTree tree, DefaultTreeCellRenderer renderer, ExplorerTreeCellEditor field) {
            super (tree, renderer, field);
        }
       
        /**
         * @see org.openquark.gems.client.explorer.ExplorerTreeCellEditor#getTreeCellEditorComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int)
         */
        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
           
            Component component = super.getTreeCellEditorComponent(tree, value, isSelected, expanded, leaf, row);

            renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, true);
            editingIcon = renderer.getIcon();
           
            boolean valueEditor = ((DefaultTreeCellEditor.EditorContainer) component).getComponent(0) instanceof ValueEditor;
            tree.setRowHeight(valueEditor ? -1 : 16);
           
            return component;
        }
    }

    /**
     * Used to listen to mouse clicked events and when one is received on a focussable node the
     * navigation helper is notified that the user wants to focus on the desired gem.
     */
    private class ExplorerMouseEventListener extends MouseAdapter {
        /**
         * Clicking on a focussable node should trigger a navigation focus event on the node.
         * @param e The mouse event that just happened
         */
        @Override
        public void mouseClicked(MouseEvent e) {
            Point location = e.getPoint();
            if (isFocusablePoint(location)) {
                TreePath path = getPathForLocation(location.x, location.y);
                Object node = path.getLastPathComponent();
                if (node instanceof ExplorerGemNode) {
                    // Indicate that the navigation helper should change to focus on the gem
                    Gem gem = ((ExplorerGemNode)node).getGem();
                    navigationHelper.focusOn(gem);
                }
            }
        }
    }
   
    /**
     * This class uses the navigation helper to determine if a node is focusable.  If it is then the
     * cursor is switched to a hand to mimic the behaviour in browsers of mousing over a link.
     */
    private class ExplorerMouseMotionListener extends MouseMotionAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            if (isFocusablePoint(e.getPoint())) {
                // We can focus on the gem and its node should be rendered as a hyperlink so change
                // to the hand cursor
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            } else {
                // The mouse is not over a hyperlink so ensure that it is reset to the default cursor
                setCursor(null);
            }
        }
    }
   
    /**
     * Whenever something in the model changes we need to make sure the cursor is reset to the default
     * cursor.  If we don't then the cursor may be left as a hand even though the mouse is no longer
     * over a hyperlink.
     */
    private class ExplorerTreeModelListener implements TreeModelListener {
        public void treeNodesChanged(TreeModelEvent e) {
            setCursor(null);
        }
       
        public void treeNodesInserted(TreeModelEvent e) {
            setCursor(null);
        }

        public void treeNodesRemoved(TreeModelEvent e) {
            setCursor(null);
        }

        public void treeStructureChanged(TreeModelEvent e) {
            setCursor(null);
        }
    }
   
    /** The TableTopExplorerOwner of the TableTopExplorer using this tree. */
    private TableTopExplorerOwner owner;
   
    /** The current background image (null if none). */
    private BufferedImage backgroundImage;

    /**
     * Controls whether the tree is drawn using the UIManager specified look and feel or a customized
     * look and feel that uses alternating light and dark bands to provide visual separation.
     */
    private boolean useBandedLookAndFeel = false;
   
    /** The set of expanded paths that was saved when the tree state was saved. */
    private Set<TreePath> savedExpandedPaths = new HashSet<TreePath>();
   
    /** The saved selection path. */
    private TreePath savedSelectionPath = null;

    /**
     * Navigation helper used to determine extra information about whether nodes can be focussed on.  The
     * navigation helper can be null in which case navigation functionality will be disabled.
     */
    private final ExplorerNavigationHelper navigationHelper;
   
    /**
     * Constructor for ExplorerGemTree.
     * @param explorerRootNode the root node to use for the tree
     * @param owner the explorer owner
     * @param tableTopExplorer the explorer this tree is for
     */
    public ExplorerTree(ExplorerRootNode explorerRootNode, TableTopExplorerOwner owner, TableTopExplorer tableTopExplorer) {
        this(explorerRootNode, owner, tableTopExplorer, null, null);
    }

    /**
     * Constructor for ExplorerGemTree.
     * @param explorerRootNode the root node to use for the tree
     * @param owner the explorer owner
     * @param tableTopExplorer the explorer this tree is for
     * @param navigationHelper A helper that allows navigation between tree nodes
     * @param cellRenderer A customized cell renderer.  If this is null then the default renderer will be
     * used
     */
    public ExplorerTree(ExplorerRootNode explorerRootNode,
            TableTopExplorerOwner owner,
            TableTopExplorer tableTopExplorer,
            ExplorerNavigationHelper navigationHelper,
            DefaultTreeCellRenderer cellRenderer) {
       
        super(explorerRootNode);
       
        this.owner = owner;
        this.navigationHelper = navigationHelper;
       
        setEditable(true);
        setRowHeight(-1);
        setInvokesStopCellEditing(true);
        setFocusCycleRoot(true);
       
        // Create a default cell renderer if necessary
        if (cellRenderer == null) {
            cellRenderer = new ExplorerCellRenderer(owner);
        }
           
        setCellRenderer(cellRenderer);
        setCellEditor(new ExplorerCellEditor(this, cellRenderer, new ExplorerTreeCellEditor(tableTopExplorer)));
       
        TreeSelectionModel selectionModel = new DefaultTreeSelectionModel();
        selectionModel.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        setSelectionModel(selectionModel);
       
        ToolTipManager.sharedInstance().registerComponent(this);

        // If we have a navigation helper then add a mouse listeners to change the cursor when it
        // is over a hyperlink, focus on a node when clicked, and listen to model changes to ensure
        // the cursor is reset correctly.
        if (navigationHelper != null) {
            addMouseListener(new ExplorerMouseEventListener());
            addMouseMotionListener(new ExplorerMouseMotionListener());
            getModel().addTreeModelListener(new ExplorerTreeModelListener());
        }
    }

    /**
     * @see javax.swing.JComponent#getToolTipText()
     */
    @Override
    public String getToolTipText(MouseEvent e) {
       
        TreePath treePath = getPathForLocation(e.getX(), e.getY());

        if (treePath == null) {
            return null;
        }
       
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
        Object userObject = node.getUserObject();
       
        if (userObject instanceof Gem.PartInput) {
            return owner.getHTMLFormattedMetadata((Gem.PartInput) userObject);
           
        } else if (userObject instanceof FunctionalAgentGem) {
            return owner.getHTMLFormattedFunctionalAgentGemDescription((FunctionalAgentGem) userObject);
           
        } else if (userObject instanceof CollectorGem) {
            // Start with the open html tag
            String toolTip = "<html>";
            
            // If available add the metadata short description      
            FunctionMetadata metadata = ((CollectorGem)userObject).getDesignMetadata();
            String shortDescription = metadata.getShortDescription();
            if (shortDescription != null && shortDescription.length() > 0) {
                toolTip += "<p>" +  shortDescription + "</p>";
            }

            // Stick in the result type information
            toolTip += ExplorerMessages.getString("ResultTypeToolTip", owner.getTypeString(((CollectorGem) userObject).getResultType()));
           
            // End with the end html tag
            toolTip += "</html>";
            return toolTip;
           
        } else if (userObject instanceof ReflectorGem) {
            return "<html>" + ExplorerMessages.getString("OutputTypeToolTip", owner.getTypeString(((ReflectorGem)userObject).getOutputPart().getType())) + "</html>";
           
        } else if (userObject instanceof CodeGem) {
            return "<HTML><B>" + ((CodeGem) userObject).getUnqualifiedName() + "</B></HTML>";
       
        } else if (userObject instanceof RecordFieldSelectionGem) {
            return "<html>" + ExplorerMessages.getString("FieldToExtractToolTip", ((RecordFieldSelectionGem)userObject).getFieldName().toString() + "</html>");
        }
       
        return null;
    }
   
    /**
     * @see java.awt.dnd.Autoscroll#autoscroll(Point)
     */
    public void autoscroll(Point point) {
        Rectangle visibleRect = getVisibleRect();
        Insets insets =  getAutoscrollInsets();
        int offset = (point.y > (getHeight() - insets.bottom)) ? 20 : -20;
        scrollRectToVisible(new Rectangle(visibleRect.x, visibleRect.y + offset, visibleRect.width, visibleRect.height));
    }
   
    /**
     * @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
     */
    public Insets getAutoscrollInsets() {
        Rectangle visibleRect = getVisibleRect();
        int topScrollSection = visibleRect.y + 30;
        int bottemScrollSection = getHeight() - (visibleRect.height + visibleRect.y - 30);
        return new Insets (topScrollSection, 0, bottemScrollSection, 0);
    }
   
    /**
     * @see javax.swing.JComponent#paintComponent(Graphics)
     * Paint the Explorer
     * @param g java.awt.Graphics
     */
    @Override
    public void paintComponent(java.awt.Graphics g) {

        if (backgroundImage != null) {

            // Paint a tiled image         
            Rectangle bounds = g.getClipBounds();

            // Determine which tiled instances intersect with bounds and draw them
            int imageWidth = backgroundImage.getWidth();
            int imageHeight = backgroundImage.getHeight();
           
            int offsetX = (bounds.x / imageWidth) * imageWidth;
            int offsetY = (bounds.y / imageHeight) * imageHeight;

            for (int yRegistration = offsetY; yRegistration < bounds.y + bounds.height; yRegistration += imageHeight) {
                for (int xRegistration = offsetX; xRegistration < bounds.x + bounds.width; xRegistration += imageWidth) {
                    g.drawImage(backgroundImage, xRegistration, yRegistration, null);
                }
            }
        }
       
        // Just paint a simple background!
        super.paintComponent(g);
    }
   
    /**
     * Sets the background image of this tree.
     * @param backgroundImage the background image to use, null for normal background.
     */
    public void setBackgroundImage(BufferedImage backgroundImage) {
       
        // we lighten the colours a bit to make it easier to read.
        if (backgroundImage == null) {
            this.backgroundImage = null;
            setOpaque(true);
            repaint();   
            return;
        }
       
        RescaleOp rescaleOp = new RescaleOp(1.1f, 35, null);
       
        // Create an RGB buffered image
        BufferedImage bimage = new BufferedImage(backgroundImage.getWidth(null), backgroundImage.getHeight(null), BufferedImage.TYPE_BYTE_GRAY);

        // Copy non-RGB image to the RGB buffered image
        Graphics2D g = bimage.createGraphics();
        g.drawImage(backgroundImage, 0, 0, null);
       
        // Copy non-RGB image to the RGB buffered image
        this.backgroundImage = rescaleOp.filter(bimage, null);
        setOpaque(false);
        repaint();
    }

    /**
     * @param useBandedLookAndFeel true if the banded look&feel should be used, false for normal LAF
     */
    public void setBandedLookAndFeel(boolean useBandedLookAndFeel) {
       
        this.useBandedLookAndFeel = useBandedLookAndFeel;
        if (useBandedLookAndFeel) {
            super.setUI(new BandedTreeUI());
        } else {   
            super.setUI(UIManager.getUI(this));
        }

        // Inform the cell renderer since it will behave slightly differently for the banded look and feel
        TreeCellRenderer cellRenderer = getCellRenderer();
        if (cellRenderer instanceof ExplorerCellRenderer) {
            ((ExplorerCellRenderer)cellRenderer).setBandedLookAndFeel(useBandedLookAndFeel);
        }
    }
   
    /**
     * Overide this method so that we can explicitly use a special banded look and feel even if a different
     * look and feel is requested.
     * @param newUI
     */
    @Override
    public void setUI(TreeUI newUI) {
        if (useBandedLookAndFeel) {
            super.setUI(new BandedTreeUI());
        } else {
            super.setUI(newUI);
        }
    }
   
   
    /**
     * Returns the user object stored in the node underneath specified location.
     * @param location the location for the node
     * @param source the component in whose coordinate space the location is
     * @return the user object or null if there is no node at the given location
     */
    public Object getUserObjectAt(Point location, JComponent source) {
        return getUserObjectAt(SwingUtilities.convertPoint(source, location, this));
    }
   
    /**
     * Returns the user object stored in the node underneath specified location.
     * @param location the location for the node
     * @return the user object or null if there is no node at the given location
     */
    public Object getUserObjectAt(Point location) {
        TreePath path = getPathForLocation(location.x, location.y);
        return path == null ? null : ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObject();
    }

    /**
     * Determines if the point in question is over a focussable point in the tree.  The node at this point
     * will be rendered as a hyperlink if this is a focussable point so this method can be used to check
     * if the cursor should be changed or to take action when the user clicks.
     * @param location The point that will be checked.  This point should be relative to the explorer tree.
     * @return Returns true if the point is over a hyperlink rendered node and false if not.
     */
    boolean isFocusablePoint(Point location) {
        // As a safety precaution, bail early if we don't have a navigation helper
        if (navigationHelper == null) {
            return false;
        }
       
        // Get the tree path for the specified location
        TreePath path = getPathForLocation(location.x, location.y);
        if (path != null) {
            // We don't want to consider the icon as part of the hyperlink
            Rectangle pathBounds = getPathBounds(path);
            Point relativePoint = new Point(location.x - pathBounds.x, location.y - pathBounds.y);
            ExplorerCellRenderer renderer = (ExplorerCellRenderer)getCellRenderer();
            if (!renderer.isPointOverIcon(relativePoint)) {
                Object node = path.getLastPathComponent();
                if (node instanceof ExplorerGemNode) {
                    // Check if the gem is focusable which means it will be rendered as a hyperlink
                    Gem gem = ((ExplorerGemNode)node).getGem();
                    if (navigationHelper.isFocusable(gem)) {
                        return true;
                    }
                }
            }
        }
       
        // Not a focusable point
        return false;
    }
   
    /**
     * Saves the current state of the tree: the selected node and the expanded nodes.
     */
    void saveState () {
       
        // Remember the currently expanded nodes.
        Enumeration<TreePath> expandedPaths = getExpandedDescendants(new TreePath(getModel().getRoot()));
        savedExpandedPaths = new HashSet<TreePath>();
       
        while (expandedPaths != null && expandedPaths.hasMoreElements()) {
            TreePath path = expandedPaths.nextElement();
            savedExpandedPaths.add(path);
        }

        // Remember the selected node.
        savedSelectionPath = getSelectionPath();
    }

    /**
     * Restores the state that was last saved. Does nothing if no state was ever saved. The restored
     * state might not exactly match the saved state if the tree model changed in such a way that a
     * full restore is not possible (ie: nodes removed).
     */   
    void restoreSavedState () {
       
        for (final TreePath treePath : savedExpandedPaths) {
            this.expandPath(treePath);
        }
       
        if (savedSelectionPath != null) {
            setSelectionPath(savedSelectionPath);
        }
    }
}


/**
* Provides a customized look and feel based that is identical to the BasicTreeUI except for alternating
* bands of colour to separate the top level of nodes in the tree.  When the root node is hidden this
* provides separation between what appear to be the 'root' nodes of the tree.
*/
class BandedTreeUI extends BasicTreeUI {

    /** Color for the light band */
    private Color lightBand = new Color(220, 220, 220, 100);
   
    /** Color for the dark band. */
    private Color darkBand = new Color(180, 180, 180, 100);
   
    /**
     * @see javax.swing.plaf.basic.BasicTreeUI#paintRow(java.awt.Graphics, java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle, javax.swing.tree.TreePath, int, boolean, boolean, boolean)
     */
    @Override
    protected void paintRow(Graphics g,
            Rectangle clipBounds,
            Insets insets,
            Rectangle bounds,
            TreePath path,
            int row,
            boolean isExpanded,
            boolean hasBeenExpanded,
            boolean isLeaf) {
       
        // Don't paint the renderer if editing this row.
        if(editingComponent != null && editingRow == row) {
            return;
        }
       
        Component component = null;
        TreeNode currentNode = (TreeNode)path.getLastPathComponent();
        TreeNode rootNode = (TreeNode)getModel().getRoot();
       
        if (currentNode != rootNode) {
           
            // Determine the colour for this particular node so that we alternate colour for
            // top level gems
            TreeNode topLevelNode = getTopLevelNode(rootNode, currentNode);
            int index = getModel().getIndexOfChild(rootNode, topLevelNode);
            if (index % 2 == 0) {
                g.setColor(darkBand);
            } else {
                g.setColor(lightBand);
            }

            // Fill in the entire space available for the current row with the previously set colour
            g.fillRect(clipBounds.x, bounds.y, clipBounds.width, bounds.height);
        }

        component = currentCellRenderer.getTreeCellRendererComponent (tree, path.getLastPathComponent(),
                tree.isRowSelected(row), isExpanded, isLeaf, row,
                tree.hasFocus());
       
        rendererPane.paintComponent (g, component, tree, bounds.x, bounds.y,
                bounds.width, bounds.height, true);
    }
   
    /**
     * Method to retrieve the top level node for the specified node. The top level node is defined to be
     * the ancestor that is one level below the root. In other words, with the root node hidden it will
     * be the top node drawn in the tree. This method should not be called on the root gem or a
     * NullPointerException will be thrown.
     * @param rootNode the real root node
     * @param currentNode the current node whose top level parent we are searching for
     * @return TreeNode the top-level parent of the current node
     */
    private TreeNode getTopLevelNode(TreeNode rootNode, TreeNode currentNode) {

        if (currentNode == null) {
            return null;
        }
       
        // Determine if this is the top level node, otherwise recurse up our parent again.
        TreeNode parentNode = currentNode.getParent();
        if (parentNode == rootNode) {
            return currentNode;
        } else {
            return getTopLevelNode(rootNode, parentNode);
        }
    }
}
TOP

Related Classes of org.openquark.gems.client.explorer.ExplorerTree$ExplorerCellEditor

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.