Package DisplayProject.controls

Source Code of DisplayProject.controls.OutlineField$VisibleRows

/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
 
o 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.
   
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder

 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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.


*/
package DisplayProject.controls;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Hashtable;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.springframework.beans.BeanUtils;

import DisplayProject.Array_Of_DisplayNode;
import DisplayProject.Array_Of_OutlineColumnDesc;
import DisplayProject.Constants;
import DisplayProject.DisplayNode;
import DisplayProject.OutlineColumnDesc;
import DisplayProject.PsuedoDoubleClickAction;
import DisplayProject.TreeField;
import DisplayProject.TreeViewModel;
import DisplayProject.UIutils;
import DisplayProject.WindowSystem;
import DisplayProject.actions.ActionMgr;
import DisplayProject.actions.PendingAction;
import DisplayProject.actions.TreeFieldCurrentNode;
import DisplayProject.actions.TreeRelated;
import DisplayProject.actions.WidthInPixels;
import DisplayProject.events.ClientEventManager;
import DisplayProject.events.NodeChangedListener;
import DisplayProject.factory.TableFactory;
import Framework.CloneHelper;
import Framework.ErrorMgr;
import Framework.ImageData;
import Framework.MsgCatalog;
import Framework.ParameterHolder;
import Framework.TextData;

/**
* The OutlineField class defines an outline field, which can display a hierarchy of nodes as an indented outline that is expandable and collapsible to show different outline levels.
* If it is representing a node structure larger than it can display, the outline field displays a scroll bar so the user can scroll through the data.
* It is implemented with a JTree that can have columns of data.
*
* Internal layout is:
* JPanel (OutlineField)
*   - JScrollPane (horizontalScrollPane). No vertical scroll so header will always remain visible.
*     - JPanel (mainContainer)
*       - Header (JPanel)
*         - headerLabelsPanel (JPanel)
*           - headerLabels (JLabel[])
*         - headerUnderline (Component)
*       - JScrollPane (verticalScrollPane)
*         - JTree
*   - JScrollBar (Vertical Scroll Bar for verticalScrollPane). Outside tree so doesn't scroll out when using horizontal scroll pane.
*
* 13 July, 2010:TF: Changed this control to support invisible columns, allowing them to be hidden and shown.
* @author Craig Mitchell.
* @since 23/04/2007.
*/
@SuppressWarnings("serial")
public class OutlineField extends JPanel implements TreeField {
    private JLabel[] headerLabels;
    private JLabel headerLeftControlPad;
    private int[] headerLabelSizes; // Original header label sizes
    private JPanel headerLabelsPanel;
    private JComponent headerUnderline;
    private DisplayNode rootNode;
    private Array_Of_OutlineColumnDesc<OutlineColumnDesc> columns; // Collection of OutlineColumnDesc
    private int[] columnSizes;
    protected OutlineFieldJTree tree;
    private JScrollPane horizontalScrollPane;
    private JScrollPane verticalScrollPane;
    private OutlineFieldCellRenderer treeCellRenderer;
    private boolean showHeader;
    private Font titleFont;
    private boolean rootDisplayed;
    private boolean controlsDisplayed;
    private JScrollBar vertScrollBar;
    private int vertScrollBarWidth;
    private GridBagConstraints vertScrollBarGBC;
    private int treeControlWidth;
    private int scrollPolicy; // Constants.NP_... CraigM 01/01/2008
    // TF:06/01/2009:Added support for the minimum size of an outline field
    private int minWidthInColumns = 0;
    private int minHeightInRows = 0;
   
    // TF:04/02/2009:JCT-628:Cached the width and height of the font for faster redrawing
    private int fontWidth = 0;
    private int fontHeight = 0;

    public static final int cTREE_CONTROL_WIDTH = 20; // This is the size of the expand/collapse control.
    public static final int cCOLUMN_SPACING = 6; // The number of pixels between the columns
    public static final int cHEADER_UNDERLINE_SIZE = 11;
    public static final int cHEADER_UNDERLINE_LINE1_Y = 3;
    public static final int cHEADER_UNDERLINE_LINE2_Y = 5;
    public static final int cHEADER_MARGIN_LEFT = 7; // The number of pixels from the left of the widget to the first column
    public static final int cHEADER_MARGIN_TOP = 4;
    public static final int cHEADER_MARGIN_BOTTOM = 3;

    private PropertyChangeListener propertyListener = new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        if (OutlineColumnDesc.PROPERTY_STATE.equals(evt.getPropertyName())) {
          // The state of the column has changed, we need to redraw.
          redrawHeaderLabels();
          redraw();
        }
      }
    };
   
    // TF:24/06/2008:Moved this variable out into a named listener, so it can be removed and added
    // back in when the model is being set.
    private TreeExpansionListener expansionListener = new TreeExpansionListener() {

        // Don't bother recalculating column widths when collapsing, as forte doesn't
        public void treeCollapsed(TreeExpansionEvent event) {
            ((DisplayNode)event.getPath().getLastPathComponent()).setOpened_PRIVATE(false);
            OutlineField.this.fireCollapsed(event);
        }

        // If we expand, recalculate the column widths, as we may have to increase their size
        public void treeExpanded(TreeExpansionEvent event) {
          if(!((DisplayNode)event.getPath().getLastPathComponent()).isOpened()) {
            ((DisplayNode)event.getPath().getLastPathComponent()).setOpened_PRIVATE(true);
            redraw();
            OutlineField.this.fireExpanded(event);
          }
        }
    };
   
    /**
     * Constructor.
     */
    public OutlineField() {
        this.tree = new OutlineFieldJTree(this);
        this.columns = null;
        this.rootNode = null;
        this.rootDisplayed = true;
        this.controlsDisplayed = true;
        this.scrollPolicy = Constants.NP_DEFAULT;
        this.treeControlWidth = cTREE_CONTROL_WIDTH;
        // Set a new cell renderer
        this.treeCellRenderer = new OutlineFieldCellRenderer(this);
        this.tree.setCellRenderer(this.treeCellRenderer);
        // TF 4/8/07 Added the showing root handles to ensure we get the arrows when we should be able to expand nodes
        this.tree.setShowsRootHandles(true);
        this.setBorder(BorderFactory.createLineBorder(Color.black));

        // CraigM:23/06/2008 - Disable the double click to expand/collapse as Forte would not allow it
        this.tree.setToggleClickCount(0);
       
        // Map Enter to a double click action (as Forte does).  CraigM 03/10/2007.
        this.tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK);
        this.tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_MASK),PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK);
        this.tree.getActionMap().put(PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK, new PsuedoDoubleClickAction());

        // Can't seem to remove the border on a tree?
        // this.tree.setBorder(BorderFactory.createEmptyBorder());
        // this.tree.setBorder(null);

        // Listen for expand/collapse so we can redraw ourselves
        // TF:24/06/2008:Extracted this code into a named variable
        this.tree.addTreeExpansionListener(expansionListener);

        // Disable the collapse if the controls are hidden.  CraigM 03/10/2007.
        this.tree.addTreeWillExpandListener(new TreeWillExpandListener() {
            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
                if (OutlineField.this.isControlsDisplayed() == false) {
                    ExpandVetoException errorVar = new ExpandVetoException(event);
                    ErrorMgr.addError(errorVar);
                    throw errorVar;
                }
            }
            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
            }
        });

        // Handle the mouse click event
//        this.tree.addMouseListener(new MouseAdapter() {
//            public void mouseClicked(MouseEvent e) {
//              // CraigM:01/07/2008 - We are only interested in the first click, otherwise we get duplicate events
//              if (e.getClickCount() == 1) {
//                  // getRowForLocation and getPathForLocation appear to not work if the tree is shifted over.  Account for the shift by using the image icons of the handles.
//                  int iconWidth = ((OutlineFieldUI) OutlineField.this.tree.getUI()).getCollapsedIcon().getIconWidth();
//                  int row = OutlineField.this.tree.getRowForLocation(e.getX()+ iconWidth, e.getY());
//                 
//                  // Clear out the selection before adding a new selection item.  CraigM: 21/02/2008.
//                  OutlineField.this.tree.clearSelection();
//                 
//                  // Need to add the selected row to the selection path
//                  OutlineField.this.tree.addSelectionRow(row);
//              }
//            }
//        });

        // The main container that holds everything
        JPanel mainContainer = new JPanel();
        mainContainer.setName("OutlineField_mainContainer");
        mainContainer.setLayout(new GridBagLayout());
        // TF:Mar 10, 2010:We want the background colour to inherit from the main outline field
//        mainContainer.setBackground(Color.white);
        mainContainer.setBackground(null);

        // Set the rows to fit to size
        this.tree.setRowHeight(0);
        this.showHeader = true;

        // Add header
        JPanel header = this.getHeader();
        GridBagConstraints gbc1 = new GridBagConstraints();
        gbc1.gridx = 0;
        gbc1.gridy = 0;
        gbc1.weightx = 1;
        gbc1.anchor = GridBagConstraints.WEST;
        gbc1.fill = GridBagConstraints.HORIZONTAL;
        mainContainer.add(header, gbc1);

        // Add tree
        GridBagConstraints gbc2 = new GridBagConstraints();
        gbc2.gridx = 0;
        gbc2.gridy = 1;
        gbc2.weightx = 1;
        gbc2.weighty = 1;
        gbc2.insets = new Insets(0,cHEADER_MARGIN_LEFT,0,0);
        gbc2.fill = GridBagConstraints.BOTH;
        this.verticalScrollPane = new JScrollPane(this.tree);
        this.verticalScrollPane.setPreferredSize(new Dimension(0,0)); // The default dimensions are stupidly large
        this.verticalScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        this.verticalScrollPane.setBorder(BorderFactory.createEmptyBorder());
        this.verticalScrollPane.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                // Set the vertical scroll bar to be more like forte
                OutlineField.this.vertScrollBar.setBlockIncrement(OutlineField.this.verticalScrollPane.getHeight());
            }
        });
        mainContainer.add(this.verticalScrollPane, gbc2);
        // TF:Mar 10, 2010:We want the background colour to inherit from the main outline field
        this.verticalScrollPane.setBackground(null);
        this.verticalScrollPane.getViewport().setBackground(null);

        // Set our scrolling policy to be horizontal only
        this.horizontalScrollPane = new JScrollPane(mainContainer);
        this.horizontalScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
        this.horizontalScrollPane.setBorder(BorderFactory.createEmptyBorder());
        // TF:Mar 10, 2010:We want the background colour to inherit from the main outline field
        this.horizontalScrollPane.setBackground(null);
        this.horizontalScrollPane.getViewport().setBackground(null);

        // Add the main scroll pane (with the horizontal scroll bar)
        this.setLayout(new GridBagLayout());
        GridBagConstraints gbc3 = new GridBagConstraints();
        gbc3.gridx = 0;
        gbc3.gridy = 0;
        gbc3.weightx = 1;
        gbc3.weighty = 1;
        gbc3.fill = GridBagConstraints.BOTH;
        this.add(this.horizontalScrollPane, gbc3);

        // Add the vertical scroll bar
        this.vertScrollBar = new JScrollBar();
        this.vertScrollBar.setUnitIncrement(10); // Increase the scroll amount from 1 to 10 (more like forte)
        this.verticalScrollPane.setVerticalScrollBar(this.vertScrollBar);
        this.vertScrollBarGBC = new GridBagConstraints();
        this.vertScrollBarGBC.gridx = 1;
        this.vertScrollBarGBC.gridy = 0;
        this.vertScrollBarGBC.fill = GridBagConstraints.VERTICAL;
        this.vertScrollBarWidth = this.vertScrollBar.getPreferredSize().width;
        this.add(this.vertScrollBar, this.vertScrollBarGBC);

        // CraigM:11/02/2009 - Initialise and set the default font (To make sure the initial size is correct)
        this.headerLeftControlPad = new JLabel();
        this.columnSizes = new int[0];
        this.headerLabels = new JLabel[0];
        this.columns = new Array_Of_OutlineColumnDesc<OutlineColumnDesc>();
        this.setFont(WindowSystem.getPortableFont(80, Constants.TF_SYSTEM_DEFAULT, false, false));
       
        /*
         * PM:10/9/07
         * Added for DnD support
         */
        this.tree.putClientProperty("qq_OutlineField", this);

      // TF:15/12/2009:DET-141:Set this up to allow inheriting of popup menu
      this.setInheritsPopupMenu(true);
    }

    /**
     * Assign the column descriptions.
     *
     * @param pColumns is a collection of OutlineColumnDesc
     */
    public void setColumnList(Array_Of_OutlineColumnDesc<OutlineColumnDesc> pColumns) {
      // TF:13/07/2010:First remove any listeners from the old columns
      for (OutlineColumnDesc oldColumn : this.columns) {
        oldColumn.removePropertyChangeListener(this.propertyListener);
      }
        this.columns = new Array_Of_OutlineColumnDesc<OutlineColumnDesc>();

        // Only set the columns that aren't invisible
        // TF:13/07/2010:Changed this to cater for invisible columns
        for (OutlineColumnDesc colDesc : pColumns) {
//            if (colDesc.getState() != Constants.FS_INVISIBLE) {
//                this.columns.add(colDesc);
//            }
          this.columns.add(colDesc);
          colDesc.removePropertyChangeListener(this.propertyListener);
          colDesc.addPropertyChangeListener(this.propertyListener);
            /*
             * PM:10/9/07
             * if the column is set to FS_DRAG
             * we need to add a transfer handler and
             * set the Drag Enabled flag.
             *
             * But we only do this once
             */
            if ((colDesc.getState() == Constants.FS_DRAG)&&
                    (!this.tree.getDragEnabled())){
                this.tree.setDragEnabled(true);
                this.tree.setTransferHandler(ClientEventManager
                    .getObjectDropTransferHandler());

            }
        }
        this.redrawHeaderLabels();
        this.redraw();
    }
   
    /**
     * Get the number of columns in the OutlineField which are visible. This returns a measure of the physical
     * columns, not the number of characters in a colum cell, like getVisibleColumns does.
     * @return
     */
    int getNumVisibleColumns() {
      int count = 0;
      for (OutlineColumnDesc desc : columns) {
        if (desc.getState() != Constants.FS_INVISIBLE) {
          count++;
        }
      }
      return count;
    }
   
    /**
     * Return the index of the last visible column in the set of columns. If there are
     * no visible columns, then -1 is returned
     * @return
     */
    private int getLastVisibleColumn() {
      for (int i = columns.size()-1; i >= 0; i--) {
        if (columns.get(i).getState() != Constants.FS_INVISIBLE) {
          return i;
        }
      }
      return -1;
    }
   
    private void redrawHeaderLabels() {
        // Remove the old header labels
      this.headerLabelsPanel.removeAll();
//        Component[] labels = this.headerLabelsPanel.getComponents();
//            for (int i=0; i<labels.length; i++) {
//                this.headerLabelsPanel.remove(labels[i]);
//        }
      int visibleColumns = getNumVisibleColumns();
        this.headerLabels = new JLabel[visibleColumns];
        this.headerLeftControlPad = new JLabel();
        this.headerLabelSizes = new int[visibleColumns];

        if (this.showHeader) {
          // Cater for the control space
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 1; // CraigM:16/12/2008 - Put the control space to the right of the first column (this is what Forte did)
            gbc.gridy = 0;
            this.headerLabelsPanel.add(this.headerLeftControlPad, gbc);

            // Add new labels header labels
            int columnCounter = 0;
            int lastVisibleColumn = this.getLastVisibleColumn();
            boolean isFirstVisible = true;
            for (int i=0; i<this.columns.size(); i++) {
                OutlineColumnDesc colDec = (OutlineColumnDesc)this.columns.get(i);
                if (colDec.getState() != Constants.FS_INVISIBLE) {
                  String columnName = colDec.getTitle().asString();
                  gbc = new GridBagConstraints();

                  // CraigM:16/12/2008 - Cater for the fact that the control space pad is on the right of the first column
                  if (isFirstVisible) {
                    gbc.gridx = 0;
                    isFirstVisible = false;
                  }
                  else {
                    gbc.gridx = columnCounter+1;
                  }
                  gbc.gridy = 0;

                  // The last label fills up the rest of the space
                  if (i == lastVisibleColumn) {
                    gbc.weightx = 1;
                  }
                  gbc.anchor = GridBagConstraints.WEST;
                  gbc.insets = new Insets(0,0,0,OutlineField.cCOLUMN_SPACING); // Put a gap between the columns

                  JLabel headerLabel = new JLabel(columnName);
                  if (this.titleFont != null){
                      headerLabel.setFont(this.titleFont);
                  }
                  // Set the column alignment
                  switch (colDec.getAlignment()) {
                      case Constants.TA_RIGHT:
                          headerLabel.setHorizontalAlignment(SwingConstants.RIGHT);
                          break;
                      case Constants.TA_CENTER:
                          headerLabel.setHorizontalAlignment(SwingConstants.CENTER);
                          break;
                      default:
                          headerLabel.setHorizontalAlignment(SwingConstants.LEFT);
                  }
                  this.headerLabels[columnCounter] = headerLabel;
                  this.headerLabelSizes[columnCounter] = headerLabel.getPreferredSize().width;
                  this.headerLabelsPanel.add(this.headerLabels[columnCounter], gbc);
                  columnCounter++;
                }
            }
        }
    }

    /**
     * Assign the root node. This method is not thread-safe and should be called from the EDT, or via the RootNode pending action
     *
     * @param pRootNode
     */
    public void setRoot(DisplayNode pRootNode) {
        DisplayNode currentNode = TreeFieldCurrentNode.get(this);
        this.rootNode = pRootNode;
       
        // TF:24/06/2008:prior to setting the root node, we need to remove the expansion listener,
        // which will fire the expansion events and also redraw the columns and mark children nodes
        // as being expanded. The events shouldn't be fired as this is a programmatic change, we
        // redraw the nodes later in this method anyone (ONCE!) and since the setCollapsed marks the
        // parents of the children as being expanded and sets their opened state, we definitely don't
        // want this either.
        this.tree.removeTreeExpansionListener(expansionListener);
        this.tree.setModel(new TreeViewModel(this.rootNode));
        this.tree.addTreeExpansionListener(expansionListener);

        // TF:25/9/07:The user object of the root node will have been reset by setting the tree, reset it to this
        if (pRootNode != null) {
            pRootNode.setUserObject(this);
        }

        // Restore the current node if possible.  CraigM 01/11/2007
        TreeFieldCurrentNode.set(this, currentNode);

        if (this.columns != null) {
            this.redraw();
        }
    }

    /**
     * A magic constant that Forte seems to use to scale the average character by
     */
    private static final double OUTLINE_AVG_CHAR_SCALING_FACTOR = 0.62;


    /**
     * Method to recalculate how wide each column should be
     */
    private void calculateColumnWidths() {
        DisplayNode[] nodes = null;
        DisplayNode root = this.getRoot(false);

        this.columnSizes = new int[this.headerLabels.length];

        if (root != null) {

            if (this.tree.isRootVisible()) {
                nodes = new DisplayNode[1];
                nodes[0] = root;
            }
            else {
                nodes = new DisplayNode[(root == null) ? 0 : root.getChildCount()];

                for (int i=0; i<nodes.length; i++) {
                    if (root != null)
                        nodes[i] = (DisplayNode)root.getChildAt(i);
                }
            }
        }

        // Loop through each column
        int columnCounter = 0;
        for (int i=0; i<this.columns.size(); i++) {
            OutlineColumnDesc colDesc = (OutlineColumnDesc)this.columns.get(i);
            if (colDesc.getState() != Constants.FS_INVISIBLE) {
              // If they have a fixed width, then it's easy
              if (colDesc.getSizePolicy() == Constants.FP_FIXED) {
                  // CraigM:02/07/2008 - Make sure the header is still fully visible (Forte did this)
                  this.columnSizes[i] = Math.max(this.headerLabelSizes[i], (int)(OUTLINE_AVG_CHAR_SCALING_FACTOR * UIutils.colsToPixels(colDesc.getMaxCharacters(), this)));
              }
 
              // Otherwise we need to look at the data in the columns to work out the size
              else {
                  if (this.showHeader) {
                      this.columnSizes[columnCounter] = this.getColumnSize(nodes, i, this.headerLabelSizes[columnCounter], 0);
                  }
                  else {
                      this.columnSizes[columnCounter] = this.getColumnSize(nodes, i, 0, 0);
                  }
              }
              columnCounter++;
            }
        }
    }

    public DisplayNode getRoot(boolean processGuiActions) {
      // CraigM:23/06/2008 - We don't always want to process the gui actions.  Eg: If the OutlineField hasn't been parented yet.
        if (processGuiActions) {
          // TF:25/9/07:We may need to process the GUI actions in case there's a root node being set on it.
          UIutils.processGUIActions();
        }
        Object _Result = this.tree.getModel().getRoot();

        if (_Result instanceof DisplayNode) {
            return (DisplayNode)_Result;
        }
        return null;
    }

    public int[] getColumnSizes() {
        return this.columnSizes;
    }

    public OutlineFieldJTree getTree() {
        return this.tree;
    }

    public DefaultTreeModel getModel() {
        return (DefaultTreeModel)this.tree.getModel();
    }


    /**
     * @return How many lines there are in the outline field. This value changes as
     * the end user opens and closes folders.
     */
    public int getTotalLines() {
        return this.tree.getRowCount();
    }

    /**
     * Create the header component
     * @return
     */
    private JPanel getHeader() {
        JPanel headerPanel = new JPanel();
        headerPanel.setName("OutlineField_headerPanel");
        headerPanel.setLayout(new GridBagLayout());
        // TF:Mar 10, 2010:We want the background colour to inherit from the main outline field
//        headerPanel.setBackground(Color.white);
        headerPanel.setBackground(null);

        // Add the labels panel
        this.headerLabelsPanel = new JPanel();
        this.headerLabelsPanel.setName("OutlineField_headerLabelsPanel");
        this.headerLabelsPanel.setLayout(new GridBagLayout());
        // TF:Mar 10, 2010:We want the background colour to inherit from the main outline field
//        this.headerLabelsPanel.setBackground(Color.white);
        this.headerLabelsPanel.setBackground(null);
        GridBagConstraints headerLabelsGBC = new GridBagConstraints();
        headerLabelsGBC.gridx = 0;
        headerLabelsGBC.gridy = 0;
        headerLabelsGBC.insets = new Insets(cHEADER_MARGIN_TOP, cHEADER_MARGIN_LEFT, cHEADER_MARGIN_BOTTOM, 0); // Allow a gap above, below, and to the left of the header labels
        headerLabelsGBC.weightx = 1;
        headerLabelsGBC.fill = GridBagConstraints.HORIZONTAL;
        headerPanel.add(this.headerLabelsPanel, headerLabelsGBC);

        // Add the underline component
        GridBagConstraints headerUnderlineGBC = new GridBagConstraints();
        headerUnderlineGBC.gridx = 0;
        headerUnderlineGBC.gridy = 1;
        headerUnderlineGBC.weightx = 1;
        headerUnderlineGBC.fill = GridBagConstraints.HORIZONTAL;

        // Anonymous inner class to draw the two lines below the header
        this.headerUnderline = new JComponent() {
            private static final long serialVersionUID = 1895116938240927830L;

            @Override                                                                                                                                                                                                                            
            public void paint(Graphics g) {
                g.drawLine(0, cHEADER_UNDERLINE_LINE1_Y, this.getWidth(), cHEADER_UNDERLINE_LINE1_Y);
                g.drawLine(0, cHEADER_UNDERLINE_LINE2_Y, this.getWidth(), cHEADER_UNDERLINE_LINE2_Y);
            };
        };
        headerPanel.add(this.headerUnderline, headerUnderlineGBC);

        return headerPanel;
    }

    /**
     * Set pLabel with the visual representation of pObj (either text or a picture).
     */
    public void setJLabel(Object pObj, JLabel pLabel) {
        if (pObj == null) {
            pLabel.setText(" ");
            pLabel.setIcon(null);
        }
        else if (pObj instanceof ImageData) {
            pLabel.setText("");
            pLabel.setIcon(new ImageIcon(((ImageData)pObj).getValue()));
        }
        else  {
            String str = pObj.toString();

            // Convert tabs to 4 spaces
            if (str.indexOf('\t') != -1) {
                str = str.replaceAll("\t", "    ");
            }

            if (str.length() == 0) {
                str = " "; // An empty string means a zero size label (this is a problem if it is the only label in the row). CraigM 28/09/2007.
            }
            pLabel.setText(str);
            pLabel.setIcon(null);
        }
    }

    /**
     * This method uses reflection to match the column name with the attribute in pNode
     *
     * @param pNode
     * @param pColumn
     * @return
     */
    public Object getData(DisplayNode pNode, int pColumn) {
        // TF:14/2/08:Fixed this method up to cater for nested property paths, eg "BEprsn.last_nm"
        String nestedPropertyName = ((OutlineColumnDesc)this.columns.get(pColumn)).getName().asString();
        String[] propertyParts = nestedPropertyName.split("\\.");
        Class<?> clazz = pNode.getClass();
        Object target = pNode;
        try {
            for (String propertyName : propertyParts) {
              // TF:18/06/2008:If the second letter is upper case, then the first letter must
              // be upper case too, as the property for say "sCode" is actually "SCode" as we
              // always uppercase the first letter after a set or get
              if (propertyName.length() >= 2 && Character.isLowerCase(propertyName.charAt(0)) && Character.isUpperCase(propertyName.charAt(1))) {
                StringBuilder builder = new StringBuilder(propertyName);
                builder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
                propertyName = builder.toString();
              }
                PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(clazz, propertyName);
                Method readMethod;
                if (pd == null || (readMethod = pd.getReadMethod()) == null) {
                    NoSuchFieldException errorVar = new NoSuchFieldException(
                            propertyParts.length == 1 ? nestedPropertyName : (nestedPropertyName + " at " +propertyName));
                    ErrorMgr.addError(errorVar);
                    throw errorVar;
                }
                readMethod.setAccessible(true);
                target = readMethod.invoke(target);
                // It's possible for the target to be null, in which case we just break out of the loop, because null
                // can be validly displayed in an outline field.
                if (target != null) {
                    clazz = target.getClass();
                }
                else {
                    return null;
                }
            }
            return target;
            /*
            StringBuffer methodName = new StringBuffer("get"+((OutlineColumnDesc)this.columns.get(pColumn)).getName().asString());

            char ch = Character.toUpperCase(methodName.charAt(3));
            methodName.setCharAt(3, ch);

            Method method = BeanUtils.findDeclaredMethodWithMinimalParameters(pNode.getClass(), methodName.toString());

            if (method == null) {
                NoSuchFieldException errorVar = new NoSuchFieldException(methodName.toString());
                ErrorMgr.addError(errorVar);
                throw errorVar;
            }
            method.setAccessible(true);
            return method.invoke(pNode, (Object[])null);
         */
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Recursive method to calculate the size of the column
     *
     * @param pNodes The current list of node we are checking (all at the same depth level)
     * @param pCol The current column in pNode we are checking -- This is the actual column, including invisible ones
     * @param pSize The current minimum size we have
     * @param pDepthLevel The number of levels deep in the tree we are (Root node/s = 0)
     * @return The size of the column (pCol) based on pNode, pSize, and pRecursionLevel.
     */
    private int getColumnSize(DisplayNode[] pNodes, int pCol, int pSize, int pDepthLevel) {
        int _Result = pSize;

        if (pNodes != null) {
            JLabel label = new JLabel();
            label.setFont(this.getFont());

            for (int i=0; i<pNodes.length; i++) {
                Object data = this.getData(pNodes[i], pCol);
                this.setJLabel(data, label);
                int width = label.getPreferredSize().width;

                if (pCol == 0) {
                    width += (pDepthLevel*cTREE_CONTROL_WIDTH);
                }

                if (width < _Result) {
                    width = _Result;
                }

                // If there are child nodes, go and get their sizes
                if (pNodes[i].isFolder() && pNodes[i].isOpened()) {
                    DisplayNode[] nodes = new DisplayNode[pNodes[i].getChildCount()];

                    for (int j=0; j<nodes.length; j++) {
                        nodes[j] = (DisplayNode)pNodes[i].getChildAt(j);
                    }
                    _Result = this.getColumnSize(nodes, pCol, width, pDepthLevel+1);
                }
                else {
                    _Result = width;
                }
            }
        }

        return _Result;
    }

    /**
     * The GetDescByName method returns the specified outline column descriptor.
     *
     * @param pName
     * @return
     *
     * Written by Craig Mitchell
     * @since 02/01/2008.
     */
    public OutlineColumnDesc getDescByName(String pName) {
        for (OutlineColumnDesc col : this.columns) {
            if (col.getName().equals(pName)) {
                return col;
            }
        }
        return null;
    }

    public OutlineColumnDesc getDescByName(TextData pName) {
        return this.getDescByName(pName.toString());
    }

    /**
     * Redraw the header to fit the column sizes
     */
    private void updateHeader() {
        if (this.showHeader) {
            int headerWidth = 0;

            // Set the padding on the left to cater for the tree control
            this.headerLeftControlPad.setPreferredSize(new Dimension(this.treeControlWidth, 0));

            for (int i=0; i<this.columnSizes.length; i++) {
                Dimension size = new Dimension(this.columnSizes[i], this.headerLabels[i].getPreferredSize().height);
                this.headerLabels[i].setPreferredSize(size);
                headerWidth += this.columnSizes[i];
            }
            this.headerUnderline.setSize(new Dimension(headerWidth, cHEADER_UNDERLINE_SIZE));
            this.headerUnderline.setPreferredSize(new Dimension(headerWidth, cHEADER_UNDERLINE_SIZE));
            this.headerLabelsPanel.setVisible(true);
            this.headerLabelsPanel.invalidate();
        }
        else {
            this.headerLabelsPanel.setVisible(false);

            // Force the underline size to the width of the columns, so the outline field will show the horizontal scroll bar if necessary
            int treeWidth = cCOLUMN_SPACING;
            for (int i=0; i<this.columnSizes.length; i++) {
                treeWidth += this.columnSizes[i] + cCOLUMN_SPACING;
            }
            this.headerUnderline.setPreferredSize(new Dimension(treeWidth,0));
        }
    }

    private class VisibleRows {
        public int firstRow;
        public int lastRow;

        public VisibleRows() {
            this.firstRow = -1;
            this.lastRow = -1;
        }
    }

    /**
     * @return The first and last row numbers that are visible on the screen.
     */
    private VisibleRows getVisibleRows() {
        VisibleRows _Result = new VisibleRows();
        Rectangle scrollBounds = this.verticalScrollPane.getBounds();
        int scrollVal = this.vertScrollBar.getValue();
        scrollBounds.y = scrollVal;

        for (int i=0; i<this.tree.getRowCount(); i++) {
            Rectangle rowBounds = this.tree.getRowBounds(i);

            if (scrollBounds.intersects(rowBounds)) {
                if (_Result.firstRow == -1) {
                    _Result.firstRow = i;
                }
                _Result.lastRow = i;
            }
            else if (_Result.firstRow != -1) {
                break;
            }
        }

        return _Result;
    }

    /**
     * Change the height of the control based in the number of rows we want to see.
     * Row size is based on the first row.
     * @param pRows
     */
    public void setVisibleRows(int pRows) {
      // Update the height in rows.  CraigM:10/02/2009.
      this.minHeightInRows = pRows;

      // Clear out the old min size height.  CraigM:10/02/2009.
      Dimension minSize = super.getMinimumSize();
      minSize.height = 0;
      this.setMinimumSize(minSize);
     
      // Force ourselves to re-layout.  CraigM:10/02/2009.
      this.invalidate();
    }

    /**
     * Get the minimum width of the control in columns
     * @return the minimum height in columns
     */
    public int getMinWidthInColumns() {
    return minWidthInColumns;
  }

  /**
   * Set the minimum width of the control in columns. Invoking this method may change the minimum size of the
   * control, and may force a re-layout of the control.
   * @param minHeightInRows The minimum width of the control, in columns.
   */
  public void setMinWidthInColumns(int minWidthInColumns) {
    if (minWidthInColumns != this.minWidthInColumns) {
      int oldValue = this.minWidthInColumns;
      this.minWidthInColumns = minWidthInColumns;
      this.firePropertyChange("minWidthInColumns", oldValue, this.minWidthInColumns);

      // We need to force a re-layout of this control to compensate for this change to the minimum size
      this.invalidate();
      this.validate();
    }
  }

  /**
   * Get the minimum height of the control in rows
   * @return the minimum height in rows
   */
  public int getMinHeightInRows() {
    return minHeightInRows;
  }

  /**
   * Set the minimum height of the control in rows. Invoking this method may change the minimum size of the
   * control, and may force a re-layout of the control.
   * @param minHeightInRows The minimum height of the control, in rows.
   */
  public void setMinHeightInRows(int minHeightInRows) {
    if (minHeightInRows != this.minHeightInRows) {
      int oldValue = this.minHeightInRows;
      this.minHeightInRows = minHeightInRows;
      this.firePropertyChange("minHeightInRows", oldValue, this.minHeightInRows);
     
      // We need to force a re-layout of this control to compensate for this change to the minimum size
      this.invalidate();
      this.validate();
    }
  }

  /**
   * Get the width that this field should be when given the width in columns. This will perform
   * calculations to ensure that the width returned is approximately equal to the width that
   * forte would have set.
   * @param pColumns
   * @return
   */
  private int columnsToPixels(int pColumns) {
    if (fontHeight == 0 || fontWidth == 0) {
      FontMetrics fm = this.getFontMetrics(this.getFont());
      // TF:04/02/2009:JCT-628:We need to use our own width routine to get the right width for the
      // font in an outline control, not UIUtils.colsToPixels
      // result.width = Math.max(UIutils.colsToPixels(this.minWidthInColumns, this), result.width);
          int[] wds = fm.getWidths();
          float av = 0;
          for (int i = 0; i < wds.length; i++)
              av += wds[i];
          av /= wds.length;
          av *= 0.92;
          this.fontWidth = (int)av;
          this.fontHeight = fm.getHeight() - 1;
    }
        int width = this.fontWidth * this.minWidthInColumns + 6;
        int vertScrollBarSize = this.hasVertScrollBar() ? this.vertScrollBarWidth : 0;
        return width + vertScrollBarSize;
  }
 
  /**
   * Get the minimum size of the control. The minimum size allowed is the greater of the set minimum size (if there
   * is one) and the minimum size derived from the minWidthInColumns and minHeightInRows properties.
   */
  @Override
    public Dimension getMinimumSize() {
    Dimension result = super.getMinimumSize();
   
    if (this.minHeightInRows > 0 || this.minWidthInColumns > 0) {
      // TF:04/02/2009:JCT-628:The "+23" is a magic scaling factor that
      // is needed to match the size to Forte.
      result.width = Math.max(result.width, columnsToPixels(this.minWidthInColumns));

      // TF:04/02/2009:JCT-628:Corrected the height to cater for the heading. Also, the size of the
      // outline field is rows is slightly smaller than the font size (1 pixel) so cater for this
      // here, and also in the OutlineFieldCellRenderer. The "+8" is a magic scaling factor that
      // is needed to match the size to Forte.
      int height = this.fontHeight * this.minHeightInRows + 8;
      if (this.headerLabelsPanel.isVisible()) {
        height += cHEADER_MARGIN_TOP + cHEADER_MARGIN_BOTTOM + this.headerLabelsPanel.getFontMetrics(this.headerLabelsPanel.getFont()).getHeight() + cHEADER_UNDERLINE_LINE2_Y;
      }
      if (this.horizontalScrollPane.getHorizontalScrollBar().isVisible()) {
        height += this.horizontalScrollPane.getHorizontalScrollBar().getHeight();
      }
      result.height = Math.max(height, result.height);
    }
    return result;
    }
    /**
     * The GetColumnCoordinates method returns the coordinates of the rectangle
     * around a column within an outline field.
     *
     * You use GetColumnCoordinates to get the specific coordinates, relative
     * to the OutlineField, of the rectangle that surrounds the column
     * specified with the column parameter.
     *
     * Since the columns may be scrolled off the current display, they can be
     * negative if the column is scrolled off to the left, or they can be
     * greater than the size of the outline field if scrolled off to the right.
     *
     * @param column number of the column within the OutlineField, numbered
     * from 1 as the first column on the left.
     * @param xLeft integer value measured in mils relative to the top left
     * @param yTop integer value measured in mils relative to the top left
     * @param xRight integer value measured in mils relative to the top left
     * @param yBottom integer value measured in mils relative to the top left
     * @return TRUE if any part of the column is currently visible, or a value
     * of FALSE, if it is not currently visible.
     *
     * NOTE: This method doesn't currently support hidden columns.
     *
     * Written by Craig Mitchell
     * @since 18/09/2007
     */
    public boolean getColumnCoordinates(int column, ParameterHolder xLeft, ParameterHolder yTop, ParameterHolder xRight, ParameterHolder yBottom) {
        column--; // Swap from Fortes 1 based to Javas 0 based

        if (this.columns.get(column).getState() == Constants.FS_INVISIBLE) {
          // Non-visible column, just return 0s
          xLeft.setInt(0);
          yTop.setInt(0);
          xRight.setInt(0);
          yBottom.setInt(0);
          return false;
        }
        // ------------------------------------------
        // Translate the column to the visible column
        // ------------------------------------------
        int visibleColumn = 0;
        for (int i = 0; i < column; i++) {
          if (this.columns.get(i).getState() != Constants.FS_INVISIBLE) {
            visibleColumn++;
          }
        }
        column = visibleColumn;
       
        if (column >= 0 && column <this.columnSizes.length) {
            // ---------------------------------------------------------------
            // Calculate the x positions without taking into account scrolling
            // ---------------------------------------------------------------
            int x1 = cHEADER_MARGIN_LEFT+this.treeControlWidth+cCOLUMN_SPACING;
            int x2;

            for (int col=0; col<column; col++) {
                x1 += this.columnSizes[col] + cCOLUMN_SPACING;
            }
            x2 = x1 + this.columnSizes[column] + cCOLUMN_SPACING;

            // ---------------------------------------------------------------
            // Convert the x positions taking into account scrolling
            // ---------------------------------------------------------------
            if (this.hasHorzScrollbar()) {
                int scrollAmt = this.horizontalScrollPane.getHorizontalScrollBar().getValue() * this.horizontalScrollPane.getHorizontalScrollBar().getUnitIncrement();
                x1 -= scrollAmt;
                x2 -= scrollAmt;
            }

            // ---------------------------------------------------------------
            // Calculate the y positions
            // ---------------------------------------------------------------
            int y1 = 0;
            int y2 = this.verticalScrollPane.getBounds().height;

            if (this.showHeader) {
                y1 = cHEADER_MARGIN_TOP + this.headerLabelsPanel.getPreferredSize().height + cHEADER_MARGIN_BOTTOM + cHEADER_UNDERLINE_SIZE;
                y2 += y1;
            }

            // ---------------------------------------------------------------
            // Convert the results to mils
            // ---------------------------------------------------------------
            xLeft.setInt(UIutils.pixelsToMils(x1));
            xRight.setInt(UIutils.pixelsToMils(x2));
            yTop.setInt(UIutils.pixelsToMils(y1));
            yBottom.setInt(UIutils.pixelsToMils(y2));

            // ---------------------------------------------------------------
            // Work out if the column is visible
            // ---------------------------------------------------------------
            boolean isVis = false;

            if ((x1 > 0 || x2 > 0) && (x1 < this.horizontalScrollPane.getBounds().width || x2 < this.horizontalScrollPane.getBounds().height) ) {
                isVis = true;
            }

            return isVis;
        }
        return false;
    }

    /**
     * @param pX
     * @return the zero based column index
     */
    public int getColumnIndex(int pX) {
        int _Result = 0;
        int x = cHEADER_MARGIN_LEFT+this.treeControlWidth;

        int col = 0;
        for (int i = 0; i < this.columns.size(); i++) {
          if (this.columns.get(i).getState() != Constants.FS_INVISIBLE) {
            if (x < pX) {
              _Result = col;
            }
            else {
              break;
            }
            col++;
            x += this.columnSizes[col] + cCOLUMN_SPACING;
          }
        }

        return _Result;
    }

    /**
     * @return the number of rows visible on the screen.
     * Note: this.tree.getVisibleRowCount() doesn't work as the tree is contained in 2 scroll panes.  See header for more details.
     */
    public int getVisibleRowCount() {
        VisibleRows rows = this.getVisibleRows();

        if (rows.firstRow != -1) {
            return rows.lastRow - rows.firstRow + 1;
        }
        return 0;
    }

    /**
     * @return the number of visible characters that can be displayed across the outline field
     */
    public int getVisibleColumns() {
        return UIutils.pixelsToCols(WidthInPixels.get(this.tree), this.tree);
    }

    public int getFirstVisibleIndex() {
        return this.getVisibleRows().firstRow;
    }

    public int getTopLine() {
        return this.getVisibleRows().firstRow;
    }

    public int getLastVisibleIndex() {
        return this.getVisibleRows().lastRow;
    }

    public void clearSelectedNodes() {
        this.tree.getSelectionModel().clearSelection();
    }

    @Override
    public void requestFocus() {
        this.tree.requestFocus();
    }

    @Override
    public boolean requestFocusInWindow() {
      // TF:Mar 10, 2010:Added this method
      return tree.requestFocusInWindow();
    }
    /**
     * @param pRelatedObj
     * @param pIsVisible - Not Implemented.  Value is ignored.
     * @return
     *
     * Written by Craig Mitchell
     * @since 02/01/2007.
     */
    public Array_Of_DisplayNode<DisplayNode> findAllRelated(Object pRelatedObj, boolean pIsVisible) {
        return TreeRelated.findAllRelated(this, pRelatedObj);
    }

    /**
     * @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.
     */
    public void requestScroll(final int pLine, final int pScrollPolicy) {
        ActionMgr.addAction(new PendingAction(null) {
            @Override
            public String toString() {
                return "OutlineField.requestScroll(" + pLine + ", " + pScrollPolicy + ")";
            }
            @Override
            public void performAction() {
                if (pScrollPolicy==Constants.AF_TOP) {
                    OutlineField.this.tree.scrollRowToVisible(OutlineField.this.getTotalLines()-1);
                    OutlineField.this.tree.scrollRowToVisible(pLine-1);
                }
                else if (pScrollPolicy==Constants.AF_BOTTOM) {
                    OutlineField.this.tree.scrollRowToVisible(0);
                    OutlineField.this.tree.scrollRowToVisible(pLine-1);
                }
                else if (pScrollPolicy==Constants.AF_MIDDLE) {
                    int visibleRows = OutlineField.this.getVisibleRowCount();
                    OutlineField.this.tree.scrollRowToVisible(OutlineField.this.getTotalLines()-1);
                    OutlineField.this.tree.scrollRowToVisible((pLine-1) - (visibleRows/2));
                }
                else {
                    OutlineField.this.tree.scrollRowToVisible(pLine-1);
                }
            }
        });
    }

    /**
     * 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
     * Written by Craig Mitchell
     * @since 1/1/2008
     */
    public void setScrollPolicy(int pScrollPolicy) {
        this.scrollPolicy = pScrollPolicy;
    }

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

    /**
     * Recursive method to force a repaint on all the nodes.
     * Note sure why, but calling reload() on the model doesn't work.
     * @param pNode
     */
    private void redrawNodeAndSubNodes(TreeNode pNode) {
        if (pNode == null)
            return;

        ((DefaultTreeModel)this.tree.getModel()).nodeChanged(pNode);

        for (int i=0; i<pNode.getChildCount(); i++) {
            this.redrawNodeAndSubNodes(pNode.getChildAt(i));
        }
    }

    /**
     * This method will refresh the details in the outline field. It is included here to mirror the
     * interface of the outline field in Forte, but should not be needed -- the outline field should
     * automatically update itself. Hence, this method has been deprecated.
     * @deprecated this method should not be needed
     */
    public void refresh() {
      ActionMgr.addAction(new PendingAction(null) {
        @Override
        public void performAction() {
          redraw();
        }
      });
      UIutils.processGUIActions();
    }
    /**
     * Recalculate column widths and redraw tree
     */
    private void redraw() {
        this.calculateColumnWidths();
        this.updateHeader();
        this.redrawNodeAndSubNodes(this.getRoot(false));

        // Some components need to be told to update themselves
        this.horizontalScrollPane.updateUI();
        this.headerLabelsPanel.invalidate();
    }

    /**
     * When a node is added, we need to check to see if we have to expand and redraw the columns.
     *
     * @param pNode
     */
    public void nodeAdded(DisplayNode pNode) {
        int depthLevel = 0;
        boolean requiresRedraw = false;
        DisplayNode tmp = (DisplayNode)pNode.getParent();

        // Expand the parent if necessary
        if (tmp!= null && tmp.isOpened() && this.tree.isExpanded(new TreePath(tmp)) == false) {
            this.tree.expandPath(new TreePath(tmp.getPath()));
        }

        // Calculate the depth
        while (tmp != null) {
            tmp = (DisplayNode)tmp.getParent();
            depthLevel++;
        }

        if (isRootDisplayed() == false) {
            depthLevel--;
        }
        DisplayNode[] nodeCol = new DisplayNode[1];
        nodeCol[0] = pNode;

        // Check if the column size needs to be increased
        int columnCount = 0;
        for (int i=0; i < this.columns.size(); i++) {
          if (this.columns.get(i).getState() != Constants.FS_INVISIBLE) {
                int colSize = this.getColumnSize(nodeCol, i, 0, depthLevel);
                if (colSize > this.columnSizes[columnCount]) {
                    requiresRedraw = true;
                    break;
                }
                columnCount++;
          }
        }

        if (requiresRedraw) {
            this.redraw();
        }
    }

    /**
     *   Description of what it did in Forte:
     *     The VisibleColumns attribute (integer) sets the width of the outline
     *     field to fit this number of average characters in the field, using
     *     the current font setting for the outline field. If you change the
     *     font, the width will change appropriately. Setting this attribute
     *     overrides the outline field�s Width attribute. If you have images in
     *     the outline field, this may not be as accurate.
     *  
     * @param pCols
     */
    public void setVisibleColumns(int pCols) {
        WidthInPixels.set(this, this.columnsToPixels(pCols));
    }

    /**
     * Show or hide the header.
     *
     * @param pShowIt true to show, false to hide.
     */
    public void setShowHeader(boolean pShowIt) {
        if (this.showHeader != pShowIt) {
            this.showHeader = pShowIt;
            redraw();
        }
    }

    /**
     * @see setShowHeader
     */
    public void setHasTitles(boolean pShowIt) {
        this.setShowHeader(pShowIt);
    }
   
    public boolean getHasTitles(){
      return getShowHeader();
    }

    public boolean getShowHeader(){
        return this.showHeader;
    }

    /**
     * Highlights the currently selected row.
     * @param pDoIt
     */
    public void setHasRowHighlights(boolean pDoIt) {
        this.treeCellRenderer.setHasRowHighlights(pDoIt);
        this.repaint();
    }

    public boolean getHasRowHighlights() {
        return this.treeCellRenderer.getHasRowHighlights();
    }

    public DisplayNode getCurrentNode() {
        TreePath[] paths = this.tree.getSelectionPaths();
        if ((paths == null) || (paths[0] == null))
            return null;
        else
            return (DisplayNode)paths[0].getLastPathComponent();
    }

    /**
     * Select the node that matches currentNode.
     *
     * @param pCurrentNode
     */
    public void setCurrentNode(DisplayNode pCurrentNode) {
        TreePath path = this.tree.getPathForRow(0);
        int counter = 0;

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

                // Scroll to the node based on the scroll policy.  CraigM 01/01/2008
                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.tree.getPathForRow(counter);
        }
    }

    /**
     * This method exposes the tree selection listener
     * @param listener
     */
    public void addTreeSelectionListener(NodeChangedListener listener) {
        this.tree.addTreeSelectionListener(listener);
    }

    /**
     * @param clone will return a copy of the list if this is true (can be slow).
     * @return the column list assigned to this outline field.
     */
    public Array_Of_OutlineColumnDesc<OutlineColumnDesc> getColumnList(boolean clone) {
      if (clone) {
        return CloneHelper.clone(this.columns, true);
      }
      else {
        return this.columns;
      }
    }

    /**
     * @return the number of nodes currently selected by the user.
     */
    public int getSelectedCount() {
        return this.tree.getSelectionCount();
    }
   
    /**
     * @return a list of the selected nodes in this outline field. If no nodes
     * are selected, an empty array will be returned.
     */
  public Array_Of_DisplayNode<DisplayNode> getSelectedNodes() {
      // TF:08/08/2009:Coded this method
      Array_Of_DisplayNode<DisplayNode> nodes = new Array_Of_DisplayNode<DisplayNode>();

      for (TreePath path : this.tree.getSelectionPaths()) {
            DisplayNode node = (DisplayNode)path.getLastPathComponent();
            nodes.add(node);
      }
      return nodes;
    }
 
  /**
   * Set the nodes in this outline field to the passed set of nodes. This method
   * is not thread-safe and must be invoked on the EDT
   */
  public void setSelectedNodes(Array_Of_DisplayNode<DisplayNode> nodes) {
    this.clearSelectedNodes();
    if (nodes != null) {
      for (DisplayNode thisNode : nodes) {
        TreePath path = new TreePath(thisNode);
        this.tree.addSelectionPath(path);
      }
    }
  }

    public void setTitleFont(Font pfont) {
        this.titleFont = pfont;
        if ((this.headerLabels != null)&&(this.titleFont != null)){
            for (int i = 0; i < this.headerLabels.length; i++){
                this.headerLabels[i].setFont(pfont);
            }
        }
        this.updateHeader();
        // TF:04/02/2009:JCT-628:The title font will affect our minimum size, so invalidate
        this.invalidate();
    }

    public void setFont(Font pFont) {
        if (this.getFont() != pFont) {
            super.setFont(pFont);

            this.fontHeight = this.fontWidth = 0;
            if (this.tree != null) {
                this.setTitleFont(pFont);
                this.treeCellRenderer.clearCache();
                this.redraw();
            }

            // Changing the font changes the header widths
            if (this.showHeader) {
                this.setColumnList(this.getColumnList(false));
            }
        }
    }

    public boolean isRootDisplayed() {
        return rootDisplayed;
    }

    /**
     * sets root displayed
     * @param rootDisplayed
     */
    public void setRootDisplayed(boolean rootDisplayed) {
        this.rootDisplayed = rootDisplayed;
        this.tree.setRootVisible(rootDisplayed);
    }

    /**
     * Show or hide the controls
     * @param pControlsDisplayed
     */
    public void setControlsDisplayed(boolean pControlsDisplayed) {
        this.controlsDisplayed = pControlsDisplayed;
        this.tree.setShowsRootHandles(pControlsDisplayed);
        this.treeControlWidth = pControlsDisplayed ? cTREE_CONTROL_WIDTH : 0;
    }

    public boolean isControlsDisplayed() {
        return this.controlsDisplayed;
    }

    /**
     * Determines whether an outline field has a vertical scroll bar.
     * @return
     */
    public boolean hasVertScrollBar(){
        int pol = this.verticalScrollPane.getVerticalScrollBarPolicy();
        return (!(pol == JScrollPane.VERTICAL_SCROLLBAR_NEVER));
    }

    /**
     * Determine whether to show the vertical scrollbar or not.
     * @param value
     * @deprecated use setHasVertScrollBar instead.
     */
    public void setVertScrollBar(boolean value) {
      this.setHasVertScrollbar(value);
    }

    public void setHasVertScrollbar(boolean value) {
      this.verticalScrollPane.setVerticalScrollBarPolicy((value) ? JScrollPane.VERTICAL_SCROLLBAR_ALWAYS : JScrollPane.VERTICAL_SCROLLBAR_NEVER);
    }
   
    /**
     * Determines whether outline field has a horizontal scroll bar.
     * @return
     */
    public boolean hasHorzScrollbar(){
        int pol = this.horizontalScrollPane.getHorizontalScrollBarPolicy();
        return (!(pol == JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
    }

    /**
     * Set whether this outline field has a horizontal scroll bar or not.
     * @param value
     */
    public void setHasHorzScrollbar(boolean value) {
        if (value) {
            this.vertScrollBarGBC.insets = new Insets(0,0,this.vertScrollBarWidth,0);
            this.horizontalScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        }
        else {
            this.vertScrollBarGBC.insets = new Insets(0,0,0,0);
            this.horizontalScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        }

        // Force a refresh of the vertical scrollbar by removing it and adding it back in
        this.remove(this.vertScrollBar);

        if (this.hasVertScrollBar()) {
            this.add(this.vertScrollBar, this.vertScrollBarGBC);
        }
    }
   
    /**
     * Set whether this outline field has a horizontal scroll bar or not.
     * @param value
     * @deprecated use setHasHorzScrollbar instead
     */
    public void setHorzScrollBar(boolean value){
      setHasHorzScrollbar(value);
    }

    private void fireCollapsed(TreeExpansionEvent event) {
        Hashtable<String, Object> qq_Params = new Hashtable<String, Object>();
        DisplayNode dn  = ((DisplayNode)event.getPath().getPath()[event.getPath().getPathCount()-1]);
        qq_Params.put( "Folder", new ParameterHolder(dn) );
        ClientEventManager.postEvent( this, "RequestFolderClose", qq_Params );
    }

    private void fireExpanded(TreeExpansionEvent event) {
        Hashtable<String, Object> qq_Params = new Hashtable<String, Object>();
        DisplayNode dn  = ((DisplayNode)event.getPath().getPath()[event.getPath().getPathCount()-1]);
        qq_Params.put( "Folder", new ParameterHolder(dn) );
        ClientEventManager.postEvent( this, "RequestFolderOpen", qq_Params );
    }

    public OutlineField cloneComponent(){
        OutlineField clone = TableFactory.newOutlineField(getName(), getColumnList(true));
        CloneHelper.cloneComponent(this, clone, new String[]{"UI", // class javax.swing.plaf.PanelUI
                "UIClassID", // class java.lang.String
                "accessibleContext", // class javax.accessibility.AccessibleContext
                "actionMap", // class javax.swing.ActionMap
                //"alignmentX", // float
                //"alignmentY", // float
                "ancestorListeners", // class [Ljavax.swing.event.AncestorListener;
                //"autoscrolls", // boolean
                //"background", // class java.awt.Color
                "border", // interface javax.swing.border.Border
                "columnList", // class DisplayProject.Array_Of_OutlineColumnDesc
                "columnSizes", // class [I
                "component", // null
                "componentCount", // int
                "componentPopupMenu", // class javax.swing.JPopupMenu
                "components", // class [Ljava.awt.Component;
                "containerListeners", // class [Ljava.awt.event.ContainerListener;
                "currentNode", // class DisplayProject.DisplayNode
                "debugGraphicsOptions", // int
                //"doubleBuffered", // boolean
                //"enabled", // boolean
                "firstVisibleIndex", // int
                "focusCycleRoot", // boolean
                "focusTraversalKeys", // null
                "focusTraversalPolicy", // class java.awt.FocusTraversalPolicy
                "focusTraversalPolicyProvider", // boolean
                "focusTraversalPolicySet", // boolean
                "focusable", // boolean
                //"font", // class java.awt.Font
                //"foreground", // class java.awt.Color
                "graphics", // class java.awt.Graphics
                //"hasRowHighlights", // boolean
                //"hasTitles", // boolean
                //"height", // int
                //"horzScrollBar", // boolean
                "inheritsPopupMenu", // boolean
                "inputMap", // null
                "inputVerifier", // class javax.swing.InputVerifier
                "insets", // class java.awt.Insets
                "lastVisibleIndex", // int
                "layout", // interface java.awt.LayoutManager
                "managingFocus", // boolean
                //"maximumSize", // class java.awt.Dimension
                //"minimumSize", // class java.awt.Dimension
                "model", // class javax.swing.tree.DefaultTreeModel
                // "name", // class java.lang.String
                "nextFocusableComponent", // class java.awt.Component
                //"opaque", // boolean
                "optimizedDrawingEnabled", // boolean
                "paintingTile", // boolean
                "preferredSize", // class java.awt.Dimension
                "registeredKeyStrokes", // class [Ljavax.swing.KeyStroke;
                "requestFocusEnabled", // boolean
                "root", // class DisplayProject.DisplayNode
                "rootDisplayed", // boolean
                "rootPane", // class javax.swing.JRootPane
                "selectedCount", // int
                //"showHeader", // boolean
                "titleFont", // class java.awt.Font
                //"toolTipText", // class java.lang.String
                "topLevelAncestor", // class java.awt.Container
                "topLine", // int
                //"totalLines", // int
                "transferHandler", // class javax.swing.TransferHandler
                "tree", // class DisplayProject.controls.OutlineFieldJTree
                "validateRoot", // boolean
                "verifyInputWhenFocusTarget", // boolean
                //"vertScrollBar", // boolean
                "vetoableChangeListeners", // class [Ljava.beans.VetoableChangeListener;
                //"visible", // boolean
                //"visibleColumns", // int
                "visibleRect", // class java.awt.Rectangle
                "visibleRowCount", // int
                //"width", // int
                //"x", // int
                //"y" // int
                });
        return clone;
    }

    public Font getTitleFont() {
        return titleFont;
    }

  @Override
  public synchronized void addMouseListener(MouseListener l) {
    // AGD 11/3/2008 - addMouseListeners to the tree as well
    tree.addMouseListener(l);
    super.addMouseListener(l);
  }

  /**
   * Loads the colum titles from a given Message Catalog
   *
   */
  //PM:23/4/08
  public void loadColumnHeadings() {
    int setNumber = getTitleSetNum();
    loadColumnHeadings(setNumber, MsgCatalog.getInstance());
  }
  /**
   * Loads the colum titles from a given Message Catalog
   * @param mcat
   *
   */
  //PM:23/4/08
  public void loadColumnHeadings(MsgCatalog mcat) {
    int setNumber = getTitleSetNum();
    loadColumnHeadings(setNumber, mcat);
  }
 
  /**
   * Loads the colum titles from a given Message Catalog
   * @param setNumber
   * @param mcat
   *
   */
  //PM:23/4/08
  public void loadColumnHeadings(int setNumber, MsgCatalog mcat){
        for (OutlineColumnDesc col : columns){
            if (col.getTitleMsgNum() > 0){
              String titleString = mcat.getString(setNumber, col.getTitleMsgNum(), TextData.valueOf(col.getTitle()));
              col.setTitle(new TextData(titleString));
            }
        }
  }
 
  public void setDefaultSet(int defaultSetNumber, MsgCatalog mcat) {
    int titleSetNumber = this.getTitleSetNum();
    if (defaultSetNumber > 0 && titleSetNumber <= 0) {
      titleSetNumber = defaultSetNumber;
    }
    if (titleSetNumber > 0) {
      this.loadColumnHeadings(titleSetNumber, mcat);
      this.setColumnList(this.getColumnList(false));
    }
  }
  public void setTitleSetNum(int num){
    putClientProperty("qq_TitleSetNum", new Integer(num));
    // TF:04/02/2009:Added in check to make sure the num > 0
    if (num > 0) {
      loadColumnHeadings(num, MsgCatalog.getInstance());
    }
   
    // CraigM: 28/04/2008: Refresh the column titles
    this.setColumnList(this.getColumnList(false));
  }
 
  public int getTitleSetNum(){
    // TF:11/02/2009:Made this null aware
    Integer i = (Integer)getClientProperty("qq_TitleSetNum");
    return i == null ? 0 : i.intValue();
  }
 
  @Override
  public void setBackground(Color bg) {
    // TF:13/07/2008:Implemented the setBackground method
    super.setBackground(bg);
//    if (this.headerLabelsPanel != null) {
//      this.headerLabelsPanel.setBackground(bg);
//      this.headerLabelsPanel.getParent().setBackground(bg);
//    }
//    if (this.horizontalScrollPane != null && this.horizontalScrollPane.getViewport().getView() != null) {
//      this.horizontalScrollPane.getViewport().getView().setBackground(bg);
//    }
//    if (this.tree != null) {
//      this.tree.setBackground(bg);
//    }
  }
 
  /**
   * Get the background colour. This obeys the forte-style rules for background colour:<p>
   * <ul>
   *   <li>If an explicit background colour has been set, use that background colour</li>
   <li>Otherwise, if any of our parents have background colour set, use that colour</li>
   <li>Otherwise, use the default colour (white)
   * </ul>
   */
  @Override
  public Color getBackground() {
    if (!isBackgroundSet()) {
      // We've been set to have a colour of inherit
      Color parentColor = UIutils.getParentBackgroundColor(this);
      if (parentColor != null) {
        return parentColor;
      }
        else {
          // There's no parent that's valid, put the default colour of white
          return Color.white;
        }
    }
    return super.getBackground();
  }
}
TOP

Related Classes of DisplayProject.controls.OutlineField$VisibleRows

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.