Package DisplayProject

Source Code of DisplayProject.ArrayFieldModel

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

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.SystemColor;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.swing.DefaultCellEditor;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import DisplayProject.actions.WidthPolicy;
import DisplayProject.controls.ArrayField;
import DisplayProject.controls.ListViewTable;
import DisplayProject.table.ArrayFieldCellEditor;
import DisplayProject.table.FormattedCellEditor;
import DisplayProject.table.TextFieldCellEditor;
import Framework.DynamicArray;
import Framework.DynamicArrayListener;
import Framework.ParameterHolder;
import Framework.TextData;


/**
* A table model used by {@link DisplayProject.controls.ArrayField}
* @author Peter
*
*/
@SuppressWarnings("serial")
public class ArrayFieldModel extends DefaultTableModel implements DynamicArrayListener, TableModelListener,  FocusListener, PropertyChangeListener {
    protected boolean allowsAppend = false;
    protected DynamicArray<Object> data = null;
    protected JTable table = null;
    protected Class<?>[] columnClasses  = {};
    protected boolean self = false;
    private int visibleRows = -1;
    private boolean ignoreRowChange = false;
    private boolean ignoreFocus = false;
    private int editingRow = -1;
    private int lastRow = -1;
    private boolean newRow = false;
    private Map<String, Boolean> cellEdits = new HashMap<String, Boolean>();
    protected PropertyDescriptor[] columnPropertyMap = null;

    public ArrayFieldModel() {
        super();
    }

    public ArrayFieldModel(JTable pTable) {
        this();
        table = pTable;
        pTable.setAutoCreateColumnsFromModel(true);
        this.addTableModelListener(this);
    }

    public ArrayFieldModel(JTable table, Class<?>[] columnClasses) {
        this(table);
        this.columnClasses = columnClasses;
    }

    public ArrayFieldModel(JTable table, Class<?>[] pColumnClasses, boolean isListView) {
        this(table);
        this.columnClasses = pColumnClasses;
        setAllowsAppend(!isListView);
    }

    @SuppressWarnings("unchecked")
  public void propertyChange(PropertyChangeEvent evt) {
        DynamicArray arr = ((DynamicArray)evt.getNewValue());
        this.setData(arr);
    }

    public Object newRow(){
        return null;
    }

    public void removeRow(int row) {
        if (getData() != null)
            getData().deleteRow(row);
    }

    public void fireChange(int reasonCode, Object newObject, int row, Object oldObject) {
        final int reason = reasonCode;
        //final Object old = oldObject;
        //final Object newO = newObject;
        final int lrow = row;
        Runnable invoker = new Runnable() {
            public void run(){
                switch (reason){
                case Framework.Constants.SP_AC_CHANGED:
                    fireTableRowsUpdated(lrow, lrow);
//                    Logger.getLogger("task.part.logmgr").debug(table.getName() + " ArrayListModel.RowsUpdated: " + newO.toString());
                    break;
                case Framework.Constants.SP_AC_INSERTED:
                    fireTableRowsInserted(lrow, lrow);
//                    Logger.getLogger("task.part.logmgr").debug(table.getName() + " ArrayListModel.RowsInserted: " + newO.toString());
                    break;
                case Framework.Constants.SP_AC_DELETED:
                    fireTableRowsDeleted(lrow, lrow);
//                    Logger.getLogger("task.part.logmgr").debug(table.getName() + " ArrayListModel.RowsDeleted: " + lrow);
                    break;
                case Framework.Constants.SP_AC_CLEARED:
                    fireTableDataChanged();
//                    Logger.getLogger("task.part.logmgr").debug(table.getName() + " ArrayListModel.RowsCleared");
                    break;
                default:
                    fireTableDataChanged();
//                    Logger.getLogger("task.part.logmgr").debug(table.getName() + " ArrayListModel.DataChanged: " + newO.toString());
                    break;
                }
            }
        };
        if (SwingUtilities.isEventDispatchThread())
            invoker.run();
        else
            SwingUtilities.invokeLater(invoker);
    }

    @SuppressWarnings("unchecked")
  public void setData(DynamicArray data) {
        this.data = data;
        this.fromData();
    }

    public void fromData() {
        if (this.data != null) {
            this.data.addListener(this);
            Runnable invoker = new Runnable() {
                public void run(){
                    ArrayFieldModel.this.self = true;
                    fireTableDataChanged();
                    ArrayFieldModel.this.self = false;
                }
            };
            if (SwingUtilities.isEventDispatchThread())
                invoker.run();
            else
                SwingUtilities.invokeLater(invoker);
        }
    }

    public boolean isCellEditable(int row, int column) {
        //PM:14/11/07 handle invalid row and column numbers
        if (row < 0 && column < 0)
            return false;
        if (!this.table.isEnabled()) {
            return false;
        }
        if (this.specialCellEdit(row, column)){
            return this.isSpecialCellEditable(row, column);
        }
//        ArrayColumn ac =  ((ArrayColumn)((ArrayColumnModel)table.getColumnModel())
//                .getColumn(column));
//        return ac.isEditable();
        return isColumnEditable(column);
    }

    public Class<?> getColumnClass(int c) {
        return columnClasses[c];
    }

    public String getColumnName(int column) {
        return ((ArrayColumn)((ArrayColumnModel)table.getColumnModel())
                .getColumn(column)).getHeaderValue().toString();   
    }

    /**
     * Get the name of the column, that is the name property of the underlying column widget
     * @param pColumnIndex the column index whose name is to be retrieved
     * @return the name of the column
     */
    public String getColumnObjName(int pColumnIndex) {
        return ((ArrayColumn)((ArrayColumnModel)table.getColumnModel()).getColumn(pColumnIndex)).getName();   
    }

    public String getBoundColumnName(int pColumnIndex) {
        return ((ArrayColumn)((ArrayColumnModel)table.getColumnModel()).getColumn(pColumnIndex)).getBoundColumn();   
    }

    public DynamicArray<Object> getData() {
        return data;
    }
   
    /**
     * @return the row we are currently editing
     */
    public int getEditingRow() {
      return this.editingRow;
    }

    public int getFullColumnCount() {
        return ((ArrayColumnModel)table.getColumnModel()).allColumns.size();
    }

    public int getColumnCount() {
        return table.getColumnCount();
    }

    public int getRowCount() {
        if (this.data != null)
        {
            return this.data.size();
        }
        else
            return 0;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        return null;
    }

    public int getColumnPosition(TextData columnName){
        return this.getColumnPosition(columnName.toString());
    }

    public int getColumnPosition(String columnName){
        TableColumn ac = null;
        ArrayList<ArrayColumn> allColumns = ((ArrayColumnModel)table.getColumnModel()).allColumns;
        for (Iterator<ArrayColumn> it = allColumns.iterator(); it.hasNext();) {
          // CraigM:30/06/2008 - Use the column name, not the header name
          ArrayColumn tc = it.next();
          if (tc.getName().equals(columnName)) {
            ac = tc;
            break;
          }
        }
        // AD:10/11/2008: Fixed to not null
        if (ac != null)
            return ac.getModelIndex();
        else
            return -1;
    }

    public boolean allowsAppend() {
        return allowsAppend;
    }

    public void setAllowsAppend(boolean pAllowsAppend) {
        this.allowsAppend = pAllowsAppend;
    }

    public void setColumnEditable(int col, boolean state){
        ((ArrayColumn)((ArrayColumnModel)table.getColumnModel())
                .getColumn(col)).setEditable(state);
    }

    public boolean isColumnEditable(int col) {
      //PM:29/4/08
      /*
       * Added a special case to allow a view only column to be 'Editable' abut the
       * cell editor is view only
       */
      ArrayColumn ac = (ArrayColumn)((ArrayColumnModel)table.getColumnModel()).getColumn(col);

      if ((ac.getCellEditor() instanceof ArrayFieldCellEditor) &&
          (((ArrayFieldCellEditor)ac.getCellEditor()).getEditor() instanceof FormattedCellEditor) &&
          // CraigM:19/01/2009 - Added check to make sure we're focusable
          ((FormattedCellEditor)((ArrayFieldCellEditor)ac.getCellEditor()).getEditor()).getFormattedEditor().isFocusable()) {
        return true;
      } else {
        return ac.isEditable();
      }
    }

    protected int realColumnGet(int col){
        return ((ArrayColumnModel)table.getColumnModel()).allColumns.size();
    }

    protected int realColumnSet(int col){
        return realColumnGet(col);
    }

    private boolean isAssociatedComponent(Component comp, boolean checkLocation) {
        //Enumeration columns = table.getColumnModel().getColumns();
        // TF:Changed this for type safety as could receive a type cast exception on it.
        if (table.getCellEditor() instanceof ArrayFieldCellEditor) {
            ArrayFieldCellEditor afe = (ArrayFieldCellEditor)table.getCellEditor();

            TableCellEditor tce = afe.getEditor();
            if (tce instanceof DefaultCellEditor && ((DefaultCellEditor)tce).getComponent() == comp) {
                return true;
            }
            else if (tce instanceof TextFieldCellEditor && ((TextFieldCellEditor)tce).getMultiLineTextField() == comp) {
                return true;
            }
        }
        if (checkLocation && comp != null) {
            //This can only work for components that are on screen (no good for cell components)
            if (comp.isShowing())
                return table.contains(comp.getLocationOnScreen());

            //This is ugly but there is no other way as the comp may no longer
            //have a parent as the cell has stopped editing
            //So we simply check if the name of the component is prefixed with
            //the table name as the standard is that each cell has the table name prefix
            else if (comp.getName() != null)
                return comp.getName().startsWith(table.getName());
        }
        return false;
    }
    public void focusGained(FocusEvent e) {
        Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
        if (((JScrollPane)table.getParent().getParent()).isAncestorOf(c))
            return;
        if (e.getSource() == table && isAssociatedComponent(e.getOppositeComponent(), true)){
            return;
        }
        // TF:12/10/07:This shouldn't be needed any more as all the array event posting is done from the ArrayField itself.
//        postFocusGained();

        if (e.getOppositeComponent() != null && e.getOppositeComponent() != table && !isAssociatedComponent(e.getOppositeComponent(), true)) {

            //We don't want to ignore row changes if the focus change has come from within the table
            if (!ignoreFocus)
                setIgnoreRowChange(true, false);
            else
                ignoreFocus = false;
        }
    }
    // TF:12/10/07:This shouldn't be needed any more as all the array event posting is done from the ArrayField itself.
//    private void postFocusGained(){
//        EventManager.startEventChain();
//        int reason = ForteKeyboardFocusManager.getTraversalReason();
//        if (reason != Constants.FC_SUPRESS) {
//            Hashtable<String, Object> params = new Hashtable<String, Object>();
//            params.put("reason", new ParameterHolder(reason));
//            ClientEventManager.postEvent( table, "AfterFocusGain", params );
//        }
//        EventManager.endEventChain();
//    }

    public void focusLost(FocusEvent e) {
        // TF:12/10/07:This shouldn't be needed any more as all the array event posting is done from the ArrayField itself.
//        if (e.getSource() != table && e.getOppositeComponent() == table)
//            return;
//        if (e.getOppositeComponent() != null && e.getOppositeComponent().getParent() == table)
//            return;
//
//        if (!table.isEditing() || table.getCellEditor().stopCellEditing()) {
//            postRowChanged(table.getSelectedRow(), -1);
//            EventManager.startEventChain();
//            int reason = ForteKeyboardFocusManager.getTraversalReason();
//            if (reason != Constants.FC_SUPRESS) {
//                Hashtable<String, Object> params = new Hashtable<String, Object>();
//                params.put("reason", new ParameterHolder(reason));
//                ClientEventManager.postEvent( table, "BeforeFocusLoss", params );
//            }
//            EventManager.endEventChain();
//        }
    }
    /**
     * @deprecated
     */
    public void valueChanged(ListSelectionEvent e) {
      //PM:29/4/08 not used
//        int last = e.getFirstIndex();
//        int current = e.getLastIndex();
        if (e.getValueIsAdjusting() ||
                (table instanceof ArrayField && ((ArrayField)table).isReverting()))
            return;

        int row = table.getSelectedRow();
        int col = table.getSelectedColumn();
        if ((row != -1) && (col != -1)) {
            if (table.isCellEditable(row, col)) {
                if (!table.isEditing() || table.getEditingRow() != row || table.getEditingColumn() != col) {
                    if (table.editCellAt(row, col))
                        //Ensure cell has focus once editing has been approved
                        table.getEditorComponent().requestFocusInWindow();
                }
            }
            else if (table.isEditing()){

                if (!table.getCellEditor().stopCellEditing()){
                    table.editCellAt(table.getEditingRow(), table.getEditingColumn());
                    return;
                }
            }
        }
        if (e.getSource() == table.getColumnModel().getSelectionModel())
            return;
        if (!ignoreRowChange) {
            DefaultListSelectionModel selectionModel = (DefaultListSelectionModel)e.getSource();
            if (!selectionModel.isSelectionEmpty() && selectionModel.getMinSelectionIndex() != -1) {
                //int rowEntry = selectionModel.getMinSelectionIndex();
                int rowEntry = e.getLastIndex();
                //int rowExit = e.getLastIndex();
                int rowExit = this.lastRow;
                if (rowExit == rowEntry)
                    rowExit = e.getFirstIndex();

                if (rowExit != rowEntry){
                    postRowChanged(rowExit, rowEntry);
                    this.lastRow = rowEntry;
                }
            }
        }
        else
            ignoreRowChange = false;
    }
    /**
     * this method effects the cell edit change and posts the row change events
     * @param e - ListSelectionEven
     * @param isRowChange - boolean
     */
    public void cellChanged(ListSelectionEvent e, boolean isRowChange) {
        if (e.getValueIsAdjusting() ||
                (table instanceof ArrayField && ((ArrayField)table).isReverting()))
            return;

        //  Handle the case of the change of cell and returning to the original location

        // TF:12/10/07:This shouldn't be needed any more as all the array event posting is done from the ArrayField itself.
//        Hashtable qq_ParamsExit = new Hashtable();
//        qq_ParamsExit.put( "reason", new ParameterHolder(Constants.FC_KEYNEXT) );
//        ClientEventManager.postEvent(table, "Traverse", qq_ParamsExit);

        int row = table.getSelectedRow();
        int col = table.getSelectedColumn();
        if ((row != -1) && (col != -1)) {
            if (table.isCellEditable(row, col)) {
                if (!table.isEditing() || table.getEditingRow() != row || table.getEditingColumn() != col) {
                    if (table.editCellAt(row, col))
                        //Ensure cell has focus once editing has been approved
                        table.getEditorComponent().requestFocusInWindow();
                }
            }
            else if (table.isEditing()){

                if (!table.getCellEditor().stopCellEditing()){
                    table.editCellAt(table.getEditingRow(), table.getEditingColumn());
                    return;
                }
            }
        }
        if (e.getSource() == table.getColumnModel().getSelectionModel())
            return;
        if (isRowChange) {
            DefaultListSelectionModel selectionModel = (DefaultListSelectionModel)e.getSource();
            if (!selectionModel.isSelectionEmpty() && selectionModel.getMinSelectionIndex() != -1) {
                //int rowEntry = selectionModel.getMinSelectionIndex();
                int rowEntry = e.getFirstIndex();
                int rowExit = e.getLastIndex();
                //int rowExit = this.lastRow;
                if (rowExit == rowEntry)
                    rowExit = e.getFirstIndex();

                if (rowExit != rowEntry){
                    postRowChanged(rowExit, rowEntry);
                    this.lastRow = rowEntry;
                }
            }
        }
    }
//    /**
//     * post the row change events
//     * @param rowExit
//     * @param rowEntry
//     */
    public void postRowChanged(int rowExit, final int rowEntry) {

        // TF:12/10/07:This shouldn't be needed any more as all the array event posting is done from the ArrayField itself.

        // TF:11/10/07: The value of the "current row" of the array field as known to Forte isn't changed until either
        // we've processed the BeforeRowExit or AfterRowAppend event, whichever comes LATER. Note that the key here is
        // that it requires the processing of events to occur before this state changes. Consider an array with 2 rows.
        // The user edits a cell in the first row and clicks the cursor in the second row. In forte, whilst processing
        // the BeforeRowExit event, asking the table what it's current row is will return the old row (1). In Java,
        // however, the EDT will blindly continue and change the current row to the new row (2) whilst the code processing
        // the BeforeRowExit event is still executing. So, if this code asks the table for it's current row, it returns
        // the wrong value.
        //
        // To get around this, we hijack the "currentRow" on the array field being changed and force it to return the right
        // value. We set this up prior to dispatching the events in the array field when the selection changes, and overwrite
        // it here, at the appropriate time. For this to work, we must force the events onto the thread that owns this window.
//      Window topLevel = (Window)this.table.getTopLevelAncestor();
//      Thread windowThread = WindowManager.getOwningThread(topLevel);
//      Runnable action = new Runnable() {
//        public void run() {
//          if (table instanceof ArrayField) {
//            ((ArrayField)table).setRowForEvents(rowEntry);
//          }
//        }
//      };
//     
//      EventHandle handle = null;
//        if (rowExit != -1) {
//            if (rowExit == this.editingRow){
//                Hashtable qq_Params = new Hashtable();
//                qq_Params.put( "row", new ParameterHolder(rowExit+1) );
//                ClientEventManager.postEvent(table, "AfterRowValueChange", qq_Params);
//                this.editingRow = -1;
//            }
//            Hashtable qq_ParamsExit = new Hashtable();
//            qq_ParamsExit.put( "row", new ParameterHolder(rowExit+1) );
//            handle = new EventHandle(table, "BeforeRowExit", qq_ParamsExit);
//            if (!newRow) {
//              handle.setForceReceiptOfEvent(windowThread);
//              handle.setCompletionNotifier(action);
//            }
//            ClientEventManager.postEvent(handle);
//        }
//        if (newRow) {
//            Hashtable qq_Params = new Hashtable();
//          qq_Params.put( "newObject", new ParameterHolder(Data.get(table.getRowCount()-1)) );
//          qq_Params.put( "row", new ParameterHolder(table.getRowCount()));
//          handle = new EventHandle(table, "AfterRowAppend", qq_Params);
//          handle.setForceReceiptOfEvent(windowThread);
//          handle.setCompletionNotifier(action);
//          ClientEventManager.postEvent(handle);
//          setNewRow(false);
//          table.requestFocusInWindow();
//        }
//        // TF:11/10/07:Create a special event to reset the flag back to the default value, as the row
//        // should change between these events
//      // Create a dummy event to which to attach the interceptor
//        handle = new EventHandle(table, "dummy, cannot be caught");
//      handle.setForceReceiptOfEvent(windowThread);
//      handle.setCompletionNotifier(new Runnable() {
//        public void run() {
//          if (table instanceof ArrayField) {
//            ((ArrayField)table).setRowForEvents(-1);
//          }
//        }
//      });         
//      ClientEventManager.postEvent(handle);
//        if (rowEntry != -1) {
//            Hashtable qq_ParamsEntry = new Hashtable();
//            qq_ParamsEntry.put( "row", new ParameterHolder(rowEntry+1) );
//            ClientEventManager.postEvent(table, "AfterRowEntry", qq_ParamsEntry);
//        }
    }

    /**
     * Determine whether a column needs to be resized, based on the data contained within the underlying table.
     * A column will only ever be resized if it's size policy if FP_AUTOSIZE or FP_DEFAULT. <p>
     * <p>
     * Note that for this method to determine when a column needs to be resized and by how much it must traverse
     * all the rows in the table that have been modified and determine if the the desired renderer is longer than
     * the current column width, and then enlarge the column appropriately if this is the case. Thus, for large
     * numbers of rows in the table, this resizing may have performance implications.<p>
     *
     * @param pModel The table column model of the table to be affected
     * @param pColumn The index of the column in the table which had had data change
     * @param pEvent The data change event
     */
    protected void resizeColumnIfNecessary(TableColumnModel pModel, int pColumn, TableModelEvent pEvent) {
        TableColumn c = pModel.getColumn(pColumn);
        if (c instanceof ArrayColumn) {
            int policy = ((ArrayColumn)c).sizePolicy;
            if (policy == Constants.FP_AUTOSIZE || policy == Constants.FP_DEFAULT) {
                int width = c.getPreferredWidth();
                int minWidth = width;
                int lastRow = pEvent.getLastRow();
                int firstRow = pEvent.getFirstRow();

                if (firstRow == TableModelEvent.HEADER_ROW) {
                    return;
                }
                if (lastRow > this.table.getRowCount()) {
                    // TF:12/11/07:The last row should be the table row count - 1, as it's zero based.
                    lastRow = this.table.getRowCount() - 1;
                }
                for (int i = firstRow; i <= lastRow; i++) {
                    Component comp = UIutils.getTableCellRenderer(this.table, i, pColumn);
                    int compWidth = comp.getPreferredSize().width;
                    if (compWidth > minWidth) {
                        minWidth = compWidth;
                    }
                }
                if (minWidth > width) {
                    c.setPreferredWidth(minWidth+2);
                }
            }
        }
    }

    public void tableChanged(TableModelEvent e) {
        // ---------------------------------------------------------------------------------------------------------
        // If our resize policy dictates that we need to, resize the columns if appropriate, but only for list views
        // ---------------------------------------------------------------------------------------------------------
        if (this.table instanceof ListViewTable && e.getType() != TableModelEvent.DELETE) {
            TableColumnModel model = this.table.getColumnModel();
            if (e.getColumn() == TableModelEvent.ALL_COLUMNS) {
                for (int i = 0; i < model.getColumnCount(); i++) {
                    this.resizeColumnIfNecessary(model, i, e);
                }
            }
            else {
                this.resizeColumnIfNecessary(model, e.getColumn(), e);               
            }
        }
        if (self) return;
        if (e.getColumn() != TableModelEvent.ALL_COLUMNS) {

            int row = e.getLastRow();
            if (this.editingRow == row)
                return;
//        Hashtable qq_Params = new Hashtable();
//        qq_Params.put( "row", new ParameterHolder(this.editingRow) );
//        Framework.ClientEventManager.postEvent(table, "AfterRowValueChange", qq_Params);
            this.editingRow = row;
        } else if (e.getType() == TableModelEvent.DELETE)
            table.editingCanceled(null); //Ensure that we have left editing mode once the row has been deleted
    }
    /**
     * The GetRowCoordinates method returns the coordinates of the rectangle around a row within an array field.<p>
     *
     * This returns the coordinates in the four output parameters xLeft, yTop, xRight, yBottom.
     * These are measured in mils, relative to the top left corner of the ArrayField, without any enclosing frame (it is the inner corner).
     * The row is considered to contain the cells containing the data, without any separator lines associated with the row.
     * @param row
     * @param xLeft
     * @param yTop
     * @param xRight
     * @param yBottom
     */
    public void getRowCoordinates(int row,  ParameterHolder xLeft, ParameterHolder yTop, ParameterHolder xRight, ParameterHolder yBottom) {
        int left, right, top, bottom = 0;
        java.awt.Rectangle rowRect = table.getCellRect(row, 0, true);
        left = rowRect.x;
        top = rowRect.y;
        bottom = top - rowRect.height;
        right = left + rowRect.width;
        for (int col = 1; col < table.getColumnCount(); col++) {
            right += table.getCellRect(row, col, true).width;
        }
        xLeft.setInt(UIutils.pixelsToMils(left));
        yTop.setInt(UIutils.pixelsToMils(top));
        xRight.setInt(UIutils.pixelsToMils(right));
        yBottom.setInt(UIutils.pixelsToMils(bottom));
    }
    /**
     * The GetColumnCoordinates method returns the coordinates of the rectangle around a column within an array field.<p>
     *
     * This returns the coordinates in the four output parameters xLeft, yTop, xRight, yBottom.
     * These are measured in mils, relative to the top left corner of the ArrayField, without any enclosing frame (it is the inner corner).
     * The column is considered to contain the cells containing the data only, and does not include the column header.
     * @param column
     * @param xLeft
     * @param yTop
     * @param xRight
     * @param yBottom
     */
    public void getColumnCoordinates(int column,  ParameterHolder xLeft, ParameterHolder yTop, ParameterHolder xRight, ParameterHolder yBottom) {
        int left, right, top, bottom = 0;
        java.awt.Rectangle colRect = table.getCellRect(0, column, true);
        left = colRect.x;
        top = colRect.y;
        bottom = top - colRect.height;
        right = left + colRect.width;
        for (int row = 1; row < table.getRowCount(); row++) {
            bottom += table.getCellRect(row, column, true).height;
        }
        xLeft.setInt(UIutils.pixelsToMils(left));
        yTop.setInt(UIutils.pixelsToMils(top));
        xRight.setInt(UIutils.pixelsToMils(right));
        yBottom.setInt(UIutils.pixelsToMils(bottom));
    }
//=============List view  methods========================
    public DisplayNode getRow(int rowIndex) {
        return ((DisplayNode)data.get(rowIndex));
    }
    public DisplayNode getCurrentRow() {
        if (table != null) {
            int rowIndex = table.getSelectedRow();
            if (rowIndex >= 0) {
                if (table.getModel() instanceof TableSorter) {
                    // If we're a sorted JTable (ie we've come from a Forte list view), the model may not
                    // match the view if it's been sorted by the user. Thus we adjust the index.
                    TableSorter aTableSorter = (TableSorter)table.getModel();
                    rowIndex = aTableSorter.modelIndex(rowIndex);
                }
                return (DisplayNode)data.get(rowIndex);
            }
        }
        return null;
    }
   
    /**
     * Set the current row in the array field model.
     * @param pNode
     * @return if null is passed as the parameter, this method always returns <code>true</code>. Otherwise, this method will
     * return <code>true</code> iff the passed node was found in the table.
     */
    public boolean setCurrentRow(DisplayNode pNode) {
        if (table != null) {
            // PM:10/10/07:Checked for null
            if (pNode == null) {
                table.clearSelection();
                return true;
            }
            else {
                int rowIndex = getRowIndex(pNode);
                if (rowIndex >= 0) {
                    table.setRowSelectionInterval(rowIndex, rowIndex);
                    return true;
                }
            }
        }
        return false;
    }
    public int getRowIndex(DisplayNode pNode) {
        //int result = Data.indexOf(pNode);
        //PM:12/10/07 we search this way because we clone the nodes to store them
        //TF:3/2/08:Changed the clone to do a shallow clone, so we can use the fast search
        /*
        int result = -1;

        int rowIndex = -1;
        if (Data != null) {
            for(DisplayNode node : (List<DisplayNode>)Data){
                rowIndex++;
                if (node.compareTo(pNode) == 0){
                    result = rowIndex;
                    break;
                }
            }
        }

        return result;
     */
        return data.indexOf(pNode);
    }

    public void setViewNodes(Array_Of_DisplayNode<? extends DisplayNode> nodes){
      // FIX:TF:6 Oct 2009:Changed the type to be ? extends DisplayNode to allow us to take
      // arrays of subclasses of DisplayNodes
        if (nodes != null) {
          for (DisplayNode node : nodes) {
                node.setNodeParent(getNodeParent());
            }
            this.setData(nodes);
        }
        else
        {
            this.setData(null);
        }
    }
    public DisplayNode getNodeParent(){
        return null;
    }
    public DisplayNode locateNode(int x, int y, ParameterHolder column){
        int rowIndex = table.rowAtPoint(new Point(x, y));
        int columnIndex = table.columnAtPoint(new Point(x, y));
        column.setInt(columnIndex);
        return (rowIndex >= 0) ? ((DisplayNode)data.get(rowIndex)) : null;
    }

    public int getVisibleRows() {
        return visibleRows;
    }

    public void setVisibleRows(int visibleRows) {
        this.visibleRows = visibleRows;
    }

    /**
     * Sets the preferred scrollable size on the table to ensure all columns are visible.
     * @param visibleRows - the number of rows you want to have visible
     * @return
     */
    public void showAllColumns(int rows) {
        this.setVisibleRows(rows);
        Enumeration<TableColumn> columnList = table.getColumnModel().getColumns();
        while (columnList.hasMoreElements()){
            int headerLines = ((ArrayColumn)columnList.nextElement()).getHeaderLines();
            if (headerLines > 1 && headerLines > ((ArrayColumnModel)table.getColumnModel()).getMaxHeaderLines())
                ((ArrayColumnModel)table.getColumnModel()).setMaxHeaderLines(headerLines);
        }

        if (((ArrayColumnModel)table.getColumnModel()).getMaxHeaderLines() > 1) {
            JTableHeader header = table.getTableHeader();
            TableCellRenderer headerRenderer = header.getDefaultRenderer();
            Component comp = headerRenderer.getTableCellRendererComponent(table, "X", false, false, 0, 0);
            String htmlStyleTag = "";
            String htmlStyleEndTag = "";
            if (comp.getFont().getStyle() == Font.BOLD) {
                htmlStyleTag = "<b>";
                htmlStyleEndTag = "</b>";
            }
            else if (comp.getFont().getStyle() == Font.ITALIC) {
                htmlStyleTag = "<i>";
                htmlStyleEndTag = "</i>";
            }

            columnList = table.getColumnModel().getColumns();
            Object[] args = { htmlStyleTag, htmlStyleEndTag };
            while (columnList.hasMoreElements()){
                ArrayColumn col = (ArrayColumn)columnList.nextElement();
                int headerLines = col.getHeaderLines();
                if (headerLines > 1) {
                    col.setHeaderValue(MessageFormat.format((String)col.getHeaderValue(), args));
                }
            }

            ((ArrayColumnModel)table.getColumnModel()).setMaxHeaderHeight(comp.getPreferredSize().height * ((ArrayColumnModel)table.getColumnModel()).getMaxHeaderLines());
        }
        int totalWidth = ((ArrayColumnModel)table.getColumnModel()).getTotalColumnWidth();
        int totalHeight = (this.getVisibleRows() * table.getRowHeight()) + ((ArrayColumnModel)table.getColumnModel()).getMaxHeaderHeight();
        table.setPreferredScrollableViewportSize(new Dimension(totalWidth,totalHeight));
    }

    public void resizeTable() {
      //PM:24/4/08 changed to allow enlarging and reducing viewport size. See NVP-26
      Dimension viewportSize = null;
      if (table.getParent() != null)
        viewportSize = table.getParent().getSize();
      else
        viewportSize = table.getPreferredScrollableViewportSize();
      ArrayColumnModel tcm = (ArrayColumnModel)table.getColumnModel();
      viewportSize.width = tcm.getMinimumTotalColumnWidth();
      int newViewportWidth = viewportSize.width;
      table.setPreferredScrollableViewportSize(viewportSize);
      JTableHeader header = table.getTableHeader();
      //PM:5/03/08 LUM-1
      JScrollPane sp = null;
      boolean proportionalResize = false;
      if (table.getParent() != null && table.getParent().getParent() instanceof JScrollPane){
        sp = (JScrollPane)table.getParent().getParent();
        proportionalResize = (WidthPolicy.get(sp) == Constants.SP_TO_PARENT) || (WidthPolicy.get(sp) == Constants.SP_TO_MATRIX_PARTNER);
        newViewportWidth = sp.getViewport().getSize().width;
      }

      // TF:26/11/07:JTable headers take their size from the first non-zero table header
      // size, so if the first column is 1 character high and the second column is 2 characters
      // high, the 2nd one would get truncated. We want the header size to be able to be variable
      // height, so we need to scan the columns and work out the sizes...
      // TF:7/2/08:test for null headers as they can be set to null when declared
      if (header != null) {
        int count = table.getColumnCount();
        Insets insets = header.getInsets();
        int minHeight = insets.top + insets.bottom;

        float weightsTotal = 0;

        for (int i = 0; i < count; i++) {
          ArrayColumn tc = (ArrayColumn)tcm.getColumn(i);
          //PM:5/03/08 LUM-1
          if (proportionalResize){
            weightsTotal += tc.getColumnWeight();
          }

          TableCellRenderer tcr = tc.getHeaderRenderer();
          if (tcr == null) {
            tcr = header.getDefaultRenderer();
          }
          Component comp = tcr.getTableCellRendererComponent(header.getTable(),
              tc.getHeaderValue(), false, false, -1, i);
          if (comp != null) {
            int thisHeight = comp.getMinimumSize().height + insets.top + insets.bottom;
            if (thisHeight > minHeight) {
              minHeight = thisHeight;
            }

            // TF:30/04/2008:Set the widths of the columns to be the larger of their headers and their assigned side
            int thisWidth = Math.max(tc.getAssignedWidth(), comp.getMinimumSize().width + insets.left + insets.right );
            if (thisWidth > tc.getMinWidth()) {
              tc.setMinWidth(thisWidth);
            }
          }

        }
        /*
         * PM:5/03/08 LUM-1
         * resize the columns based on their weights
         */
        int extraSpace = 0;
        // TF:4/7/08:Aggregate the error over the columns to prevent infinite layouts
        double errorSum = 0.0;
        if (newViewportWidth != viewportSize.width){
          extraSpace = newViewportWidth - viewportSize.width;
          for (int i = 0; i < count; i++){
            ArrayColumn tc = (ArrayColumn)tcm.getColumn(i);
            float weight = tc.getColumnWeight();
            if (weight > 0) {
              int currentWidth = tc.getWidth();
              int newWidth = currentWidth;
              float fraction = weight / weightsTotal;
              if (newViewportWidth > viewportSize.width) {
                int extraSpaceProduct = (int)(fraction * extraSpace);
                newWidth = extraSpaceProduct + tc.getMinWidth();
                // The error is the difference between the true value and the rounded down value
                double error = (fraction * extraSpace) - extraSpaceProduct;
                errorSum += error;
                if (errorSum >= 1.0) {
                  newWidth += 1;
                  errorSum -= 1.0;
                }
              } else if (newViewportWidth < viewportSize.width) {
                int extraSpaceProduct = (int)(fraction * Math.abs(extraSpace));
                newWidth =  currentWidth - extraSpaceProduct;
                // The error is the difference between the true value and the rounded down value
                double error = (fraction * extraSpace) - extraSpaceProduct;
                errorSum += error;
                if (errorSum >= 1.0) {
                  newWidth -= 1;
                  errorSum -= 1.0;
                }
                if (newWidth < tc.getMinWidth())
                  newWidth = tc.getMinWidth();
              }
              // TF+CraigM:20/06/2008 - Check widths before setting to avoid focus loss issues
              if (tc.getPreferredWidth() != newWidth) {
                tc.setPreferredWidth(newWidth);
              }
              if (tc.getWidth() != newWidth) {
                tc.setWidth(newWidth);
              }
            }
          }
        }

        Dimension d = new Dimension(newViewportWidth, minHeight);
        if (!d.equals(header.getMinimumSize())) {
          header.setMinimumSize(d);
          header.setPreferredSize(d);
          header.setSize(d);
        }
      }

      int visibleRows = ((ArrayField)table).getVisibleRows();
      //PM:12/7/07 added the resize of the containing ScrollPane to behave like Forte
      if (sp != null){
        sp = (JScrollPane)table.getParent().getParent();
        if (visibleRows > -1) {
          //PM:25/8/07 Added to size vp correctly on generation
          viewportSize.height = visibleRows * table.getRowHeight();
        }
        else {
          viewportSize.height = sp.getHeight();
        }

        // TF:26/11/07: Fixed this to compensate for the header height
        Insets ins = sp.getInsets();
        int scrollBarWidth = sp.getVerticalScrollBar().getWidth();
        Dimension spDim = new Dimension(
            viewportSize.width + ins.left + ins.right + scrollBarWidth,
            viewportSize.height + ins.top + ins.bottom + ((header != null) ? header.getPreferredSize().height : 0));
        if (!(sp.isMinimumSizeSet() && spDim.equals(sp.getMinimumSize()))) {
          sp.setMinimumSize(spDim);               
          sp.revalidate();
        }
      }
      else {
        // The array field is not parented onto a scroll pane, set the minimum size to allow
        // proper layout to occur.
        if (visibleRows > -1) {
          //PM:25/8/07 Added to size vp correctly on generation
          viewportSize.height = visibleRows * table.getRowHeight();
        }
        else {
          viewportSize.height = 0;
        }
        if (header != null) {
          viewportSize.height += header.getPreferredSize().height;
        }
        if (!(table.isMinimumSizeSet() && table.getMinimumSize().equals(viewportSize))) {
          table.setMinimumSize(viewportSize);
        }
      }
    }

    //This determines if the append row has occurred from within the table or from outside
    public void shouldIgnoreRowChange(MouseEvent event) {
        if (!table.isFocusOwner()) {
            KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager();

            if (!isAssociatedComponent((Component)fm.getFocusOwner(), false)) {
                setIgnoreRowChange(true, true);
            }
        }
    }

    private void setIgnoreRowChange(boolean ignoreChange, boolean ignoreFocus) {
        this.ignoreRowChange = ignoreChange;
        this.ignoreFocus = ignoreFocus;
    }

    public void resetFlags() {
        setIgnoreRowChange(false, false);
    }
    public class RowChange implements ListSelectionListener {

        public RowChange() {
            super();
        }

        public void valueChanged(ListSelectionEvent e) {
            cellChanged(e, true);
        }

    }
    public class ColumnChange implements ListSelectionListener {

        public ColumnChange() {
            super();
        }

        public void valueChanged(ListSelectionEvent e) {
            cellChanged(e, false);
        }
    }
    public boolean isNewRow() {
        return newRow;
    }

    public void setNewRow(boolean newRow) {
        this.newRow = newRow;
    }

    public void addRow(Object[] rowData) {
        checkData();
        for (int i = 0; i < rowData.length; i++)
            this.data.add(rowData[i]);
//    super.addRow(rowData);
    }

    /*
     * this method is only used for ListView when manipulating nodes
     */
    @SuppressWarnings("unchecked")
  private void checkData(){
        if (this.getData() == null)
            this.setData(new Array_Of_DisplayNode());
    }
    /**
     * This method provides a mechanism to enable or disable editing in a single cell.
     * NOTE: it should only be used with care
     * @param row
     * @param col
     * @param editable
     */
    public void setCellEditable(int row, int col, boolean editable){
        String key = Integer.toString(row) + ":" + Integer.toString(col);
        if (specialCellEdit(row, col)){
            this.cellEdits.remove(key);
        }
        this.cellEdits.put(key, new Boolean(editable));
    }

    private boolean specialCellEdit(int row, int col){
        String key = Integer.toString(row) + ":" + Integer.toString(col);
        return this.cellEdits.containsKey(key);

    }

    private boolean isSpecialCellEditable(int row, int col){
        String key = Integer.toString(row) + ":" + Integer.toString(col);
        return ((Boolean)this.cellEdits.get(key)).booleanValue();

    }
    /**
     * test a cell to see if it blanked out
     * @param row
     * @param column
     * @return
     */
    public boolean isCellBlank(int row, int column){
        DefaultTableCellRenderer cr = (DefaultTableCellRenderer)table.getCellRenderer(row, column);
        return cr.getBackground().equals(cr.getForeground());
    }
    /**
     * blanks out a cell i.e. makes it unreadable
     * @param row
     * @param column
     * @param blank
     */
    public void setCellBlank(int row, int column, boolean blank){
        DefaultTableCellRenderer cr = (DefaultTableCellRenderer)table.getCellRenderer(row, column);
        if (blank){
            cr.setBackground(SystemColor.control);
            cr.setForeground(SystemColor.control);
        } else {
            cr.setBackground(null);
            cr.setForeground(null);
        }
    }

    public PropertyDescriptor[] getColumnPropertyMap() {
        return columnPropertyMap;
    }

    public void setColumnPropertyMap(PropertyDescriptor[] columnPropertyMap) {
        this.columnPropertyMap = columnPropertyMap;
    }

}
TOP

Related Classes of DisplayProject.ArrayFieldModel

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.