/*
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;
import java.awt.Color;
import java.awt.Component;
import java.awt.FocusTraversalPolicy;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Stroke;
import java.beans.PropertyChangeSupport;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import DisplayProject.actions.ActionMgr;
import DisplayProject.actions.CaptionFont;
import DisplayProject.actions.Column;
import DisplayProject.actions.FrameWeight;
import DisplayProject.actions.Height;
import DisplayProject.actions.HeightPolicy;
import DisplayProject.actions.Parent;
import DisplayProject.actions.PendingAction;
import DisplayProject.actions.Row;
import DisplayProject.actions.Width;
import DisplayProject.actions.WidthPolicy;
import DisplayProject.binding.BindingManager;
import DisplayProject.controls.DropList;
import DisplayProject.controls.Panel;
import DisplayProject.factory.CompoundFieldFactory;
import DisplayProject.table.ArrayFieldCellHelper;
import Framework.CloneHelper;
import Framework.ErrorMgr;
import Framework.ForteKeyboardFocusManager;
import Framework.ParameterHolder;
import Framework.TextData;
import Framework.UsageException;
/**
* The GridField class defines a compound field that organizes child widgets into cells and aligns the cells in rows and columns.
*/
@SuppressWarnings("serial")
public class GridField extends JPanel implements MatrixField, CloneableComponent {
protected TextData caption;
protected int cellGravity = Constants.CG_DEFAULT;
/**
* Whether invisible children partake in the sizing and layout or not. Default value is false
*/
protected boolean ignoreInvisibleChildren = false;
protected Object DataObject = null;
protected GridFieldLayout layout;
protected int insertPolicy = Constants.IP_DEFAULT;
protected int horzDividerWeight = Constants.W_NONE;
protected int vertDividerWeight = Constants.W_NONE;
// TF:09/05/2008:Fixed these up to point to default, not LS_SOLID
protected int horzDividerStyle = Constants.LS_DEFAULT;
protected int vertDividerStyle = Constants.LS_DEFAULT;
protected Font captionFont = null;
protected boolean collapsed = true;
protected PropertyChangeSupport listeners;
protected int topMargin = 0;
protected int bottomMargin = 0;
protected int leftMargin = 0;
protected int rightMargin = 0;
protected String generatedName = null;
protected BindingManager bindingManager;
public GridField() {
super();
this.layout = new GridFieldLayout();
super.setLayout(this.layout);
this.addPropertyChangeListener(layout);
setFocusable(false);
this.cellGravity = Constants.CG_CENTER;
//PM 9/7/07 made opaque like forte but inherits background colour
this.setOpaque(true);
this.setBackground(null);
// TF:26/07/2008:Made the foreground colour inherit also
this.setForeground(null);
GridTitledBorder border = new GridTitledBorder();
border.setFrameWeight(Constants.W_NONE);
this.setBorder(border);
// TF:13/11/2009:DET-130:Added status line listener
this.addMouseListener(StatusTextListener.sharedInstance());
// TF:11/12/2009:DET-141:Allowed this control to inherit its popup menus
this.setInheritsPopupMenu(true);
}
public GridField(String name) {
this();
setName(name);
}
public GridField(String name, boolean generatedName) {
this();
if (generatedName) {
this.generatedName = name;
} else {
setName(name);
}
}
/**
* The grid field is intimately tied into the grid field layout so we
* cannot allow a new layout to be set. This method doesn't throw any
* exceptions to allow visual editors to work properly.
*/
@Override
public void setLayout(LayoutManager mgr) {
//throw new UsageException("Grid fields cannot have their layouts set programatically.");
}
/**
* Set the frame colour for the Grid Field. This method is not thread-safe and
* should be called on the EDT.
* @param c The colour to set the frame to
*/
public void setFrameColor(Color c) {
if (getBorder() instanceof GridTitledBorder) {
((GridTitledBorder)getBorder()).setFrameColor(c);
}
else if (getBorder() instanceof TitledBorder) {
((TitledBorder)getBorder()).setTitleColor(c);
}
}
/**
* Get the colour of the frame for the grid field. If no colour has been set,
* BLACK will be returned.
* @return
*/
public Color getFrameColor() {
if (getBorder() instanceof GridTitledBorder) {
return ((GridTitledBorder)getBorder()).getFrameColor();
}
else if (getBorder() instanceof TitledBorder) {
return ((TitledBorder)getBorder()).getTitleColor();
}
return Color.black;
}
/**
* Set the weight of the frame (border around the grid field). The frame weight should be one of the following:<p>
* <table>
* <tr><td><b>Constants.W_DEFAULT</b></td><td>Set the frame to be the default. This is a 3d indented border</td></tr>
* <tr><td><b>Constants.W_NONE</b></td><td>Remove the frame</td></tr>
* <tr><td><b>Constants.W_ONEPIXEL</b></td><td>Set the frame to be one pixel wide</td></tr>
* <tr><td><b>Constants.W_TWOPIXELS</b></td><td>Set the frame to be two pixels wide</td></tr>
* <tr><td><b>Constants.W_THREEPIXELS</b></td><td>Set the frame to be three pixels wide</td></tr>
* <tr><td><b>Constants.W_MEDIUM</b></td><td>Set the frame to a medium weight</td></tr>
* <tr><td><b>Constants.W_HEAVY</b></td><td>Set the frame to a heavy weight</td></tr>
* <tr><td><b>Constants.W_THIN</b></td><td>Set the frame to a thin weight</td></tr>
* <tr><td><b>Constants.W_VERYTHIN</b></td><td>Set the frame to a very thin weight</td></tr>
* <tr><td><b>Constants.W_VERYHEAVY</b></td><td>Set the frame to a very heavy weight</td></tr>
* </table>
* @param pWeight the weight to set
*/
public void setFrameWeight(int pWeight) {
if (getBorder() instanceof GridTitledBorder) {
((GridTitledBorder)getBorder()).setFrameWeight(pWeight);
}
}
/**
* Return the weight of the frame (border around the grid field)
* @return
*/
public int getFrameWeight() {
if (getBorder() instanceof GridTitledBorder) {
return ((GridTitledBorder)getBorder()).getFrameWeight();
}
return Constants.W_NONE;
}
/**
* @return the first component
* Written by Craig Mitchell
* @since 04/12/2007
*/
public Component getComponentTopLeft() {
for (int x=0; x<this.layout.getRows(); x++) {
for (int y=0; y<this.layout.getColumns(); y++) {
Component comp = this.getChildInCell(x, y);
if (comp != null) {
return comp;
}
}
}
return null;
}
/**
* @return the last component
* Written by Craig Mitchell
* @since 04/12/2007
*/
public Component getComponentBottomRight() {
for (int x=this.layout.getRows()-1; x>=0; x--) {
for (int y=this.layout.getColumns()-1; y>=0; y--) {
Component comp = this.getChildInCell(x+1, y+1);
if (comp != null) {
return comp;
}
}
}
return null;
}
/**
* Go through the grid to find the neighbour.
*
* @param pComp if null, it will start at the top of the grid if pAbove is false, or bottom or if pAbove is true.
* @param pAbove
* @return the component below or above pComponent. Will wrap around columns. Null if none.
*
* Written by Craig Mitchell
* @since 04/12/2007
*/
public Component getComponentNeighbour(Component pComp, boolean pAbove) {
int row = 0;
int col = 0;
// If no component is supplied, start at the start/end
if (pComp == null) {
if (pAbove) {
return this.getComponentBottomRight();
}
else {
return this.getComponentTopLeft();
}
}
else {
GridCell cell = this.layout.findConstraints(pComp);
if (cell != null) {
row = cell.row;
col = cell.column;
}
}
// Keep going through the grid until we find a component, or we reach the end of the grid
while (true) {
// Move the row up or down
row = pAbove ? row-1 : row+1;
// Fix the row and column
if (row == -1) {
row = this.layout.getRows()-1;
col--;
}
else if (row == this.layout.getRows()) {
row = 0;
col++;
}
// Did we fall off the end of the grid
if (col==-1 || col == this.layout.getColumns()) {
return null;
}
Component result = this.getChildInCell(row+1, col+1);
// Return if we found a component
if (result != null) {
return result;
}
}
}
protected void forceRelayout() {
int widthPolicy = LayoutManagerHelper.getWidthPolicy(this);
int heightPolicy = LayoutManagerHelper.getHeightPolicy(this);
if (widthPolicy != Constants.SP_EXPLICIT && heightPolicy != Constants.SP_EXPLICIT) {
// We can reset the minimum size as it's not explicitly set
this.setMinimumSize(null);
}
this.setPreferredSize(null);
this.invalidate();
this.validate();
}
/**
* The MinHeight property, same as the Forte property minHeightInPixels.
* @see #getMinHeight()
*/
protected int minHeight = 0;
/**
* The minHeight property specifies the absolute minimum width for the field in pixels. Setting a minimum width for
* a field ensures that the field will never get resized below this minimum.<p>
* <p>
* The GridField layout manager uses this minimum width when calculating the size of a field that has its
* WidthPolicy set to SP_TO_PARENT and is contained by a grid field. In addition, the grid field size will
* always be large enough to display the entire character field at its specified minimum width.<p>
* <p>
* Note that this attribute only has meaning for resizeable fields. Because fixed-size fields cannot change
* size, the layout manager will never change their natural size.
* @return the minimum height of the grid field
*/
public int getMinHeight() {
return minHeight;
}
public void setMinHeight(int pMinHeight) {
int oldValue = this.minHeight;
this.minHeight = pMinHeight;
if (oldValue != pMinHeight) {
firePropertyChange("minHeight", oldValue, pMinHeight);
}
}
protected int minWidth = 0;
public int getMinWidth() {
return minWidth;
}
public void setMinWidth(int pMinWidth) {
int oldValue = this.minWidth;
this.minWidth = pMinWidth;
if (oldValue != pMinWidth) {
firePropertyChange("minWidth", oldValue, pMinWidth);
}
}
/**
* Returning the bottom margin for inter-cell spacing in mils
* @return
*/
public int getCellBottomMargin() {
return UIutils.pixelsToMils(bottomMargin);
}
/**
* Returning the bottom margin for inter-cell spacing in pixels
* @return
*/
public int getCellBottomMarginPixels() {
return bottomMargin;
}
/**
* Set the bottom margin of the grid field. The bottom margin used for a widget will be the greater of this value
* and the bottom margin for that particular cell. The passed value should be in mils, however note that for historical
* reasons, this amount is actually half the required amount. It would be nice to eliminate this, but that would
* mean re-generating all existing code, which is not possible
*/
public void setCellBottomMargin(int cellMarginBottom) {
int oldValue = bottomMargin;
bottomMargin = UIutils.milsToPixels(cellMarginBottom * 2);
firePropertyChange("cellBottomMargin", oldValue, bottomMargin);
}
/**
* Set the bottom margin of the grid field, in pixels
* @param cellTopMargin
*/
public void setCellBottomMarginPixels(int cellBottomMargin) {
int oldValue = bottomMargin;
bottomMargin = cellBottomMargin;
firePropertyChange("cellBottomMargin", oldValue, bottomMargin);
}
/**
* Returning the top margin for inter-cell spacing in mils
* @return
*/
public int getCellTopMargin() {
return UIutils.pixelsToMils(topMargin);
}
/**
* Returning the top margin for inter-cell spacing in pixels
* @return
*/
public int getCellTopMarginPixels() {
return topMargin;
}
/**
* Set the top margin of the grid field. The top margin used for a widget will be the greater of this value
* and the top margin for that particular cell. The passed value should be in mils, however note that for historical
* reasons, this amount is actually half the required amount. It would be nice to eliminate this, but that would
* mean re-generating all existing code, which is not possible
*/
public void setCellTopMargin(int cellTopMargin) {
int oldValue = topMargin;
topMargin = UIutils.milsToPixels(cellTopMargin * 2);
firePropertyChange("cellTopMargin", oldValue, topMargin);
}
/**
* Set the top margin of the grid field, in pixels
* @param cellTopMargin
*/
public void setCellTopMarginPixels(int cellTopMargin) {
int oldValue = topMargin;
topMargin = cellTopMargin;
firePropertyChange("cellTopMargin", oldValue, topMargin);
}
/**
* Returning the left margin for inter-cell spacing in mils
* @return
*/
public int getCellLeftMargin() {
return UIutils.pixelsToMils(leftMargin);
}
/**
* Returning the left margin for inter-cell spacing in pixels
* @return
*/
public int getCellLeftMarginPixels() {
return bottomMargin;
}
/**
* Set the left margin of the grid field. The left margin used for a widget will be the greater of this value
* and the left margin for that particular cell. The passed value should be in mils, however note that for historical
* reasons, this amount is actually half the required amount. It would be nice to eliminate this, but that would
* mean re-generating all existing code, which is not possible
*/
public void setCellLeftMargin(int cellLeftMargin) {
int oldValue = leftMargin;
leftMargin = UIutils.milsToPixels(cellLeftMargin * 2);
firePropertyChange("cellLeftMargin", oldValue, leftMargin);
}
/**
* Set the left margin of the grid field, in pixels
* @param cellTopMargin
*/
public void setCellLeftMarginPixels(int cellLeftMargin) {
int oldValue = leftMargin;
leftMargin = cellLeftMargin;
firePropertyChange("cellLeftMargin", oldValue, leftMargin);
}
/**
* Returning the right margin for inter-cell spacing in mils
* @return
*/
public int getCellRightMargin() {
return UIutils.pixelsToMils(bottomMargin);
}
/**
* Returning the right margin for inter-cell spacing in pixels
* @return
*/
public int getCellRightMarginPixels() {
return rightMargin;
}
/**
* Set the right margin of the grid field. The right margin used for a widget will be the greater of this value
* and the right margin for that particular cell. The passed value should be in mils, however note that for historical
* reasons, this amount is actually half the required amount. It would be nice to eliminate this, but that would
* mean re-generating all existing code, which is not possible
*/
public void setCellRightMargin(int cellRightMargin) {
int oldValue = rightMargin;
rightMargin = UIutils.milsToPixels(cellRightMargin * 2);
firePropertyChange("cellRightMargin", oldValue, rightMargin);
}
/**
* Set the top margin of the grid field, in pixels
* @param cellTopMargin
*/
public void setCellRightMarginPixels(int cellRightMargin) {
int oldValue = rightMargin;
rightMargin = cellRightMargin;
firePropertyChange("cellRightMargin", oldValue, rightMargin);
}
public TextData getCaption() {
if (caption == null) {
Border border = getBorder();
if ((border != null) && (border instanceof TitledBorder)) {
String title = ((TitledBorder) border).getTitle();
caption = (title == null) ? null : new TextData(title);
}
}
return caption;
}
public void setCaption(String pCaption) {
if (pCaption == null) {
this.setCaption((TextData)null);
}
else {
this.setCaption(new TextData(pCaption));
}
}
public void setCaption(TextData pCaption) {
TextData oldValue = caption;
caption = pCaption;
Border border = getBorder();
if (border != null) {
if (border instanceof GridTitledBorder) {
((GridTitledBorder)border).setTitle(pCaption.toString());
this.revalidate();
this.repaint();
}
if (border instanceof TitledBorder) {
if (pCaption != null) {
((TitledBorder) border).setTitle(pCaption.toString());
} else {
((TitledBorder) border).setTitle("");
}
} else {
if (pCaption != null) {
TitledBorder tb = new GridTitledBorder(border, caption.toString(), TitledBorder.LEFT, TitledBorder.TOP);
if (this.captionFont != null) {
tb.setTitleFont(this.captionFont);
}
setBorder(tb);
}
}
} else {
GridTitledBorder gridBorder;
if (pCaption != null) {
gridBorder = new GridTitledBorder(pCaption.toString());
} else {
gridBorder = new GridTitledBorder("");
}
if (this.captionFont != null) {
gridBorder.setTitleFont(this.captionFont);
}
setBorder(gridBorder);
}
firePropertyChange("caption", oldValue, caption);
}
public int getCellGravity() {
return cellGravity;
}
public void setCellGravity(int pCellGravity) {
switch (cellGravity) {
case Constants.CG_BOTTOMCENTER:
case Constants.CG_BOTTOMLEFT:
case Constants.CG_BOTTOMRIGHT:
case Constants.CG_CENTER:
case Constants.CG_INHERIT:
case Constants.CG_MIDDLELEFT:
case Constants.CG_MIDDLERIGHT:
case Constants.CG_TOPCENTER:
case Constants.CG_TOPLEFT:
case Constants.CG_TOPRIGHT:
int oldValue = cellGravity;
cellGravity = pCellGravity;
firePropertyChange("cellGravity", oldValue, cellGravity);
break;
default:
UsageException errorVar = new UsageException("Cell gravity passed invalid value of " + pCellGravity);
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
/**
* Add a new component into the grid field. We need to honour the insert policy so
* if it's reject we do nothing here
* @param comp
*/
public void add(JComponent comp) {
if (this == comp)
return;
if (comp == this.getParent())
return;
// TF:20/8/07:If this child is already there, remove it first
remove(comp);
if (layout.applyInsertPolicy(this, comp, false)) {
GridCell cell = getConstraints(comp);
super.add(comp, cell);
}
this.invalidate();
}
/**
* Add a new component into the grid field. We need to honour the insert policy so
* if it's reject we do nothing here
* @param comp
*/
public void add(JComponent comp, Object constraints) {
// TF:20/8/07:If this child is already there, remove it first
//remove(comp);
// TF:6/3/08:We need to check to see if the existing component is already parented to anything. If it is, then
// we need to remove it and it's constraints prior to adding it, otherwise we may pick up the old constraints with
// the wrong indicies when trying to remove them later (as part of the add)
if (comp.getParent() != null) {
comp.getParent().remove(comp);
// We don't want to remove the constraints as if we add the component back in, we want to reuse them. Only reset the cell gravity TF+CraigM: 19/03/2008
// GridField.removeConstraints(comp);
GridField.getConstraints(comp).cellGravity = Constants.CG_DEFAULT;
}
GridCell cell;
if (constraints instanceof GridBagConstraints) {
// We must merge the new parts from the constraints against the existing cell. For example, if the field
// is a grid field, it can have it's own constraints from having properties set on it such as height and
// width policies. When it's added to it's parent grid, it's added with the values from the GridBagConstraints
// so we must pick and choose which ones we really want.
GridCell newCell = new GridCell((GridBagConstraints)constraints);
cell = GridField.getConstraints(comp);
cell.merge(newCell);
}
else if (constraints instanceof GridCell) {
cell = (GridCell)constraints;
}
else {
cell = new GridCell();
}
GridField.setConstraints(comp, cell);
if (layout.applyInsertPolicy(this, comp, true)) {
super.add(comp, cell);
this.invalidate();
}
}
public boolean isIgnoreInvisibleChildren() {
return ignoreInvisibleChildren;
}
public void setIgnoreInvisibleChildren(boolean pIgnoreInvisibleChildren) {
boolean oldValue = ignoreInvisibleChildren;
ignoreInvisibleChildren = pIgnoreInvisibleChildren;
layout.ignoreInvisibleChildren = pIgnoreInvisibleChildren;
firePropertyChange("ignoreInvisibleChildren", oldValue, ignoreInvisibleChildren);
}
public int getColumnAlignment() {
return layout.getColumnAlignment();
}
public void setColumnAlignment(int pAlignment) {
int oldValue = layout.getColumnAlignment();
layout.setColumnAlignment(pAlignment);
firePropertyChange("columnAlignment", oldValue, pAlignment);
}
public int getRowAlignment() {
return layout.getRowAlignment();
}
public void setRowAlignment(int pAlignment) {
int oldValue = layout.getRowAlignment();
layout.setRowAlignment(pAlignment);
firePropertyChange("rowAlignment", oldValue, pAlignment);
}
public void deleteColumn(int position, boolean force) {
layout.deleteColumn(this, position, force);
}
public void deleteRow(int position, boolean force) {
layout.deleteRow(this, position, force);
}
public void expandFieldForPrint(boolean isRecursive, boolean removeScrollbars) {
RuntimeException errorVar = new RuntimeException("GridField.ExpandFieldForPrint() is not implemented");
ErrorMgr.addError(errorVar);
throw errorVar;
}
/**
* Return the child in the cell identified by row and column. The cells are 1-based
* indicies. If there is no value in the passed cell or the passed cell is invalid, null is returned.
*/
public JComponent getChildInCell(int row, int column) {
for (Component c : getComponents()) {
GridCell cell = layout.findConstraints(c);
if ((cell.row == row - 1) && (cell.column == column - 1)) {
return (JComponent)c;
}
}
return null;
}
public Object getDataObject() {
return this.DataObject;
}
public void setDataObject(Object dataObject) {
this.DataObject = dataObject;
/* this.listeners will only be not null when
* it is set in a cell renderer /editor
*/
if (this.listeners != null){
listeners.firePropertyChange("dataObject", null, this.DataObject);
}
}
/**
* Add a column into the model at the passed position. The position
* indicies are 1-based. If the collapsed attribute is true,
* this method does nothing. This method is thread-safe and may be
* called on or off the GUI thread
*/
public void insertColumn(final int position) {
ActionMgr.addAction(new PendingAction(null) {
@Override
public String toString() {
return GridField.this.getName() + ".insertColumn(" + position + ")";
}
@Override
public void performAction() {
layout.insertColumn(GridField.this, position);
}
});
}
/**
* Add a row into the model. If the collapsed attribute is true,
* this method does nothing. This method is thread-safe and may be
* called on or off the GUI thread
*/
public void insertRow(final int position) {
ActionMgr.addAction(new PendingAction(null) {
@Override
public String toString() {
return GridField.this.getName() + ".insertRow(" + position + ")";
}
@Override
public void performAction() {
layout.insertRow(GridField.this, position);
}
});
}
private class ColumnJustifyWeight extends PendingAction {
public int column;
public int weight;
private ColumnJustifyWeight(int pColumn, int pWeight) {
super(GridField.this);
column = pColumn;
weight = pWeight;
}
@Override
public String toString() {
return GridField.this.getName() + ".setColumnJustifyWeight(" + column + ", " + weight + ")";
}
@Override
public void performAction() {
layout.setColumnJustifyWeight(column, weight);
GridField.this.invalidate();
}
}
private class ColumnJustifyWeightFilter implements ActionMgr.Filter {
int column;
public ColumnJustifyWeightFilter(int pColumn) {
column = pColumn;
}
public boolean filter(PendingAction pAction) {
if (pAction.getComponent() == GridField.this && pAction instanceof ColumnJustifyWeight) {
return ((ColumnJustifyWeight)pAction).column == column;
}
return false;
}
}
/**
* return the weight of the column in the grid field. This method is thread-safe.
* @param row
* @return
*/
public int getColumnJustifyWeight(int column) {
PendingAction action = ActionMgr.getAction(new ColumnJustifyWeightFilter(column));
if (action != null) {
return ((ColumnJustifyWeight)action).weight;
}
Integer val = layout.columnWeights.get(column);
return val == null ? 0 : val.intValue();
}
/**
* Sets the justify weight for the specified column. This method is thread-safe and can be invoked on any thread
*/
public void setColumnJustifyWeight(final int column, final int weight) {
ActionMgr.addAction(new ColumnJustifyWeight(column, weight));
}
/**
* Set the justify weights for the grid field. Succeeds only if all the weights >= 0 and
* weights.size == rows.
* @param weights
* @return True if the method succeeds, false otherwise.
*/
public void setColumnJustifyWeight(int[] weights) {
layout.setColumnJustifyWeights(weights);
}
public int[] getColumnJustifyWeight() {
return layout.getColumnJustifyWeights();
}
private class RowJustifyWeight extends PendingAction {
public int row;
public int weight;
private RowJustifyWeight(int pRow, int pWeight) {
super(GridField.this);
row = pRow;
weight = pWeight;
}
@Override
public String toString() {
return GridField.this.getName() + ".setRowJustifyWeight(" + row + ", " + weight + ")";
}
@Override
public void performAction() {
layout.setRowJustifyWeight(row, weight);
GridField.this.invalidate();
}
}
private class RowJustifyWeightFilter implements ActionMgr.Filter {
int row;
public RowJustifyWeightFilter(int pRow) {
row = pRow;
}
public boolean filter(PendingAction pAction) {
if (pAction.getComponent() == GridField.this && pAction instanceof RowJustifyWeight) {
return ((RowJustifyWeight)pAction).row == row;
}
return false;
}
}
/**
* return the weight of the row in the grid field. This method is thread-safe.
* @param row
* @return
*/
public int getRowJustifyWeight(int row) {
PendingAction action = ActionMgr.getAction(new RowJustifyWeightFilter(row));
if (action != null) {
return ((RowJustifyWeight)action).weight;
}
Integer val = layout.rowWeights.get(row);
return (val == null) ? 0 : val.intValue();
}
/**
* Sets the justify weight for the specified row. This method is thread-safe and can be invoked on any thread
*/
public void setRowJustifyWeight(int row, int weight) {
ActionMgr.addAction(new RowJustifyWeight(row, weight));
}
/**
* Set the justify weights for the grid field. Succeeds only if all the weights >= 0 and
* weights.size == rows.
* @param weights
* @return True if the method succeeds, false otherwise.
*/
public void setRowJustifyWeight(int[] weights) {
layout.setRowJustifyWeights(weights);
}
public int[] getRowJustifyWeight() {
return layout.getRowJustifyWeights();
}
public int getInsertPolicy() {
return insertPolicy;
}
/**
* Set the insert policy for the grid field.
*/
public void setInsertPolicy(int pInsertPolicy) {
int oldValue = this.insertPolicy;
switch (pInsertPolicy) {
case Constants.IP_EXPAND:
case Constants.IP_REJECT:
case Constants.IP_REPLACE:
this.insertPolicy = pInsertPolicy;
firePropertyChange("insertPolicy", oldValue, pInsertPolicy);
break;
case Constants.IP_EXPAND_COLUMNS:
case Constants.IP_EXPAND_ROWS:
// These doesn't seem to be supported in Forte, so do nothing
break;
default:
UsageException errorVar = new UsageException("insert policy of " + pInsertPolicy + " is not supported");
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
public int getRows() {
return this.layout.getRows();
}
/**
* Sets the number of rows in a grid field. If the number of used
* rows is more than the passed value, the number of rows will be
* set to the number of rows used. This method is thread-safe and may be
* called on or off the GUI thread
*/
public void setRows(final int pRows) {
ActionMgr.addAction(new PendingAction(null) {
@Override
public String toString() {
return GridField.this.getName() + ".setRows(" + pRows + ")";
}
@Override
public void performAction() {
layout.setRows(GridField.this, pRows);
}
});
}
public int getColumns() {
return this.layout.getColumns();
}
/**
* Sets the number of columns in a grid field. If the number of used
* columns is more than the passed value, the number of columns will be
* set to the number of columns used. This method is thread-safe and may be
* called on or off the GUI thread
*/
public void setColumns(final int pColumns) {
ActionMgr.addAction(new PendingAction(null) {
@Override
public String toString() {
return GridField.this.getName() + ".setColumns(" + pColumns + ")";
}
@Override
public void performAction() {
layout.setRows(GridField.this, pColumns);
}
});
}
public int getHorzDividerStyle() {
return this.horzDividerStyle;
}
public void setHorzDividerStyle(int pHorzDivideStyle) {
switch (pHorzDivideStyle) {
case Constants.LS_DASH:
case Constants.LS_DASHDOT:
case Constants.LS_DASHDOTDOT:
case Constants.LS_DEFAULT:
case Constants.LS_DOT:
case Constants.LS_SOLID:
int oldValue = horzDividerStyle;
horzDividerStyle = pHorzDivideStyle;
firePropertyChange("horzDividerStyle", oldValue, pHorzDivideStyle);
break;
default:
UsageException errorVar = new UsageException("Invalid parameter "+pHorzDivideStyle);
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
public int getHorzDividerWeight() {
return horzDividerWeight;
}
public void setHorzDividerWeight(int pHorzDivideWeight) {
int oldValue = horzDividerWeight;
horzDividerWeight = pHorzDivideWeight;
firePropertyChange("horzDividerWeight", oldValue, pHorzDivideWeight);
this.forceRelayout();
}
/**
* Setting the line weight effectively sets both the vertical and the horizontal
* divider weights. This was an undocumented method in Forte, but appeared to have
* this effect.
* @param pLineWeight
*/
public void setLineWeight(int pLineWeight) {
int oldValue = horzDividerWeight;
horzDividerWeight = pLineWeight;
firePropertyChange("horzDividerWeight", oldValue, pLineWeight);
oldValue = vertDividerWeight;
vertDividerWeight = pLineWeight;
firePropertyChange("vertDividerWeight", oldValue, pLineWeight);
this.forceRelayout();
}
/**
* This undocumented method in Forte returned the same value as the vertical
* divider weight.
* @return the (vertical) line weight for the grid field
* @deprecated use {@link #getVertDividerWeight()} instead
*/
public int getLineWeight() {
return getVertDividerWeight();
}
public int getVertDividerStyle() {
return vertDividerStyle;
}
public void setVertDividerStyle(int pVertDivideStyle) {
switch (pVertDivideStyle) {
case Constants.LS_DASH:
case Constants.LS_DASHDOT:
case Constants.LS_DASHDOTDOT:
case Constants.LS_DEFAULT:
case Constants.LS_DOT:
case Constants.LS_SOLID:
int oldValue = vertDividerStyle;
vertDividerStyle = pVertDivideStyle;
firePropertyChange("vertDividerStyle", oldValue, pVertDivideStyle);
break;
default:
UsageException errorVar = new UsageException("Invalid parameter " + pVertDivideStyle);
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
public int getVertDividerWeight() {
return vertDividerWeight;
}
public void setVertDividerWeight(int pVertDivideWeight) {
int oldValue = pVertDivideWeight;
vertDividerWeight = pVertDivideWeight;
firePropertyChange("vertDividerWeight", oldValue, pVertDivideWeight);
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
Color oldColor = g2d.getColor();
g2d.setColor(this.getFrameColor());
Insets insets = getInsets();
int columns = layout.getColumns();
int rows = layout.getRows();
// draw vertical dividers
if (this.vertDividerWeight != Constants.W_NONE && this.vertDividerWeight != Constants.W_DEFAULT && columns > 1) {
// TF:09/05/2008:Corrected the behaviour of "Default" line style for a grid field
int lineStyle = this.vertDividerStyle;
if (lineStyle == Constants.LS_DEFAULT) {
lineStyle = Constants.LS_DOT;
}
int[] colStarts = layout.getColStartLocs(this);
int lineWidth = UIutils.getLineWeightInPixels(vertDividerWeight);
// The columns start locations determines where each child cell starts. This already caters
// for all insets and so on.
Stroke old = g2d.getStroke();
g2d.setStroke(UIutils.makeStroke(this.vertDividerWeight, lineStyle));
int bottom = getHeight() - insets.bottom;
for (int c = 1; c < columns; c++) {
g2d.drawLine(colStarts[c] - lineWidth, insets.top, colStarts[c] - lineWidth, bottom);
}
g2d.setStroke(old);
}
// draw horizontal dividers
if (this.horzDividerWeight != Constants.W_NONE && this.horzDividerWeight != Constants.W_DEFAULT && rows > 1) {
// TF:09/05/2008:Corrected the behaviour of "Default" line style for a grid field
int lineStyle = this.vertDividerStyle;
if (lineStyle == Constants.LS_DEFAULT) {
lineStyle = Constants.LS_DOT;
}
int[] rowStarts = layout.getRowStartLocs(this);
int lineWidth = UIutils.getLineWeightInPixels(horzDividerWeight);
Stroke old = g2d.getStroke();
g2d.setStroke(UIutils.makeStroke(this.horzDividerWeight, lineStyle));
int right = getWidth() - insets.right;
for (int r = 1; r < rows; r++) {
g2d.drawLine(insets.left, rowStarts[r] - lineWidth, right, rowStarts[r] - lineWidth);
}
g2d.setStroke(old);
}
g2d.setColor(oldColor);
}
/**
* This returns the coordinates in the four output parameters xLeft, yTop, xRight, yBottom.
* These are measured in mils, relative to the top left corner of the GridField, excluding any frame around the GridField.
* The rectangle is considered to contain the inner extent of the cell, excluding any margin and separator line area.
* @param row
* @param column
* @param xLeft
* @param yTop
* @param xRight
* @param yBottom
* @return
*/
public boolean getCellCoordinates(int row, int column,
ParameterHolder xLeft, ParameterHolder yTop,
ParameterHolder xRight, ParameterHolder yBottom) {
// Adjust to 0-based measurements, not 1-based
row--;
column--;
if (row < 0 || row >= layout.getRows() || column < 0 || column >= layout.getColumns()) {
return false;
}
int[] rowStartLocs = layout.getRowStartLocs(this);
int[] rowHeights = layout.getRowHeights(this);
int[] colStartLocs = layout.getColStartLocs(this);
int[] colWidths = layout.getColWidths(this);
xLeft.setInt(UIutils.pixelsToMils(colStartLocs[column]));
xRight.setInt(UIutils.pixelsToMils(colStartLocs[column] + colWidths[column]));
yTop.setInt(UIutils.pixelsToMils(rowStartLocs[row]));
yBottom.setInt(UIutils.pixelsToMils(rowStartLocs[row] + rowHeights[row]));
return true;
}
/**
* The isCollapsed attribute (boolean) determines how a matrix field treats empty rows and columns. When IsCollapsed is set to TRUE, the matrix field deletes a row or column when all the component cells in the row or column become empty
* @return
*/
public boolean isCollapsed() {
return layout.isCollapsed();
}
public void setCollapsed(boolean value) {
layout.setCollapsed(this, value);
this.validate();
}
/**
* The CaptionFont attribute specifies the font used for the grid field�s caption as set with the Caption attribute
*/
public Font getCaptionFont() {
return CaptionFont.get(this);
}
/**
* The CaptionFont attribute specifies the font used for the grid field�s caption as set with the Caption attribute
* @param font
*/
public void setCaptionFont(Font font) {
Font oldValue = this.captionFont;
this.captionFont = font;
super.setFont(font);
Border border = getBorder();
if (border != null) {
if (border instanceof TitledBorder) {
((TitledBorder) border).setTitleFont(font);
}
}
firePropertyChange("captionFont", oldValue, font);
}
/**
* Set the font for this GridField. This simply sets the caption font
*/
@Override
public void setFont(Font font) {
setCaptionFont(font);
}
/**
* Set the height-wise matrix partner for this grid field. Stored on the layout
* manager because this property affects only the layout
* @param partner
*/
public void setHeightMatrixPartner(GridField partner) {
GridField oldValue = layout.getHeightMatrixPartner();
layout.setHeightMatrixPartner(partner);
firePropertyChange("heightMatrixPartner", oldValue, partner);
}
/**
* Return the height-wise matrix partner for the associated grid field.
* @return
*/
public GridField getHeightMatrixPartner() {
return layout.getHeightMatrixPartner();
}
/**
* Set the width-wise matrix partner for this grid field. Stored on the layout
* manager because this property affects only the layout
* @param partner
*/
public void setWidthMatrixPartner(GridField partner) {
GridField oldValue = layout.getWidthMatrixPartner();
layout.setWidthMatrixPartner(partner);
firePropertyChange("widthMatrixPartner", oldValue, partner);
}
/**
* Return the width-wise matrix partner for the associated grid field.
* @return
*/
public GridField getWidthMatrixPartner() {
return layout.getWidthMatrixPartner();
}
public GridField cloneComponent(CompoundField newParent) {
TextData caption = this.getCaption();
int frameWeight = FrameWeight.get(this);
String name = getName() + "_clone";
GridField clone = CompoundFieldFactory.newGridField(name,
frameWeight,
(caption == null) ? "" : caption.toString(),
(Color)null);
BeanUtils.copyProperties(this, clone, new String[]{"parent"});
// Set the parent prior to setting the array part information so the
// binding can map across properly
if (newParent != null) {
Row.set(clone, Row.get(this));
Column.set(clone, Column.get(this));
Parent.set(clone, newParent);
}
// Set up the binding information for this component. This must be done prior
// to setting up the kids
ArrayFieldCellHelper.cloneComponentArrayParts(this, clone);
for (Component c : getComponents()) {
JComponent clonedKid;
// For grid fields and panels, we need to ensure that we call the overload that
// allows the parent to be set PRIOR to calling cloneComponentArrayParts. The
// problem is that this method will scan up the parent list looking for a valid
// binding manager and if we don't have one then the binding will die a horrible
// death. Hence, the only compound field that can be used to contain other widgets
// within a grid field inside an array field are a grid field or a panel (currently)
if (c instanceof GridField) {
clonedKid = ((GridField)c).cloneComponent(clone);
}
else if (c instanceof Panel) {
clonedKid = ((Panel)c).cloneComponent(clone);
}
else {
clonedKid = (JComponent)CloneHelper.clone(c, true);
}
// CraigM:17/10/2008 - Properties to do with the layout in the grid don't clone properly, so clone them now.
Row.set(clonedKid, Row.get((JComponent)c));
Column.set(clonedKid, Column.get((JComponent)c));
WidthPolicy.set(clonedKid, WidthPolicy.get((JComponent)c));
HeightPolicy.set(clonedKid, HeightPolicy.get((JComponent)c));
// Drop lists don't seem to like having their height and width set
if (c instanceof DropList == false) {
Height.set(clonedKid, Height.get((JComponent)c));
Width.set(clonedKid, Width.get((JComponent)c));
}
Parent.set(clonedKid, clone);
// Now that the kids are mapped to the new parent, we must add in their binding
// information if necessary
ArrayFieldCellHelper.cloneComponentArrayParts((JComponent)c, clonedKid);
}
return clone;
}
public GridField cloneComponent(){
return cloneComponent(null);
}
public BindingManager getBindingManager() {
return bindingManager;
}
public void setBindingManager(BindingManager bindingManager) {
this.bindingManager = bindingManager;
}
private static final String cGBC_LOOKUP_KEY = "qq_gbc";
/**
* Get the constraints for the passed component. This method will never return null, allocating
* new constraints for the component if necessary
* @param comp
* @return
*/
public static GridCell getConstraints(JComponent comp) {
return GridField.getConstraints(comp, true);
}
/**
* This method retrieves the grid bag constraints associated with a component, abstracting away
* from the storage mechanism.
* @param comp
* @return
*/
public static GridCell getConstraints(JComponent comp, boolean pAllocate) {
GridCell cell = (GridCell)comp.getClientProperty(cGBC_LOOKUP_KEY);
if (cell == null && pAllocate) {
cell = new GridCell();
setConstraints(comp, cell);
}
return cell;
}
/**
* This method rremoves the constraints associate with a particular component. This could be done
* only after the component has benen removed from its parent
* from the storage mechanism.
* @param comp
* @return
*/
public static void removeConstraints(JComponent comp) {
comp.putClientProperty(cGBC_LOOKUP_KEY, null);
}
/**
* This method stores the grid bag contstraints against the passed component. They may be retrived using the
* {@link #getConstraints(JComponent)} method
* @param comp
* @param gbc
*/
public static void setConstraints(JComponent comp, GridCell gbc) {
if (gbc == null && comp.getClientProperty(cGBC_LOOKUP_KEY) == null) {
return;
}
comp.putClientProperty(cGBC_LOOKUP_KEY, gbc);
}
/**
* Get the height policy associated with this grid field
* @return
*/
public int getHeightPolicy() {
return GridField.getConstraints(this).heightPolicy;
}
/**
* Set the height policy for this grid field. This method is not thread-safe and must be called from the EDT
* @beaninfo isBound = true
* isPreferred = true
* @param pHeightPolicy
*/
public void setHeightPolicy(int pHeightPolicy) {
switch (pHeightPolicy) {
case Constants.SP_EXPLICIT:
case Constants.SP_NATURAL:
case Constants.SP_TO_MATRIX_PARTNER:
case Constants.SP_TO_PARENT:
case Constants.SP_TO_PARTNER:
int oldValue = getHeightPolicy();
GridField.getConstraints(this).setHeightPolicy(pHeightPolicy);
firePropertyChange("heightPolicy", oldValue, pHeightPolicy);
break;
default:
UsageException errorVar = new UsageException("The value " + pHeightPolicy + " is not allowed for a height policy");
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
/**
* Get the width policy associated with this grid field
* @return
*/
public int getWidthPolicy() {
return GridField.getConstraints(this).widthPolicy;
}
/**
* Set the width policy for this grid field. This method is not thread-safe and must be called from the EDT
* @beaninfo isBound = true
* isPreferred = true
* @param pHeightPolicy
*/
public void setWidthPolicy(int pWidthPolicy) {
switch (pWidthPolicy) {
case Constants.SP_EXPLICIT:
case Constants.SP_NATURAL:
case Constants.SP_TO_MATRIX_PARTNER:
case Constants.SP_TO_PARENT:
case Constants.SP_TO_PARTNER:
int oldValue = getWidthPolicy();
GridField.getConstraints(this).setWidthPolicy(pWidthPolicy);
this.validate();
this.invalidate();
firePropertyChange("widthPolicy", oldValue, pWidthPolicy);
break;
default:
UsageException errorVar = new UsageException("The value " + pWidthPolicy + " is not allowed for a width policy");
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
/* (non-Javadoc)
* @see javax.swing.JPanel#paramString()
*/
@Override
protected String paramString() {
String string = super.paramString();
// If there is no name but has a generated name then replace the first "," with the Generated Name
if (!StringUtils.hasText(this.getName()) && StringUtils.hasText(this.getGeneratedName())) {
string = string.replaceFirst(",", this.getGeneratedName() + ",");
}
return string;
}
/**
* @return the generatedName
*/
public String getGeneratedName() {
return generatedName;
}
/*
* CraigM:14/10/2008 - Handle any special request focus calls
*
* @see javax.swing.JComponent#requestFocusInWindow()
*/
@Override
public boolean requestFocusInWindow() {
// If we are in an ArrayField
if (ArrayFieldCellHelper.getArrayField(this) != null) {
// We should have a FocusTraversalPolicy (setup in GridFieldCellEditor)
FocusTraversalPolicy traversal = this.getFocusTraversalPolicy();
if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYPREV) {
return traversal.getLastComponent(this).requestFocusInWindow();
}
else {
return traversal.getFirstComponent(this).requestFocusInWindow();
}
}
else {
return super.requestFocusInWindow();
}
}
@Override
public void setEnabled(boolean enabled)
{
// COLET 12/01/2009 : Do not pass this event to our super class. We are going to keep it to our selves. The ITC framework handles recursively setting our children enabled. See WidgetState.set().
Border border = getBorder();
if(border != null && border instanceof GridTitledBorder) {
((GridTitledBorder)border).setEnabled(enabled);
}
}
// private class GridFieldFocusTraversalPolicy extends SortingFocusTraversalPolicy {
//
// private Component[][] componentTable;
//
// /**
// * Populate the component table
// */
// private void formComponentTable() {
// componentTable = new Component[getRows()][getColumns()];
// Component[] children = getComponents();
// for (Component child : children) {
// GridCell cell = GridCell.get((JComponent)child);
// if (cell.row >= 0 && cell.row < getRows() && cell.column >= 0 && cell.column < getColumns()) {
// componentTable[cell.row][cell.column] = child;
// }
// }
// }
//
// @Override
// public Component getComponentAfter(Container container, Component component) {
// if (component.getParent() == GridField.this) {
// formComponentTable();
// }
// else {
// return super.getComponentAfter(container, component);
// }
// }
// }
}