Package DisplayProject.controls

Source Code of DisplayProject.controls.TreeViewWidget

package DisplayProject.controls;

import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;

import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import DisplayProject.Constants;
import DisplayProject.DisplayNode;
import DisplayProject.PsuedoDoubleClickAction;
import DisplayProject.TreeField;
import DisplayProject.TreeViewCellRenderer;
import DisplayProject.TreeViewModel;
import DisplayProject.UIutils;
import DisplayProject.actions.ActionMgr;
import DisplayProject.actions.PendingAction;
import DisplayProject.actions.WidgetState;
import Framework.ErrorMgr;
import Framework.ForteKeyboardFocusManager;

/**
* The customised JTree widget.
*
* @author Craig Mitchell
* @since 10/04/2008
*/
@SuppressWarnings("serial")
public class TreeViewWidget extends JTree implements Autoscroll, TreeField {
    private static final String uiClassID = "TreeFieldUI";
    private boolean isAutoScrollEnabled = true; // CraigM: 10/04/2008
    private int scrollPolicy; // Constants.NP_... CraigM:16/05/2008

    /**
     * Main constructor.
     *
     * @param newModel
     */
    public TreeViewWidget(TreeViewModel dtm) {
      super(dtm);
      this.scrollPolicy = Constants.NP_DEFAULT;
      this.addTreeSelectionListener(dtm);
        this.setCellRenderer(new TreeViewCellRenderer());
        this.setShowsRootHandles(true);
        this.putClientProperty("JTree.lineStyle", "Angled");
        this.setDoubleBuffered(true);
        this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
//      Forte fires the double click event when the user presses the ENTER key while in a TreeView - do the same here
        this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK);
        this.getActionMap().put(PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK, new PsuedoDoubleClickAction());

//      When the control gets focus set focus to the first node if none already selected
        this.addFocusListener(new FocusAdapter(){
            public void focusGained(FocusEvent e) {
                if (TreeViewWidget.this.getRowCount() > 0 && TreeViewWidget.this.getSelectionCount() == 0) {
                  TreeViewWidget.this.setSelectionRow(0);
                }
            }
        });
       
        // When a node is collapsed or expanded, ensure the corresponding DisplayNode is kept in Sync
        this.addTreeExpansionListener(new TreeExpansionListener() {
            public void treeExpanded(TreeExpansionEvent event) {
                Object node = event.getPath().getLastPathComponent();
                TreeViewWidget.this.setSelectionPath(event.getPath());
                if (node instanceof DisplayNode) {
                    ((DisplayNode)node).setOpened_PRIVATE(true);
                }
            }
            public void treeCollapsed(TreeExpansionEvent event) {
                Object node = event.getPath().getLastPathComponent();
                TreeViewWidget.this.setSelectionPath(event.getPath());
                if (node instanceof DisplayNode) {
                    ((DisplayNode)node).setOpened_PRIVATE(false);
                }
            }
        });

        // Clicking the right mouse button should also select the node.  CraigM: 19/03/2008
        this.addMouseListener(new MouseAdapter() {
          @Override
          public void mouseClicked(MouseEvent e) {
            // CraigM:12/06/2008 - Don't allow selections if we are INACTIVE
            if (SwingUtilities.isRightMouseButton(e) && WidgetState.get(TreeViewWidget.this) != Constants.FS_INACTIVE) {
              int row = TreeViewWidget.this.getRowForLocation(e.getX(), e.getY());

              if (row != -1) {
                TreeViewWidget.this.setSelectionRow(row);
              }
            }
          }
        });
       
        // In forte, trees could never be expanded if they didn't have children
        this.addTreeWillExpandListener(new TreeWillExpandListener() {
      /*
       * (non-Javadoc)
       *
       * @see javax.swing.event.TreeWillExpandListener#treeWillCollapse(javax.swing.event.TreeExpansionEvent)
       */
      public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
        // CraigM:12/06/2008 - Don't allow expand / collapse if we are inactive
        if (WidgetState.get(TreeViewWidget.this) == Constants.FS_INACTIVE) {
          throw new ExpandVetoException(event);
        }
      }

      /* (non-Javadoc)
       * @see javax.swing.event.TreeWillExpandListener#treeWillExpand(javax.swing.event.TreeExpansionEvent)
       */
      public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
        // CraigM:12/06/2008 - Don't allow expand / collapse if we are inactive
        if (WidgetState.get(TreeViewWidget.this) == Constants.FS_INACTIVE) {
          throw new ExpandVetoException(event);
        }

        Object node = event.getPath().getLastPathComponent();
        if (node instanceof DisplayNode) {
          if (((DisplayNode) node).isLeaf()) {
            ExpandVetoException errorVar = new ExpandVetoException(event);
            ErrorMgr.addError(errorVar);
            throw errorVar;
          }
        }
      }
    });
       
        // If drag is enabled, then when the enter key is pressed, we expand/collapse the node.
        // No clue why Forte only did this if drag was enabled.  CraigM: 24/03/2008.
        this.addKeyListener(new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
              if (TreeViewWidget.this.getDragEnabled()) {
                if (TreeViewWidget.this.getSelectionRows().length > 0) {
                  int row = TreeViewWidget.this.getSelectionRows()[0];
                 
                  if (TreeViewWidget.this.isExpanded(row)) {
                    TreeViewWidget.this.collapseRow(row);
                  }
                  else {
                    TreeViewWidget.this.expandRow(row);
                  }
                }
              }
            }
          }
        });
      // TF:15/12/2009:DET-141:Set this up to allow inheriting of popup menu
      this.setInheritsPopupMenu(true);
    }
   
    /* (non-Javadoc)
     * @see javax.swing.JTree#getUIClassID()
     */
    @Override
    public String getUIClassID() {
        return uiClassID;
    }

    /* (non-Javadoc)
     * @see javax.swing.JTree#updateUI()
     */
    @Override
    public void updateUI() {
        this.setUI(TreeFieldUI.createUI(this));
        this.invalidate();
    }

    /**
     * Enables/Disables Autoscroll when doing drag and drop.
     * Default is enabled.
     *
     * CraigM: 10/04/2008
     *
     * @see Autoscroll
     *
     * @param pIsAutoScrollEnabled
     */
    public void setIsAutoScrollEnabled(boolean pIsAutoScrollEnabled) {
      this.isAutoScrollEnabled = pIsAutoScrollEnabled;
    }

    public boolean getIsAutoScrollEnabled() {
      return this.isAutoScrollEnabled;
    }

    /* (non-Javadoc)
     * @see javax.swing.JTree#setModel(javax.swing.tree.TreeModel)
     */
    public void setModel(TreeModel newModel) {
        // TF:21/9/07:Removed old listener prior to adding in a new one
        if (getModel() instanceof TreeViewModel) {
            removeTreeWillExpandListener((TreeViewModel)getModel());
        }
        super.setModel(newModel);

        if (newModel != null) {
            ((TreeViewModel)newModel).setTree(this);
            addTreeWillExpandListener(((TreeViewModel)newModel));
        }

        TreeNode tr = (TreeNode)newModel.getRoot();
        if (tr != null) {
            TreePath tp = new TreePath(tr);
            checkExpand(tp);
            scrollPathToVisible(tp);
        }
    }
   
    /**
     * @param fromNode
     * @return A collection containing fromNode and all nodes under it (all its children, their children, etc.
     * CraigM:29/07/2008.
     */
    private ArrayList<DisplayNode> getNodes(DisplayNode fromNode) {
      return this.getNodes(fromNode, new ArrayList<DisplayNode>());
    }
   
    /**
     * Recursive method only to be called from {@link TreeViewWidget#getNodes(DisplayNode)}
     * CraigM:29/07/2008.
     */
    @SuppressWarnings("unchecked")
    private ArrayList<DisplayNode> getNodes(DisplayNode fromNode, ArrayList<DisplayNode> list) {
      list.add(fromNode);

      for (Enumeration<DisplayNode> e = fromNode.children(); e.hasMoreElements();) {
        this.getNodes(e.nextElement(), list);
      }
     
      return list;
    }

    /**
   * Check if the parent and any of its children are expanded. If so
   * then make sure that are also expanded on the UI.
   *
   * @param parent
     * CraigM:29/07/2008.
   */
    private void checkExpand(TreePath parent) {
      DisplayNode node = (DisplayNode)parent.getLastPathComponent();
      HashMap<DisplayNode, Boolean> isOpenedStates = new HashMap<DisplayNode, Boolean>();
    ArrayList<DisplayNode> nodes = this.getNodes(node);

    for (DisplayNode aNode : nodes) {
      isOpenedStates.put(aNode, aNode.isOpened());
    }
    this.checkExpand(parent, isOpenedStates);
    }

    /**
     * Recursive method only to be called from {@link TreeViewWidget#checkExpand(TreePath)}
     * CraigM:29/07/2008.
     */
    @SuppressWarnings("unchecked")
  private void checkExpand(TreePath parent, HashMap<DisplayNode, Boolean> isOpenedStates) {
      DisplayNode node = (DisplayNode)parent.getLastPathComponent();

      // CraigM:29/07/2008 - We need to store all the isOpened states as when we ask a node to expand/collapse, the parent
      // nodes are automatically expanded, which overrides the correct isOpened value.
      if (isOpenedStates == null) {
        isOpenedStates = new HashMap<DisplayNode, Boolean>();
        ArrayList<DisplayNode> nodes = this.getNodes(node);
        for (DisplayNode aNode : nodes) {
          isOpenedStates.put(aNode, aNode.isOpened());
        }
      }
     
        if (node.getChildCount() > 0){
            for (Enumeration<DisplayNode> e = node.children(); e.hasMoreElements();){
                TreePath path = parent.pathByAddingChild(e.nextElement());
                checkExpand(path, isOpenedStates);
            }
        }
        // CraigM:29/07/2008 - Use the stored isOpened value
        // if (node.getIsOpened()){
        if (isOpenedStates.get(node)){
            expandPath(parent);
        } else {
          collapsePath(parent);
        }
    }
  /* (non-Javadoc)
   * @see java.awt.Component#addMouseListener(java.awt.event.MouseListener)
   */
  @Override
  public synchronized void addMouseListener(MouseListener l) {
         // AGD 11/3/08
         // Make sure that the drag mouse listener is always the last registered
         // This ensures that the other events get processed before the Drag
         // happens. This is the same sequence as Forte.
        MouseListener[] mlList = this.getMouseListeners();
       
        // Check if the Drag mouse listener is there
        MouseListener foundDragListener = null;
        for (int i = 0; i < mlList.length; i++) {

          // instanceof does not work with a static inner class so using the name instead
          if (mlList[i].getClass().getName().equals("javax.swing.plaf.basic.BasicTreeUI$TreeDragGestureRecognizer")) {
            foundDragListener = mlList[i];
            // remove the found Drag Listener to be added later
            this.removeMouseListener(foundDragListener);
           
            //assume there is only one and break
            break;
      };
    }
        // Add the Listener
        super.addMouseListener(l);
       
        // Add the found Drag listener at the end if it exists
        if (foundDragListener != null) {
            super.addMouseListener(foundDragListener);
    }
  }

  /*
   * Allow auto scrolling up and down when doing drag and drop.  CraigM: 10/04/2008
   *
   * @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
   */
  public void autoscroll(Point cursorLocn) {
    int height = this.getParent().getHeight();
    int yPos = cursorLocn.y + this.getLocation().y;

    // Scroll up
    if (yPos < height/2) {
      Point newLocation = this.getLocation();
      newLocation.x = 0-newLocation.x;
      newLocation.y = 0-newLocation.y;
      newLocation.y -= this.getRowHeight();
      this.scrollRectToVisible(new Rectangle(newLocation));
    }
    // Scroll down
    else {
      Point newLocation = this.getLocation();
      newLocation.x = 0-newLocation.x;
      newLocation.y = 0-newLocation.y;
      newLocation.y += this.getRowHeight() + height;
      this.scrollRectToVisible(new Rectangle(newLocation));
    }
  }

  /*
   * Set the trigger points when using auto scrolling when doing drag and drop.  CraigM: 10/04/2008
   *
   * @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
   */
  public Insets getAutoscrollInsets() {
    if (this.isAutoScrollEnabled) {
      // Get the y position of the top of the screen (taking into account scrolling)
      int top = 0-this.getLocation().y;

      // Get the y position of the bottom of the screen (taking into account scrolling)
      int bottom = this.getHeight() - this.getParent().getHeight() - top;

      // Set our margins to be 15 pixels inside the visible area (top and bottom)
      return new Insets(top+15,0,bottom+15,0);
    }
    else {
      return new Insets(0,0,0,0);
    }
  }

  /**
   * Calling this has no effect as tree view fields only have one column.  CraigM:12/05/2008.
   * @param pCols
   */
  public void setVisibleColumns(int pCols) {
    // Do nothing
  }

    /**
     * The ScrollPolicy attribute (boolean) determines where to scroll the current node in an outline field when an application sets it as the current node. The current node always appears in view.<br>
     * <br>
     * ScrollPolicy uses the following values:<br>
     * Value    Definition<br>
     * NP_AUTOMATIC  Automatic scrolling - puts the node somewhere in view.<br>
     * NP_BOTTOM  Scrolls current node to the bottom of outline field display.<br>
     * NP_DEFAULT  Automatic scrolling - puts the node somewhere in view.<br>
     * NP_MIDDLE  Scrolls current node to the middle of outline field display.<br>
     * NP_NOSCROLL  Does not scroll specified node.<br>
     * NP_TOP    Scrolls current node to the top of outline field display.<br>
     *
     * @param pScrollPolicy
     *
     * CraigM:16/05/2008 - Copied from OutlineField.
     */
    public void setScrollPolicy(int pScrollPolicy) {
        this.scrollPolicy = pScrollPolicy;
    }

    public int getScrollPolicy() {
        return this.scrollPolicy;
    }

    /*
     * CraigM:12/06/2008 - If we are inactive, we should not show our popup menu
     *
     * @see java.awt.Component#setFocusable(boolean)
     */
    @Override
    public void setFocusable(boolean focusable) {
      super.setFocusable(focusable);

      if (this.getComponentPopupMenu() != null) {
        if (WidgetState.get(this) == Constants.FS_INACTIVE) {
          // Set the size to 0, effectively stopping the popup menu from showing
            this.getComponentPopupMenu().setPreferredSize(new Dimension(0,0));
        }
        else {
          // Restore the size
            this.getComponentPopupMenu().setPreferredSize(null);
        }
      }
    }

    /*
     * CraigM:12/06/2008 - Disallow selection of nodes if we are not focusable.
     *
     * @see javax.swing.JTree#setSelectionPath(javax.swing.tree.TreePath)
     */
    @Override
    public void setSelectionPath(TreePath path) {
      // Allow the selection change if we are not inactive or it has been requested not via a mouse click
      if (WidgetState.get(this) != Constants.FS_INACTIVE || ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_MOUSECLICK) {
        super.setSelectionPath(path);
      }
    }

    /**
     * @param pLine The line parameter specifies a line to scroll to a
     * specified position in the outline field display.
     * @param pScrollPolicy The scrollPolicy parameter specifies the position
     * to which to scroll the specified line. It uses the following values:
     * AF_AUTOMATIC  If line is above display area, scrolls to first outline
     *         field display line. If line is beneath display area,
     *         scrolls to last outline field display line. No scrolling
     *         occurs if line is already in display area.
     * AF_BOTTOM  Scrolls line to bottom outline field display line.
     * AF_DEFAULT  If line is above display area, scrolls line to top outline
     *         field display line. If line is beneath display area,
     *         scrolls line to bottom outline field display line. No
     *         scrolling occurs if line is already in display area.
     * AF_MIDDLE  Scrolls line to middle outline field display line.
     * AF_TOP    Scrolls line to top outline field display line.
     *
     * CraigM:16/05/2008 - Copied from OutlineField.
     */
    public void requestScroll(final int pLine, final int pScrollPolicy) {
        ActionMgr.addAction(new PendingAction(null) {
            @Override
            public String toString() {
                return "TreeViewWidget.requestScroll(" + pLine + ", " + pScrollPolicy + ")";
            }
            @Override
            public void performAction() {
                if (pScrollPolicy==Constants.AF_TOP) {
                  TreeViewWidget.this.scrollRowToVisible(TreeViewWidget.this.getRowCount()-1);
                  TreeViewWidget.this.scrollRowToVisible(pLine-1);
                }
                else if (pScrollPolicy==Constants.AF_BOTTOM) {
                  TreeViewWidget.this.scrollRowToVisible(0);
                  TreeViewWidget.this.scrollRowToVisible(pLine-1);
                }
                else if (pScrollPolicy==Constants.AF_MIDDLE) {
                    int visibleRows = TreeViewWidget.this.getVisibleRowCount();
                    TreeViewWidget.this.scrollRowToVisible(TreeViewWidget.this.getRowCount()-1);
                    TreeViewWidget.this.scrollRowToVisible((pLine-1) - (visibleRows/2));
                }
                else {
                  TreeViewWidget.this.scrollRowToVisible(pLine-1);
                }
            }
        });
    }

    /**
     * Select the node that matches currentNode.
     *
     * @param pCurrentNode
     *
     * CraigM:16/05/2008 - Copied from OutlineField.
     */
    public void setCurrentNode(DisplayNode pCurrentNode) {
      if (pCurrentNode == null) {
        this.clearSelection();
        return;
      }
        TreePath path = this.getPathForRow(0);
        int counter = 0;

        while (path != null) {
            if (path.getLastPathComponent() == pCurrentNode) {
                // Select the node
                this.setSelectionPath(path);

                // Scroll to the node based on the scroll policy.
                if (this.scrollPolicy != Constants.NP_NOSCROLL) {
                    switch (this.scrollPolicy) {
                    case Constants.NP_TOP:
                        this.requestScroll(counter+1, Constants.AF_TOP);
                        break;
                    case Constants.NP_BOTTOM:
                        this.requestScroll(counter+1, Constants.AF_BOTTOM);
                        break;
                    case Constants.NP_MIDDLE:
                        this.requestScroll(counter+1, Constants.AF_MIDDLE);
                        break;
                    default:
                        this.requestScroll(counter+1, Constants.AF_DEFAULT);
                        break;
                    }
                }
                break;
            }
            counter++;
            path = this.getPathForRow(counter);
        }
    }
   
    /* CraigM:12/05/2008 - Added implements TreeField
   * @see DisplayProject.TreeField#setTitleSetNum(int)
   */
  public void setTitleSetNum(int num) {
    putClientProperty("qq_TitleSetNum", new Integer(num));
  }
 
  /* CraigM:12/05/2008 - Added implements TreeField
   * @see DisplayProject.TreeField#getTitleSetNum()
   */
  public int getTitleSetNum() {
    return (Integer)getClientProperty("qq_TitleSetNum");
  }

  /**
   * Return the number of the first line displayed. The returned value will be the one-based, ordinal number
   * of the first visible row. The count does not include rows collapsed rows. This method does not need to
   * be invoked from the EDT
   * @return The 1-based ordinal number of the first visible row
   */
  public int getTopLine() {
    // If we're not on the EDT, there may be pending actions which will affect this, so process them first
    UIutils.processGUIActions();
    if (this.getParent() != null && this.getParent().getParent() instanceof JScrollPane) {
      JScrollPane sp = (JScrollPane)this.getParent().getParent();
          Point p = sp.getViewport().getViewPosition();
          Insets i = sp.getInsets();
          return this.getClosestRowForLocation(p.x + i.left, p.y + i.top);
    }
    else {
      Insets i = this.getInsets();
      return this.getClosestRowForLocation(i.left, i.top);
    }
  }

  /**
   * Return the display node corresponding to the number of the first line displayed. This was an undocumented
   * method in Forte, and is reproduced here for completeness. This method does not need to
   * be invoked from the EDT
   * @return The DisplayNode corresponding to the first visible row
   */
  public DisplayNode getFirstVisibleThreadedNode() {
    // If we're not on the EDT, there may be pending actions which will affect this, so process them first
    UIutils.processGUIActions();
    TreePath path = null;
    if (this.getParent() != null && this.getParent().getParent() instanceof JScrollPane) {
      JScrollPane sp = (JScrollPane)this.getParent().getParent();
      Point p = sp.getViewport().getViewPosition();
      Insets i = sp.getInsets();
      path = this.getClosestPathForLocation(p.x + i.left, p.y + i.top);
    }
    else {
      Insets i = this.getInsets();
      path =  this.getClosestPathForLocation(i.left, i.top);
    }
    if (path != null) {
      return (DisplayNode)path.getLastPathComponent();
    }
    else {
      return null;
    }
  }
 
  /**
   * @deprecated This was a questionable practice in Forte/UDS and makes no sense in a modern GUI
   * @param b
   */
  //PM:15/05/2008:CMA-10
  public void setDraggableFoldersIgnoreDoubleClick(boolean b) {
    //throw new UnsupportedOperationException("DraggableFoldersIgnoreDoubleClick property is not implemented");
  }
 
  /**
   * @deprecated This was a questionable practice in Forte/UDS and makes no sense in a modern GUI
   */
  public boolean getDraggableFoldersIgnoreDoubleClick() {
    return false;
  }
}
TOP

Related Classes of DisplayProject.controls.TreeViewWidget

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.