/*
Copyright (c) 2003-2008 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.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.plaf.basic.BasicHTML;
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 javax.swing.text.View;
import org.apache.log4j.Logger;
import DisplayProject.ArrayColumn;
import DisplayProject.ArrayColumnModel;
import DisplayProject.ArrayFieldModel;
import DisplayProject.Constants;
import DisplayProject.DataField;
import DisplayProject.FocusHelper;
import DisplayProject.MatrixField;
import DisplayProject.PasswordDataField;
import DisplayProject.PsuedoDoubleClickAction;
import DisplayProject.StatusTextListener;
import DisplayProject.TableJButton;
import DisplayProject.TableRowInterceptor;
import DisplayProject.UIutils;
import DisplayProject.WindowManager;
import DisplayProject.actions.StatusText;
import DisplayProject.actions.WidgetState;
import DisplayProject.actions.WidthPolicy;
import DisplayProject.events.ClickListener;
import DisplayProject.events.ClientEventManager;
import DisplayProject.events.InstallableListener;
import DisplayProject.factory.TableFactory;
import DisplayProject.table.ArrayFieldCellEditor;
import DisplayProject.table.ArrayFieldCellHelper;
import DisplayProject.table.ArrayFieldCellRenderer;
import DisplayProject.table.ArrayFieldEmptyCellRenderer;
import DisplayProject.table.ArrayFieldTableCellEditor;
import DisplayProject.table.ArrayTableComponentCorrector;
import DisplayProject.table.ComboBoxCellRenderer;
import DisplayProject.table.FormattedCellEditor;
import DisplayProject.table.FormattedCellRenderer;
import DisplayProject.table.StateColourForRow;
import Framework.CloneHelper;
import Framework.EventHandle;
import Framework.EventManager;
import Framework.ForteKeyboardFocusManager;
import Framework.ParameterHolder;
import Framework.TextData;
/**
* This subclasses {@link javax.swing.JTable} and provides behaviour similar to the Forte ArrayField.
*
* The ArrayField class defines a widget that displays data from an array of objects in a tabular format.
* It defines array fields, compound fields that display widgets in a tabular format, a group of cells organized
* into rows and columns, like a spreadsheet. The widgets represent data from an array of objects:
* Each row of the array field presents data from a corresponding row in an array of objects.
* In addition to the tabular display of widgets, an array field can have optional column titles and an optional scroll bar.
*
* @author Craig Mitchell
*/
@SuppressWarnings("serial")
public class ArrayField extends JTable implements MatrixField {
public class CellRowCol {
public int row, col;
public CellRowCol() {
this.row = -1;
this.col = -1;
}
}
private CellRowCol previousCell = new CellRowCol(); // The cell were we we came from
private boolean reverting = false;
private CellRowCol lastCell = new CellRowCol();
private boolean losingFocus = false; // CraigM 17/08/2007
private boolean paintEmptyRows = true;
private boolean paintEmptyRowRectangles = true; // CraigM:31/07/2008.
private boolean paintBodyBorder = false;
/**
* We store the current row so when the AfterValueChange event fires, we can get the row that it occured on instead
* of the currently selected row (which could be a different row as the AfterValueChange event gets fired by a focus loss
* event, which is only triggered after the focus has moved away from the current cell). CraigM 24/08/2007 & TF 11/10/07.
*/
private int rowForEvents = -1; // TF:11/10/07
private Border headerBorder; //PM:29/4/08
private static final Border headerRendererBorderMiddle = new EmptyBorder(2,0,5,0); // CraigM: 07/08/2008.
private static final Border headerRendererBorderLeft = new EmptyBorder(2,4,5,0); // CraigM: 07/08/2008.
private static final Border headerRendererBorderRight = new EmptyBorder(2,0,5,4); // CraigM: 07/08/2008.
private static final Border headerRendererBorderOneCol = new EmptyBorder(2,4,5,4); // CraigM: 07/08/2008.
private Color headerBackground; //CraigM:31/07/2008
private JTableHeader oldHeader = null; // CraigM:05/06/2008
private boolean isAttemptingToEditCell = false; // TF:09/01/2009:DET-70
private boolean highlightSelectedRow = true; //TF:04/03/2010: highlight the selected row or not
// TF:03/07/2008:Removed this as it's replaced with the rowForEvents field
// private int storedCurrentRow = -1;
protected boolean paintTitleBorder = true;
/**
* If we're tabbing forwards through an array field, and the first column we tab into is disabled, we'll register
* this disabled cell as an editable cell, then quickly stop editing it and start editing the next editable cell.
* However, this temporary editing means that we lose the row for the current events when the temporary editor is
* removed. We shouldn't alter the current row for events if we're calling changeSelectionForwards or
* changeSelectionBackwards. This flag will prevent that. If the value is 0, we can change the value ok.
*/
private int allowRowForEventsChangeCounter = 0;
public ArrayField(String name, int rowHeight, int headerFontStyle) {
super();
setOpaque(false);
setShowGrid(false);
setDoubleBuffered(true);
setSurrendersFocusOnKeystroke(true);
setCellSelectionEnabled(false);
setColumnSelectionAllowed(false);
setName(name);
if (rowHeight > 0){//PM:22/05/2008:fixed for express
setRowHeight( rowHeight );
}
setColumnModel(new ArrayColumnModel()); //Added to reduce code generated
setRowSelectionAllowed(true);
setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
setSelectionBackground(UIutils.PaleYellow); // This is the pale yellow row highlight - If you want it yellow use: UIutils.PaleYellow
setSelectionForeground(Color.BLACK);
setIntercellSpacing(new Dimension()); // CraigM: 19/07/2007 - Remove the gaps between the rows
// CraigM:22/07/2008 - Set our own UI to disable any drag operations
setUI(new ArrayFieldUI());
// Removed. CraigM: 27/02/2008.
// setDefaultEditor(BooleanData.class, new BooleanDataCellEditor());
// setDefaultRenderer(BooleanData.class, new BooleanDataCellRenderer());
TableRowInterceptor tri = new TableRowInterceptor();
EventManager.registerInterceptor(this, "AfterRowAppend", tri);
EventManager.registerInterceptor(this, "AfterRowEntry", tri);
EventManager.registerInterceptor(this, "AfterRowValueChange", tri);
EventManager.registerInterceptor(this, "BeforeRowExit", tri);
// TF:29/9/07:Removed SHIFT-SPACE and CTRL-SPACE from the action map because it causes a selection change,
// and is unfortunately easy to hit by accident
InputMap im = this.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK), "none");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK), "none");
// Disable enter functionality in Java that moves the selection to the next row, and allow the item to accept the enter.
// This enables buttons to accept enter and be pressed. CraigM 12/11/2007.
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "none");
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK);
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_MASK),PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK);
getActionMap().put(PsuedoDoubleClickAction.PSUED0_DOUBLE_CLICK, new PsuedoDoubleClickAction());
//PM:27/9/07
/*
* changed the default background colour to be inherited from the parent
* i.e. set it to null
* This makes it similar to forte in that header is rendered in the correct color
* and cell renderers work better.
*
* The side effect is that the empty rows have the default background too.
*/
setBackground(null);
if (WidthPolicy.get(this) == Constants.SP_NATURAL) {
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
}
else {
setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
}
//FIX:TF:H:23/1/2007: If there is a cell editor installed, the source object isn't actually
// the table, but rather the editor. Hence we use this table as the event table
//TF:27/9/07:added to stop editing if the table looses focus.
// This is a workaround to a known Sun bug. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4709394 for more information
this.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
ArrayFieldStatusTextListener arrayFieldStatusTextListener = new ArrayFieldStatusTextListener();
addMouseListener(arrayFieldStatusTextListener);
addMouseMotionListener(arrayFieldStatusTextListener);
addMouseListener(new MouseAdapter() {
/* Remembers mouse pressed point */
private Point pressedPoint = null;
public void mousePressed(MouseEvent e) {
pressedPoint = e.getPoint();
}
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e) && e.isPopupTrigger()) {
int row = rowAtPoint(e.getPoint());
int column = columnAtPoint(e.getPoint());
changeSelection(row, column, false, false);
}
// DK:09/09/2008:Use mousePressed/mouseReleased event catch instead of mouseClick
//because click event dependent to delay between mouse press and release events.
//In the case if some job need to be done in between of these actions mouseClick event does not fired.
//For example focusGain event is coming in between of pressed/released events and we have some processing in focusGain listener.
if (pressedPoint != null
&& ArrayField.this.isAllowsAppendAndEditable()) { // Changed to call isAllowsAndCanAppend. CraigM: 07/04/2008.
// Listen for a click outside the rows. Add a new row if required.
final int emptyRowStart = getRowHeight() * getRowCount();
final int emptyRowEnd = getRowHeight() * (getRowCount()+1);
if (pressedPoint.getY() > emptyRowStart && pressedPoint.getY() < emptyRowEnd
&& e.getY() > emptyRowStart && e.getY() < emptyRowEnd) {
ArrayField.this.appendRow(e);
}
}
pressedPoint = null;
}
});
this.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
final int reason = ForteKeyboardFocusManager.getTraversalReason();
if (reason == Constants.FC_SUPRESS) {
return;
}
Component opposite = (Component)e.getOppositeComponent();
// DK:09/09/2008:Repost AfterFocusGain to all the parents if the ArrayField receive the focus not from the same ArrayField or it's editor
if (opposite == null || ArrayFieldCellHelper.getArrayField(opposite) != ArrayField.this) {
Container parent = ArrayField.this;
while (parent != null) {
postAfterFocusGain(reason, parent);
parent = parent.getParent();
}
}
if (opposite == null) {
opposite = ArrayField.this;
}
JTable tab = ArrayFieldCellHelper.getArrayField(opposite);
if (getRowCount() > 0 && (tab == null || tab != ArrayField.this)) {
// There is a Java bug where if we exit a JTable via clicking on something else, the
// focus loss is never posted, this is a problem if we only have 1 item in the array,
// so we clear out the last cell on focus gain too. CraigM 03/10/2007
if (ArrayField.this.getRowCount() == 1 && ArrayField.this.getFirstEditableColumn() == ArrayField.this.getLastEditableColumn()) {
ArrayField.this.lastCell.col = -1;
ArrayField.this.lastCell.row = -1;
}
// We we are going backwards, select the last item. CraigM 02/09/2007
if (reason == Constants.FC_KEYPREV) {
// CraigM:17/10/2008 - Fixed to go to last editable column
changeSelection(ArrayField.this.getRowCount()-1, ArrayField.this.getLastEditableColumn(), false, false);
}
// Only select the first item if we tab into the ArrayField. CraigM: 27/03/2008
else if (reason == Constants.FC_KEYNEXT) {
// CraigM:17/10/2008 - Fixed to go to first editable column
changeSelection(0, ArrayField.this.getFirstEditableColumn(), false, false);
}
}
}
public void focusLost(FocusEvent e) {
if (ArrayField.this.paintEmptyRows) {
ArrayField.this.repaint();
}
// CraigM:18/08/2008 - No need to clear the selection on focus loss
final int reason = ForteKeyboardFocusManager.getTraversalReason();
// if (reason != Constants.FC_SUPRESS) {
Component opposite = (Component)e.getOppositeComponent();
//Repost BeforeFocusLoss to all the parents if opposite component is not the same ArrayField or it is not editor
if (opposite == null || ArrayFieldCellHelper.getArrayField(opposite) != ArrayField.this) {
// DK:22/09/2008: post AfterRowValueChange if the event was not posted before
postRowChange();
Container parent = ArrayField.this;
boolean isFirst = true;
while (parent != null) {
postBeforeFocusLoss(reason, parent, isFirst);
isFirst = false;
parent = parent.getParent();
}
}
// }
// Component opposite = (Component)e.getOppositeComponent();
// if (opposite == null || !(opposite instanceof JComponent))
// return;
// JTable tab = ArrayFieldCellHelper.getArrayField(opposite);
// if ((tab == null) || (tab != ArrayField.this)) {
// getSelectionModel().clearSelection();
// }
}
});
final JTableHeader header = getTableHeader();
header.setResizingAllowed(false);
header.setReorderingAllowed(false);
if (headerFontStyle != Font.PLAIN) {
header.setFont(header.getFont().deriveFont(headerFontStyle));
}
// Make the header look like Forte - Simple labels. CraigM 23/08/2007.
header.setDefaultRenderer( new TableCellRenderer() {
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) {
if (ArrayField.this.getColumnModel().getColumn(column) instanceof ArrayColumn) {
// TF:25/08/2009:DET-112:Changed this to instantiate a TextGraphic instead of a JLabel
// because code that gets the header through ArrayField.getColumnTitle() is expecting a TextGraphic
// JLabel label = new JLabel(value.toString());
// JLabel label = new JLabel();
// label.setText(value.toString());
// TextGraphic label = new TextGraphic(value.toString());
// label.setStandardJavaSizing(true);
//
TextGraphic label = new TextGraphic();
label.setStandardJavaSizing(true);
label.setText(value.toString());
ArrayColumn ac = (ArrayColumn)ArrayField.this.getColumnModel().getColumn(column);
label.setOpaque(true);
if (ac.getHeaderFont() != null) label.setFont(ac.getHeaderFont());
if (ac.getHeaderForeground() != null) label.setForeground(ac.getHeaderForeground());
label.setHorizontalAlignment(ac.getHeaderHorizontalAlignment());
label.setVerticalAlignment(ac.getHeaderVerticalAlignment());
// CraigM:31/07/2008 - Use the defined header background if one exists
if (ArrayField.this.headerBackground != null) {
label.setBackground(ArrayField.this.headerBackground);
}
else {
label.setBackground(header.getBackground());
}
// Put a margin around the labels. CraigM: 07/08/2008.
if (ArrayField.this.getColumnCount() == 1) {
label.setBorder(ArrayField.headerRendererBorderOneCol);
}
else if (column == 0) {
label.setBorder(ArrayField.headerRendererBorderLeft);
}
else if (column == ArrayField.this.getColumnCount()-1) {
label.setBorder(ArrayField.headerRendererBorderRight);
}
else {
label.setBorder(ArrayField.headerRendererBorderMiddle); // Put a margin around the labels
}
// CraigM: 15/07/2008 - If we are html formatted, we need to get Java to realise the label and calculate its actual size
if (label.getText().startsWith("<html>")) {
// TF:01/11/2008:Changed this expensive way of getting the size of a label with a much cheaper one.
label.setPreferredSize(null);
label.setMinimumSize(null);
// TF:28/08/2009:We must validate the label, or we get the wrong size
label.validate();
Dimension d = label.getMinimumSize();
View v = (View) label.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
d.width = (int)v.getPreferredSpan(View.X_AXIS);
}
label.setPreferredSize(d);
label.setMinimumSize(d);
/*
label.setPreferredSize(null);
label.setMinimumSize(null);
final JFrame f = new JFrame();
f.setUndecorated(true);
f.getContentPane().add(label);
f.pack();
Dimension dim = label.getSize();
label.setPreferredSize(dim);
label.setMinimumSize(dim);
f.getContentPane().remove(label);
// Not sure why I can't just call f.dispose() directly, but for some reason, it deadlocks the EDT.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.dispose();
}
});
*/
}
if (ac.getSizePolicy() == Constants.FP_FIXED) {
Dimension size = label.getPreferredSize();
size.width = ac.getWidth();
label.setMinimumSize(size);
}
// COLET 13/01/2009 : Add support for rendering the label as disabled based on a hint from the array column.
label.setEnabled(ac.isHeaderEnabled());
return label;
}
else {
return new JLabel("Column: "+column); // For visual editors. CraigM 20/02/2008
}
}
});
// Make the header look like Forte - Add the border. CraigM 23/08/2007.
this.headerBorder = new Border() {
public Insets getBorderInsets(Component c) {
return new Insets(0,0,0,0);
}
public boolean isBorderOpaque() {
return false;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
//PM:22/11/07 optional title border
if (ArrayField.this.paintTitleBorder){
//PM:15/11/07 -fixed border width
int paintWidth = ArrayField.this.getWidth()-1;
g.setColor(Color.black);
g.drawRect(x, y, paintWidth, height-4);
}
}
};
//PM:29/4/08 moved border to a field
header.setBorder(this.headerBorder);
/**
* body border like forte
* @author Peter
* @since 28/9/07
*/
this.setBorder(new Border() {
public Insets getBorderInsets(Component c) {
return new Insets(0,0,0,0);
}
public boolean isBorderOpaque() {
return false;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
if (ArrayField.this.paintBodyBorder){
g.setColor(Color.black);
((Graphics2D)g).setStroke(new BasicStroke(1.0f));
g.drawRect(x, y, width-1, height-1);
}
}
});
// TF:15/12/2009:DET-141:Set this up to allow inheriting of popup menu
this.setInheritsPopupMenu(true);
this.setBackground(null);
}
public void appendRow(int column) {
// TF:11/03/2009:DET-82:We cannot append a row onto a model if there is no model
if (this.getModel() instanceof ArrayFieldModel && ((ArrayFieldModel)this.getModel()).getData() != null) {
((ArrayFieldModel)this.getModel()).newRow();
if (getFirstEditableColumn() < 0) {
// No editable columns, we must post an event here
postAfterRowAppend(this.getRowCount(), ((ArrayFieldModel)this.getModel()).getData().get(this.getRowCount()-1), this);
}
else {
newRow = true;
}
int lvNewRow = this.getRowCount()-1;
this.changeSelection(lvNewRow, column, false, false);
if (this.editCellAt(lvNewRow, column)) {
TableCellEditor tce = ((ArrayFieldCellEditor)this.getCellEditor()).getEditor();
if (tce instanceof FormattedCellEditor){
((FormattedCellEditor)tce).getFormattedEditor().requestFocusInWindow();
((FormattedCellEditor)tce).getFormattedEditor().selectAll();
}
}
this.repaint(); // Update the empty rows graphics
}
}
private void appendRow(MouseEvent e) {
int col = this.columnAtPoint(e.getPoint());
// CraigM:23/01/2009 - You are only allowed to append if you click in an editable column
if (((ArrayFieldModel)this.getModel()).isColumnEditable(col)) {
// TF:10/10/07:We need to set New Row BEFORE doing the append row, because the append row will attempt to post
// the event which says there is a new row, but only if this flag is set to true.
((ArrayFieldModel)this.getModel()).setNewRow(true);
this.appendRow(col);
//This is required because Java does not fire a focus change until after the mouse
//activate row append. At an application level, we need to know about it before the append.
// TF:12/10/07:This shouldn't be needed any more
// if (this.isEditing() && this.getEditorComponent() instanceof DataField)
// EventManager.delayEventPostingUntil("BeforeFocusLoss");
// notify the Model that a new row has bee added
((ArrayFieldModel)this.getModel()).shouldIgnoreRowChange(e); //Required to prevent multiple AfterRowEntry events
// CraigM:12/06/2008 - If they created a new row on a button column, create an event as if they had pressed the button
ArrayColumnModel cm = (ArrayColumnModel)getColumnModel();
// CraigM:26/06/2008 - Don't use the getRealColumn method as we have obtained this column from x,y coords
Component comp = this.getColumnTemplate((ArrayColumn)cm.getColumn(col));
// CraigM:25/07/2008 - Do this for DataFields as well
if (comp instanceof TableJButton || comp instanceof DataField) {
// CraigM:12/06/2008 - Create a new mouse event with x,y coords in the middle of the button
e = new MouseEvent(comp,
e.getID(),
e.getWhen(),
e.getModifiers(),
comp.getWidth() / 2,
comp.getHeight() / 2,
e.getClickCount(),
e.isPopupTrigger());
ClientEventManager.postEvent(comp, "Click", ClickListener.makeParamList(e));
}
}
}
/**
* @return The current row. This will not return the new row until just before the afterRowEntry event will be processed
* by the owning thread. This method returns a 0-based offset for the current row, or -1 if no row is selected
*/
public int getCurrentRow() {
return this.getRowForEvents();
}
public CellRowCol getPreviousCell() {
return this.previousCell;
}
public boolean isHighlightSelectedRow() {
return highlightSelectedRow;
}
public void setHighlightSelectedRow(boolean highlightSelectedRow) {
this.highlightSelectedRow = highlightSelectedRow;
}
private JComponent getColumnTemplate(ArrayColumn pCol) {
if (pCol != null){ //PM:1/5/08 added null check
TableCellEditor editor = pCol.getEditor();
// CraigM:23/06/2008 - Go directly to the component
if (editor instanceof ArrayFieldTableCellEditor) {
return (JComponent)((ArrayFieldTableCellEditor)editor).getComponent();
}
// CraigM:23/06/2008 - Should never get here
Component comp = pCol.getCellEditor().getTableCellEditorComponent(this, null, true, -1, -1);
while (comp instanceof JPanel) {
comp = ((JPanel)comp).getComponent(0);
}
if (comp instanceof JComponent) {
return (JComponent)comp;
}
// TF:03/07/2008:We need to get the renderer if there is no editor, such as a JLabel
comp = pCol.getCellRenderer().getTableCellRendererComponent(this, null, false, false, 0, 0);
while (comp instanceof JPanel) {
comp = ((JPanel)comp).getComponent(0);
}
if (comp instanceof JComponent) {
return (JComponent)comp;
}
}
return null;
}
public JComponent getColumnTemplate(String pCol) {
Enumeration<TableColumn> cols = this.getColumnModel().getColumns();
while (cols.hasMoreElements()) {
ArrayColumn aCol = (ArrayColumn)cols.nextElement();
if (aCol.getName().equals(pCol)) {
return this.getColumnTemplate(aCol);
}
}
return null;
}
public JComponent getColumnTemplate(TextData pCol) {
return getColumnTemplate(pCol.toString());
}
/**
* Get the component that is used as the column template for the passed column. This will be
* the component that is used to edit the column when editable.<p>
* <p>
* The pCol parameter is a 1-based index which represents the real column in the array field
* @param pCol
* @return
*/
public JComponent getColumnTemplate(int pCol) {
ArrayColumnModel cm = (ArrayColumnModel)getColumnModel();
//PM:28/4/08 corrected for real column
return this.getColumnTemplate(cm.getRealColumn(pCol-1));
}
/**
* Get the title of the column with the passed index. The index is the real index into the
* array (not just into the visible columns), and should be 0 based.
* @param column the 0-based index into the array column model (using the real model)
* @return the TextGraphic used to render the column title.
*/
public TextGraphic getColumnTitle(int column){
//PM:28/4/08 corrected for real column
ArrayColumnModel cm = (ArrayColumnModel)getColumnModel();
// TF:3/7/08:Made this 0-based for consistency with Java, not 1-based as per Forte
String title = cm.getRealColumn(column).getHeaderValue().toString();
return (TextGraphic)getTableHeader().getDefaultRenderer().getTableCellRendererComponent(this, title, false, false, 0, column);
}
// TF:30/9/07: Commented out this method. It doesn't seem to do much and affects things like
// object drop, which drops onto the selected row, which is the highlighted row instead of
// where the cursor was located
// @Override
// public int getSelectedRow() {
// if (this.lastCell.row >= 0) {
// return this.lastCell.row;
// }
// return super.getSelectedRow();
// }
/*
* Store the table header incase they want it back.
* CraigM:05/06/2008.
* @see javax.swing.JTable#setTableHeader(javax.swing.table.JTableHeader)
*/
@Override
public void setTableHeader(JTableHeader tableHeader) {
oldHeader = tableHeader;
super.setTableHeader(tableHeader);
}
/**
* Put the table header back.
* CraigM:05/06/2008.
*/
public void restoreTableHeader() {
if (oldHeader != null) {
this.setTableHeader(oldHeader);
}
}
@Override
public void setBackground(Color bg) {
super.setBackground(bg);
this.setOpaque(true);
JTableHeader header = getTableHeader();
if (header != null){
header.setBackground(bg);
}
}
public void columnAdded(TableColumnModelEvent e) {
super.columnAdded(e);
if (getAutoResizeMode() == JTable.AUTO_RESIZE_OFF)
((ArrayFieldModel)getModel()).resizeTable();
}
public void columnRemoved(TableColumnModelEvent e) {
super.columnRemoved(e);
if (getAutoResizeMode() == JTable.AUTO_RESIZE_OFF)
((ArrayFieldModel)getModel()).resizeTable();
}
public boolean isCellEditable(int row, int column) {
if (isEnabled()){
// TF:10/02/2009:Changed this to cater for the stateColourForRow interface
ArrayColumn ac = (ArrayColumn) getColumnModel().getColumn(column);
if (ac.getStateColour() != null) {
return ac.getStateColour().isEnabled(row);
}
ArrayFieldModel alm = (ArrayFieldModel)getModel();
return alm.isCellEditable(row, column);
}
else
return false;
}
/**
* Under the normal scenario, editors can be removed from the array field when focus is changed. However, there are
* some very rare circumstances where we don't want the editor to be allowed to terminate. For example, when we are
* using a fillin field in an array the use of the arrow keys change the selection. This calls setSelectedItem, which
* will eventually terminate editing on the array field. In this case this is undesirable, because even setting the
* editor straight back up as the table editor after setting the selected item allows a minute, but non-zero window
* in which an up or down arrow (for example) can come in and is focused to the array field, and not the editor which
* really should be receiving it. This is treated as a request to change active cell, which is thoroughly undesirable<p>
* <p>
* To fix this, we prevent the editor from being discared. This should be used in only very rare circumstances, with
* great caution.
*/
private boolean allowEditorRemoval = true;
public void setAllowEditorRemoval(boolean allowEditorRemoval) {
this.allowEditorRemoval = allowEditorRemoval;
}
/**
* New row is set when we've appended a new row onto the array field, but not posted the event to the user yet.
*/
boolean newRow = false;
/**
* The lastEditRow is the row that was lasted edited in this array field. It's set to -1 if the last editor wasn't in this array field
*/
int lastEditRow = -1;
public int getLastEditRow() {
return lastEditRow;
}
/**
* The rowBeingEdited is the row that changes have been made in, or -1 if no changes have been made
*/
int rowBeingEdited = -1;
protected void postAfterFocusGain(int pReason, Component pObject) {
if (ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("reason", new ParameterHolder(pReason));
ClientEventManager.postEvent(pObject, "AfterFocusGain", params);
}
}
protected void postAfterFocusGain(int pReason, Component pObject, final int pNewRow) {
// 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.getTopLevelAncestor();
Thread windowThread = WindowManager.getOwningThread(topLevel);
Runnable action = new Runnable() {
public void run() {
setRowForEvents(pNewRow-1);
}
};
if (pReason != Constants.FC_SUPRESS) {
Hashtable<String, Object> params = new Hashtable<String, Object>();
params.put("reason", new ParameterHolder(pReason));
EventHandle handle = new EventHandle(pObject, "AfterFocusGain", params);
handle.setForceReceiptOfEvent(windowThread);
handle.setCommencementNotifier(action);
ClientEventManager.postEvent(handle);
}
}
/**
* A flag to indicate whether there is an AfterRowAppend event pending
*/
private boolean isAddRowEventPending = false;
/**
* Determine if a row is being added to this array field. This method returns true from
* the time the user initiates a request for a new row (eg clicks on an empty line in the
* array field, or tabs into it) until either just before the AfterRowAppend event is
* processed by the main thread (or could have been processed by the main thread if it's
* not registered for this event), or PurgeEvents is called.<p>
* <p>
* False is returned at all other times.
* @return
*/
public boolean isAddingRow() {
return isAddRowEventPending;
}
/**
* Post an AfterRowAppend event to any threads that are interested in the event
* @param pRow The row to post (1-based)
* @param newObject The new object for this row
* @param pObject the component to which the event applies, normally this ArrayField
*/
protected void postAfterRowAppend(int pRow, Object newObject, Component pObject) {
if (ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_SUPRESS) {
isAddRowEventPending = true;
FocusHelper.addPurgeAction(new Runnable() {
public void run() {
isAddRowEventPending = false;
}
}, this, "AfterRowAppend");
Hashtable<String, Object> params = new Hashtable<String, Object>();
params.put("row", new ParameterHolder(pRow));
params.put("newObject", new ParameterHolder(newObject));
EventHandle handle = new EventHandle(pObject, "AfterRowAppend", params);
Window topLevel = (Window)this.getTopLevelAncestor();
Thread windowThread = WindowManager.getOwningThread(topLevel);
handle.setForceReceiptOfEvent(windowThread);
handle.setCommencementNotifier(new Runnable() {
public void run() {
isAddRowEventPending = false;
}
});
ClientEventManager.postEvent(handle);
}
}
protected void postAfterRowEntry(int pRow, Component pObject) {
if (ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("row", new ParameterHolder(pRow));
ClientEventManager.postEvent(pObject, "AfterRowEntry", params);
}
}
private void postRowChange() {
if (lastEditRow >= 0) {
if (rowBeingEdited == lastEditRow) {
// We actually made changes to this row, post the AfterRowValueChange
postAfterRowValueChange(rowBeingEdited + 1, this);
rowBeingEdited = -1;
}
// We need to fire the before row exit event
// TF:13/03/2009:We cannot use the getEditingRow, because this will be the new row
// that we have started editing (editCellAt has already been called), so we must
// use the lastEditRow at this point.
// postBeforeRowExit(this.getEditingRow()+1, this);
postBeforeRowExit(this.lastEditRow+1, this);
}
}
/**
* Update the row that has had its contents changed. CraigM:05/01/2009.
*/
public void setDataChangedInRow() {
this.rowBeingEdited = this.getCurrentRow();
}
protected void postAfterRowValueChange(int pRow, Component pObject) {
if (ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("row", new ParameterHolder(pRow));
ClientEventManager.postEvent(pObject, "AfterRowValueChange", params);
}
}
protected void postBeforeFocusLoss(int pReason, Component pObject, boolean pFirst) {
// See if it's one of the components that knows how to lose focus itself.
if (pReason != Constants.FC_SUPRESS) {
try {
EventManager.startEventChain();
if (pObject instanceof ArrayTableComponentCorrector) {
// TF:03/11/2008:In order to get the correct events, we must call lose focus on the actual editor
// FIXED: CCY: 9 Sep 2009 - Extra logic added to support DataField and PasswordDataField.
JComponent component = ((ArrayTableComponentCorrector)pObject).correctSubscriptionComponent();
if (component instanceof DataField) {
((DataField) component).loseFocus(pReason);
} else if (component instanceof PasswordDataField) {
((PasswordDataField) component).loseFocus(pReason);
}
}
else if (pObject instanceof MultiLineTextField) {
((MultiLineTextField)pObject).focusLost();
}
else if (pObject instanceof FillInField && ((FillInField)pObject).getEditor() instanceof FillInField.BorderedComboBoxEditor) {
FillInField fif = (FillInField)pObject;
FillInField.BorderedComboBoxEditor editor = (FillInField.BorderedComboBoxEditor)fif.getEditor();
editor.loseFocus(null);
}
else {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("reason", new ParameterHolder(pReason));
// TF:10/06/2009:Added a check for the First action, and the object type being a JComponent
if (pFirst && pObject instanceof JComponent) {
FocusHelper.addSetFocusPurgeAction((JComponent)pObject);
}
ClientEventManager.postEvent(pObject, "BeforeFocusLoss", params);
}
} finally {
EventManager.endEventChain();
}
}
}
protected void postBeforeRowExit(int pRow, Component pObject) {
if (ForteKeyboardFocusManager.getTraversalReason() != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("row", new ParameterHolder(pRow));
ClientEventManager.postEvent(pObject, "BeforeRowExit", params);
}
}
protected void postTraverse(int pReason, Component pSource, Component pTarget, Component pObject) {
// See if it's one of the components that knows how to lose focus itself.
if (pReason != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
params.put("reason", new ParameterHolder(pReason));
params.put("source", new ParameterHolder(pSource));
params.put("target", new ParameterHolder(pTarget));
ClientEventManager.postEvent(pObject, "Traverse", params);
}
}
@Override
public void removeEditor() {
if (allowEditorRemoval) {
// TF:11/10/07:Set this old row up as the row to be returned to other threads temporarily, until after the
// AfterRowEntry event has been dispatched.
this.setRowForEvents(lastEditRow);
EventManager.startEventChain();
Component currentEditor = ArrayFieldCellHelper.getArrayEditorComponent(this);
// TF:12/11/07:Null check, for example, if we're deleting a row programatically off the
// array and the array doesn't have focus.
if (currentEditor != null) {
postBeforeFocusLoss(ForteKeyboardFocusManager.getTraversalReason(), currentEditor, true);
// The KeyboardFocusManager should have moved the permanent focus owner prior to getting here,
// so we can use it to see where we came from. If we came from this array, don't fire the event
Component newOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
if (newOwner != this && ArrayFieldCellHelper.getArrayField(newOwner) != this) {
// TF:8/2/08: fire a RowAfterValueChange event if we've modified the
// row and we're leaving the array field
// TF:09/01/2009:DET-70:Do not post this event if we're attempting to edit a row in this table
// TF:13/03/2009:DET-85:Also flag not to post the BeforeRowExit next time we re-edit the cell
if (!isAttemptingToEditCell) {
this.postRowChange();
}
lastEditRow = -1;
}
super.removeEditor();
}
EventManager.endEventChain();
}
}
/**
* This method sets up the cell at the passed coordinates for edit, and returns true if the cell is editable.
* It also has the side effect of posting all the events needed to accurately reflect the forte model.
*/
public boolean editCellAt(final int row, final int column, EventObject e) {
// TF:12/10/07:If we're currently editing the cell we've been requested to edit, do nothing.
if (getEditingColumn() == column && getEditingRow() == row) {
return true;
}
boolean ok;
try {
// ---------------------------------------------------------------------------------------------------
// TF:12.10/07:Changed all the events so they're posted either here or in the removeEditor
// Obtain some information about the state of focus. At this point, the current focus owner is the old
// focus owner, and it's possible we're editing a cell in this array
// ---------------------------------------------------------------------------------------------------
EventManager.startEventChain();
Component currentOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
ArrayField oldArray = ArrayFieldCellHelper.getArrayField(currentOwner);
final boolean wasArrayChild = (oldArray == this || currentOwner == this);
if (!wasArrayChild && oldArray != null){
currentOwner = oldArray;
}
// This code is now obsolete, events are posted in the correct order
// if (e != null) {
// if ((getSelectedRow() != row) && isEditing() && (getEditorComponent() instanceof DataField))
// EventManager.delayEventPostingUntil("BeforeFocusLoss");
// }
// ---------------------------------------------------------------------------------------------------------
// Invoke the superclass and get whether we've actually started editing yet.
// TF:09/01/2009:DET-70:We need to know that we're attempting to re-edit a cell, otherwise the focus will be
// lost to a null parent temporarily, which will cause us to incorrectly post a BeforeRowExit event.
// ---------------------------------------------------------------------------------------------------------
isAttemptingToEditCell = true;
ok = super.editCellAt(row, column, e);
isAttemptingToEditCell = false;
if (ok) {
// TF:11/10/07:Set this old row up as the row to be returned to other threads temporarily, until after the
// AfterRowEntry event has been dispatched.
this.setRowForEvents(lastEditRow);
if (wasArrayChild){
// ----------------------------------------------------------------------------------------------
// Focus used to be on this array and it still is, Post BeforeRowExit if we're moving between row
// ----------------------------------------------------------------------------------------------
if (lastEditRow != row) {
this.postRowChange();
}
}
if (newRow) {
postAfterRowAppend(row+1, ((ArrayFieldModel)this.getModel()).getData().get(row), this);
newRow = false;
}
final int reason = ForteKeyboardFocusManager.getTraversalReason();
final Component newEditor = ArrayFieldCellHelper.getArrayEditorComponent(this);
final Component currentOwnerFinal = currentOwner;
final int lastEditRowFinal = lastEditRow;
/*The flag is used to define whether we need to repost AfterFocusGain when editor will gain focus*/
final boolean[] needToRepostAfterFocusGain = new boolean[]{true};
// DK:15/07/2008:post all the focus events by focusGained() of the editor component
//it will allow to process BeforeFocusLoss actions on previous component
//before AfterFocusGain on the editor.
final Runnable processForteEvents = new Runnable() {
public void run() {
try {
// DK:20/08/2008:start new EventChain to ensure the Forte events are performed in the same thread.
EventManager.startEventChain();
if (lastEditRowFinal != row) {
postAfterFocusGain(reason, newEditor, row+1);
}
else {
postAfterFocusGain(reason, newEditor);
}
Container parent = newEditor.getParent();
if (newEditor instanceof ArrayTableComponentCorrector) {
// ------------------------------------------------------------------------------------------
// TF:11/02/2009:FTL-6:We need to un-correct the posting component to ensure that we have the
// correct component that actually has the parent set of it. However, when we posted the
// events, we needed to use the corrected posting component
// ------------------------------------------------------------------------------------------
parent = ((ArrayTableComponentCorrector)newEditor).correctSubscriptionComponent().getParent();
}
while (parent != null) {
if (wasArrayChild || !needToRepostAfterFocusGain[0] || parent.isAncestorOf(currentOwnerFinal)) {
break;
}
else {
postAfterFocusGain(reason, parent);
}
parent = parent.getParent();
}
// need to fire afterRowEntry AFTER the focus gain events but BEFORE the TRAVERSE events
if (lastEditRowFinal != row) {
postAfterRowEntry(row + 1, ArrayField.this);
}
parent = newEditor.getParent();
while (parent != null) {
postTraverse(reason, currentOwnerFinal, newEditor, parent);
parent = parent.getParent();
}
} finally {
EventManager.endEventChain();
}
}
};
// TF:14/10/2009:Added a check for JFormattedTextField as (1) it's faster, and (2) we can miss some focus events if
// the cell is in select only mode for instance. This should still fire row entry events, so we need this.
if (newEditor instanceof JButton || newEditor instanceof JFormattedTextField) {
// DK:05/08/2008:if editor component is a button the table will repost all mouse events to the button
//by the way focusGained event happen after actionPerformed event for the button.
//But we need to process all the Forte actions before the action.
//That is why we need to process all the actions right now.
processForteEvents.run();
} else {
// DK:06/08/2008:add the focus listener to process Forte actions to all the child components.
//It will handle components like editable JComboBox where editor will receive focus instead of JComboBox itself
final List<Component> focusableChildren = UIutils.getFocusableChildren(newEditor);
FocusAdapter editorFocusListener = new FocusAdapter() {
public void focusGained(FocusEvent e) {
//remove the listener because we need to process it only once
for (Component component : focusableChildren) {
component.removeFocusListener(this);
}
//Do not need to repost if focus came from ArrayField. The reposting should be done by ArrayField focusGained event.
needToRepostAfterFocusGain[0] = e.getOppositeComponent() != ArrayField.this;
processForteEvents.run();
}
};
for (Component component : focusableChildren) {
//add the same listener to all the focusable components
component.addFocusListener(editorFocusListener);
}
}
lastEditRow = row;
}
}
finally {
EventManager.endEventChain();
}
// 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
EventHandle handle = new EventHandle(this, "dummy, cannot be caught");
Window topLevel = (Window)this.getTopLevelAncestor();
Thread windowThread = WindowManager.getOwningThread(topLevel);
handle.setForceReceiptOfEvent(windowThread);
handle.setCommencementNotifier(new Runnable() {
public void run() {
setRowForEvents(-1);
}
});
ClientEventManager.postEvent(handle);
/*PM:27/9/07
* This is code ensures that the click events
* are passed to the editor component. This causes the
* Forte Click events to be fired
*
* This does not work for OuterClick as there is no event
*/
if (ok && e instanceof MouseEvent){
MouseEvent me = (MouseEvent)e;
JComponent editor = ((JComponent)((JComponent)getEditorComponent()).getComponent(0));
if (editor instanceof JScrollPane){
editor = ((JScrollPane)editor).getViewport();
}
MouseListener[] listeners = editor.getMouseListeners();
for (MouseListener listener : listeners){
if (!(listener instanceof InstallableListener))
continue;
if (listener != null) {
int id = me.getID();
switch(id) {
case MouseEvent.MOUSE_PRESSED:
// CraigM:20/08/2008 - Mouse pressed event (not clicked)
// listener.mouseClicked(me);
listener.mousePressed(me);
break;
case MouseEvent.MOUSE_RELEASED:
listener.mouseReleased(me);
break;
case MouseEvent.MOUSE_CLICKED:
listener.mouseClicked(me);
break;
case MouseEvent.MOUSE_EXITED:
listener.mouseExited(me);
break;
case MouseEvent.MOUSE_ENTERED:
listener.mouseEntered(me);
break;
}
}
}
}
return ok;
}
/* Override this method so if the model changes behind our back, we can retain the currently selected item
* @see javax.swing.JTable#clearSelection()
* TF:9/11/09:I suspect that this code is now redundant. The changes to the model should now not fire a tableChanged event
* but rather a tableRowChanged or tableCellChanged event. (Unless the whole table changes, in which case it's fair game to
* lose the current selection) This means that the underlying table should maintain the selection itself, so we should be able
* to remove this code.
*/
// @Override
// public void clearSelection() {
// Component focusedComponent = FocusManager.getCurrentManager().getFocusOwner();
// super.clearSelection();
//
// if (focusedComponent != null && ArrayFieldCellHelper.getArrayField(focusedComponent) == this) {
// if (this.lastCell.col > 0 && this.lastCell.row > 0) {
// final int row = this.lastCell.row;
// final int col = this.lastCell.col;
// //PM:10/10/07 - removed the invoke later, this cause spurious focus loss events
// // SwingUtilities.invokeLater(new Runnable() {
// // public void run() {
// ArrayField.this.changeSelection(row, col, false, false);
// // }
// // });
// }
// }
// }
public boolean isLosingFocus() {
return this.losingFocus;
}
/**
* Change the selection in the table without requesting the focus to editor components and posting any Forte actions.
* The method is used to restore selection when focus is currently out of the array field.
* For example when user click Insert button - focus is on the button but selection in the table should be moved to the new row.
* @param rowIndex row to select
* @param columnIndex columnt to select
* @param toggle
* @param extend
*/
public void changeSelectionWithoutFocusRequest(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
super.changeSelection(rowIndex, columnIndex, toggle, extend);
// DK:30/07/2008:we need to update RowForEvents field to synchronise selection and Forte events.
// DK:20/08/2008:set rowForEvents to -1 because the field is used only during Forte events processing
//in all other cases it should be -1 to get current selected row index instead.
setRowForEvents(-1);
}
/**
* This override handles the focus traversal inside the JTable. This includes:
* - Tabbing between cells.
* - Tabbing into and out of the JTable.
* - Creation of a new row via tabbing.
*/
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
int traverseReason = ForteKeyboardFocusManager.getTraversalReason();
int firstEditableColumn = this.getFirstEditableColumn();
int lastEditableColumn = this.getLastEditableColumn();
this.losingFocus = false;
// --------------------------------------------------------------------
// Fix up the requested cell to be in the actual editable cell range
// --------------------------------------------------------------------
// We want a cell that is too far right
if (columnIndex > lastEditableColumn) {
// We actually want the next row
if (traverseReason == Constants.FC_KEYNEXT) {
columnIndex = firstEditableColumn;
// Are we on the very last editable cell (and we can't append), move the selection back to the start
if (rowIndex == this.getRowCount()-1 && this.isAllowsAppendAndEditable() == false) {
rowIndex = 0;
}
else {
rowIndex++;
}
}
// We don't want to next row, just move it back
else {
columnIndex = lastEditableColumn;
}
}
// We want a cell that is too far left
else if (columnIndex < firstEditableColumn) {
// We actually want the previous row
if (traverseReason == Constants.FC_KEYPREV) {
columnIndex = lastEditableColumn;
rowIndex--;
// Wrap around the table (will be handled correctly later with exiting the table backwards)
if (rowIndex == -1) {
rowIndex = this.getRowCount()-1;
columnIndex = lastEditableColumn;
}
}
// We don't want the previous row, just move it along to the first editable cell
else {
columnIndex = firstEditableColumn;
}
}
// --------------------------------------------------------------------
// Is there anything to actually edit
// --------------------------------------------------------------------
// TF:23/9/07:Changed this so tables with just one editable column will behave nicely
if (firstEditableColumn < 0 || firstEditableColumn > lastEditableColumn) {
if (traverseReason == Constants.FC_KEYPREV) {
this.transferFocusBackward();
}
else {
this.transferFocus();
}
this.postRowChange();
}
// ------------------------------------------------------------------
// Added skip on tab check. CraigM 30/01/2008.
// ------------------------------------------------------------------
else if (((ArrayColumn)this.getColumnModel().getColumn(columnIndex)).isSkipOnTab()) {
// User has pressed SHIFT+TAB which means we are going backwards
if (traverseReason == Constants.FC_KEYPREV) {
this.changeSelectionBackward(rowIndex, columnIndex);
}
else if (traverseReason == Constants.FC_KEYNEXT) {
this.changeSelectionForward(rowIndex, columnIndex);
}
}
// --------------------------------------------------------------------
// Create a new row with tab key
// --------------------------------------------------------------------
else if (traverseReason == Constants.FC_KEYNEXT &&
this.lastCell.row == this.getRowCount()-1 &&
this.lastCell.col == lastEditableColumn &&
columnIndex == firstEditableColumn &&
(rowIndex == 0 || rowIndex >= this.getRowCount()) &&
this.isAllowsAppendAndEditable()) {
this.appendRow(columnIndex);
}
// --------------------------------------------------------------------
// Create a new row with down arrow. CraigM:19/01/2009.
// --------------------------------------------------------------------
else if (traverseReason == Constants.FC_KEYDOWN && // User pressed the down arrow
this.newRow == false && // We aren't already creating a row
rowIndex == this.getRowCount()-1 && // We are on on the last row
rowIndex == this.lastCell.row && // The down arrow didn't move our cell to the last row
this.isAllowsAppendAndEditable()) { // We are allowed to create a new row
this.appendRow(columnIndex);
}
// --------------------------------------------------------------------
// Exit the table forward.
// If we moved from the last cell to the first cell (via a tab press), we actually want to exit the table.
// --------------------------------------------------------------------
else if (traverseReason == Constants.FC_KEYNEXT &&
this.lastCell.row == this.getRowCount()-1 &&
this.lastCell.col == lastEditableColumn &&
rowIndex == 0 &&
columnIndex == firstEditableColumn &&
((ArrayFieldModel)this.getModel()).allowsAppend() == false) {
this.transferFocus();
}
// --------------------------------------------------------------------
// Exit the table forwards if we try to enter a row which is beyond our
// row count, and we don't allow appending.
// --------------------------------------------------------------------
else if (rowIndex >= this.getRowCount() &&
((ArrayFieldModel)this.getModel()).allowsAppend() == false) {
this.transferFocus();
}
// --------------------------------------------------------------------
// Exit the table backwards.
// If we moved from the first cell to the last cell (via a shift-tab press), we actually want to exit the table.
// --------------------------------------------------------------------
else if (traverseReason == Constants.FC_KEYPREV &&
this.lastCell.row == 0 &&
this.lastCell.col == firstEditableColumn &&
rowIndex == this.getRowCount()-1 &&
columnIndex == lastEditableColumn) {
this.transferFocusBackward();
}
// --------------------------------------------------------------------
// Focus was requested, however, there is nothing to focus on!
// --------------------------------------------------------------------
else if (traverseReason == Constants.FC_MOUSECLICK &&
rowIndex > this.getRowCount() &&
((ArrayFieldModel)this.getModel()).allowsAppend() == false) {
this.transferFocus();
}
// --------------------------------------------------------------------
// Transfer focus between cells
// --------------------------------------------------------------------
else {
// TF:10/10/07:Use the lastCell.row in preference to the selected row, if available, as the selected row is not
// necessarily updated by the time we get here.
int oldRow = this.lastCell.row >= 0 ? this.lastCell.row : getSelectedRow();
Logger.getLogger(TableFactory.class).debug("Changing selection [" + rowIndex + "," + columnIndex + "]");
// TF:25/05/2010:Forte never allowed partial rows to be selected, so always set the toggle to be
// false, which prevents issues with ctrl-clicking a row
// super.changeSelection(rowIndex, columnIndex, toggle, extend);
super.changeSelection(rowIndex, columnIndex, false, extend);
if ((rowIndex != -1)
&& (columnIndex != -1)
&& isCellEditable(rowIndex, columnIndex)
&& (columnModel.getColumn(columnIndex).getCellEditor() != null))
{
// TF:03/07/2008:If we use the mouse click to change focus, we can get the selection model to be adjusting
// even when we're transfering focus to a data field (which shouldn't care that it's adjusting). This can
// result in infinite stack trace when trying to find the next place to get focus.
if (!this.getSelectionModel().getValueIsAdjusting() || ArrayFieldCellHelper.getArrayEditorComponent(this) instanceof DataField) {
//Ensure cell has focus once editing has been approved
if (editCellAt(rowIndex, columnIndex))
{
// CraigM:09/07/2008 - Request the focus on the actual component (not the wrapper panel).
final Component editorComponent = ArrayFieldCellHelper.getArrayEditorComponent(this, false);
editorComponent.requestFocusInWindow(); // Note: This requestFocusInWindow has special coding in GridField
}
}
// If the value is adjusting (meaning there are more events coming) and we are a drop list.
// Then we will need to tell the drop list after it has done all its events, that it needs
// to show its popup menu. CraigM: 01/04/2008
else if (ArrayFieldCellHelper.getArrayEditorComponent(this) instanceof DropList) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (ArrayFieldCellHelper.getArrayEditorComponent(ArrayField.this) instanceof DropList) {
((DropList)ArrayFieldCellHelper.getArrayEditorComponent(ArrayField.this)).showPopup();
}
}
});
}
}
if (oldRow != rowIndex) {
((ArrayFieldModel)getModel()).postRowChanged(oldRow, rowIndex);
}
// Remember where we were last editing
if (this.lastCell.row > -1 && this.lastCell.col > -1 && isCellEditable(this.lastCell.row, this.lastCell.col)) {
this.previousCell.row = this.lastCell.row;
this.previousCell.col = this.lastCell.col;
}
// Don't rely on getSelectedRow and getSelectedColumn as they don't seem to get updated in time.
this.lastCell.row = rowIndex;
this.lastCell.col = columnIndex;
// ------------------------------------------------------------------
// If we have landed on a non editable cell, move the selection along
// Added check for editor component being not enabled. CraigM 31/10/2007.
// TF:10/02/2009:Added check for stateColourForRow
// ------------------------------------------------------------------
StateColourForRow sfr = ((ArrayColumn)getColumnModel().getColumn(columnIndex)).getStateColour();
if ((rowIndex > -1 && columnIndex > -1 && isCellEditable(rowIndex, columnIndex) == false) ||
((!(sfr != null && sfr.isEnabled(rowIndex))) &&
(ArrayFieldCellHelper.getArrayEditorComponent(this) != null &&
ArrayFieldCellHelper.getArrayEditorComponent(this).isEnabled() == false))) {
// User has pressed SHIFT+TAB which means we are going backwards
if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYPREV) {
this.changeSelectionBackward(rowIndex, columnIndex);
}
else {
this.changeSelectionForward(rowIndex, columnIndex);
}
}
// ------------------------------------------------------------------
// Added skip on tab check. CraigM 30/01/2008.
// ------------------------------------------------------------------
// else if (ArrayFieldCellHelper.getArrayEditorComponent(this) != null &&
// ((JComponent)ArrayFieldCellHelper.getArrayEditorComponent(this)).getClientProperty("SkipOnTab") != null) {
//
// // User has pressed SHIFT+TAB which means we are going backwards
// if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYPREV) {
// this.changeSelectionBackward();
// }
// else if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYNEXT) {
// this.changeSelectionForward();
// }
// }
// DropLists and things like that mess up the empty row painting, so we just repaint after every cell traversal
if (this.paintEmptyRows) {
this.repaint();
}
}
}
/**
* Traverse the focus forward inside the JTable
* Written by Craig Mitchell
*/
public void changeSelectionForward(int pRow, int pCol) {
this.allowRowForEventsChangeCounter++;
if (this.getLastEditableColumn() == pCol) {
// TF:06/04/2009:DET-92: If we're trying to transfer the focus forwards, and we allow
// appends but there is nothing editable, we can get into an infinite loop where we are
// seeking a location to put the focus and there's nowhere to put it. This check allows
// us to avoid this situation.
if (pRow + 1 > this.getRowCount()) {
// Nothing can be edited, move focus forwards
this.transferFocus();
}
else {
this.changeSelection(pRow + 1, this.getFirstEditableColumn(), false, false);
}
}
else {
this.changeSelection(pRow, pCol+1, false, false);
}
this.allowRowForEventsChangeCounter--;
}
/**
* Traverse the focus backwards (If SHIFT-TAB is pressed) inside the JTable.
* Written by Craig Mitchell
*/
public void changeSelectionBackward(int pRow, int pCol) {
this.allowRowForEventsChangeCounter++;
if (pCol == 0) {
this.changeSelection(pRow - 1, this.getLastEditableColumn(), false, false);
}
else {
this.changeSelection(pRow, pCol-1, false, false);
}
this.allowRowForEventsChangeCounter--;
}
/**
* Get the last column in the array field that is editable. If there are no editable columns, this
* method returns -1
* @return the index of the last editable column, or -1 if there are none.
*/
public int getLastEditableColumn() {
ArrayFieldModel model = (ArrayFieldModel)getModel();
int columnCount = model.getColumnCount()-1;
for (int i = columnCount; i >= 0; i--){
if (model.isColumnEditable(i)){
return i;
}
}
return -1;
}
/**
* Get the first column in the array field that is editable. If there are no editable columns, this
* method returns -1
* @return the index of the first editable column, or -1 if there are none.
*/
public int getFirstEditableColumn() {
// TF:24/9/07: Changed this logic to return something other than 0 (a valid column) if there are no editable columns
ArrayFieldModel model = (ArrayFieldModel)getModel();
int columnCount = model.getColumnCount()-1;
// TF:29/9/07:Fixed this up to go up to the proper index
for (int i = 0; i <= columnCount; i++) {
if (model.isColumnEditable(i)) {
return i;
}
}
return -1;
}
public void revertSelection() {
//We need to set the valueIsAdjusting flag on the selection models to false
//because for some reason Java occasionally thinks it is an adjusting change.
//We then need to flag this table as reverting so we ignore the valueChanged()
//invocation that results from the setValueIsAdjusting() call.
reverting = true;
getSelectionModel().setValueIsAdjusting(false);
getColumnModel().getSelectionModel().setValueIsAdjusting(false);
reverting = false;
changeSelection(getPreviousCell().row, getPreviousCell().col, false, false);
((ArrayFieldModel)getModel()).resetFlags();
}
public boolean isReverting() {
return this.reverting;
}
/*
* this allows the viewport to to resize based on the packed column size
* @see javax.swing.JTable#getPreferredScrollableViewportSize()
*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public void setAllowsAppend(boolean flag) {
((ArrayFieldModel)getModel()).setAllowsAppend(flag);
}
public boolean isAllowsAppend() {
return ((ArrayFieldModel)getModel()).allowsAppend();
}
public boolean getAllowsAppend() {
return this.isAllowsAppend();
}
/**
* @return true if we are marked as allowing append, we have a non null data model, and we are in FS_UPDATE mode.
* CraigM: 07/04/2008.
*/
private boolean isAllowsAppendAndEditable() {
return (this.isAllowsAppend() &&
(((ArrayFieldModel)this.getModel()).getData() != null) &&
(WidgetState.getWorstStateFromParents(this, WidgetState.get((JComponent)this)) == Constants.FS_UPDATE));
}
public int getVisibleRows() {
return ((ArrayFieldModel)getModel()).getVisibleRows();
}
public void setVisibleRows(int visibleRows) {
((ArrayFieldModel)getModel()).setVisibleRows(visibleRows);
((ArrayFieldModel)getModel()).resizeTable();
}
private void scrollToRow(int pRow, int pColumn) {
java.awt.Rectangle rect = this.getCellRect(pRow, pColumn, true);
this.scrollRectToVisible(rect);
// this.clearSelection(); CraigM:21/08/2008 - Don't need to clear the selection
// CraigM:30/05/2008 - Check the row is in range
if (pRow > -1 && pRow < this.getRowCount()) {
// this.setRowSelectionInterval(pRow, pRow);
this.changeSelection(pRow, 0, false, false); // CraigM:21/08/2008 - Actually change the selection.
}
// ((DefaultTableModel)this.getModel()).fireTableDataChanged(); // notify the model. CraigM:21/08/2008 - Nothing has changed.
}
/**
* Scroll the JTable to a particular cell
*
* @param pRow 0 based
* @param pColumn 0 based
*
* Written by Craig Mitchell
* @since 20/02/2008
*/
public void scrollToCell(int pRow, int pColumn, int pPolicy) {
switch (pPolicy) {
case Constants.AF_TOP:
this.scrollToRow(this.getRowCount()-1, pColumn);
this.scrollToRow(pRow, pColumn);
break;
case Constants.AF_BOTTOM:
this.scrollToRow(0, pColumn);
this.scrollToRow(pRow, pColumn);
break;
case Constants.AF_MIDDLE:
int visibleRows = this.getVisibleRows();
this.scrollToRow(this.getRowCount()-1, pColumn);
this.scrollToRow(pRow - (visibleRows/2), pColumn);
break;
default:
this.scrollToRow(pRow, pColumn);
break;
}
}
@Override
public void transferFocus() {
this.lastCell.col = -1;
this.lastCell.row = -1;
losingFocus = true;
super.transferFocus();
this.postRowChange();
}
@Override
public void transferFocusBackward() {
this.lastCell.col = -1;
this.lastCell.row = -1;
losingFocus = true;
super.transferFocusBackward();
this.postRowChange();
}
/**
* Changes the behaviour of a table in a JScrollPane to be more like
* the behaviour of JList, which expands to fill the available space.
* JTable normally restricts its size to just what's needed by its
* model.
*/
public boolean getScrollableTracksViewportHeight() {
if (getParent() instanceof JViewport) {
JViewport parent = (JViewport) getParent();
return (parent.getHeight() > getPreferredSize().height);
}
return false;
}
/**
* By default, empty rows will be painted
*/
public void setPaintEmptyRows(boolean pDoIt) {
this.paintEmptyRows = pDoIt;
}
/**
* By default, empty row rectangles will be painted. CraigM:31/07/2008.
*/
public void setPaintEmptyRowRectangles(boolean pDoIt) {
this.paintEmptyRowRectangles = pDoIt;
}
/**
* Paints empty rows too, after letting the UI delegate do
* its painting.
*/
public void paintComponent(final Graphics g) {
super.paintComponent(g);
if (this.paintEmptyRows) {
this.paintEmptyRows(g);
}
}
/**
* Render and paint empty rows.
*
* CraigM:04/11/2008 - Fixed to round to the row heights
*/
private void paintEmptyRows(Graphics g) {
int rowCount = this.getRowCount();
Rectangle visibleRect = this.getVisibleRect();
int visibleRows = visibleRect.height / rowHeight;
int currentHeightOfAllRows = this.getHeight() / rowHeight * rowHeight; // Current height of all the rows (rounded down to the row height)
int screenHeight = visibleRows * rowHeight; // Height of the visible area (rounded down to the row height)
int requiredHeightForAllRows = rowCount * rowHeight; // Height needed to display all the rows
// TF:10/03/2009:Added an efficiency measure of only painting the empty rows if they're needed
int topRow;
Rectangle parentVisibleRect = null;
if (this.getParent() instanceof JViewport) {
JViewport port = (JViewport)this.getParent();
topRow = this.rowAtPoint(new Point(0, port.getViewRect().y));
parentVisibleRect = port.getVisibleRect();
}
else {
topRow = this.rowAtPoint(this.getCellRect(0, 0, true).getLocation());
}
if (topRow + visibleRows < rowCount) {
// There are no visible, empty rows, so just abort
return;
}
// CraigM:03/11/2008 - Cater for append row area
if (this.isAllowsAppendAndEditable()) { // CraigM:14/01/2009 - Also check if we are editable
requiredHeightForAllRows += rowHeight;
}
// If more then a screen of information has been removed, then resize the array. CraigM: 09/04/2008
if (Math.max(requiredHeightForAllRows, screenHeight) < currentHeightOfAllRows-1) {
// TF:30/12/2009:DET-146:If we're too small for the window, then don't allow the resize
if (parentVisibleRect == null || parentVisibleRect.height > visibleRect.height) {
Dimension dim = this.getPreferredSize();
dim.height = Math.max(requiredHeightForAllRows, screenHeight);
this.setPreferredSize(null);
this.setSize(dim);
this.setMinimumSize(dim);
}
}
// Is it too small. CraigM: 11/04/2008
else if (requiredHeightForAllRows > currentHeightOfAllRows+1) {
Dimension dim = this.getPreferredSize();
dim.height = requiredHeightForAllRows;
this.setPreferredSize(null);
this.setSize(dim);
this.setMinimumSize(dim);
}
// Increase the size to cater for a blank row. CraigM 30/11/2007.
if (this.isAllowsAppendAndEditable() && rowCount * rowHeight >= currentHeightOfAllRows) {
Dimension dim = this.getPreferredSize();
dim.height += rowHeight;
this.setPreferredSize(dim);
this.setSize(dim);
this.setMinimumSize(dim);
}
// If there is room, draw the blank rows
if (rowCount * rowHeight < currentHeightOfAllRows) {
int inset = 4;
float dash1[] = {4.0f};
BasicStroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 2.0f, dash1, 0.0f);
TableColumnModel colModel = this.getColumnModel();
ArrayFieldModel model = (ArrayFieldModel)getModel();
Stroke defaultStroke = ((Graphics2D)g).getStroke();
for (int row = rowCount; row <= currentHeightOfAllRows/rowHeight; row++) {
int xPos = 0; // PM this needs to be zero because the line does not repaint correctly
for (int col = 0; col < colModel.getColumnCount(); col++) {
ArrayColumn column = (ArrayColumn)this.getColumnModel().getColumn(col);
Color backgroundColour = this.getBackground();
// Rectangle place holders
g.setColor(backgroundColour);
g.fillRect(xPos, row*rowHeight, column.getWidth(), rowHeight);
// Details in rectangle place holders
if (this.paintEmptyRowRectangles && rowHeight > inset*2 && column.getWidth() > inset*2) {
// If we can append a row, we are on the last row, and this column is editable
if (this.isAllowsAppendAndEditable() && row == rowCount && model.isColumnEditable(col)) {
TableCellRenderer renderer = ((ArrayFieldCellRenderer)column.getCellRenderer()).getRenderer();
// The renderer supports painting in an empty row. CraigM:20/01/2009.
if (renderer instanceof ArrayFieldEmptyCellRenderer) {
// Paint an editable row (not faded)
// TF:27/11/2009:Changed this to pass in the width, which allows us to draw the proper image when the column is resized.
BufferedImage img = ((ArrayFieldEmptyCellRenderer)renderer).getImage(column.getWidth());
g.drawImage(img, xPos + ((column.getWidth() - img.getWidth()) / 2), (row*rowHeight) + ((rowHeight - img.getHeight()) / 2), null);
}
// Draw a filled white rectangle
else {
Color emptyCellColor = backgroundColour.equals(Color.gray) ? Color.lightGray : Color.gray;
g.setColor(emptyCellColor);
g.drawRect(xPos+inset, (row*rowHeight)+inset, column.getWidth()-(inset*2), rowHeight-(inset*2));
}
}
else {
TableCellRenderer renderer = ((ArrayFieldCellRenderer)column.getCellRenderer()).getRenderer();
// The renderer supports painting in an empty row. CraigM:20/01/2009.
if (renderer instanceof ArrayFieldEmptyCellRenderer) {
// These are non editable rows, so fade them out a little
((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f));
BufferedImage img = ((ArrayFieldEmptyCellRenderer)renderer).getImage(column.getWidth());
g.drawImage(img, xPos + ((column.getWidth() - img.getWidth()) / 2), (row*rowHeight) + ((rowHeight - img.getHeight()) / 2), null);
((Graphics2D)g).setComposite(AlphaComposite.Src);
}
else {
Color emptyCellColor = backgroundColour.equals(Color.lightGray) ? Color.gray : Color.lightGray;
g.setColor(emptyCellColor);
((Graphics2D)g).setStroke(dashed);
g.drawRect(xPos+inset, (row*rowHeight)+inset, column.getWidth()-(inset*2), rowHeight-(inset*2));
}
}
}
// --- End ---
xPos += column.getWidth();
}
}
((Graphics2D)g).setStroke(defaultStroke);
}
}
@Override
public void paintImmediately(int x, int y, int w, int h) {
/* PM:10/10/07
* This method is overridden to force the repaint of the entire table area.
* It fixes a bug where the ghosting and new row rectangles are not drawn correctly
* when a row is deleted from the table
*/
super.paintImmediately(0, 0, getWidth(), getHeight());
}
public boolean isPaintBodyBorder() {
return paintBodyBorder;
}
/**
* Sets the body border paint flag. With this flag set to true
* a border is painted around the body of the JTable similar to Forte
* @param paintBodyBorder
*/
public void setPaintBodyBorder(boolean paintBodyBorder) {
this.paintBodyBorder = paintBodyBorder;
}
@Override
protected void processMouseEvent(final MouseEvent e) {
// TF:28/9/07: Horrible, horrible, horrible.... but effective. Java sometimes posts events to the array when they
// really should be dispatched elsewhere. This seems particularly prevalent when entering a new cell in the table
// with the mouse (via a click event). Subsequent events go to the correct editor, but not this inital mouse
// click that requests the focus and editor change. Hence we will listen for this case and redispatch the event
// to what we believe is the correct component ourselves.
int row = -1;
int column = -1;
if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getComponent() == this) {
Point p = new Point(e.getX(), e.getY());
row = ArrayField.this.rowAtPoint(p);
column = ArrayField.this.columnAtPoint(p);
if (row >= 0 && column >= 0) {
// TF:31/10/08:If we right click, we want the current selection to change to the
// clicked row, and the editor, if any, to be removed. Java does not do this by
// default.
// CraigM:03/11/2008 - The right click to change cells is handled in the MouseAdapter.mouseClicked method above.
// if (e.getButton() == MouseEvent.BUTTON3) {
// if (isEditing()) {
// ArrayField.this.getCellEditor().stopCellEditing();
// }
// ArrayField.this.getSelectionModel().setSelectionInterval(row, row);
// }
Component c = null;
TableCellEditor tce = ArrayField.this.getCellEditor(row, column);
// Get the component (via new mechanism). CraigM: 28/03/2008
if (tce instanceof ArrayFieldCellEditor) {
c = ((ArrayFieldCellEditor)tce).getComponent();
// TF:31/10/08:Corrected this to use Peter's new methods
if (c instanceof ArrayTableComponentCorrector) {
c = ((ArrayTableComponentCorrector)c).correctPostingComponent();
}
}
if (c == null) {
TableCellRenderer tcr = getCellRenderer(row, column);
if (tcr != null) {
c = tcr.getTableCellRendererComponent(ArrayField.this, getValueAt(row, column), false, false, row, column);
}
}
// If the component is editable, then the event will be fired in the EditCellAt method. CraigM: 28/03/2008.
if (this.isCellEditable(row, column) == false) {
MouseEvent newEvent;
if (c != null) {
// See if there's a root level panel in there hiding the real children
if (c instanceof JPanel && c.getName() == null && ((JPanel)c).getComponentCount() == 1) {
c = ((JPanel)c).getComponent(0);
}
// repost the event, don't adjusted for this cell so the poster can work out the row / column
newEvent = new MouseEvent( c,
e.getID(),
e.getWhen(),
e.getModifiers(),
e.getX(),
e.getY(),
e.getClickCount(),
e.isPopupTrigger(),
e.getButton());
}
else {
// This isn't clicked on anywhere in the table where there is a cell editor or renderer, so it must be on the table itself
newEvent = e;
}
MouseListener[] listeners = c.getMouseListeners();
for (MouseListener listener : listeners) {
if (listener instanceof InstallableListener) {
listener.mouseClicked(newEvent);
}
}
}
}
super.processMouseEvent(e);
// If we don't own focus, and there's a request on the queue to give us focus, defer processing of the click
// code until after this event is processed. This is needed because: if the mouse event comes first, it will
// set the current row properly based on the location of the click. However, the focus event will reset the
// focus to the first row, overwriting the current row and causing the code to mis-behave. Unfortunately, it's
// the click event above which generates the focus event, so we can't look for the focus event until AFTER we
// have processed this event. And if there is, we must then defer processing of the change selection until
// after the Focus event, so we defer this event until then.
//
// NB: There is a possibility of a race condition, if code in response to the mouse click or focus event obtains
// the current row, which may or may not slip in before this code is processed. Hence the TableRow.get waits for
// the EDT to become idle before returning this value.
if (row >= 0 && column >= 0) {
// See if there's a focus event on the queue giving us focus
FocusEvent focusEvent = (FocusEvent)Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent(FocusEvent.FOCUS_GAINED);
if (focusEvent != null && !focusEvent.isTemporary() && focusEvent.getComponent() == this) {
// Yes, we will need to change focus back to this row, AFTER the focus has been processed. Hence
// we will put an invocation event on the event queue to reset the focus
final int newRow = row;
final int newColumn = column;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
changeSelection(newRow, newColumn, false, false);
}
});
}
}
}
else {
super.processMouseEvent(e);
}
}
/**
* Return the row that is to be used if the code invokes "GetCurrentRow" on this table. If this value hasn't
* been set or is invalid, the current selection is used. This returns a 0-based index
*/
public int getRowForEvents() {
return rowForEvents >= 0 ? rowForEvents : getSelectedRow();
}
/**
* Set the row that is to be used for "GetCurrentRow" on this table.
* @param rowForEvents
*/
public void setRowForEvents(int rowForEvents) {
if (allowRowForEventsChangeCounter == 0) {
this.rowForEvents = rowForEvents;
}
}
/**
* We need to cater for the rows being edited if the row being edited is deleted. The change selection seems to cater
* for most of this logic, so we'll just reuse it here.
* @param e
*/
public void tableChanged(TableModelEvent e) {
int oldRow = this.getSelectedRow();
int oldColumn = this.getSelectedColumn();
super.tableChanged(e);
if (e.getType() == TableModelEvent.DELETE) {
if (oldRow < this.getRowCount()) {
this.changeSelection(oldRow, oldColumn, false, false);
}
else {
//DK: 6/03/2009: do not request focus if the table was not in focus before table change event.
if (isEditing()) {
this.changeSelection(oldRow - 1, oldColumn, false, false);
} else {
this.changeSelectionWithoutFocusRequest(oldRow - 1, oldColumn, false, false);
}
}
}
// CraigM:05/01/2009 - rowBeingEdited is now updated via UIUtils.setDataChangedFlag method.
// else if (e.getColumn() != TableModelEvent.ALL_COLUMNS) {
// this.rowBeingEdited = e.getLastRow();
// }
}
public int getFirstVisibleRow() {
Point p = this.getVisibleRect().getLocation();
return this.rowAtPoint(p);
}
public boolean isPaintTitleBorder() {
return paintTitleBorder;
}
public void setPaintTitleBorder(boolean paintTitleBorder) {
this.paintTitleBorder = paintTitleBorder;
}
// public void setVisibleRowCount(int rows){
// this.setPreferredScrollableViewportSize(new Dimension(
// this.getPreferredScrollableViewportSize().width,
// rows*this.getRowHeight()
// ));
// }
/**
* TF:26/11/07:We need to ensure that the table size is updated when we need to perform a layout
*/
@Override
public void doLayout() {
((ArrayFieldModel)getModel()).resizeTable();
super.doLayout();
}
/**
* @return A clone of ourselves (only the widget, doesn't clone the data behind it).
* Written by Craig Mitchell
* @since 24/12/2007
*/
public ArrayField cloneComponent() {
ArrayField clone = TableFactory.newArrayField(this.getName(), this.getRowHeight());
clone.setModel(this.getModel());
clone.setColumnModel(new ArrayColumnModel());
// This copyProperties was just copied from ListView, so it's probably not correct.
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
"columnModel", // class DisplayProject.ArrayColumnModel
"component", // null
"componentCount", // int
//"componentPopupMenu", // class javax.swing.JPopupMenu
"components", // class [Ljava.awt.Component;
"containerListeners", // class [Ljava.awt.event.ContainerListener;
"currentNode", // class DisplayProject.DisplayNode
"currentRow", // int
"debugGraphicsOptions", // int
//"doubleBuffered", // boolean
//"enabled", // boolean
"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
//"hasHorzScrollbar", // boolean
//"hasTitles", // boolean
//"hasVertScrollbar", // boolean
//"height", // int
"inheritsPopupMenu", // boolean
"inputMap", // null
"inputVerifier", // class javax.swing.InputVerifier
"insets", // class java.awt.Insets
"layout", // interface java.awt.LayoutManager
"list", // class javax.swing.JList
"listData", // class [Ljava.lang.Object;
//"listStyle", // int
"managingFocus", // boolean
//"maximumSize", // class java.awt.Dimension
//"minimumSize", // class java.awt.Dimension
"name", // class java.lang.String
"nextFocusableComponent", // class java.awt.Component
//"opaque", // boolean
//"optimizedDrawingEnabled", // boolean
"paintingTile", // boolean
//"popupMenu", // class javax.swing.JPopupMenu
"preferredSize", // class java.awt.Dimension
"registeredKeyStrokes", // class [Ljavax.swing.KeyStroke;
"requestFocusEnabled", // boolean
"rootNode", // class DisplayProject.DisplayNode
"rootPane", // class javax.swing.JRootPane
"rootVisible", // boolean
"selectedCount", // int
"selectedNodes", // class DisplayProject.Array_Of_DisplayNode
"selectedRow", // int
"selectionBackground", // class java.awt.Color
"selectionForeground", // class java.awt.Color
"selectionMode", // int
"selectionModel", // interface javax.swing.ListSelectionModel
"sortToggle", // boolean
"sortType", // int
"sorter", // class DisplayProject.TableSorter
"table", // class javax.swing.JTable
"tableHeader", // class javax.swing.table.JTableHeader
"tableModel", // class DisplayProject.ArrayListModel
//"toolTipText", // class java.lang.String
"topLevelAncestor", // class java.awt.Container
"topLine", // int
"transferHandler", // class javax.swing.TransferHandler
"validateRoot", // boolean
"verifyInputWhenFocusTarget", // boolean
"vetoableChangeListeners", // class [Ljava.beans.VetoableChangeListener;
"viewNodes", // class DisplayProject.Array_Of_DisplayNode
//"visible", // boolean
//"visibleColumns", // int
"visibleRect", // class java.awt.Rectangle
//"width", // int
//"x", // int
//"y" // int
});
return clone;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void deleteColumn(int position, boolean force) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void deleteRow(int position, boolean force) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public TextData getCaption() {
return null;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public Font getCaptionFont() {
return null;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getCellBottomMargin() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getCellGravity() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getCellLeftMargin() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getCellRightMargin() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getCellTopMargin() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public JComponent getChildInCell(int row, int column) {
return null;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getHorzDividerStyle() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getHorzDividerWeight() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getInsertPolicy() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getRows() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getVertDividerStyle() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public int getVertDividerWeight() {
return 0;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void insertColumn(int position) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void insertRow(int position) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public boolean isCollapsed() {
return false;
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCaption(TextData caption) {
}
private boolean isDoingPaint = false;
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCaptionFont(Font font) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCellBottomMargin(int cellMarginBottom) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCellGravity(int cellGravity) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCellLeftMargin(int cellMarginLeft) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCellRightMargin(int cellMarginRight) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCellTopMargin(int cellMarginTop) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setCollapsed(boolean value) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setHorzDividerStyle(int horzDivideStyle) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setHorzDividerWeight(int horzDivideWeight) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setInsertPolicy(int insertPolicy) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setVertDividerStyle(int vertDivideStyle) {
}
/**
* This method will not be Implemented.
*/
@Deprecated
public void setVertDividerWeight(int vertDivideWeight) {
}
//PM:29/4/08
public Border getHeaderBorder() {
return headerBorder;
}
//PM:29/4/08
public void setHeaderBorder(Border headerBorder) {
this.headerBorder = headerBorder;
this.getTableHeader().setBorder(headerBorder);
}
/**
* Set the background colour for the tables header. CraigM:31/07/2008.
* @param colour
*/
public void setHeaderBackground(Color colour) {
this.headerBackground = colour;
}
/**
* Returns the total number of columns in the array including hidden columns
* @return
*/
//PM:1/5/08
public int getRealColumnCount() {
ArrayColumnModel cm = (ArrayColumnModel)getColumnModel();
return cm.getRealColumnCount();
}
private HashMap<String, String> cellToolTips = null;
private String formKey(int row, String columnName) {
return columnName + ":" + row;
}
/**
* Set the tool tip for a particular cell in the array field, given the name
* of the column and the row.
* @param row
* @param columnName
* @param value
*/
public void setCellToolTip(int row, String columnName, String value) {
String key = formKey(row, columnName);
if (cellToolTips == null) {
cellToolTips = new HashMap<String, String>(20);
}
cellToolTips.put(key, value);
}
/**
* This method prepares the renderer for a cell. It is overridden to allow
* a custom tool tip to get set.
*/
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (cellToolTips != null && c instanceof JComponent) {
JComponent jc = (JComponent)c;
String value = cellToolTips.get(formKey(row, getColumnName(column)));
if ("".equals(value)) {
value = null;
}
jc.setToolTipText(value);
}
return c;
}
@Override
public void paint(Graphics g) {
isDoingPaint = true;
super.paint(g);
isDoingPaint = false;
}
/**
* PM:4/11/08: support for multiple selection
* This interface is used to expose the isRowSelected method of the underlying JTable. This is needed when the customer use special classes to allow multiple rows to be selected for the array field.
*/
public interface RowSelection {
public boolean isRowSelected(int row, Object rowData);
}
/**
* The row selector to use, or null if we have to use the default.
*/
private RowSelection rowSelector = null;
/**
* Determine if the row is selected for a particular row. Overridden to allow multiple selection.
*/
@Override
public boolean isRowSelected(int row) {
if (rowSelector == null || !isDoingPaint) {
return super.isRowSelected(row);
}
else {
// mulit row selector is active and painting
Object o = ((ArrayFieldModel)this.getModel()).getData().get(row);
return rowSelector.isRowSelected(row, o);
}
}
/**
* Get the row selector
* @return
*/
public RowSelection getRowSelector() {
return rowSelector;
}
/**
* Set the row selector. This allows us to do multiple selection.
* @param rowSelector
*/
public void setRowSelector(RowSelection rowSelector) {
this.rowSelector = rowSelector;
}
/**
* Override the method to get right tool tip text from renderer.
*/
public String getToolTipText(MouseEvent event) {
String tip = null;
Point p = event.getPoint();
// Locate the renderer under the event location
int hitColumnIndex = columnAtPoint(p);
int hitRowIndex = rowAtPoint(p);
Component component = null;
if ((hitColumnIndex != -1) && (hitRowIndex != -1)) {
TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex);
component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex);
// DK:19/08/2008:get a real renderer component instead of wrapper panel. See ArrayFieldCellRenderer.
if (component instanceof JPanel) {
JPanel wrapPanel = (JPanel) component;
Component[] childComponents = wrapPanel.getComponents();
if (childComponents.length > 0) {
component = childComponents[0];
}
}
// TF:15/12/2009:DET-143:Allowed this to get the tool tip from an editor component
else if (hitColumnIndex != -1 && this.isAllowsAppendAndEditable()) {
int emptyRowStart = getRowHeight() * getRowCount();
int emptyRowEnd = getRowHeight() * (getRowCount()+1);
if (event.getY() > emptyRowStart && event.getY() < emptyRowEnd) {
ArrayColumn column = (ArrayColumn)this.getColumnModel().getColumn(hitColumnIndex);
component = column.getComponent();
}
}
// Now have to see if the component is a JComponent before
// getting the tip
if (component instanceof JComponent) {
// Convert the event to the renderer's coordinate system
java.awt.Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false);
p.translate(-cellRect.x, -cellRect.y);
MouseEvent newEvent = new MouseEvent(component, event.getID(),
event.getWhen(), event.getModifiers(),
p.x, p.y,
event.getClickCount(),
event.isPopupTrigger(),
MouseEvent.NOBUTTON);
tip = ((JComponent)component).getToolTipText(newEvent);
}
}
// No tip from the renderer get our own tip
if (tip == null) {
tip = getToolTipText();
}
return tip;
}
/**
* Updates status line text depended to status text defined for renderer of pointed column under mouse.
* @author klimontd
*/
private class ArrayFieldStatusTextListener extends MouseAdapter implements MouseMotionListener {
public void mouseEntered(MouseEvent e) {
updateStatusText(e);
}
private void updateStatusText(MouseEvent e) {
TextData statusTextValue = null;
int columnAtPoint = columnAtPoint(e.getPoint());
TableColumn column = getColumnModel().getColumn(columnAtPoint);
if (column instanceof ArrayColumn) {
ArrayColumn arrayColumn = (ArrayColumn) column;
TableCellRenderer renderer = arrayColumn.getRenderer();
if (renderer instanceof FormattedCellRenderer) {
FormattedCellRenderer formatedRenderer = (FormattedCellRenderer) renderer;
statusTextValue = (TextData) formatedRenderer.getDataFieldOriginalComponent().getClientProperty(StatusText.cSTATUS_TEXT);
} else if (renderer instanceof ComboBoxCellRenderer) {
statusTextValue = (TextData) ((ComboBoxCellRenderer)renderer).getComboBox().getClientProperty(StatusText.cSTATUS_TEXT);
} else {
Component tableCellRendererComponent = renderer.getTableCellRendererComponent(ArrayField.this, null, false, false, 0, 0);
if (tableCellRendererComponent instanceof JComponent) {
JComponent jComponent = (JComponent) tableCellRendererComponent;
statusTextValue = (TextData) jComponent.getClientProperty(
StatusText.cSTATUS_TEXT);
}
}
}
if (statusTextValue == null) {
statusTextValue = (TextData)((JComponent)e.getSource()).getClientProperty(StatusText.cSTATUS_TEXT);
// if (statusTextValue == null) {
// statusTextValue = new TextData();//RCH Clear the value
// //RCH return;
// }
}
StatusTextListener.sharedInstance().setStatusTextForComponent((JComponent)e.getSource(), statusTextValue);
}
public void mouseExited(MouseEvent e) {
StatusTextListener.sharedInstance().resetStatusTextForComponent((JComponent)e.getSource());
}
public void mouseMoved(MouseEvent e) {
updateStatusText(e);
}
public void mouseDragged(MouseEvent e) {
}
}
}