0001: /*
0002: * $Id: JXTable.java,v 1.179 2007/02/09 13:39:14 kleopatra Exp $
0003: *
0004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
0005: * Santa Clara, California 95054, U.S.A. All rights reserved.
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
0020: */
0021:
0022: package org.jdesktop.swingx;
0023:
0024: import java.awt.Color;
0025: import java.awt.Component;
0026: import java.awt.ComponentOrientation;
0027: import java.awt.Container;
0028: import java.awt.Cursor;
0029: import java.awt.Dimension;
0030: import java.awt.Point;
0031: import java.awt.Rectangle;
0032: import java.awt.event.ActionEvent;
0033: import java.awt.print.PrinterException;
0034: import java.beans.PropertyChangeEvent;
0035: import java.lang.reflect.Field;
0036: import java.text.DateFormat;
0037: import java.text.NumberFormat;
0038: import java.util.Collections;
0039: import java.util.Date;
0040: import java.util.Enumeration;
0041: import java.util.HashMap;
0042: import java.util.Hashtable;
0043: import java.util.Iterator;
0044: import java.util.List;
0045: import java.util.Map;
0046: import java.util.Vector;
0047: import java.util.logging.Level;
0048: import java.util.logging.Logger;
0049: import java.util.regex.Matcher;
0050: import java.util.regex.Pattern;
0051:
0052: import javax.swing.Action;
0053: import javax.swing.ActionMap;
0054: import javax.swing.DefaultCellEditor;
0055: import javax.swing.Icon;
0056: import javax.swing.ImageIcon;
0057: import javax.swing.JCheckBox;
0058: import javax.swing.JComponent;
0059: import javax.swing.JLabel;
0060: import javax.swing.JScrollPane;
0061: import javax.swing.JTable;
0062: import javax.swing.JTextField;
0063: import javax.swing.JViewport;
0064: import javax.swing.KeyStroke;
0065: import javax.swing.ListSelectionModel;
0066: import javax.swing.ScrollPaneConstants;
0067: import javax.swing.SizeSequence;
0068: import javax.swing.UIDefaults;
0069: import javax.swing.UIManager;
0070: import javax.swing.border.Border;
0071: import javax.swing.border.EmptyBorder;
0072: import javax.swing.border.LineBorder;
0073: import javax.swing.event.ChangeEvent;
0074: import javax.swing.event.ChangeListener;
0075: import javax.swing.event.ListSelectionEvent;
0076: import javax.swing.event.TableColumnModelEvent;
0077: import javax.swing.event.TableModelEvent;
0078: import javax.swing.table.DefaultTableCellRenderer;
0079: import javax.swing.table.JTableHeader;
0080: import javax.swing.table.TableCellEditor;
0081: import javax.swing.table.TableCellRenderer;
0082: import javax.swing.table.TableColumn;
0083: import javax.swing.table.TableColumnModel;
0084: import javax.swing.table.TableModel;
0085:
0086: import org.jdesktop.swingx.action.BoundAction;
0087: import org.jdesktop.swingx.decorator.ComponentAdapter;
0088: import org.jdesktop.swingx.decorator.DefaultSelectionMapper;
0089: import org.jdesktop.swingx.decorator.FilterPipeline;
0090: import org.jdesktop.swingx.decorator.Highlighter;
0091: import org.jdesktop.swingx.decorator.HighlighterPipeline;
0092: import org.jdesktop.swingx.decorator.PatternHighlighter;
0093: import org.jdesktop.swingx.decorator.PipelineEvent;
0094: import org.jdesktop.swingx.decorator.PipelineListener;
0095: import org.jdesktop.swingx.decorator.ResetDTCRColorHighlighter;
0096: import org.jdesktop.swingx.decorator.SearchHighlighter;
0097: import org.jdesktop.swingx.decorator.SelectionMapper;
0098: import org.jdesktop.swingx.decorator.SizeSequenceMapper;
0099: import org.jdesktop.swingx.decorator.SortController;
0100: import org.jdesktop.swingx.decorator.SortKey;
0101: import org.jdesktop.swingx.decorator.SortOrder;
0102: import org.jdesktop.swingx.event.TableColumnModelExtListener;
0103: import org.jdesktop.swingx.icon.ColumnControlIcon;
0104: import org.jdesktop.swingx.plaf.LookAndFeelAddons;
0105: import org.jdesktop.swingx.renderer.ButtonProvider;
0106: import org.jdesktop.swingx.renderer.DefaultTableRenderer;
0107: import org.jdesktop.swingx.renderer.FormatStringValue;
0108: import org.jdesktop.swingx.renderer.LabelProvider;
0109: import org.jdesktop.swingx.table.ColumnControlButton;
0110: import org.jdesktop.swingx.table.ColumnFactory;
0111: import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
0112: import org.jdesktop.swingx.table.TableColumnExt;
0113: import org.jdesktop.swingx.table.TableColumnModelExt;
0114:
0115: /**
0116: * <p>
0117: * A JXTable is a JTable with built-in support for row sorting, filtering, and
0118: * highlighting, column visibility and a special popup control on the column
0119: * header for quick access to table configuration. You can instantiate a JXTable
0120: * just as you would a JTable, using a TableModel. However, a JXTable
0121: * automatically wraps TableColumns inside a TableColumnExt instance.
0122: * TableColumnExt supports visibility, sortability, and prototype values for
0123: * column sizing, none of which are available in TableColumn. You can retrieve
0124: * the TableColumnExt instance for a column using {@link #getColumnExt(Object)}
0125: * or {@link #getColumnExt(int colnumber)}.
0126: *
0127: * <p>
0128: * A JXTable is, by default, sortable by clicking on column headers; each
0129: * subsequent click on a header reverses the order of the sort, and a sort arrow
0130: * icon is automatically drawn on the header. Sorting can be disabled using
0131: * {@link #setSortable(boolean)}. Sorting on columns is handled by a Sorter
0132: * instance which contains a Comparator used to compare values in two rows of a
0133: * column. You can replace the Comparator for a given column by using
0134: * <code>getColumnExt("column").setComparator(customComparator)</code>
0135: *
0136: * <p>
0137: * Columns can be hidden or shown by setting the visible property on the
0138: * TableColumnExt using {@link TableColumnExt#setVisible(boolean)}. Columns can
0139: * also be shown or hidden from the column control popup.
0140: *
0141: * <p>
0142: * The column control popup is triggered by an icon drawn to the far right of
0143: * the column headers, above the table's scrollbar (when installed in a
0144: * JScrollPane). The popup allows the user to select which columns should be
0145: * shown or hidden, as well as to pack columns and turn on horizontal scrolling.
0146: * To show or hide the column control, use the
0147: * {@link #setColumnControlVisible(boolean show)}method.
0148: *
0149: * <p>
0150: * Rows can be filtered from a JXTable using a Filter class and a
0151: * FilterPipeline. One assigns a FilterPipeline to the table using
0152: * {@link #setFilters(FilterPipeline)}. Filtering hides, but does not delete or
0153: * permanently remove rows from a JXTable. Filters are used to provide sorting
0154: * to the table--rows are not removed, but the table is made to believe rows in
0155: * the model are in a sorted order.
0156: *
0157: * <p>
0158: * One can automatically highlight certain rows in a JXTable by attaching
0159: * Highlighters in the {@link #setHighlighters(HighlighterPipeline)}method. An
0160: * example would be a Highlighter that colors alternate rows in the table for
0161: * readability; AlternateRowHighlighter does this. Again, like Filters,
0162: * Highlighters can be chained together in a HighlighterPipeline to achieve more
0163: * interesting effects.
0164: *
0165: * <p>
0166: * You can resize all columns, selected columns, or a single column using the
0167: * methods like {@link #packAll()}. Packing combines several other aspects of a
0168: * JXTable. If horizontal scrolling is enabled using
0169: * {@link #setHorizontalScrollEnabled(boolean)}, then the scrollpane will allow
0170: * the table to scroll right-left, and columns will be sized to their preferred
0171: * size. To control the preferred sizing of a column, you can provide a
0172: * prototype value for the column in the TableColumnExt using
0173: * {@link TableColumnExt#setPrototypeValue(Object)}. The prototype is used as
0174: * an indicator of the preferred size of the column. This can be useful if some
0175: * data in a given column is very long, but where the resize algorithm would
0176: * normally not pick this up.
0177: *
0178: * <p>
0179: * JXTable guarantees to delegate creation and configuration of TableColumnExt
0180: * to a ColumnFactory. By default, the application-wide shared ColumnFactory is used.
0181: * You can install a custom ColumnFactory, either application-wide by
0182: * {@link ColumnFactory#setInstance(ColumnFactory)} or per table instance by
0183: * {@link #setColumnFactory(ColumnFactory)}.
0184: *
0185: * <p>
0186: * Last, you can also provide searches on a JXTable using the Searchable property.
0187: *
0188: * <p>
0189: * Keys/Actions registered with this component:
0190: *
0191: * <ul>
0192: * <li> "find" - open an appropriate search widget for searching cell content. The
0193: * default action registeres itself with the SearchFactory as search target.
0194: * <li> "print" - print the table
0195: * <li> {@link JXTable#HORIZONTALSCROLL_ACTION_COMMAND} - toggle the horizontal scrollbar
0196: * <li> {@link JXTable#PACKSELECTED_ACTION_COMMAND} - resize the selected column to fit the widest
0197: * cell content
0198: * <li> {@link JXTable#PACKALL_ACTION_COMMAND} - resize all columns to fit the widest
0199: * cell content in each column
0200: *
0201: * </ul>
0202: *
0203: * <p>
0204: * Key bindings.
0205: *
0206: * <ul>
0207: * <li> "control F" - bound to actionKey "find".
0208: * </ul>
0209: *
0210: * <p>
0211: * Client Properties.
0212: *
0213: * <ul>
0214: * <li> {@link JXTable#MATCH_HIGHLIGHTER} - set to Boolean.TRUE to
0215: * use a SearchHighlighter to mark a cell as matching.
0216: * </ul>
0217: *
0218: * @author Ramesh Gupta
0219: * @author Amy Fowler
0220: * @author Mark Davidson
0221: * @author Jeanette Winzenburg
0222: */
0223: public class JXTable extends JTable implements
0224: TableColumnModelExtListener {
0225:
0226: private static final Logger LOG = Logger.getLogger(JXTable.class
0227: .getName());
0228:
0229: /**
0230: * Identifier of show horizontal scroll action,
0231: * used in JXTable's <code>ActionMap</code>.
0232: *
0233: */
0234: public static final String HORIZONTALSCROLL_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
0235: + "horizontalScroll";
0236:
0237: /**
0238: * Identifier of pack table action, used in JXTable's <code>ActionMap</code>.
0239: */
0240: public static final String PACKALL_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
0241: + "packAll";
0242:
0243: /**
0244: * Identifier of pack selected column action, used in JXTable's <code>ActionMap</code>.
0245: */
0246: public static final String PACKSELECTED_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
0247: + "packSelected";
0248:
0249: /**
0250: * The prefix marker to find table related properties
0251: * in the <code>ResourceBundle</code>.
0252: */
0253: public static final String UIPREFIX = "JXTable.";
0254:
0255: /** key for client property to use SearchHighlighter as match marker. */
0256: public static final String MATCH_HIGHLIGHTER = AbstractSearchable.MATCH_HIGHLIGHTER;
0257:
0258: static {
0259: // Hack: make sure the resource bundle is loaded
0260: LookAndFeelAddons.getAddon();
0261: }
0262:
0263: /** The FilterPipeline for the table. */
0264: protected FilterPipeline filters;
0265:
0266: /** The HighlighterPipeline for the table. */
0267: protected HighlighterPipeline highlighters;
0268:
0269: /**
0270: * The Highlighter used to hack around DefaultTableCellRenderer's color memory.
0271: */
0272: protected Highlighter resetDefaultTableCellRendererHighlighter;
0273:
0274: /** The ComponentAdapter for model data access. */
0275: protected ComponentAdapter dataAdapter;
0276:
0277: /** The handler for mapping view/model coordinates of row selection. */
0278: private SelectionMapper selectionMapper;
0279:
0280: /** flag to indicate if table is interactively sortable. */
0281: private boolean sortable;
0282:
0283: /** Listens for changes from the filters. */
0284: private PipelineListener pipelineListener;
0285:
0286: /** Listens for changes from the highlighters. */
0287: private ChangeListener highlighterChangeListener;
0288:
0289: /** the factory to use for column creation and configuration. */
0290: private ColumnFactory columnFactory;
0291:
0292: /** The default number of visible rows (in a ScrollPane). */
0293: private int visibleRowCount = 18;
0294:
0295: private SizeSequenceMapper rowModelMapper;
0296:
0297: private Field rowModelField;
0298:
0299: private boolean rowHeightEnabled;
0300:
0301: /**
0302: * Flag to indicate if the column control is visible.
0303: */
0304: private boolean columnControlVisible;
0305: /**
0306: * ScrollPane's original vertical scroll policy. If the column control is
0307: * visible the policy is set to ALWAYS.
0308: */
0309: private int verticalScrollPolicy;
0310:
0311: /**
0312: * The component used a column control in the upper trailing corner of
0313: * an enclosing <code>JScrollPane</code>.
0314: */
0315: private JComponent columnControlButton;
0316:
0317: /**
0318: * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
0319: */
0320: private RolloverProducer rolloverProducer;
0321:
0322: /**
0323: * RolloverController: listens to cell over events and repaints
0324: * entered/exited rows.
0325: */
0326: private TableRolloverController linkController;
0327:
0328: /** field to store the autoResizeMode while interactively setting
0329: * horizontal scrollbar to visible.
0330: */
0331: private int oldAutoResizeMode;
0332:
0333: /** property to control the tracksViewportHeight behaviour. */
0334: private boolean fillsViewportHeight;
0335:
0336: /** flag to indicate enhanced auto-resize-off behaviour is on.
0337: * This is set/reset in setHorizontalScrollEnabled.
0338: */
0339: private boolean intelliMode;
0340:
0341: /** internal flag indicating that we are in super.doLayout().
0342: * (used in columnMarginChanged to not update the resizingCol's prefWidth).
0343: */
0344: private boolean inLayout;
0345:
0346: /**
0347: * Flag to distinguish internal settings of rowheight from client code
0348: * settings. The rowHeight will be internally adjusted to font size on
0349: * instantiation and in updateUI if the height has not been set explicitly
0350: * by the application.
0351: * @see #adminSetRowHeight(int)
0352: * @see #setRowHeight(int)
0353: */
0354: protected boolean isXTableRowHeightSet;
0355:
0356: /** property to control search behaviour. */
0357: protected Searchable searchable;
0358:
0359: /** property to control table's editability as a whole. */
0360: private boolean editable;
0361:
0362: /** Instantiates a JXTable with a default table model, no data. */
0363: public JXTable() {
0364: init();
0365: }
0366:
0367: /**
0368: * Instantiates a JXTable with a specific table model.
0369: *
0370: * @param dm The model to use.
0371: */
0372: public JXTable(TableModel dm) {
0373: super (dm);
0374: init();
0375: }
0376:
0377: /**
0378: * Instantiates a JXTable with a specific table model.
0379: *
0380: * @param dm The model to use.
0381: */
0382: public JXTable(TableModel dm, TableColumnModel cm) {
0383: super (dm, cm);
0384: init();
0385: }
0386:
0387: /**
0388: * Instantiates a JXTable with a specific table model, column model, and
0389: * selection model.
0390: *
0391: * @param dm The table model to use.
0392: * @param cm The colomn model to use.
0393: * @param sm The list selection model to use.
0394: */
0395: public JXTable(TableModel dm, TableColumnModel cm,
0396: ListSelectionModel sm) {
0397: super (dm, cm, sm);
0398: init();
0399: }
0400:
0401: /**
0402: * Instantiates a JXTable for a given number of columns and rows.
0403: *
0404: * @param numRows Count of rows to accomodate.
0405: * @param numColumns Count of columns to accomodate.
0406: */
0407: public JXTable(int numRows, int numColumns) {
0408: super (numRows, numColumns);
0409: init();
0410: }
0411:
0412: /**
0413: * Instantiates a JXTable with data in a vector or rows and column names.
0414: *
0415: * @param rowData Row data, as a Vector of Objects.
0416: * @param columnNames Column names, as a Vector of Strings.
0417: */
0418: public JXTable(Vector rowData, Vector columnNames) {
0419: super (rowData, columnNames);
0420: init();
0421: }
0422:
0423: /**
0424: * Instantiates a JXTable with data in a array or rows and column names.
0425: *
0426: * @param rowData Row data, as a two-dimensional Array of Objects (by row,
0427: * for column).
0428: * @param columnNames Column names, as a Array of Strings.
0429: */
0430: public JXTable(Object[][] rowData, Object[] columnNames) {
0431: super (rowData, columnNames);
0432: init();
0433: }
0434:
0435: /**
0436: * Initializes the table for use.
0437: *
0438: */
0439: private void init() {
0440: setEditable(true);
0441: setSortable(true);
0442: setRolloverEnabled(true);
0443: setTerminateEditOnFocusLost(true);
0444: // guarantee getFilters() to return != null
0445: setFilters(null);
0446: initActionsAndBindings();
0447: // instantiate row height depending ui setting or font size.
0448: updateRowHeightUI(false);
0449: setFillsViewportHeight(true);
0450: }
0451:
0452: /**
0453: * Property to enable/disable rollover support. This can be enabled to show
0454: * "live" rollover behaviour, f.i. the cursor over LinkModel cells. Default
0455: * is enabled. If rollover effects are not used, this property should be
0456: * disabled.
0457: *
0458: * @param rolloverEnabled
0459: */
0460: public void setRolloverEnabled(boolean rolloverEnabled) {
0461: boolean old = isRolloverEnabled();
0462: if (rolloverEnabled == old)
0463: return;
0464: if (rolloverEnabled) {
0465: rolloverProducer = createRolloverProducer();
0466: addMouseListener(rolloverProducer);
0467: addMouseMotionListener(rolloverProducer);
0468: getLinkController().install(this );
0469:
0470: } else {
0471: removeMouseListener(rolloverProducer);
0472: removeMouseMotionListener(rolloverProducer);
0473: rolloverProducer = null;
0474: getLinkController().release();
0475: }
0476: firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
0477: }
0478:
0479: protected TableRolloverController getLinkController() {
0480: if (linkController == null) {
0481: linkController = createLinkController();
0482: }
0483: return linkController;
0484: }
0485:
0486: protected TableRolloverController createLinkController() {
0487: return new TableRolloverController();
0488: }
0489:
0490: /**
0491: * creates and returns the RolloverProducer to use.
0492: *
0493: * @return <code>RolloverProducer</code>
0494: */
0495: protected RolloverProducer createRolloverProducer() {
0496: RolloverProducer r = new RolloverProducer() {
0497: @Override
0498: protected void updateRolloverPoint(JComponent component,
0499: Point mousePoint) {
0500: JTable table = (JTable) component;
0501: int col = table.columnAtPoint(mousePoint);
0502: int row = table.rowAtPoint(mousePoint);
0503: if ((col < 0) || (row < 0)) {
0504: row = -1;
0505: col = -1;
0506: }
0507: rollover.x = col;
0508: rollover.y = row;
0509: }
0510:
0511: };
0512: return r;
0513: }
0514:
0515: /**
0516: * Returns the rolloverEnabled property.
0517: *
0518: * @return <code>true</code> if rollover is enabled
0519: */
0520: public boolean isRolloverEnabled() {
0521: return rolloverProducer != null;
0522: }
0523:
0524: /**
0525: * listens to rollover properties.
0526: * Repaints effected component regions.
0527: * Updates link cursor.
0528: *
0529: * @author Jeanette Winzenburg
0530: */
0531: public static class TableRolloverController<T extends JTable>
0532: extends RolloverController<T> {
0533:
0534: private Cursor oldCursor;
0535:
0536: // --------------------------- JTable rollover
0537:
0538: @Override
0539: protected void rollover(Point oldLocation, Point newLocation) {
0540: if (oldLocation != null) {
0541: Rectangle r = component.getCellRect(oldLocation.y,
0542: oldLocation.x, false);
0543: r.x = 0;
0544: r.width = component.getWidth();
0545: component.repaint(r);
0546: }
0547: if (newLocation != null) {
0548: Rectangle r = component.getCellRect(newLocation.y,
0549: newLocation.x, false);
0550: r.x = 0;
0551: r.width = component.getWidth();
0552: component.repaint(r);
0553: }
0554: setRolloverCursor(newLocation);
0555: }
0556:
0557: /**
0558: * overridden to return false if cell editable.
0559: */
0560: @Override
0561: protected boolean isClickable(Point location) {
0562: return super .isClickable(location)
0563: && !component
0564: .isCellEditable(location.y, location.x);
0565: }
0566:
0567: @Override
0568: protected RolloverRenderer getRolloverRenderer(Point location,
0569: boolean prepare) {
0570: TableCellRenderer renderer = component.getCellRenderer(
0571: location.y, location.x);
0572: RolloverRenderer rollover = renderer instanceof RolloverRenderer ? (RolloverRenderer) renderer
0573: : null;
0574: if ((rollover != null) && !rollover.isEnabled()) {
0575: rollover = null;
0576: }
0577: if ((rollover != null) && prepare) {
0578: component.prepareRenderer(renderer, location.y,
0579: location.x);
0580: }
0581: return rollover;
0582: }
0583:
0584: private void setRolloverCursor(Point location) {
0585: if (hasRollover(location)) {
0586: if (oldCursor == null) {
0587: oldCursor = component.getCursor();
0588: component.setCursor(Cursor
0589: .getPredefinedCursor(Cursor.HAND_CURSOR));
0590: }
0591: } else {
0592: if (oldCursor != null) {
0593: component.setCursor(oldCursor);
0594: oldCursor = null;
0595: }
0596: }
0597:
0598: }
0599:
0600: @Override
0601: protected Point getFocusedCell() {
0602: int leadRow = component.getSelectionModel()
0603: .getLeadSelectionIndex();
0604: int leadColumn = component.getColumnModel()
0605: .getSelectionModel().getLeadSelectionIndex();
0606: return new Point(leadColumn, leadRow);
0607: }
0608:
0609: }
0610:
0611: //--------------------------------- ColumnControl && Viewport
0612:
0613: /**
0614: * Returns the column control visible property.
0615: * <p>
0616: *
0617: * @return boolean to indicate whether the column control is visible.
0618: * @see #setColumnControlVisible(boolean)
0619: * @see #setColumnControl(JComponent)
0620: */
0621: public boolean isColumnControlVisible() {
0622: return columnControlVisible;
0623: }
0624:
0625: /**
0626: * Sets the column control visible property. If true and
0627: * <code>JXTable</code> is contained in a <code>JScrollPane</code>, the
0628: * table adds the column control to the trailing corner of the scroll pane.
0629: * <p>
0630: *
0631: * Note: if the table is not inside a <code>JScrollPane</code> the column
0632: * control is not shown even if this returns true. In this case it's the
0633: * responsibility of the client code to actually show it.
0634: * <p>
0635: *
0636: * The default value is <code>false</code>.
0637: *
0638: * @param visible boolean to indicate if the column control should be shown
0639: * @see #isColumnControlVisible()
0640: * @see #setColumnControl(JComponent)
0641: *
0642: */
0643: public void setColumnControlVisible(boolean visible) {
0644: boolean old = columnControlVisible;
0645: this .columnControlVisible = visible;
0646: configureColumnControl();
0647: firePropertyChange("columnControlVisible", old,
0648: columnControlVisible);
0649: }
0650:
0651: /**
0652: * Returns the component used as column control. Lazily creates the
0653: * control to the default if it is <code>null</code>.
0654: *
0655: * @return component for column control, guaranteed to be != null.
0656: * @see #setColumnControl(JComponent)
0657: * @see #createDefaultColumnControl()
0658: */
0659: public JComponent getColumnControl() {
0660: if (columnControlButton == null) {
0661: columnControlButton = createDefaultColumnControl();
0662: }
0663: return columnControlButton;
0664: }
0665:
0666: /**
0667: * Sets the component used as column control. Updates the enclosing
0668: * <code>JScrollPane</code> if appropriate. Passing a <code>null</code>
0669: * parameter restores the column control to the default.
0670: * <p>
0671: * The component is automatically visible only if the
0672: * <code>columnControlVisible</code> property is <code>true</code> and
0673: * the table is contained in a <code>JScrollPane</code>.
0674: *
0675: * <p>
0676: * NOTE: from the table's perspective, the column control is simply a
0677: * <code>JComponent</code> to add to and keep in the trailing corner of
0678: * the scrollpane. (if any). It's up the concrete control to configure
0679: * itself from and keep synchronized to the columns' states.
0680: * <p>
0681: *
0682: * @param columnControl the <code>JComponent</code> to use as
0683: * columnControl.
0684: * @see #getColumnControl()
0685: * @see #createDefaultColumnControl()
0686: * @see #setColumnControlVisible(boolean)
0687: *
0688: */
0689: public void setColumnControl(JComponent columnControl) {
0690: // PENDING JW: release old column control? who's responsible?
0691: // Could implement CCB.autoRelease()?
0692: JComponent old = columnControlButton;
0693: this .columnControlButton = columnControl;
0694: configureColumnControl();
0695: firePropertyChange("columnControl", old, getColumnControl());
0696: }
0697:
0698: /**
0699: * Creates the default column control used by this table.
0700: * This implementation returns a <code>ColumnControlButton</code> configured
0701: * with default <code>ColumnControlIcon</code>.
0702: *
0703: * @return the default component used as column control.
0704: * @see #setColumnControl(JComponent)
0705: * @see org.jdesktop.swingx.table.ColumnControlButton
0706: * @see org.jdesktop.swingx.icon.ColumnControlIcon
0707: */
0708: protected JComponent createDefaultColumnControl() {
0709: return new ColumnControlButton(this , new ColumnControlIcon());
0710: }
0711:
0712: /**
0713: * Sets the language-sensitive orientation that is to be used to order
0714: * the elements or text within this component. <p>
0715: *
0716: * Overridden to work around a core bug:
0717: * <code>JScrollPane</code> can't cope with
0718: * corners when changing component orientation at runtime.
0719: * This method explicitly re-configures the column control. <p>
0720: *
0721: * @param o the ComponentOrientation for this table.
0722: * @see java.awt.Component#setComponentOrientation(ComponentOrientation)
0723: */
0724: @Override
0725: public void setComponentOrientation(ComponentOrientation o) {
0726: super .setComponentOrientation(o);
0727: configureColumnControl();
0728: }
0729:
0730: /**
0731: * Configures the enclosing <code>JScrollPane</code>. <p>
0732: *
0733: * Overridden to addionally configure the upper trailing corner
0734: * with the column control.
0735: *
0736: * @see #configureColumnControl()
0737: *
0738: */
0739: @Override
0740: protected void configureEnclosingScrollPane() {
0741: super .configureEnclosingScrollPane();
0742: configureColumnControl();
0743: }
0744:
0745: /**
0746: * Configures the upper trailing corner of an enclosing
0747: * <code>JScrollPane</code>.
0748: *
0749: * Adds/removes the <code>ColumnControl</code> depending on the
0750: * <code>columnControlVisible</code> property.<p>
0751: *
0752: * @see #setColumnControlVisible(boolean)
0753: * @see #setColumnControl(JComponent)
0754: */
0755: protected void configureColumnControl() {
0756: Container p = getParent();
0757: if (p instanceof JViewport) {
0758: Container gp = p.getParent();
0759: if (gp instanceof JScrollPane) {
0760: JScrollPane scrollPane = (JScrollPane) gp;
0761: // Make certain we are the viewPort's view and not, for
0762: // example, the rowHeaderView of the scrollPane -
0763: // an implementor of fixed columns might do this.
0764: JViewport viewport = scrollPane.getViewport();
0765: if (viewport == null || viewport.getView() != this ) {
0766: return;
0767: }
0768: if (isColumnControlVisible()) {
0769: verticalScrollPolicy = scrollPane
0770: .getVerticalScrollBarPolicy();
0771: scrollPane.setCorner(
0772: JScrollPane.UPPER_TRAILING_CORNER,
0773: getColumnControl());
0774:
0775: scrollPane
0776: .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
0777: } else {
0778: if (verticalScrollPolicy != 0) {
0779: // Fix #155-swingx: reset only if we had force always before
0780: // PENDING: JW - doesn't cope with dynamically changing the policy
0781: // shouldn't be much of a problem because doesn't happen too often??
0782: scrollPane
0783: .setVerticalScrollBarPolicy(verticalScrollPolicy);
0784: }
0785: try {
0786: scrollPane
0787: .setCorner(
0788: JScrollPane.UPPER_TRAILING_CORNER,
0789: null);
0790: } catch (Exception ex) {
0791: // Ignore spurious exception thrown by JScrollPane. This
0792: // is a Swing bug!
0793: }
0794:
0795: }
0796: }
0797: }
0798: }
0799:
0800: //--------------------- actions
0801:
0802: /**
0803: * A small class which dispatches actions. <p>
0804: * TODO (?): Is there a way that we can
0805: * make this static? <p>
0806: *
0807: * PENDING JW: don't use UIAction ... we are in OO-land!
0808: */
0809: private class Actions extends UIAction {
0810: Actions(String name) {
0811: super (name);
0812: }
0813:
0814: public void actionPerformed(ActionEvent evt) {
0815: if ("print".equals(getName())) {
0816: try {
0817: print();
0818: } catch (PrinterException ex) {
0819: // REMIND(aim): should invoke pluggable application error
0820: // handler
0821: LOG.log(Level.WARNING, "", ex);
0822: }
0823: } else if ("find".equals(getName())) {
0824: find();
0825: }
0826: }
0827:
0828: }
0829:
0830: /**
0831: * Registers additional, per-instance <code>Action</code>s to the
0832: * this table's ActionMap. Binds the search accelerator (as returned
0833: * by the SearchFactory) to the find action.
0834: *
0835: *
0836: */
0837: private void initActionsAndBindings() {
0838: // Register the actions that this class can handle.
0839: ActionMap map = getActionMap();
0840: map.put("print", new Actions("print"));
0841: map.put("find", new Actions("find"));
0842: map.put(PACKALL_ACTION_COMMAND, createPackAllAction());
0843: map
0844: .put(PACKSELECTED_ACTION_COMMAND,
0845: createPackSelectedAction());
0846: map.put(HORIZONTALSCROLL_ACTION_COMMAND,
0847: createHorizontalScrollAction());
0848:
0849: KeyStroke findStroke = SearchFactory.getInstance()
0850: .getSearchAccelerator();
0851: getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
0852: findStroke, "find");
0853: }
0854:
0855: /**
0856: * Creates and returns the default <code>Action</code> for toggling
0857: * the horizontal scrollBar.
0858: */
0859: private Action createHorizontalScrollAction() {
0860: String actionName = getUIString(HORIZONTALSCROLL_ACTION_COMMAND);
0861: BoundAction action = new BoundAction(actionName,
0862: HORIZONTALSCROLL_ACTION_COMMAND);
0863: action.setStateAction();
0864: action.registerCallback(this , "setHorizontalScrollEnabled");
0865: action.setSelected(isHorizontalScrollEnabled());
0866: return action;
0867: }
0868:
0869: /**
0870: * Returns a potentially localized value from the UIManager. The given
0871: * key is prefixed by this table's <code>UIPREFIX</code> before
0872: * doing the lookup. Returns the key, if no value is found.
0873: *
0874: * @param key the bare key to look up in the UIManager.
0875: * @return the value mapped to UIPREFIX + key or key if no value is found.
0876: */
0877: private String getUIString(String key) {
0878: String text = UIManager.getString(UIPREFIX + key);
0879: return text != null ? text : key;
0880: }
0881:
0882: /**
0883: * Creates and returns the default <code>Action</code>
0884: * for packing the selected column.
0885: */
0886: private Action createPackSelectedAction() {
0887: String text = getUIString(PACKSELECTED_ACTION_COMMAND);
0888: BoundAction action = new BoundAction(text,
0889: PACKSELECTED_ACTION_COMMAND);
0890: action.registerCallback(this , "packSelected");
0891: action.setEnabled(getSelectedColumnCount() > 0);
0892: return action;
0893: }
0894:
0895: /**
0896: * Creates and returns the default <b>Action </b>
0897: * for packing all columns.
0898: */
0899: private Action createPackAllAction() {
0900: String text = getUIString(PACKALL_ACTION_COMMAND);
0901: BoundAction action = new BoundAction(text,
0902: PACKALL_ACTION_COMMAND);
0903: action.registerCallback(this , "packAll");
0904: return action;
0905: }
0906:
0907: //------------------ bound action callback methods
0908:
0909: /**
0910: * Resizes all columns to fit their content. <p>
0911: *
0912: * By default this method is bound to the pack all columns
0913: * <code>Action</code> and registered in the table's <code>ActionMap</code>.
0914: *
0915: */
0916: public void packAll() {
0917: packTable(-1);
0918: }
0919:
0920: /**
0921: * Resizes the lead column to fit its content. <p>
0922: *
0923: * By default this method is bound to the pack selected column
0924: * <code>Action</code> and registered in the table's <code>ActionMap</code>.
0925: */
0926: public void packSelected() {
0927: int selected = getColumnModel().getSelectionModel()
0928: .getLeadSelectionIndex();
0929: if (selected >= 0) {
0930: packColumn(selected, -1);
0931: }
0932: }
0933:
0934: /**
0935: * {@inheritDoc} <p>
0936: *
0937: * Overridden to update the enabled state of the pack selected column
0938: * <code>Action</code>.
0939: */
0940: @Override
0941: public void columnSelectionChanged(ListSelectionEvent e) {
0942: super .columnSelectionChanged(e);
0943: if (e.getValueIsAdjusting())
0944: return;
0945: Action packSelected = getActionMap().get(
0946: PACKSELECTED_ACTION_COMMAND);
0947: if ((packSelected != null)) {
0948: packSelected.setEnabled(!((ListSelectionModel) e
0949: .getSource()).isSelectionEmpty());
0950: }
0951: }
0952:
0953: //----------------------- scrollable control
0954:
0955: /**
0956: * Sets the enablement of enhanced horizontal scrolling. If enabled, it
0957: * toggles an auto-resize mode which always fills the <code>JViewport</code>
0958: * horizontally and shows the horizontal scrollbar if necessary.
0959: * <p>
0960: *
0961: * The default value is <code>false</code>.
0962: * <p>
0963: *
0964: * Note: this is <strong>not</strong> a bound property, though it follows
0965: * bean naming conventions.
0966: *
0967: * PENDING: Probably should be... If so, could be taken by a
0968: * listening Action as in the app-framework.
0969: * <p>
0970: * PENDING JW: the name is mis-leading?
0971: *
0972: * @param enabled a boolean indicating whether enhanced auto-resize mode is
0973: * enabled.
0974: * @see #isHorizontalScrollEnabled()
0975: */
0976: public void setHorizontalScrollEnabled(boolean enabled) {
0977: /*
0978: * PENDING JW: add a "real" mode? Problematic because there are several
0979: * places in core which check for #AUTO_RESIZE_OFF, can't use different
0980: * value without unwanted side-effects. The current solution with tagging
0981: * the #AUTO_RESIZE_OFF by a boolean flag #intelliMode is brittle - need
0982: * to be very careful to turn off again ... Another problem is to keep the
0983: * horizontalScrollEnabled toggling action in synch with this property.
0984: * Yet another problem is the change notification: currently this is _not_
0985: * a bound property.
0986: *
0987: */
0988: if (enabled == (isHorizontalScrollEnabled()))
0989: return;
0990: if (enabled) {
0991: // remember the resizeOn mode if any
0992: if (getAutoResizeMode() != AUTO_RESIZE_OFF) {
0993: oldAutoResizeMode = getAutoResizeMode();
0994: }
0995: setAutoResizeMode(AUTO_RESIZE_OFF);
0996: // setAutoResizeModel always disables the intelliMode
0997: // must set after calling and update the action again
0998: intelliMode = true;
0999: updateHorizontalAction();
1000: } else {
1001: setAutoResizeMode(oldAutoResizeMode);
1002: }
1003: }
1004:
1005: /**
1006: * Returns the current setting for horizontal scrolling.
1007: *
1008: * @return the enablement of enhanced horizontal scrolling.
1009: * @see #setHorizontalScrollEnabled(boolean)
1010: */
1011: public boolean isHorizontalScrollEnabled() {
1012: return intelliMode && getAutoResizeMode() == AUTO_RESIZE_OFF;
1013: }
1014:
1015: /**
1016: * {@inheritDoc}
1017: * <p>
1018: *
1019: * Overridden for internal bookkeeping related to the enhanced
1020: * auto-resize behaviour.
1021: * <p>
1022: *
1023: * Note: to enable/disable the enhanced auto-resize mode use exclusively
1024: * <code>setHorizontalScrollEnabled</code>, this method can't cope with it.
1025: *
1026: * @see #setHorizontalScrollEnabled(boolean)
1027: *
1028: */
1029: @Override
1030: public void setAutoResizeMode(int mode) {
1031: if (mode != AUTO_RESIZE_OFF) {
1032: oldAutoResizeMode = mode;
1033: }
1034: intelliMode = false;
1035: super .setAutoResizeMode(mode);
1036: updateHorizontalAction();
1037: }
1038:
1039: /**
1040: * Synchs selected state of horizontal scrolling <code>Action</code> to
1041: * enablement of enhanced auto-resize behaviour.
1042: */
1043: protected void updateHorizontalAction() {
1044: Action showHorizontal = getActionMap().get(
1045: HORIZONTALSCROLL_ACTION_COMMAND);
1046: if (showHorizontal instanceof BoundAction) {
1047: ((BoundAction) showHorizontal)
1048: .setSelected(isHorizontalScrollEnabled());
1049: }
1050: }
1051:
1052: /**
1053: *{@inheritDoc} <p>
1054: *
1055: * Overridden to support enhanced auto-resize behaviour enabled and
1056: * necessary.
1057: *
1058: * @see #setHorizontalScrollEnabled(boolean)
1059: */
1060: @Override
1061: public boolean getScrollableTracksViewportWidth() {
1062: boolean shouldTrack = super .getScrollableTracksViewportWidth();
1063: if (isHorizontalScrollEnabled()) {
1064: return hasExcessWidth();
1065: }
1066: return shouldTrack;
1067: }
1068:
1069: /**
1070: * Layouts column width. The exact behaviour depends on the
1071: * <code>autoResizeMode</code> property. <p>
1072: * Overridden to support enhanced auto-resize behaviour enabled and
1073: * necessary.
1074: *
1075: * @see #setAutoResizeMode(int)
1076: * @see #setHorizontalScrollEnabled(boolean)
1077: */
1078: @Override
1079: public void doLayout() {
1080: int resizeMode = getAutoResizeMode();
1081: // fool super...
1082: if (isHorizontalScrollEnabled() && hasRealizedParent()
1083: && hasExcessWidth()) {
1084: autoResizeMode = oldAutoResizeMode;
1085: }
1086: inLayout = true;
1087: super .doLayout();
1088: inLayout = false;
1089: autoResizeMode = resizeMode;
1090: }
1091:
1092: /**
1093: *
1094: * @return boolean to indicate whether the table has a realized parent.
1095: */
1096: private boolean hasRealizedParent() {
1097: return (getWidth() > 0) && (getParent() != null)
1098: && (getParent().getWidth() > 0);
1099: }
1100:
1101: /**
1102: * PRE: hasRealizedParent()
1103: *
1104: * @return boolean to indicate whether the table has widths excessing parent's width
1105: */
1106: private boolean hasExcessWidth() {
1107: return getPreferredSize().width < getParent().getWidth();
1108: }
1109:
1110: /**
1111: * {@inheritDoc}<p>
1112: *
1113: * Overridden to support enhanced auto-resize behaviour enabled and
1114: * necessary.
1115: *
1116: * @see #setHorizontalScrollEnabled(boolean)
1117: */
1118: @Override
1119: public void columnMarginChanged(ChangeEvent e) {
1120: if (isEditing()) {
1121: removeEditor();
1122: }
1123: TableColumn resizingColumn = getResizingColumn();
1124: // Need to do this here, before the parent's
1125: // layout manager calls getPreferredSize().
1126: if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF
1127: && !inLayout) {
1128: resizingColumn.setPreferredWidth(resizingColumn.getWidth());
1129: }
1130: resizeAndRepaint();
1131: }
1132:
1133: /**
1134: * Returns the column which is interactively resized. The return value is
1135: * null if the header is null or has no resizing column.
1136: *
1137: * @return the resizing column.
1138: */
1139: private TableColumn getResizingColumn() {
1140: return (tableHeader == null) ? null : tableHeader
1141: .getResizingColumn();
1142: }
1143:
1144: /**
1145: * Sets the flag which controls the scrollableTracksViewportHeight property.
1146: * If true the table's height will be always at least as large as the
1147: * containing parent, if false the table's height will be independent of
1148: * parent's height.
1149: * <p>
1150: *
1151: * The default value is <code>true</code>.
1152: * <p>
1153: *
1154: * Note: this a backport from Mustang's <code>JTable</code>.
1155: *
1156: * @param fillsViewportHeight boolean to indicate whether the table should
1157: * always fill parent's height.
1158: * @see #getFillsViewportHeight()
1159: * @see #getScrollableTracksViewportHeight()
1160: */
1161: public void setFillsViewportHeight(boolean fillsViewportHeight) {
1162: if (fillsViewportHeight == getFillsViewportHeight())
1163: return;
1164: boolean old = getFillsViewportHeight();
1165: this .fillsViewportHeight = fillsViewportHeight;
1166: firePropertyChange("fillsViewportHeight", old,
1167: getFillsViewportHeight());
1168: revalidate();
1169: }
1170:
1171: /**
1172: * Returns the flag which controls the scrollableTracksViewportHeight
1173: * property.
1174: *
1175: * @return true if the table's height will always be at least as large
1176: * as the containing parent, false if it is independent
1177: * @see #setFillsViewportHeight(boolean)
1178: * @see #getScrollableTracksViewportHeight()
1179: */
1180: public boolean getFillsViewportHeight() {
1181: return fillsViewportHeight;
1182: }
1183:
1184: /**
1185: * {@inheritDoc} <p>
1186: *
1187: * Overridden to control the tracksHeight property depending on
1188: * fillsViewportHeight and relative size to containing parent.
1189: *
1190: * @return true if the control flag is true and the containing parent
1191: * height > prefHeight, else returns false.
1192: * @see #setFillsViewportHeight(boolean)
1193: *
1194: */
1195: @Override
1196: public boolean getScrollableTracksViewportHeight() {
1197: return getFillsViewportHeight()
1198: && getParent() instanceof JViewport
1199: && (((JViewport) getParent()).getHeight() > getPreferredSize().height);
1200: }
1201:
1202: //------------------------ override super because of filter-awareness
1203:
1204: /**
1205: * Returns the row count in the table; if filters are applied, this is the
1206: * filtered row count.
1207: */
1208: @Override
1209: public int getRowCount() {
1210: // RG: If there are no filters, call superclass version rather than
1211: // accessing model directly
1212: return filters == null ? super .getRowCount() : filters
1213: .getOutputSize();
1214: }
1215:
1216: /**
1217: * Convert row index from view coordinates to model coordinates accounting
1218: * for the presence of sorters and filters.
1219: *
1220: * @param row
1221: * row index in view coordinates
1222: * @return row index in model coordinates
1223: */
1224: public int convertRowIndexToModel(int row) {
1225: return getFilters() != null ? getFilters()
1226: .convertRowIndexToModel(row) : row;
1227: }
1228:
1229: /**
1230: * Convert row index from model coordinates to view coordinates accounting
1231: * for the presence of sorters and filters.
1232: *
1233: * @param row
1234: * row index in model coordinates
1235: * @return row index in view coordinates
1236: */
1237: public int convertRowIndexToView(int row) {
1238: return getFilters() != null ? getFilters()
1239: .convertRowIndexToView(row) : row;
1240: }
1241:
1242: /**
1243: * Overridden to account for row index mapping.
1244: * {@inheritDoc}
1245: */
1246: @Override
1247: public Object getValueAt(int row, int column) {
1248: return getModel().getValueAt(convertRowIndexToModel(row),
1249: convertColumnIndexToModel(column));
1250: }
1251:
1252: /**
1253: * Overridden to account for row index mapping. This implementation
1254: * respects the cell's editability, that is it has no effect if
1255: * <code>!isCellEditable(row, column)</code>.
1256: *
1257: * {@inheritDoc}
1258: * @see #isCellEditable(int, int)
1259: */
1260: @Override
1261: public void setValueAt(Object aValue, int row, int column) {
1262: if (!isCellEditable(row, column))
1263: return;
1264: getModel().setValueAt(aValue, convertRowIndexToModel(row),
1265: convertColumnIndexToModel(column));
1266: }
1267:
1268: /**
1269: * Returns true if the cell at <code>row</code> and <code>column</code>
1270: * is editable. Otherwise, invoking <code>setValueAt</code> on the cell
1271: * will have no effect.
1272: * <p>
1273: * Overridden to account for row index mapping and to support a layered
1274: * editability control:
1275: * <ul>
1276: * <li> per-table: <code>JXTable.isEditable()</code>
1277: * <li> per-column: <code>TableColumnExt.isEditable()</code>
1278: * <li> per-cell: controlled by the model
1279: * <code>TableModel.isCellEditable()</code>
1280: * </ul>
1281: * The view cell is considered editable only if all three layers are enabled.
1282: *
1283: * @param row the row index in view coordinates
1284: * @param column the column index in view coordinates
1285: * @return true if the cell is editable
1286: *
1287: * @see #setValueAt(Object, int, int)
1288: * @see #isEditable()
1289: * @see TableColumnExt#isEditable
1290: * @see TableModel#isCellEditable
1291: */
1292: @Override
1293: public boolean isCellEditable(int row, int column) {
1294: if (!isEditable())
1295: return false;
1296: boolean editable = getModel().isCellEditable(
1297: convertRowIndexToModel(row),
1298: convertColumnIndexToModel(column));
1299: if (editable) {
1300: TableColumnExt tableColumn = getColumnExt(column);
1301: if (tableColumn != null) {
1302: editable = editable && tableColumn.isEditable();
1303: }
1304: }
1305: return editable;
1306: }
1307:
1308: /**
1309: * Overridden to update selectionMapper
1310: */
1311: @Override
1312: public void setSelectionModel(ListSelectionModel newModel) {
1313: super .setSelectionModel(newModel);
1314: getSelectionMapper().setViewSelectionModel(getSelectionModel());
1315: }
1316:
1317: /**
1318: * {@inheritDoc}
1319: */
1320: @Override
1321: public void setModel(TableModel newModel) {
1322: // JW: need to look here? is done in tableChanged as well.
1323: boolean wasEnabled = getSelectionMapper().isEnabled();
1324: getSelectionMapper().setEnabled(false);
1325: try {
1326: super .setModel(newModel);
1327: } finally {
1328: getSelectionMapper().setEnabled(wasEnabled);
1329: }
1330: }
1331:
1332: /**
1333: * additionally updates filtered state.
1334: * {@inheritDoc}
1335: */
1336: @Override
1337: public void tableChanged(TableModelEvent e) {
1338: if (getSelectionModel().getValueIsAdjusting()) {
1339: // this may happen if the uidelegate/editor changed selection
1340: // and adjusting state
1341: // before firing a editingStopped
1342: // need to enforce update of model selection
1343: getSelectionModel().setValueIsAdjusting(false);
1344: }
1345: // JW: make SelectionMapper deaf ... super doesn't know about row
1346: // mapping and sets rowSelection in model coordinates
1347: // causing complete confusion.
1348: boolean wasEnabled = getSelectionMapper().isEnabled();
1349: getSelectionMapper().setEnabled(false);
1350: try {
1351: super .tableChanged(e);
1352: updateSelectionAndRowModel(e);
1353: } finally {
1354: getSelectionMapper().setEnabled(wasEnabled);
1355: }
1356: use(filters);
1357: }
1358:
1359: /**
1360: * reset model selection coordinates in SelectionMapper after
1361: * model events.
1362: *
1363: * @param e
1364: */
1365: private void updateSelectionAndRowModel(TableModelEvent e) {
1366: if (isStructureChanged(e) || isDataChanged(e)) {
1367:
1368: // JW fixing part of #172 - trying to adjust lead/anchor to valid
1369: // indices (at least in model coordinates) after super's default clearSelection
1370: // in dataChanged/structureChanged.
1371: hackLeadAnchor(e);
1372:
1373: getSelectionMapper().clearModelSelection();
1374: getRowModelMapper().clearModelSizes();
1375: updateViewSizeSequence();
1376:
1377: // JW: c&p from JTable
1378: } else if (e.getType() == TableModelEvent.INSERT) {
1379: int start = e.getFirstRow();
1380: int end = e.getLastRow();
1381: if (start < 0) {
1382: start = 0;
1383: }
1384: if (end < 0) {
1385: end = getModel().getRowCount() - 1;
1386: }
1387:
1388: // Adjust the selectionMapper to account for the new rows.
1389: int length = end - start + 1;
1390: getSelectionMapper().insertIndexInterval(start, length,
1391: true);
1392: getRowModelMapper().insertIndexInterval(start, length,
1393: getRowHeight());
1394:
1395: } else if (e.getType() == TableModelEvent.DELETE) {
1396: int start = e.getFirstRow();
1397: int end = e.getLastRow();
1398: if (start < 0) {
1399: start = 0;
1400: }
1401: if (end < 0) {
1402: end = getModel().getRowCount() - 1;
1403: }
1404:
1405: int deletedCount = end - start + 1;
1406: // Adjust the selectionMapper to account for the new rows
1407: getSelectionMapper().removeIndexInterval(start, end);
1408: getRowModelMapper()
1409: .removeIndexInterval(start, deletedCount);
1410:
1411: }
1412: // nothing to do on TableEvent.updated
1413:
1414: }
1415:
1416: /**
1417: * Convenience method to detect dataChanged table event type.
1418: *
1419: * @param e the event to examine.
1420: * @return true if the event is of type dataChanged, false else.
1421: */
1422: protected boolean isDataChanged(TableModelEvent e) {
1423: if (e == null)
1424: return false;
1425: return e.getType() == TableModelEvent.UPDATE
1426: && e.getFirstRow() == 0
1427: && e.getLastRow() == Integer.MAX_VALUE;
1428: }
1429:
1430: /**
1431: * Convenience method to detect update table event type.
1432: *
1433: * @param e the event to examine.
1434: * @return true if the event is of type update and not dataChanged, false else.
1435: */
1436: protected boolean isUpdate(TableModelEvent e) {
1437: if (isStructureChanged(e))
1438: return false;
1439: return e.getType() == TableModelEvent.UPDATE
1440: && e.getLastRow() < Integer.MAX_VALUE;
1441: }
1442:
1443: /**
1444: * Convenience method to detect a structureChanged table event type.
1445: * @param e the event to examine.
1446: * @return true if the event is of type structureChanged or null, false else.
1447: */
1448: protected boolean isStructureChanged(TableModelEvent e) {
1449: return e == null
1450: || e.getFirstRow() == TableModelEvent.HEADER_ROW;
1451: }
1452:
1453: /**
1454: * Trying to hack around #172-swingx: lead/anchor of row selection model
1455: * is not adjusted to valid (not even model indices!) in the
1456: * usual clearSelection after dataChanged/structureChanged.
1457: *
1458: * Note: as of jdk1.5U6 the anchor/lead of the view selectionModel is
1459: * unconditionally set to -1 after data/structureChanged.
1460: *
1461: * @param e
1462: */
1463: private void hackLeadAnchor(TableModelEvent e) {
1464: int lead = getSelectionModel().getLeadSelectionIndex();
1465: int anchor = getSelectionModel().getAnchorSelectionIndex();
1466: int lastRow = getModel().getRowCount() - 1;
1467: if ((lead > lastRow) || (anchor > lastRow)) {
1468: lead = anchor = lastRow;
1469: getSelectionModel().setAnchorSelectionIndex(lead);
1470: getSelectionModel().setLeadSelectionIndex(lead);
1471: }
1472: }
1473:
1474: /**
1475: * Called if individual row height mapping need to be updated.
1476: * This implementation guards against unnessary access of
1477: * super's private rowModel field.
1478: */
1479: protected void updateViewSizeSequence() {
1480: SizeSequence sizeSequence = null;
1481: if (isRowHeightEnabled()) {
1482: sizeSequence = getSuperRowModel();
1483: }
1484: getRowModelMapper().setViewSizeSequence(sizeSequence,
1485: getRowHeight());
1486: }
1487:
1488: /**
1489: * @return <code>SelectionMapper</code>
1490: */
1491: public SelectionMapper getSelectionMapper() {
1492: // JW: why is this public? Probably made so accidentally?
1493: // maybe not: was introduced in version 1.148 when applying
1494: // Jesse's patch to #386-swingx (added functionality to
1495: // turn off the mapping
1496: if (selectionMapper == null) {
1497: selectionMapper = new DefaultSelectionMapper(filters,
1498: getSelectionModel());
1499: }
1500: return selectionMapper;
1501: }
1502:
1503: //----------------------------- filters
1504:
1505: /** Returns the FilterPipeline for the table. */
1506: public FilterPipeline getFilters() {
1507: // PENDING: this is guaranteed to be != null because
1508: // init calls setFilters(null) which enforces an empty
1509: // pipeline
1510: return filters;
1511: }
1512:
1513: /**
1514: * setModel() and setFilters() may be called in either order.
1515: *
1516: * @param pipeline
1517: */
1518: private void use(FilterPipeline pipeline) {
1519: if (pipeline != null) {
1520: // check JW: adding listener multiple times (after setModel)?
1521: if (initialUse(pipeline)) {
1522: pipeline
1523: .addPipelineListener(getFilterPipelineListener());
1524: pipeline.assign(getComponentAdapter());
1525: } else {
1526: pipeline.flush();
1527: }
1528: }
1529: }
1530:
1531: /**
1532: * @return true is not yet used in this JXTable, false otherwise
1533: */
1534: private boolean initialUse(FilterPipeline pipeline) {
1535: if (pipelineListener == null)
1536: return true;
1537: PipelineListener[] l = pipeline.getPipelineListeners();
1538: for (int i = 0; i < l.length; i++) {
1539: if (pipelineListener.equals(l[i]))
1540: return false;
1541: }
1542: return true;
1543: }
1544:
1545: /**
1546: * Sets the FilterPipeline for filtering table rows, maybe null
1547: * to remove all previously applied filters.
1548: *
1549: * Note: the current "interactive" sortState is preserved (by
1550: * internally copying the old sortKeys to the new pipeline, if any).
1551: *
1552: * @param pipeline the <code>FilterPipeline</code> to use, null removes
1553: * all filters.
1554: */
1555: public void setFilters(FilterPipeline pipeline) {
1556: FilterPipeline old = getFilters();
1557: List<? extends SortKey> sortKeys = null;
1558: if (old != null) {
1559: old.removePipelineListener(pipelineListener);
1560: sortKeys = old.getSortController().getSortKeys();
1561: }
1562: if (pipeline == null) {
1563: pipeline = new FilterPipeline();
1564: }
1565: filters = pipeline;
1566: filters.getSortController().setSortKeys(sortKeys);
1567: // JW: first assign to prevent (short?) illegal internal state
1568: // #173-swingx
1569: use(filters);
1570: getRowModelMapper().setFilters(filters);
1571: getSelectionMapper().setFilters(filters);
1572: }
1573:
1574: /** returns the listener for changes in filters. */
1575: protected PipelineListener getFilterPipelineListener() {
1576: if (pipelineListener == null) {
1577: pipelineListener = createPipelineListener();
1578: }
1579: return pipelineListener;
1580: }
1581:
1582: /** creates the listener for changes in filters. */
1583: protected PipelineListener createPipelineListener() {
1584: PipelineListener l = new PipelineListener() {
1585: public void contentsChanged(PipelineEvent e) {
1586: updateOnFilterContentChanged();
1587: }
1588: };
1589: return l;
1590: }
1591:
1592: /**
1593: * method called on change notification from filterpipeline.
1594: */
1595: protected void updateOnFilterContentChanged() {
1596: revalidate();
1597: repaint();
1598: // this is a quick fix for #445-swingx: sort icon not updated on
1599: // programatic sorts
1600: if (getTableHeader() != null) {
1601: getTableHeader().repaint();
1602: }
1603: }
1604:
1605: //-------------------------------- sorting
1606:
1607: /**
1608: * Sets "sortable" property indicating whether or not this table
1609: * supports sortable columns. If <code>sortable</code> is
1610: * <code>true</code> then sorting will be enabled on all columns whose
1611: * <code>sortable</code> property is <code>true</code>. If
1612: * <code>sortable</code> is <code>false</code> then sorting will be
1613: * disabled for all columns, regardless of each column's individual
1614: * <code>sorting</code> property. The default is <code>true</code>.
1615: *
1616: * @see TableColumnExt#isSortable()
1617: * @param sortable
1618: * boolean indicating whether or not this table supports sortable
1619: * columns
1620: */
1621: public void setSortable(boolean sortable) {
1622: if (sortable == isSortable())
1623: return;
1624: this .sortable = sortable;
1625: if (!isSortable())
1626: resetSortOrder();
1627: firePropertyChange("sortable", !sortable, sortable);
1628: }
1629:
1630: /**
1631: * Returns the table's sortable property.
1632: *
1633: * @return true if the table is sortable.
1634: */
1635: public boolean isSortable() {
1636: return sortable;
1637: }
1638:
1639: /**
1640: * Resets sorting of all columns.
1641: *
1642: */
1643: public void resetSortOrder() {
1644: // JW PENDING: think about notification instead of manual repaint.
1645: SortController controller = getSortController();
1646: if (controller != null) {
1647: controller.setSortKeys(null);
1648: }
1649: if (getTableHeader() != null) {
1650: getTableHeader().repaint();
1651: }
1652: }
1653:
1654: /**
1655: *
1656: * Toggles the sort order of the column at columnIndex.
1657: * <p>
1658: * The exact behaviour is defined by the SortController's
1659: * toggleSortOrder implementation. Typically a unsorted
1660: * column is sorted in ascending order, a sorted column's
1661: * order is reversed.
1662: * <p>
1663: * Respects the tableColumnExt's sortable and comparator
1664: * properties: routes the column's comparator to the SortController
1665: * and does nothing if !isSortable(column).
1666: * <p>
1667: *
1668: * PRE: 0 <= columnIndex < getColumnCount()
1669: *
1670: * @param columnIndex the columnIndex in view coordinates.
1671: *
1672: */
1673: public void toggleSortOrder(int columnIndex) {
1674: if (!isSortable(columnIndex))
1675: return;
1676: SortController controller = getSortController();
1677: if (controller != null) {
1678: TableColumnExt columnExt = getColumnExt(columnIndex);
1679: controller.toggleSortOrder(
1680: convertColumnIndexToModel(columnIndex),
1681: columnExt != null ? columnExt.getComparator()
1682: : null);
1683: }
1684: }
1685:
1686: /**
1687: * Decides if the column at columnIndex can be interactively sorted.
1688: * <p>
1689: * Here: true if both this table and the column sortable property is
1690: * enabled, false otherwise.
1691: *
1692: * @param columnIndex column in view coordinates
1693: * @return boolean indicating whether or not the column is sortable
1694: * in this table.
1695: */
1696: protected boolean isSortable(int columnIndex) {
1697: boolean sortable = isSortable();
1698: TableColumnExt tableColumnExt = getColumnExt(columnIndex);
1699: if (tableColumnExt != null) {
1700: sortable = sortable && tableColumnExt.isSortable();
1701: }
1702: return sortable;
1703: }
1704:
1705: /**
1706: * Sorts the table by the given column using SortOrder.
1707: *
1708: *
1709: * Respects the tableColumnExt's sortable and comparator
1710: * properties: routes the column's comparator to the SortController
1711: * and does nothing if !isSortable(column).
1712: * <p>
1713: *
1714: * PRE: 0 <= columnIndex < getColumnCount()
1715: * <p>
1716: *
1717: *
1718: * @param columnIndex the column index in view coordinates.
1719: * @param sortOrder the sort order to use. If null or SortOrder.UNSORTED,
1720: * this method has the same effect as resetSortOrder();
1721: *
1722: */
1723: public void setSortOrder(int columnIndex, SortOrder sortOrder) {
1724: if ((sortOrder == null) || !sortOrder.isSorted()) {
1725: resetSortOrder();
1726: return;
1727: }
1728: if (!isSortable(columnIndex))
1729: return;
1730: SortController sortController = getSortController();
1731: if (sortController != null) {
1732: TableColumnExt columnExt = getColumnExt(columnIndex);
1733: SortKey sortKey = new SortKey(sortOrder,
1734: convertColumnIndexToModel(columnIndex),
1735: columnExt != null ? columnExt.getComparator()
1736: : null);
1737: sortController.setSortKeys(Collections
1738: .singletonList(sortKey));
1739: }
1740: }
1741:
1742: /**
1743: * Returns the SortOrder of the given column.
1744: *
1745: * @param columnIndex the column index in view coordinates.
1746: * @return the interactive sorter's SortOrder if matches the column
1747: * or SortOrder.UNSORTED
1748: */
1749: public SortOrder getSortOrder(int columnIndex) {
1750: SortController sortController = getSortController();
1751: if (sortController == null)
1752: return SortOrder.UNSORTED;
1753: SortKey sortKey = SortKey.getFirstSortKeyForColumn(
1754: sortController.getSortKeys(),
1755: convertColumnIndexToModel(columnIndex));
1756: return sortKey != null ? sortKey.getSortOrder()
1757: : SortOrder.UNSORTED;
1758: }
1759:
1760: /**
1761: *
1762: * Toggles the sort order of the column with identifier.
1763: * <p>
1764: * The exact behaviour is defined by the SortController's
1765: * toggleSortOrder implementation. Typically a unsorted
1766: * column is sorted in ascending order, a sorted column's
1767: * order is reversed.
1768: * <p>
1769: * Respects the tableColumnExt's sortable and comparator
1770: * properties: routes the column's comparator to the SortController
1771: * and does nothing if !isSortable(column).
1772: * <p>
1773: *
1774: * PENDING: JW - define the behaviour if the identifier is not found.
1775: * This can happen if either there's no column at all with the identifier
1776: * or if there's no column of type TableColumnExt.
1777: * Currently does nothing, that is does not change sort state.
1778: *
1779: * @param identifier the column identifier.
1780: *
1781: */
1782: public void toggleSortOrder(Object identifier) {
1783: if (!isSortable(identifier))
1784: return;
1785: SortController controller = getSortController();
1786: if (controller != null) {
1787: TableColumnExt columnExt = getColumnExt(identifier);
1788: if (columnExt == null)
1789: return;
1790: controller.toggleSortOrder(columnExt.getModelIndex(),
1791: columnExt.getComparator());
1792: }
1793: }
1794:
1795: /**
1796: * Sorts the table by the given column using the SortOrder.
1797: *
1798: *
1799: * Respects the tableColumnExt's sortable and comparator
1800: * properties: routes the column's comparator to the SortController
1801: * and does nothing if !isSortable(column).
1802: * <p>
1803: *
1804: * PENDING: JW - define the behaviour if the identifier is not found.
1805: * This can happen if either there's no column at all with the identifier
1806: * or if there's no column of type TableColumnExt.
1807: * Currently does nothing, that is does not change sort state.
1808: *
1809: * @param identifier the column's identifier.
1810: * @param sortOrder the sort order to use. If null or SortOrder.UNSORTED,
1811: * this method has the same effect as resetSortOrder();
1812: *
1813: */
1814: public void setSortOrder(Object identifier, SortOrder sortOrder) {
1815: if ((sortOrder == null) || !sortOrder.isSorted()) {
1816: resetSortOrder();
1817: return;
1818: }
1819: if (!isSortable(identifier))
1820: return;
1821: SortController sortController = getSortController();
1822: if (sortController != null) {
1823: TableColumnExt columnExt = getColumnExt(identifier);
1824: if (columnExt == null)
1825: return;
1826: SortKey sortKey = new SortKey(sortOrder, columnExt
1827: .getModelIndex(), columnExt.getComparator());
1828: sortController.setSortKeys(Collections
1829: .singletonList(sortKey));
1830: }
1831: }
1832:
1833: /**
1834: * Returns the SortOrder of the given column.
1835: *
1836: * PENDING: JW - define the behaviour if the identifier is not found.
1837: * This can happen if either there's no column at all with the identifier
1838: * or if there's no column of type TableColumnExt.
1839: * Currently returns SortOrder.UNSORTED.
1840: *
1841: * @param identifier the column's identifier.
1842: * @return the interactive sorter's SortOrder if matches the column
1843: * or SortOrder.UNSORTED
1844: */
1845: public SortOrder getSortOrder(Object identifier) {
1846: SortController sortController = getSortController();
1847: if (sortController == null)
1848: return SortOrder.UNSORTED;
1849: TableColumnExt columnExt = getColumnExt(identifier);
1850: if (columnExt == null)
1851: return SortOrder.UNSORTED;
1852: int modelIndex = columnExt.getModelIndex();
1853: SortKey sortKey = SortKey.getFirstSortKeyForColumn(
1854: sortController.getSortKeys(), modelIndex);
1855: return sortKey != null ? sortKey.getSortOrder()
1856: : SortOrder.UNSORTED;
1857: }
1858:
1859: /**
1860: * Decides if the column with identifier can be interactively sorted.
1861: * <p>
1862: * Here: true if both this table and the column sortable property is
1863: * enabled, false otherwise.
1864: *
1865: * @param identifier the column's identifier
1866: * @return boolean indicating whether or not the column is sortable
1867: * in this table.
1868: */
1869: protected boolean isSortable(Object identifier) {
1870: boolean sortable = isSortable();
1871: TableColumnExt tableColumnExt = getColumnExt(identifier);
1872: if (tableColumnExt != null) {
1873: sortable = sortable && tableColumnExt.isSortable();
1874: }
1875: return sortable;
1876: }
1877:
1878: /**
1879: * returns the currently active SortController. Can be null
1880: * on the very first call after instantiation.
1881: * @return the currently active <code>SortController</code> may be null
1882: */
1883: protected SortController getSortController() {
1884: // // this check is for the sake of the very first call after instantiation
1885: if (filters == null)
1886: return null;
1887: return getFilters().getSortController();
1888: }
1889:
1890: /**
1891: *
1892: * @return the currently interactively sorted TableColumn or null
1893: * if there is not sorter active or if the sorted column index
1894: * does not correspond to any column in the TableColumnModel.
1895: */
1896: public TableColumn getSortedColumn() {
1897: // bloody hack: get primary SortKey and
1898: // check if there's a column with it available
1899: SortController controller = getSortController();
1900: if (controller != null) {
1901: SortKey sortKey = SortKey.getFirstSortingKey(controller
1902: .getSortKeys());
1903: if (sortKey != null) {
1904: int sorterColumn = sortKey.getColumn();
1905: List<TableColumn> columns = getColumns(true);
1906: for (Iterator<TableColumn> iter = columns.iterator(); iter
1907: .hasNext();) {
1908: TableColumn column = iter.next();
1909: if (column.getModelIndex() == sorterColumn) {
1910: return column;
1911: }
1912: }
1913:
1914: }
1915: }
1916: return null;
1917: }
1918:
1919: /**
1920: * overridden to remove the interactive sorter if the
1921: * sorted column is no longer contained in the ColumnModel.
1922: */
1923: @Override
1924: public void columnRemoved(TableColumnModelEvent e) {
1925: // JW - old problem: need access to removed column
1926: // to get hold of removed modelIndex
1927: // to remove interactive sorter if any
1928: // no way
1929: // int modelIndex = convertColumnIndexToModel(e.getFromIndex());
1930: updateSorterAfterColumnRemoved();
1931: super .columnRemoved(e);
1932: }
1933:
1934: /**
1935: * guarantee that the interactive sorter is removed if its column
1936: * is removed.
1937: *
1938: */
1939: private void updateSorterAfterColumnRemoved() {
1940: TableColumn sortedColumn = getSortedColumn();
1941: if (sortedColumn == null) {
1942: resetSortOrder();
1943: }
1944: }
1945:
1946: // ----------------- enhanced column support: delegation to TableColumnModel
1947: /**
1948: * Returns the <code>TableColumn</code> at view position
1949: * <code>columnIndex</code>. The return value is not <code>null</code>.
1950: *
1951: * <p>
1952: * NOTE: This delegate method is added to protect developer's from
1953: * unexpected exceptions in jdk1.5+. Super does not expose the
1954: * <code>TableColumn</code> access by index which may lead to unexpected
1955: * <code>IllegalArgumentException</code>: If client code assumes the
1956: * delegate method is available, autoboxing will convert the given int to an
1957: * Integer which will call the getColumn(Object) method.
1958: *
1959: *
1960: * @param viewColumnIndex index of the column with the object in question
1961: *
1962: * @return the <code>TableColumn</code> object that matches the column
1963: * index
1964: * @throws ArrayIndexOutOfBoundsException if viewColumnIndex out of allowed
1965: * range.
1966: *
1967: * @see #getColumn(Object)
1968: * @see #getColumnExt(int)
1969: * @see TableColumnModel#getColumn(int)
1970: */
1971: public TableColumn getColumn(int viewColumnIndex) {
1972: return getColumnModel().getColumn(viewColumnIndex);
1973: }
1974:
1975: /**
1976: * Returns a <code>List</code> of visible <code>TableColumn</code>s.
1977: *
1978: * @return a <code>List</code> of visible columns.
1979: * @see #getColumns(boolean)
1980: */
1981: public List<TableColumn> getColumns() {
1982: return Collections.list(getColumnModel().getColumns());
1983: }
1984:
1985: /**
1986: * Returns the margin between columns.
1987: * <p>
1988: *
1989: * Convenience to expose column model properties through
1990: * <code>JXTable</code> api.
1991: *
1992: * @return the margin between columns
1993: *
1994: * @see #setColumnMargin(int)
1995: * @see TableColumnModel#getColumnMargin()
1996: */
1997: public int getColumnMargin() {
1998: return getColumnModel().getColumnMargin();
1999: }
2000:
2001: /**
2002: * Sets the margin between columns.
2003: *
2004: * Convenience to expose column model properties through
2005: * <code>JXTable</code> api.
2006: *
2007: * @param value margin between columns; must be greater than or equal to
2008: * zero.
2009: * @see #getColumnMargin()
2010: * @see TableColumnModel#setColumnMargin(int)
2011: */
2012: public void setColumnMargin(int value) {
2013: getColumnModel().setColumnMargin(value);
2014: }
2015:
2016: // ----------------- enhanced column support: delegation to TableColumnModelExt
2017:
2018: /**
2019: * Returns the number of contained columns. The count includes or excludes invisible
2020: * columns, depending on whether the <code>includeHidden</code> is true or
2021: * false, respectively. If false, this method returns the same count as
2022: * <code>getColumnCount()</code>. If the columnModel is not of type
2023: * <code>TableColumnModelExt</code>, the parameter value has no effect.
2024: *
2025: * @param includeHidden a boolean to indicate whether invisible columns
2026: * should be included
2027: * @return the number of contained columns, including or excluding the
2028: * invisible as specified.
2029: * @see #getColumnCount()
2030: * @see TableColumnModelExt#getColumnCount(boolean)
2031: */
2032: public int getColumnCount(boolean includeHidden) {
2033: if (getColumnModel() instanceof TableColumnModelExt) {
2034: return ((TableColumnModelExt) getColumnModel())
2035: .getColumnCount(includeHidden);
2036: }
2037: return getColumnCount();
2038: }
2039:
2040: /**
2041: * Returns a <code>List</code> of contained <code>TableColumn</code>s.
2042: * Includes or excludes invisible columns, depending on whether the
2043: * <code>includeHidden</code> is true or false, respectively. If false, an
2044: * <code>Iterator</code> over the List is equivalent to the
2045: * <code>Enumeration</code> returned by <code>getColumns()</code>.
2046: * If the columnModel is not of type
2047: * <code>TableColumnModelExt</code>, the parameter value has no effect.
2048: * <p>
2049: *
2050: * NOTE: the order of columns in the List depends on whether or not the
2051: * invisible columns are included, in the former case it's the insertion
2052: * order in the latter it's the current order of the visible columns.
2053: *
2054: * @param includeHidden a boolean to indicate whether invisible columns
2055: * should be included
2056: * @return a <code>List</code> of contained columns.
2057: *
2058: * @see #getColumns()
2059: * @see TableColumnModelExt#getColumns(boolean)
2060: */
2061: public List<TableColumn> getColumns(boolean includeHidden) {
2062: if (getColumnModel() instanceof TableColumnModelExt) {
2063: return ((TableColumnModelExt) getColumnModel())
2064: .getColumns(includeHidden);
2065: }
2066: return getColumns();
2067: }
2068:
2069: /**
2070: * Returns the first <code>TableColumnExt</code> with the given
2071: * <code>identifier</code>. The return value is null if there is no contained
2072: * column with <b>identifier</b> or if the column with <code>identifier</code> is not
2073: * of type <code>TableColumnExt</code>. The returned column
2074: * may be visible or hidden.
2075: *
2076: * @param identifier the object used as column identifier
2077: * @return first <code>TableColumnExt</code> with the given identifier or
2078: * null if none is found
2079: *
2080: * @see #getColumnExt(int)
2081: * @see #getColumn(Object)
2082: * @see TableColumnModelExt#getColumnExt(Object)
2083: */
2084: public TableColumnExt getColumnExt(Object identifier) {
2085: if (getColumnModel() instanceof TableColumnModelExt) {
2086: return ((TableColumnModelExt) getColumnModel())
2087: .getColumnExt(identifier);
2088: } else {
2089: // PENDING: not tested!
2090: try {
2091: TableColumn column = getColumn(identifier);
2092: if (column instanceof TableColumnExt) {
2093: return (TableColumnExt) column;
2094: }
2095: } catch (Exception e) {
2096: // TODO: handle exception
2097: }
2098: }
2099: return null;
2100: }
2101:
2102: /**
2103: * Returns the <code>TableColumnExt</code> at view position
2104: * <code>columnIndex</code>. The return value is null, if the column at
2105: * position <code>columnIndex</code> is not of type
2106: * <code>TableColumnExt</code>. The returned column is visible.
2107: *
2108: * @param viewColumnIndex the index of the column desired
2109: * @return the <code>TableColumnExt</code> object that matches the column
2110: * index
2111: * @throws ArrayIndexOutOfBoundsException if columnIndex out of allowed
2112: * range, that is if
2113: * <code> (columnIndex < 0) || (columnIndex >= getColumnCount())</code>.
2114: *
2115: * @see #getColumnExt(Object)
2116: * @see #getColumn(int)
2117: * @see TableColumnModelExt#getColumnExt(int)
2118: */
2119: public TableColumnExt getColumnExt(int viewColumnIndex) {
2120: TableColumn column = getColumn(viewColumnIndex);
2121: if (column instanceof TableColumnExt) {
2122: return (TableColumnExt) column;
2123: }
2124: return null;
2125: }
2126:
2127: // ---------------------- enhanced TableColumn/Model support: convenience
2128:
2129: /**
2130: * Reorders the columns in the sequence given array. Logical names that do
2131: * not correspond to any column in the model will be ignored. Columns with
2132: * logical names not contained are added at the end.
2133: *
2134: * PENDING JW - do we want this? It's used by JNTable.
2135: *
2136: * @param identifiers array of logical column names
2137: *
2138: * @see #getColumns(boolean)
2139: */
2140: public void setColumnSequence(Object[] identifiers) {
2141: /*
2142: * JW: not properly tested (not in all in fact) ...
2143: */
2144: List columns = getColumns(true);
2145: Map map = new HashMap();
2146: for (Iterator iter = columns.iterator(); iter.hasNext();) {
2147: // PENDING: handle duplicate identifiers ...
2148: TableColumn column = (TableColumn) iter.next();
2149: map.put(column.getIdentifier(), column);
2150: getColumnModel().removeColumn(column);
2151: }
2152: for (int i = 0; i < identifiers.length; i++) {
2153: TableColumn column = (TableColumn) map.get(identifiers[i]);
2154: if (column != null) {
2155: getColumnModel().addColumn(column);
2156: columns.remove(column);
2157: }
2158: }
2159: for (Iterator iter = columns.iterator(); iter.hasNext();) {
2160: TableColumn column = (TableColumn) iter.next();
2161: getColumnModel().addColumn(column);
2162: }
2163: }
2164:
2165: //--------------- implement TableColumnModelExtListener
2166:
2167: /**
2168: * {@inheritDoc}
2169: *
2170: * Listens to column property changes.
2171: *
2172: */
2173: public void columnPropertyChange(PropertyChangeEvent event) {
2174: if (event.getPropertyName().equals("editable")) {
2175: updateEditingAfterColumnChanged((TableColumn) event
2176: .getSource(), (Boolean) event.getNewValue());
2177: } else if (event.getPropertyName().equals("sortable")) {
2178: updateSortingAfterColumnChanged((TableColumn) event
2179: .getSource(), (Boolean) event.getNewValue());
2180:
2181: }
2182:
2183: }
2184:
2185: /**
2186: * Adjusts editing state after column's property change. Cancels ongoing
2187: * editing if the sending column is the editingColumn and the
2188: * column's editable changed to <code>false</code>, otherwise does nothing.
2189: *
2190: * @param column the <code>TableColumn</code> which sent the change notifcation
2191: * @param editable the new value of the column's editable property
2192: */
2193: private void updateEditingAfterColumnChanged(TableColumn column,
2194: boolean editable) {
2195: if (!isEditing())
2196: return;
2197: int viewIndex = convertColumnIndexToView(column.getModelIndex());
2198: if ((viewIndex < 0) || (viewIndex != getEditingColumn()))
2199: return;
2200: getCellEditor().cancelCellEditing();
2201: }
2202:
2203: /**
2204: * @param column the <code>TableColumn</code> which sent the change notifcation
2205: * @param sortable the new value of the column's sortable property
2206: */
2207: private void updateSortingAfterColumnChanged(TableColumn column,
2208: boolean sortable) {
2209: TableColumn sortedColumn = getSortedColumn();
2210: if ((sortedColumn == null) || (sortedColumn != column))
2211: return;
2212: // here we assume that there's only one sorted column
2213: // nothing is done to enforce at-least-one sorted column
2214: // todo: search forum for when that was problem
2215: resetSortOrder();
2216: }
2217:
2218: // -------------------------- ColumnFactory
2219:
2220: /**
2221: * Creates, configures and adds default <code>TableColumn</code>s for
2222: * columns in this table's <code>TableModel</code>. Removes all currently
2223: * contained <code>TableColumn</code>s. The exact type and configuration
2224: * of the columns is controlled by the <code>ColumnFactory</code>.
2225: * <p>
2226: *
2227: * @see org.jdesktop.swingx.table.ColumnFactory
2228: *
2229: */
2230: @Override
2231: public void createDefaultColumnsFromModel() {
2232: // JW: when could this happen?
2233: if (getModel() == null)
2234: return;
2235: // Remove any current columns
2236: removeColumns();
2237: createAndAddColumns();
2238: }
2239:
2240: /**
2241: * Creates and adds <code>TableColumn</code>s for each
2242: * column of the table model. <p>
2243: *
2244: *
2245: */
2246: private void createAndAddColumns() {
2247: /*
2248: * PENDING: go the whole distance and let the factory decide which model
2249: * columns to map to view columns? That would introduce an collection
2250: * managing operation into the factory, sprawling? Can't (and probably
2251: * don't want to) move all collection related operations over - the
2252: * ColumnFactory relies on TableColumnExt type columns, while
2253: * the JXTable has to cope with all the base types.
2254: *
2255: */
2256: for (int i = 0; i < getModel().getColumnCount(); i++) {
2257: // add directly to columnModel - don't go through this.addColumn
2258: // to guarantee full control of ColumnFactory
2259: // addColumn has the side-effect to set the header!
2260: getColumnModel().addColumn(
2261: getColumnFactory().createAndConfigureTableColumn(
2262: getModel(), i));
2263: }
2264: }
2265:
2266: /**
2267: * Remove all columns, make sure to include hidden.
2268: * <p>
2269: */
2270: private void removeColumns() {
2271: /*
2272: * TODO: promote this method to superclass, and change
2273: * createDefaultColumnsFromModel() to call this method
2274: */
2275: List<TableColumn> columns = getColumns(true);
2276: for (Iterator<TableColumn> iter = columns.iterator(); iter
2277: .hasNext();) {
2278: getColumnModel().removeColumn(iter.next());
2279:
2280: }
2281: }
2282:
2283: /**
2284: * Returns the ColumnFactory. <p>
2285: *
2286: * @return the columnFactory to use for column creation and
2287: * configuration.
2288: *
2289: * @see #setColumnFactory(ColumnFactory)
2290: * @see org.jdesktop.swingx.table.ColumnFactory
2291: */
2292: public ColumnFactory getColumnFactory() {
2293: /*
2294: * TODO JW: think about implications of not/ copying the reference
2295: * to the shared instance into the table's field? Better
2296: * access the getInstance() on each call? We are on single thread
2297: * anyway...
2298: * Furthermore, we don't expect the instance to change often, typically
2299: * it is configured on startup. So we don't really have to worry about
2300: * changes which would destabilize column state?
2301: */
2302: if (columnFactory == null) {
2303: return ColumnFactory.getInstance();
2304: // columnFactory = ColumnFactory.getInstance();
2305: }
2306: return columnFactory;
2307: }
2308:
2309: /**
2310: * Sets the <code>ColumnFactory</code> to use for column creation and
2311: * configuration. The default value is the shared application
2312: * ColumnFactory.
2313: *
2314: * @param columnFactory the factory to use, <code>null</code> indicates
2315: * to use the shared application factory.
2316: *
2317: * @see #getColumnFactory()
2318: * @see org.jdesktop.swingx.table.ColumnFactory
2319: */
2320: public void setColumnFactory(ColumnFactory columnFactory) {
2321: /*
2322: *
2323: * TODO auto-configure columns on set? or add public table api to
2324: * do so? Mostly, this is meant to be done once in the lifetime
2325: * of the table, preferably before a model is set ... overshoot?
2326: *
2327: */
2328: ColumnFactory old = getColumnFactory();
2329: this .columnFactory = columnFactory;
2330: firePropertyChange("columnFactory", old, getColumnFactory());
2331: }
2332:
2333: // -------------------------------- enhanced sizing support
2334:
2335: /**
2336: * Packs all the columns to their optimal size. Works best with auto
2337: * resizing turned off.
2338: *
2339: * @param margin the margin to apply to each column.
2340: *
2341: * @see #packColumn(int, int)
2342: * @see #packColumn(int, int, int)
2343: */
2344: public void packTable(int margin) {
2345: for (int c = 0; c < getColumnCount(); c++)
2346: packColumn(c, margin, -1);
2347: }
2348:
2349: /**
2350: * Packs an indivudal column in the table.
2351: *
2352: * @param column The Column index to pack in View Coordinates
2353: * @param margin The Margin to apply to the column width.
2354: *
2355: * @see #packColumn(int, int, int)
2356: * @see #packTable(int)
2357: */
2358: public void packColumn(int column, int margin) {
2359: packColumn(column, margin, -1);
2360: }
2361:
2362: /**
2363: * Packs an indivual column in the table to less than or equal to the
2364: * maximum witdth. If maximum is -1 then the column is made as wide as it
2365: * needs.
2366: *
2367: * @param column the column index to pack in view coordinates
2368: * @param margin the margin to apply to the column
2369: * @param max the maximum width the column can be resized to, -1 means no limit
2370: *
2371: * @see #packColumn(int, int)
2372: * @see #packTable(int)
2373: * @see ColumnFactory#packColumn(JXTable, TableColumnExt, int, int)
2374: */
2375: public void packColumn(int column, int margin, int max) {
2376: getColumnFactory().packColumn(this , getColumnExt(column),
2377: margin, max);
2378: }
2379:
2380: /**
2381: * Returns the preferred number of rows to show in a
2382: * <code>JScrollPane</code>.
2383: *
2384: * @return the number of rows to show in a <code>JScrollPane</code>
2385: * @see #setVisibleRowCount(int)
2386: */
2387: public int getVisibleRowCount() {
2388: return visibleRowCount;
2389: }
2390:
2391: /**
2392: * Sets the preferred number of rows to show in a <code>JScrollPane</code>.
2393: * <p>
2394: *
2395: * TODO JW - make bound property, reset scrollablePref(? distinguish
2396: * internal from client code triggered like in rowheight?) and re-layout.
2397: *
2398: * @param visibleRowCount number of rows to show in a <code>JScrollPane</code>
2399: * @see #getVisibleRowCount()
2400: */
2401: public void setVisibleRowCount(int visibleRowCount) {
2402: this .visibleRowCount = visibleRowCount;
2403: }
2404:
2405: /**
2406: * {@inheritDoc} <p>
2407: *
2408: * TODO JW: refactor and comment.
2409: *
2410: */
2411: @Override
2412: public Dimension getPreferredScrollableViewportSize() {
2413: Dimension prefSize = super .getPreferredScrollableViewportSize();
2414:
2415: // JTable hardcodes this to 450 X 400, so we'll calculate it
2416: // based on the preferred widths of the columns and the
2417: // visibleRowCount property instead...
2418:
2419: if (prefSize.getWidth() == 450 && prefSize.getHeight() == 400) {
2420: TableColumnModel columnModel = getColumnModel();
2421: int columnCount = columnModel.getColumnCount();
2422:
2423: int w = 0;
2424: for (int i = 0; i < columnCount; i++) {
2425: TableColumn column = columnModel.getColumn(i);
2426: initializeColumnPreferredWidth(column);
2427: w += column.getPreferredWidth();
2428: }
2429: prefSize.width = w;
2430: JTableHeader header = getTableHeader();
2431: // remind(aim): height is still off...???
2432: int rowCount = getVisibleRowCount();
2433: prefSize.height = rowCount
2434: * getRowHeight()
2435: + (header != null ? header.getPreferredSize().height
2436: : 0);
2437: setPreferredScrollableViewportSize(prefSize);
2438: }
2439: return prefSize;
2440: }
2441:
2442: /**
2443: * Initialize the preferredWidth of the specified column based on the
2444: * column's prototypeValue property. If the column is not an instance of
2445: * <code>TableColumnExt</code> or prototypeValue is <code>null</code>
2446: * then the preferredWidth is left unmodified.
2447: * <p>
2448: *
2449: * TODO JW - need to cleanup getScrollablePreferred (refactor and inline)
2450: * update doc - what exactly happens is left to the columnfactory.
2451: *
2452: * @param column TableColumn object representing view column
2453: * @see org.jdesktop.swingx.table.TableColumnExt#setPrototypeValue
2454: */
2455: protected void initializeColumnPreferredWidth(TableColumn column) {
2456: if (column instanceof TableColumnExt) {
2457: getColumnFactory().configureColumnWidths(this ,
2458: (TableColumnExt) column);
2459: }
2460: }
2461:
2462: // ----------------- scrolling support
2463: /**
2464: * Scrolls vertically to make the given row visible. This might not have any
2465: * effect if the table isn't contained in a <code>JViewport</code>.
2466: * <p>
2467: *
2468: * Note: this method has no precondition as it internally uses
2469: * <code>getCellRect</code> which is lenient to off-range coordinates.
2470: *
2471: * @param row the view row index of the cell
2472: *
2473: * @see #scrollColumnToVisible(int)
2474: * @see #scrollCellToVisible(int, int)
2475: * @see #scrollRectToVisible(Rectangle)
2476: */
2477: public void scrollRowToVisible(int row) {
2478: Rectangle cellRect = getCellRect(row, 0, false);
2479: Rectangle visibleRect = getVisibleRect();
2480: cellRect.x = visibleRect.x;
2481: cellRect.width = visibleRect.width;
2482: scrollRectToVisible(cellRect);
2483: }
2484:
2485: /**
2486: * Scrolls horizontally to make the given column visible. This might not
2487: * have any effect if the table isn't contained in a <code>JViewport</code>.
2488: * <p>
2489: *
2490: * Note: this method has no precondition as it internally uses
2491: * <code>getCellRect</code> which is lenient to off-range coordinates.
2492: *
2493: * @param column the view column index of the cell
2494: *
2495: * @see #scrollRowToVisible(int)
2496: * @see #scrollCellToVisible(int, int)
2497: * @see #scrollRectToVisible(Rectangle)
2498: */
2499: public void scrollColumnToVisible(int column) {
2500: Rectangle cellRect = getCellRect(0, column, false);
2501: Rectangle visibleRect = getVisibleRect();
2502: cellRect.y = visibleRect.y;
2503: cellRect.height = visibleRect.height;
2504: scrollRectToVisible(cellRect);
2505: }
2506:
2507: /**
2508: * Scrolls to make the cell at row and column visible. This might not have
2509: * any effect if the table isn't contained in a <code>JViewport</code>.
2510: * <p>
2511: *
2512: * Note: this method has no precondition as it internally uses
2513: * <code>getCellRect</code> which is lenient to off-range coordinates.
2514: *
2515: * @param row the view row index of the cell
2516: * @param column the view column index of the cell
2517: *
2518: * @see #scrollColumnToVisible(int)
2519: * @see #scrollRowToVisible(int)
2520: * @see #scrollRectToVisible(Rectangle)
2521: */
2522: public void scrollCellToVisible(int row, int column) {
2523: Rectangle cellRect = getCellRect(row, column, false);
2524: scrollRectToVisible(cellRect);
2525: }
2526:
2527: //----------------------- delegating methods?? from super
2528: /**
2529: * Returns the selection mode used by this table's selection model.
2530: * <p>
2531: * PENDING JW - setter?
2532: *
2533: * @return the selection mode used by this table's selection model
2534: * @see ListSelectionModel#getSelectionMode()
2535: */
2536: public int getSelectionMode() {
2537: return getSelectionModel().getSelectionMode();
2538: }
2539:
2540: //----------------------- Search support
2541:
2542: /** Opens the find widget for the table. */
2543: private void find() {
2544: SearchFactory.getInstance()
2545: .showFindInput(this , getSearchable());
2546: }
2547:
2548: /**
2549: *
2550: * @return a not-null Searchable for this editor.
2551: */
2552: public Searchable getSearchable() {
2553: if (searchable == null) {
2554: searchable = new TableSearchable();
2555: }
2556: return searchable;
2557: }
2558:
2559: /**
2560: * sets the Searchable for this editor. If null, a default
2561: * searchable will be used.
2562: *
2563: * @param searchable
2564: */
2565: public void setSearchable(Searchable searchable) {
2566: this .searchable = searchable;
2567: }
2568:
2569: public class TableSearchable extends AbstractSearchable {
2570:
2571: private SearchHighlighter searchHighlighter;
2572:
2573: @Override
2574: protected void findMatchAndUpdateState(Pattern pattern,
2575: int startRow, boolean backwards) {
2576: SearchResult matchRow = null;
2577: if (backwards) {
2578: // CHECK: off-one end still needed?
2579: // Probably not - the findXX don't have side-effects any longer
2580: // hmmm... still needed: even without side-effects we need to
2581: // guarantee calling the notfound update at the very end of the
2582: // loop.
2583: for (int r = startRow; r >= -1 && matchRow == null; r--) {
2584: matchRow = findMatchBackwardsInRow(pattern, r);
2585: updateState(matchRow);
2586: }
2587: } else {
2588: for (int r = startRow; r <= getSize()
2589: && matchRow == null; r++) {
2590: matchRow = findMatchForwardInRow(pattern, r);
2591: updateState(matchRow);
2592: }
2593: }
2594: // KEEP - JW: Needed to update if loop wasn't entered!
2595: // the alternative is to go one off in the loop. Hmm - which is
2596: // preferable?
2597: // updateState(matchRow);
2598:
2599: }
2600:
2601: /**
2602: * called if sameRowIndex && !hasEqualRegEx. Matches the cell at
2603: * row/lastFoundColumn against the pattern. PRE: lastFoundColumn valid.
2604: *
2605: * @param pattern
2606: * @param row
2607: * @return an appropriate <code>SearchResult</code> if matching or null
2608: */
2609: @Override
2610: protected SearchResult findExtendedMatch(Pattern pattern,
2611: int row) {
2612: return findMatchAt(pattern, row,
2613: lastSearchResult.foundColumn);
2614: }
2615:
2616: /**
2617: * Searches forward through columns of the given row. Starts at
2618: * lastFoundColumn or first column if lastFoundColumn < 0. returns an
2619: * appropriate SearchResult if a matching cell is found in this row or
2620: * null if no match is found. A row index out off range results in a
2621: * no-match.
2622: *
2623: * @param pattern
2624: * @param row
2625: * the row to search
2626: * @return an appropriate <code>SearchResult</code> if a matching cell
2627: * is found in this row or null if no match is found
2628: */
2629: private SearchResult findMatchForwardInRow(Pattern pattern,
2630: int row) {
2631: int startColumn = (lastSearchResult.foundColumn < 0) ? 0
2632: : lastSearchResult.foundColumn;
2633: if (isValidIndex(row)) {
2634: for (int column = startColumn; column < getColumnCount(); column++) {
2635: SearchResult result = findMatchAt(pattern, row,
2636: column);
2637: if (result != null)
2638: return result;
2639: }
2640: }
2641: return null;
2642: }
2643:
2644: /**
2645: * Searches forward through columns of the given row. Starts at
2646: * lastFoundColumn or first column if lastFoundColumn < 0. returns an
2647: * appropriate SearchResult if a matching cell is found in this row or
2648: * null if no match is found. A row index out off range results in a
2649: * no-match.
2650: *
2651: * @param pattern
2652: * @param row
2653: * the row to search
2654: * @return an appropriate <code>SearchResult</code> if a matching cell is found
2655: * in this row or null if no match is found
2656: */
2657: private SearchResult findMatchBackwardsInRow(Pattern pattern,
2658: int row) {
2659: int startColumn = (lastSearchResult.foundColumn < 0) ? getColumnCount() - 1
2660: : lastSearchResult.foundColumn;
2661: if (isValidIndex(row)) {
2662: for (int column = startColumn; column >= 0; column--) {
2663: SearchResult result = findMatchAt(pattern, row,
2664: column);
2665: if (result != null)
2666: return result;
2667: }
2668: }
2669: return null;
2670: }
2671:
2672: /**
2673: * Matches the cell content at row/col against the given Pattern.
2674: * Returns an appropriate SearchResult if matching or null if no
2675: * matching
2676: *
2677: * @param pattern
2678: * @param row
2679: * a valid row index in view coordinates
2680: * @param column
2681: * a valid column index in view coordinates
2682: * @return an appropriate <code>SearchResult</code> if matching or null
2683: */
2684: protected SearchResult findMatchAt(Pattern pattern, int row,
2685: int column) {
2686: Object value = getValueAt(row, column);
2687: if (value != null) {
2688: Matcher matcher = pattern.matcher(value.toString());
2689: if (matcher.find()) {
2690: return createSearchResult(matcher, row, column);
2691: }
2692: }
2693: return null;
2694: }
2695:
2696: /**
2697: * Called if startIndex is different from last search, reset the column
2698: * to -1 and make sure a backwards/forwards search starts at last/first
2699: * row, respectively.
2700: *
2701: * @param startIndex
2702: * @param backwards
2703: * @return adjusted <code>startIndex</code>
2704: */
2705: @Override
2706: protected int adjustStartPosition(int startIndex,
2707: boolean backwards) {
2708: lastSearchResult.foundColumn = -1;
2709: return super .adjustStartPosition(startIndex, backwards);
2710: }
2711:
2712: /**
2713: * Moves the internal start for matching as appropriate and returns the
2714: * new startIndex to use. Called if search was messaged with the same
2715: * startIndex as previously.
2716: *
2717: * @param startRow
2718: * @param backwards
2719: * @return new start index to use
2720: */
2721: @Override
2722: protected int moveStartPosition(int startRow, boolean backwards) {
2723: if (backwards) {
2724: lastSearchResult.foundColumn--;
2725: if (lastSearchResult.foundColumn < 0) {
2726: startRow--;
2727: }
2728: } else {
2729: lastSearchResult.foundColumn++;
2730: if (lastSearchResult.foundColumn >= getColumnCount()) {
2731: lastSearchResult.foundColumn = -1;
2732: startRow++;
2733: }
2734: }
2735: return startRow;
2736: }
2737:
2738: /**
2739: * Checks if the startIndex is a candidate for trying a re-match.
2740: *
2741: *
2742: * @param startIndex
2743: * @return true if the startIndex should be re-matched, false if not.
2744: */
2745: @Override
2746: protected boolean isEqualStartIndex(final int startIndex) {
2747: return super .isEqualStartIndex(startIndex)
2748: && isValidColumn(lastSearchResult.foundColumn);
2749: }
2750:
2751: /**
2752: * checks if row is in range: 0 <= row < getRowCount().
2753: *
2754: * @param column
2755: * @return true if the column is in range, false otherwise
2756: */
2757: private boolean isValidColumn(int column) {
2758: return column >= 0 && column < getColumnCount();
2759: }
2760:
2761: @Override
2762: protected int getSize() {
2763: return getRowCount();
2764: }
2765:
2766: @Override
2767: protected void moveMatchMarker() {
2768: int row = lastSearchResult.foundRow;
2769: int column = lastSearchResult.foundColumn;
2770: Pattern pattern = lastSearchResult.pattern;
2771: if ((row < 0) || (column < 0)) {
2772: if (markByHighlighter()) {
2773: getSearchHighlighter().setPattern(null);
2774: }
2775: return;
2776: }
2777: if (markByHighlighter()) {
2778: Rectangle cellRect = getCellRect(row, column, true);
2779: if (cellRect != null) {
2780: scrollRectToVisible(cellRect);
2781: }
2782: ensureInsertedSearchHighlighters();
2783: // TODO (JW) - cleanup SearchHighlighter state management
2784: getSearchHighlighter().setPattern(pattern);
2785: int modelColumn = convertColumnIndexToModel(column);
2786: getSearchHighlighter().setHighlightCell(row,
2787: modelColumn);
2788: } else { // use selection
2789: changeSelection(row, column, false, false);
2790: if (!getAutoscrolls()) {
2791: // scrolling not handled by moving selection
2792: Rectangle cellRect = getCellRect(row, column, true);
2793: if (cellRect != null) {
2794: scrollRectToVisible(cellRect);
2795: }
2796: }
2797: }
2798: }
2799:
2800: private boolean markByHighlighter() {
2801: return Boolean.TRUE
2802: .equals(getClientProperty(MATCH_HIGHLIGHTER));
2803: }
2804:
2805: private SearchHighlighter getSearchHighlighter() {
2806: if (searchHighlighter == null) {
2807: searchHighlighter = createSearchHighlighter();
2808: }
2809: return searchHighlighter;
2810: }
2811:
2812: private void ensureInsertedSearchHighlighters() {
2813: if (getHighlighters() == null) {
2814: setHighlighters(new HighlighterPipeline(
2815: new Highlighter[] { getSearchHighlighter() }));
2816: } else if (!isInPipeline(getSearchHighlighter())) {
2817: getHighlighters()
2818: .addHighlighter(getSearchHighlighter());
2819: }
2820: }
2821:
2822: private boolean isInPipeline(
2823: PatternHighlighter searchHighlighter) {
2824: Highlighter[] inPipeline = getHighlighters()
2825: .getHighlighters();
2826: if ((inPipeline.length > 0)
2827: && (searchHighlighter
2828: .equals(inPipeline[inPipeline.length - 1]))) {
2829: return true;
2830: }
2831: getHighlighters().removeHighlighter(searchHighlighter);
2832: return false;
2833: }
2834:
2835: protected SearchHighlighter createSearchHighlighter() {
2836: return new SearchHighlighter();
2837: }
2838:
2839: }
2840:
2841: // ----------------------------------- uniform data model access
2842: /**
2843: * @return the unconfigured ComponentAdapter.
2844: */
2845: protected ComponentAdapter getComponentAdapter() {
2846: if (dataAdapter == null) {
2847: dataAdapter = new TableAdapter(this );
2848: }
2849: return dataAdapter;
2850: }
2851:
2852: /**
2853: * Convenience to access a configured ComponentAdapter.
2854: *
2855: * @param row the row index in view coordinates.
2856: * @param column the column index in view coordinates.
2857: * @return the configured ComponentAdapter.
2858: */
2859: protected ComponentAdapter getComponentAdapter(int row, int column) {
2860: ComponentAdapter adapter = getComponentAdapter();
2861: adapter.row = row;
2862: adapter.column = column;
2863: return adapter;
2864: }
2865:
2866: protected static class TableAdapter extends ComponentAdapter {
2867: private final JXTable table;
2868:
2869: /**
2870: * Constructs a <code>TableDataAdapter</code> for the specified target
2871: * component.
2872: *
2873: * @param component
2874: * the target component
2875: */
2876: public TableAdapter(JXTable component) {
2877: super (component);
2878: table = component;
2879: }
2880:
2881: /**
2882: * Typesafe accessor for the target component.
2883: *
2884: * @return the target component as a {@link javax.swing.JTable}
2885: */
2886: public JXTable getTable() {
2887: return table;
2888: }
2889:
2890: @Override
2891: public String getColumnName(int columnIndex) {
2892: TableColumn column = getColumnByModelIndex(columnIndex);
2893: return column == null ? "" : column.getHeaderValue()
2894: .toString();
2895: }
2896:
2897: protected TableColumn getColumnByModelIndex(int modelColumn) {
2898: List columns = table.getColumns(true);
2899: for (Iterator iter = columns.iterator(); iter.hasNext();) {
2900: TableColumn column = (TableColumn) iter.next();
2901: if (column.getModelIndex() == modelColumn) {
2902: return column;
2903: }
2904: }
2905: return null;
2906: }
2907:
2908: @Override
2909: public String getColumnIdentifier(int columnIndex) {
2910:
2911: TableColumn column = getColumnByModelIndex(columnIndex);
2912: Object identifier = column != null ? column.getIdentifier()
2913: : null;
2914: return identifier != null ? identifier.toString() : null;
2915: }
2916:
2917: @Override
2918: public int getColumnCount() {
2919: return table.getModel().getColumnCount();
2920: }
2921:
2922: @Override
2923: public int getRowCount() {
2924: return table.getModel().getRowCount();
2925: }
2926:
2927: /**
2928: * {@inheritDoc}
2929: */
2930: @Override
2931: public Object getValueAt(int row, int column) {
2932: return table.getModel().getValueAt(row, column);
2933: }
2934:
2935: @Override
2936: public void setValueAt(Object aValue, int row, int column) {
2937: table.getModel().setValueAt(aValue, row, column);
2938: }
2939:
2940: @Override
2941: public boolean isCellEditable(int row, int column) {
2942: return table.getModel().isCellEditable(row, column);
2943: }
2944:
2945: @Override
2946: public boolean isTestable(int column) {
2947: return getColumnByModelIndex(column) != null;
2948: }
2949:
2950: //-------------------------- accessing view state/values
2951:
2952: /**
2953: * {@inheritDoc}
2954: */
2955: @Override
2956: public Object getFilteredValueAt(int row, int column) {
2957: return getValueAt(table.convertRowIndexToModel(row), column);
2958: // return table.getValueAt(row, modelToView(column)); // in view coordinates
2959: }
2960:
2961: /**
2962: * {@inheritDoc}
2963: */
2964: @Override
2965: public Object getValue() {
2966: return table.getValueAt(row, column);
2967: }
2968:
2969: /**
2970: * {@inheritDoc}
2971: */
2972: @Override
2973: public boolean isSelected() {
2974: return table.isCellSelected(row, column);
2975: }
2976:
2977: /**
2978: * {@inheritDoc}
2979: */
2980: @Override
2981: public boolean hasFocus() {
2982: boolean rowIsLead = (table.getSelectionModel()
2983: .getLeadSelectionIndex() == row);
2984: boolean colIsLead = (table.getColumnModel()
2985: .getSelectionModel().getLeadSelectionIndex() == column);
2986: return table.isFocusOwner() && (rowIsLead && colIsLead);
2987: }
2988:
2989: /**
2990: * {@inheritDoc}
2991: */
2992: @Override
2993: public int modelToView(int columnIndex) {
2994: return table.convertColumnIndexToView(columnIndex);
2995: }
2996:
2997: /**
2998: * {@inheritDoc}
2999: */
3000: @Override
3001: public int viewToModel(int columnIndex) {
3002: return table.convertColumnIndexToModel(columnIndex);
3003: }
3004:
3005: }
3006:
3007: // --------------------- managing renderers/editors
3008:
3009: /**
3010: * Returns the HighlighterPipeline assigned to the table, null if none.
3011: *
3012: * @return the HighlighterPipeline assigned to the table.
3013: * @see #setHighlighters(HighlighterPipeline)
3014: */
3015: public HighlighterPipeline getHighlighters() {
3016: return highlighters;
3017: }
3018:
3019: /**
3020: * Assigns a HighlighterPipeline to the table, maybe null to remove all
3021: * Highlighters.<p>
3022: *
3023: * The default value is <code>null</code>.
3024: *
3025: * @param pipeline the HighlighterPipeline to use for renderer decoration.
3026: * @see #getHighlighters()
3027: * @see #addHighlighter(Highlighter)
3028: * @see #removeHighlighter(Highlighter)
3029: *
3030: */
3031: public void setHighlighters(HighlighterPipeline pipeline) {
3032: HighlighterPipeline old = getHighlighters();
3033: if (old != null) {
3034: old.removeChangeListener(getHighlighterChangeListener());
3035: }
3036: highlighters = pipeline;
3037: if (highlighters != null) {
3038: highlighters
3039: .addChangeListener(getHighlighterChangeListener());
3040: }
3041: firePropertyChange("highlighters", old, getHighlighters());
3042: repaint();
3043: }
3044:
3045: /**
3046: * Sets the <code>Highlighter</code>s to the table, replacing any old settings.
3047: * Maybe null to remove all highlighters.<p>
3048: *
3049: *
3050: * @param highlighters the highlighters to use for renderer decoration.
3051: * @see #getHighlighters()
3052: * @see #addHighlighter(Highlighter)
3053: * @see #removeHighlighter(Highlighter)
3054: *
3055: */
3056: public void setHighlighters(Highlighter... highlighters) {
3057: HighlighterPipeline pipeline = null;
3058: if ((highlighters != null) && (highlighters.length > 0)
3059: && (highlighters[0] != null)) {
3060: pipeline = new HighlighterPipeline(highlighters);
3061: }
3062: setHighlighters(pipeline);
3063: }
3064:
3065: /**
3066: * Adds a Highlighter.
3067: * <p>
3068: *
3069: * If the <code>HighlighterPipeline</code> returned from getHighlighters()
3070: * is null, creates and sets a new pipeline containing the given
3071: * <code>Highlighter</code>. Else, appends the <code>Highlighter</code>
3072: * to the end of the pipeline.
3073: *
3074: * @param highlighter the <code>Highlighter</code> to add.
3075: * @throws NullPointerException if <code>Highlighter</code> is null.
3076: * @see #removeHighlighter(Highlighter)
3077: * @see #setHighlighters(HighlighterPipeline)
3078: */
3079: public void addHighlighter(Highlighter highlighter) {
3080: HighlighterPipeline pipeline = getHighlighters();
3081: if (pipeline == null) {
3082: setHighlighters(new HighlighterPipeline(
3083: new Highlighter[] { highlighter }));
3084: } else {
3085: pipeline.addHighlighter(highlighter);
3086: }
3087: }
3088:
3089: /**
3090: * Removes the Highlighter. <p>
3091: *
3092: * Does nothing if the HighlighterPipeline is null or does not contain
3093: * the given Highlighter.
3094: *
3095: * @param highlighter the highlighter to remove.
3096: * @see #addHighlighter(Highlighter)
3097: * @see #setHighlighters(HighlighterPipeline)
3098: */
3099: public void removeHighlighter(Highlighter highlighter) {
3100: if ((getHighlighters() == null))
3101: return;
3102: getHighlighters().removeHighlighter(highlighter);
3103: }
3104:
3105: /**
3106: * Returns the <code>ChangeListener</code> to use with highlighters. Lazily
3107: * creates the listener.
3108: *
3109: * @return the ChangeListener for observing changes of highlighters,
3110: * guaranteed to be <code>not-null</code>
3111: */
3112: protected ChangeListener getHighlighterChangeListener() {
3113: if (highlighterChangeListener == null) {
3114: highlighterChangeListener = createHighlighterChangeListener();
3115: }
3116: return highlighterChangeListener;
3117: }
3118:
3119: /**
3120: * Creates and returns the ChangeListener observing Highlighters.
3121: * <p>
3122: * Here: repaints the table on receiving a stateChanged.
3123: *
3124: * @return the ChangeListener defining the reaction to changes of
3125: * highlighters.
3126: */
3127: protected ChangeListener createHighlighterChangeListener() {
3128: ChangeListener l = new ChangeListener() {
3129:
3130: public void stateChanged(ChangeEvent e) {
3131: repaint();
3132:
3133: }
3134:
3135: };
3136: return l;
3137: }
3138:
3139: /**
3140: * {@inheritDoc}
3141: * <p>
3142: *
3143: * Overridden to fix core bug #4614616 (NPE if <code>TableModel</code>'s
3144: * <code>Class</code> for the column is an interface). This method
3145: * guarantees to always return a <code>not null</code> value. Returns the
3146: * default renderer for <code>Object</code> if super returns
3147: * <code>null</code>.
3148: *
3149: *
3150: */
3151: @Override
3152: public TableCellRenderer getCellRenderer(int row, int column) {
3153: TableCellRenderer renderer = super .getCellRenderer(row, column);
3154: if (renderer == null) {
3155: renderer = getDefaultRenderer(Object.class);
3156: }
3157: return renderer;
3158: }
3159:
3160: /**
3161: * Returns the decorated <code>Component</code> used as a stamp to render
3162: * the specified cell. Overrides superclass version to provide support for
3163: * cell decorators.
3164: * <p>
3165: *
3166: * Adjusts component orientation (guaranteed to happen before applying
3167: * Highlighters).
3168: *
3169: *
3170: * @param renderer the <code>TableCellRenderer</code> to prepare
3171: * @param row the row of the cell to render, where 0 is the first row
3172: * @param column the column of the cell to render, where 0 is the first
3173: * column
3174: * @return the decorated <code>Component</code> used as a stamp to render
3175: * the specified cell
3176: * @see org.jdesktop.swingx.decorator.Highlighter
3177: */
3178: @Override
3179: public Component prepareRenderer(TableCellRenderer renderer,
3180: int row, int column) {
3181: // #258-swingx: hacking around DefaultTableCellRenderer color memory.
3182: // resetDefaultTableCellRendererColors(renderer, row, column);
3183: Component stamp = super .prepareRenderer(renderer, row, column);
3184: // #145-swingx: default renderers don't respect componentOrientation.
3185: adjustComponentOrientation(stamp);
3186: // #258-swingx: hacking around DefaultTableCellRenderer color memory.
3187: resetDefaultTableCellRendererColors(stamp, row, column);
3188: if (highlighters == null) {
3189: return stamp; // no need to decorate renderer with highlighters
3190: } else {
3191: return highlighters.apply(stamp, getComponentAdapter(row,
3192: column));
3193: }
3194: }
3195:
3196: /**
3197: * Method to hack around #258-swingx: apply a specialized
3198: * <code>Highlighter</code> to force reset the color "memory" of
3199: * <code>DefaultTableCellRenderer</code>. This is called for each
3200: * renderer in <code>prepareRenderer</code> before calling super.
3201: * Subclasses which are sure to solve the problem at the core (that is in a
3202: * well-behaved DefaultTableCellRenderer) should override this method to do
3203: * nothing.
3204: *
3205: * @param renderer the <code>TableCellRenderer</code> to hack
3206: * @param row the row of the cell to render
3207: * @param column the column index of the cell to render
3208: *
3209: * @see #prepareRenderer(TableCellRenderer, int, int)
3210: * @see org.jdesktop.swingx.decorator.ResetDTCRColorHighlighter
3211: */
3212: protected void resetDefaultTableCellRendererColors(
3213: TableCellRenderer renderer, int row, int column) {
3214: if (!(renderer instanceof DefaultTableCellRenderer))
3215: return;
3216: ComponentAdapter adapter = getComponentAdapter(row, column);
3217: if (resetDefaultTableCellRendererHighlighter == null) {
3218: resetDefaultTableCellRendererHighlighter = new ResetDTCRColorHighlighter();
3219: }
3220: // hacking around DefaultTableCellRenderer color memory.
3221: resetDefaultTableCellRendererHighlighter.highlight(
3222: (DefaultTableCellRenderer) renderer, adapter);
3223: }
3224:
3225: /**
3226: * Method to hack around #258-swingx: apply a specialized <code>Highlighter</code>
3227: * to force reset the color "memory" of <code>DefaultTableCellRenderer</code>.
3228: * This is called for each renderer in <code>prepareRenderer</code> after
3229: * calling super, but before applying the HighlighterPipeline. Subclasses
3230: * which are sure to solve the problem at the core (that is in
3231: * a well-behaved DefaultTableCellRenderer) should override this method
3232: * to do nothing. <p>
3233: *
3234: * @param renderer the <code>TableCellRenderer</code> to hack
3235: * @param row the row of the cell to render
3236: * @param column the column index of the cell to render
3237: *
3238: * @see #prepareRenderer(TableCellRenderer, int, int)
3239: * @see #resetDefaultTableCellRendererColors(TableCellRenderer, int, int)
3240: * @see org.jdesktop.swingx.decorator.ResetDTCRColorHighlighter
3241: *
3242: */
3243: protected void resetDefaultTableCellRendererColors(
3244: Component renderer, int row, int column) {
3245: ComponentAdapter adapter = getComponentAdapter(row, column);
3246: if (resetDefaultTableCellRendererHighlighter == null) {
3247: resetDefaultTableCellRendererHighlighter = new ResetDTCRColorHighlighter();
3248: }
3249: // hacking around DefaultTableCellRenderer color memory.
3250: resetDefaultTableCellRendererHighlighter.highlight(renderer,
3251: adapter);
3252: }
3253:
3254: /**
3255: * {@inheritDoc} <p>
3256: *
3257: * Overridden to adjust the editor's component orientation.
3258: */
3259: @Override
3260: public Component prepareEditor(TableCellEditor editor, int row,
3261: int column) {
3262: Component comp = super .prepareEditor(editor, row, column);
3263: adjustComponentOrientation(comp);
3264: return comp;
3265: }
3266:
3267: /**
3268: * Adjusts the <code>Component</code>'s orientation to this
3269: * <code>JXTable</code>'s CO if appropriate. The parameter must not be
3270: * <code>null</code>.
3271: * <p>
3272: *
3273: * This implementation synchs the CO always.
3274: *
3275: * @param stamp the <code>Component</code> who's CO may need to be synched,
3276: * must not be <code>null</code>.
3277: */
3278: protected void adjustComponentOrientation(Component stamp) {
3279: if (stamp.getComponentOrientation().equals(
3280: getComponentOrientation()))
3281: return;
3282: stamp.applyComponentOrientation(getComponentOrientation());
3283: }
3284:
3285: /**
3286: * Returns a new instance of the default renderer for the specified class.
3287: * This differs from <code>getDefaultRenderer()</code> in that it returns
3288: * a <b>new </b> instance each time so that the renderer may be set and
3289: * customized on a particular column. <p>
3290: *
3291: * NOTE: this doesn't work with swingx renderers! Do we really need it? It
3292: * had been used in JNTable which is practically obsolete. If needed, we
3293: * could make all renderer support classes clonable.
3294: *
3295: * @param columnClass Class of value being rendered
3296: * @return TableCellRenderer instance which renders values of the specified
3297: * type
3298: * @see #getDefaultRenderer(Class)
3299: */
3300: public TableCellRenderer getNewDefaultRenderer(Class columnClass) {
3301: TableCellRenderer renderer = getDefaultRenderer(columnClass);
3302: if (renderer != null) {
3303: try {
3304: return renderer.getClass().newInstance();
3305: } catch (Exception e) {
3306: LOG
3307: .fine("could not create renderer for "
3308: + columnClass);
3309: }
3310: }
3311: // JW PENDING: must not return null!
3312: return null;
3313: }
3314:
3315: /**
3316: * Creates default cell renderers for objects, numbers, doubles, dates,
3317: * booleans, and icons.
3318: * <p>
3319: * Overridden so we can act as factory for renderers plus hacking around
3320: * huge memory consumption of UIDefaults (see #6345050 in core Bug parade)
3321: * <p>
3322: * {@inheritDoc}
3323: */
3324: @Override
3325: protected void createDefaultRenderers() {
3326: // super.createDefaultRenderers();
3327: // This duplicates JTable's functionality in order to make the renderers
3328: // available in getNewDefaultRenderer(); If JTable's renderers either
3329: // were public, or it provided a factory for *new* renderers, this would
3330: // not be needed
3331:
3332: // hack around #6345050 - new UIDefaults()
3333: // is created with a huge initialCapacity
3334: // giving a dummy key/value array as parameter reduces that capacity
3335: // to length/2.
3336: Object[] dummies = new Object[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0,
3337: 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, };
3338: defaultRenderersByColumnClass = new UIDefaults(dummies);
3339: defaultRenderersByColumnClass.clear();
3340: // use swingx renderers by default
3341: setDefaultRenderer(Object.class, new DefaultTableRenderer());
3342: LabelProvider controller = new LabelProvider(
3343: FormatStringValue.NUMBER_TO_STRING);
3344: controller.setHorizontalAlignment(JLabel.RIGHT);
3345: setDefaultRenderer(Number.class, new DefaultTableRenderer(
3346: controller));
3347: setDefaultRenderer(Date.class, new DefaultTableRenderer(
3348: FormatStringValue.DATE_TO_STRING));
3349: TableCellRenderer renderer = new DefaultTableRenderer(
3350: new LabelProvider(JLabel.CENTER));
3351: setDefaultRenderer(Icon.class, renderer);
3352: setDefaultRenderer(ImageIcon.class, renderer);
3353: setDefaultRenderer(Boolean.class, new DefaultTableRenderer(
3354: new ButtonProvider()));
3355:
3356: // // standard renderers
3357: // // Objects
3358: // setLazyRenderer(Object.class,
3359: // "javax.swing.table.DefaultTableCellRenderer");
3360: //
3361: // // Numbers
3362: // setLazyRenderer(Number.class,
3363: // "org.jdesktop.swingx.JXTable$NumberRenderer");
3364: //
3365: // // Doubles and Floats
3366: // setLazyRenderer(Float.class,
3367: // "org.jdesktop.swingx.JXTable$DoubleRenderer");
3368: // setLazyRenderer(Double.class,
3369: // "org.jdesktop.swingx.JXTable$DoubleRenderer");
3370: //
3371: // // Dates
3372: // setLazyRenderer(Date.class, "org.jdesktop.swingx.JXTable$DateRenderer");
3373: //
3374: // // Icons and ImageIcons
3375: // setLazyRenderer(Icon.class, "org.jdesktop.swingx.JXTable$IconRenderer");
3376: // setLazyRenderer(ImageIcon.class,
3377: // "org.jdesktop.swingx.JXTable$IconRenderer");
3378: //
3379: // // Booleans
3380: // setLazyRenderer(Boolean.class,
3381: // "org.jdesktop.swingx.JXTable$BooleanRenderer");
3382:
3383: }
3384:
3385: /** c&p'ed from super */
3386: private void setLazyValue(Hashtable h, Class c, String s) {
3387: h.put(c, new UIDefaults.ProxyLazyValue(s));
3388: }
3389:
3390: /** c&p'ed from super */
3391: private void setLazyRenderer(Class c, String s) {
3392: setLazyValue(defaultRenderersByColumnClass, c, s);
3393: }
3394:
3395: /** c&p'ed from super */
3396: private void setLazyEditor(Class c, String s) {
3397: setLazyValue(defaultEditorsByColumnClass, c, s);
3398: }
3399:
3400: /*
3401: * Default Type-based Renderers: JTable's default table cell renderer
3402: * classes are private and JTable:getDefaultRenderer() returns a *shared*
3403: * cell renderer instance, thus there is no way for us to instantiate a new
3404: * instance of one of its default renderers. So, we must replicate the
3405: * default renderer classes here so that we can instantiate them when we
3406: * need to create renderers to be set on specific columns.
3407: */
3408:
3409: /**
3410: * The default renderer for <code>Number</code> types. Aligns to
3411: * <code>RIGHT</code>.
3412: */
3413: public static class NumberRenderer extends DefaultTableCellRenderer {
3414: public NumberRenderer() {
3415: super ();
3416: // JW: RIGHT is the correct thing to do for bidi-compliance
3417: // numbers are right aligned even if text is LToR
3418: setHorizontalAlignment(JLabel.RIGHT);
3419: }
3420: }
3421:
3422: /**
3423: * Default renderer for <code>Float</code> and <code>Double</code>
3424: * types. Uses a <code>NumberFormat</code> to renderer. The format can be
3425: * provided by client code, if null the general-purpose number format for
3426: * the current locale is used.
3427: *
3428: */
3429: public static class DoubleRenderer extends NumberRenderer {
3430: private final NumberFormat formatter;
3431:
3432: public DoubleRenderer() {
3433: this (null);
3434: }
3435:
3436: public DoubleRenderer(NumberFormat formatter) {
3437: if (formatter == null) {
3438: formatter = NumberFormat.getInstance();
3439: }
3440: this .formatter = formatter;
3441: }
3442:
3443: @Override
3444: public void setValue(Object value) {
3445: setText((value == null) ? "" : formatter.format(value));
3446: }
3447: }
3448:
3449: /**
3450: * The default renderer for <code>Date</code> types. Uses a
3451: * <code>DateFormat</code> to render. The format can be provided by client
3452: * code, if null the general-purpose date format for the current locale is
3453: * used.
3454: */
3455: public static class DateRenderer extends DefaultTableCellRenderer {
3456: private final DateFormat formatter;
3457:
3458: public DateRenderer() {
3459: this (null);
3460: }
3461:
3462: public DateRenderer(DateFormat formatter) {
3463: if (formatter == null) {
3464: formatter = DateFormat.getDateInstance();
3465: }
3466: this .formatter = formatter;
3467: }
3468:
3469: @Override
3470: public void setValue(Object value) {
3471: setText((value == null) ? "" : formatter.format(value));
3472: }
3473: }
3474:
3475: /**
3476: * The default renderer for <code>Icon</code> and <code>ImageIcon</code> types.<p>
3477: *
3478: * Note: it's registered for both the interface and a concrete class because
3479: * <code>JTable</code> class-based lookup doesn't cope well with interfaces.
3480: *
3481: */
3482: public static class IconRenderer extends DefaultTableCellRenderer {
3483: public IconRenderer() {
3484: super ();
3485: setHorizontalAlignment(JLabel.CENTER);
3486: }
3487:
3488: @Override
3489: public void setValue(Object value) {
3490: setIcon((value instanceof Icon) ? (Icon) value : null);
3491: }
3492: }
3493:
3494: /*
3495: * re- c&p'd from 1.5 JTable.
3496: */
3497: /**
3498: * The default renderer for <code>Boolean</code> types.
3499: */
3500: public static class BooleanRenderer extends JCheckBox implements // , UIResource
3501: TableCellRenderer {
3502: private static final Border noFocusBorder = new EmptyBorder(1,
3503: 1, 1, 1);
3504:
3505: public BooleanRenderer() {
3506: super ();
3507: setHorizontalAlignment(JLabel.CENTER);
3508: setBorderPainted(true);
3509: }
3510:
3511: public Component getTableCellRendererComponent(JTable table,
3512: Object value, boolean isSelected, boolean hasFocus,
3513: int row, int column) {
3514: if (isSelected) {
3515: setForeground(table.getSelectionForeground());
3516: super .setBackground(table.getSelectionBackground());
3517: } else {
3518: setForeground(table.getForeground());
3519: setBackground(table.getBackground());
3520: }
3521: setSelected((value != null && ((Boolean) value)
3522: .booleanValue()));
3523:
3524: if (hasFocus) {
3525: setBorder(UIManager
3526: .getBorder("Table.focusCellHighlightBorder"));
3527: } else {
3528: setBorder(noFocusBorder);
3529: }
3530:
3531: return this ;
3532: }
3533: }
3534:
3535: /**
3536: * Creates default cell editors for objects, numbers, and boolean values.
3537: * <p>
3538: * Overridden to hook enhanced editors (f.i. <code>NumberEditorExt</code>)plus
3539: * hacking around huge memory consumption of UIDefaults (see #6345050 in
3540: * core Bug parade)
3541: *
3542: * @see DefaultCellEditor
3543: */
3544: @Override
3545: protected void createDefaultEditors() {
3546: Object[] dummies = new Object[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0,
3547: 6, 0, 7, 0, 8, 0, 9, 0, 10, 0,
3548:
3549: };
3550: defaultEditorsByColumnClass = new UIDefaults(dummies);
3551: defaultEditorsByColumnClass.clear();
3552: // defaultEditorsByColumnClass = new UIDefaults();
3553:
3554: // Objects
3555: setLazyEditor(Object.class,
3556: "org.jdesktop.swingx.JXTable$GenericEditor");
3557:
3558: // Numbers
3559: // setLazyEditor(Number.class, "org.jdesktop.swingx.JXTable$NumberEditor");
3560: setLazyEditor(Number.class,
3561: "org.jdesktop.swingx.table.NumberEditorExt");
3562:
3563: // Booleans
3564: setLazyEditor(Boolean.class,
3565: "org.jdesktop.swingx.JXTable$BooleanEditor");
3566:
3567: }
3568:
3569: /**
3570: * Default editor registered for <code>Object</code>. The editor tries
3571: * to create a new instance of the column's class by reflection. It
3572: * assumes that the class has a constructor taking a single <code>String</code>
3573: * parameter. <p>
3574: *
3575: * The editor can be configured with a custom <code>JTextField</code>.
3576: *
3577: */
3578: public static class GenericEditor extends DefaultCellEditor {
3579:
3580: Class[] argTypes = new Class[] { String.class };
3581: java.lang.reflect.Constructor constructor;
3582: Object value;
3583:
3584: public GenericEditor() {
3585: this (new JTextField());
3586: }
3587:
3588: public GenericEditor(JTextField textField) {
3589: super (textField);
3590: getComponent().setName("Table.editor");
3591: }
3592:
3593: @Override
3594: public boolean stopCellEditing() {
3595: String s = (String) super .getCellEditorValue();
3596: // Here we are dealing with the case where a user
3597: // has deleted the string value in a cell, possibly
3598: // after a failed validation. Return null, so that
3599: // they have the option to replace the value with
3600: // null or use escape to restore the original.
3601: // For Strings, return "" for backward compatibility.
3602: if ("".equals(s)) {
3603: if (constructor.getDeclaringClass() == String.class) {
3604: value = s;
3605: }
3606: super .stopCellEditing();
3607: }
3608:
3609: try {
3610: value = constructor.newInstance(new Object[] { s });
3611: } catch (Exception e) {
3612: ((JComponent) getComponent()).setBorder(new LineBorder(
3613: Color.red));
3614: return false;
3615: }
3616: return super .stopCellEditing();
3617: }
3618:
3619: @Override
3620: public Component getTableCellEditorComponent(JTable table,
3621: Object value, boolean isSelected, int row, int column) {
3622: this .value = null;
3623: ((JComponent) getComponent()).setBorder(new LineBorder(
3624: Color.black));
3625: try {
3626: Class type = table.getColumnClass(column);
3627: // Since our obligation is to produce a value which is
3628: // assignable for the required type it is OK to use the
3629: // String constructor for columns which are declared
3630: // to contain Objects. A String is an Object.
3631: if (type == Object.class) {
3632: type = String.class;
3633: }
3634: constructor = type.getConstructor(argTypes);
3635: } catch (Exception e) {
3636: return null;
3637: }
3638: return super .getTableCellEditorComponent(table, value,
3639: isSelected, row, column);
3640: }
3641:
3642: @Override
3643: public Object getCellEditorValue() {
3644: return value;
3645: }
3646: }
3647:
3648: /**
3649: *
3650: * Editor for <code>Number</code>s. <p>
3651: * Note: this is no longer registered by default.
3652: * The current default is <code>NumberEditorExt</code>
3653: * which differs from this in being locale-aware.
3654: *
3655: */
3656: public static class NumberEditor extends GenericEditor {
3657:
3658: public NumberEditor() {
3659: ((JTextField) getComponent())
3660: .setHorizontalAlignment(JTextField.RIGHT);
3661: }
3662: }
3663:
3664: /**
3665: * The default editor for <code>Boolean</code> types.
3666: */
3667: public static class BooleanEditor extends DefaultCellEditor {
3668: public BooleanEditor() {
3669: super (new JCheckBox());
3670: JCheckBox checkBox = (JCheckBox) getComponent();
3671: checkBox.setHorizontalAlignment(JCheckBox.CENTER);
3672: }
3673: }
3674:
3675: // ----------------------------- enhanced editing support
3676:
3677: /**
3678: * Returns the editable property of the <code>JXTable</code> as a whole.
3679: *
3680: * @return boolean to indicate if the table is editable.
3681: * @see #setEditable
3682: */
3683: public boolean isEditable() {
3684: return editable;
3685: }
3686:
3687: /**
3688: * Sets the editable property. This property allows to mark all cells in a
3689: * table as read-only, independent of their per-column editability as
3690: * returned by <code>TableColumnExt.isEditable</code> and their per-cell
3691: * editability as returned by the <code>TableModel.isCellEditable</code>.
3692: * If a cell is read-only in its column or model layer, this property has no
3693: * effect.
3694: * <p>
3695: *
3696: * The default value is <code>true</code>.
3697: *
3698: * @param editable the flag to indicate if the table is editable.
3699: * @see #isEditable
3700: * @see #isCellEditable(int, int)
3701: */
3702: public void setEditable(boolean editable) {
3703: boolean old = isEditable();
3704: this .editable = editable;
3705: firePropertyChange("editable", old, isEditable());
3706: }
3707:
3708: /**
3709: * Returns the property which determines the edit termination behaviour on
3710: * focus lost.
3711: *
3712: * @return boolean to indicate whether an ongoing edit should be terminated
3713: * if the focus is moved to somewhere outside of the table.
3714: * @see #setTerminateEditOnFocusLost(boolean)
3715: */
3716: public boolean isTerminateEditOnFocusLost() {
3717: return Boolean.TRUE
3718: .equals(getClientProperty("terminateEditOnFocusLost"));
3719: }
3720:
3721: /**
3722: * Sets the property to determine whether an ongoing edit should be
3723: * terminated if the focus is moved to somewhere outside of the table. If
3724: * true, terminates the edit, does nothing otherwise. The exact behaviour is
3725: * implemented in <code>JTable.CellEditorRemover</code>: "outside" is
3726: * interpreted to be on a component which is not under the table hierarchy
3727: * but inside the same toplevel window, "terminate" does so in any case,
3728: * first tries to stop the edit, if that's unsuccessful it cancels the edit.
3729: * <p>
3730: * The default value is <code>true</code>.
3731: *
3732: * @param terminate the flag to determine whether or not to terminate the
3733: * edit
3734: * @see #isTerminateEditOnFocusLost()
3735: */
3736: public void setTerminateEditOnFocusLost(boolean terminate) {
3737: // JW: we can leave the propertyChange notification to the
3738: // putClientProperty - the key and method name are the same
3739: putClientProperty("terminateEditOnFocusLost", terminate);
3740: }
3741:
3742: /**
3743: * Returns the autoStartsEdit property.
3744: *
3745: * @return boolean to indicate whether a keyStroke should try to start
3746: * editing.
3747: * @see #setAutoStartEditOnKeyStroke(boolean)
3748: */
3749: public boolean isAutoStartEditOnKeyStroke() {
3750: return !Boolean.FALSE
3751: .equals(getClientProperty("JTable.autoStartsEdit"));
3752: }
3753:
3754: /**
3755: * Sets the autoStartsEdit property. If true, keystrokes are passed-on to
3756: * the cellEditor of the lead cell to let it decide whether to start an
3757: * edit.
3758: * <p>
3759: * The default value is <code>true</code>.
3760: * <p>
3761: *
3762: * @param autoStart boolean to determine whether a keyStroke should try to
3763: * start editing.
3764: * @see #isAutoStartEditOnKeyStroke()
3765: */
3766: public void setAutoStartEditOnKeyStroke(boolean autoStart) {
3767: boolean old = isAutoStartEditOnKeyStroke();
3768: // JW: we have to take over propertyChange notification
3769: // because the key and method name are different.
3770: // As a consequence, there are two events fired: one for
3771: // the client prop and one for this method.
3772: putClientProperty("JTable.autoStartsEdit", autoStart);
3773: firePropertyChange("autoStartEditOnKeyStroke", old,
3774: isAutoStartEditOnKeyStroke());
3775: }
3776:
3777: // ---------------------------- updateUI support
3778:
3779: /**
3780: * {@inheritDoc}
3781: * <p>
3782: * Additionally updates auto-adjusted row height and highlighters.
3783: * <p>
3784: * Another of the override motivation is to fix core issue (?? ID): super
3785: * fails to update <b>all</b> renderers/editors.
3786: */
3787: @Override
3788: public void updateUI() {
3789: super .updateUI();
3790: if (columnControlButton != null) {
3791: columnControlButton.updateUI();
3792: }
3793: for (Enumeration defaultEditors = defaultEditorsByColumnClass
3794: .elements(); defaultEditors.hasMoreElements();) {
3795: updateEditorUI(defaultEditors.nextElement());
3796: }
3797:
3798: for (Enumeration defaultRenderers = defaultRenderersByColumnClass
3799: .elements(); defaultRenderers.hasMoreElements();) {
3800: updateRendererUI(defaultRenderers.nextElement());
3801: }
3802: List columns = getColumns(true);
3803: for (Iterator iter = columns.iterator(); iter.hasNext();) {
3804: TableColumn column = (TableColumn) iter.next();
3805: updateEditorUI(column.getCellEditor());
3806: updateRendererUI(column.getCellRenderer());
3807: updateRendererUI(column.getHeaderRenderer());
3808: }
3809: updateRowHeightUI(true);
3810: updateHighlighterUI();
3811: }
3812:
3813: /**
3814: * Updates highlighter after <code>updateUI</code> changes.
3815: *
3816: * @see org.jdesktop.swingx.decorator.Highlighter.UIHighlighter
3817: */
3818: protected void updateHighlighterUI() {
3819: if (getHighlighters() == null)
3820: return;
3821: getHighlighters().updateUI();
3822: }
3823:
3824: /**
3825: * Auto-adjusts rowHeight to something more pleasing then the default. This
3826: * method is called after instantiation and after updating the UI. Does
3827: * nothing if the given parameter is <code>true</code> and the rowHeight
3828: * had been already set by client code. The underlying problem is that raw
3829: * types can't implement UIResource.
3830: * <p>
3831: * This implementation asks the UIManager for a default value (stored with
3832: * key "JXTable.rowHeight"). If none is available, calculates a "reasonable"
3833: * height from the table's fontMetrics, assuming that most renderers/editors
3834: * will have a border with top/bottom of 1.
3835: * <p>
3836: *
3837: * @param respectRowSetFlag a boolean to indicate whether client-code flag
3838: * should be respected.
3839: * @see #isXTableRowHeightSet
3840: */
3841: protected void updateRowHeightUI(boolean respectRowSetFlag) {
3842: if (respectRowSetFlag && isXTableRowHeightSet)
3843: return;
3844: int uiHeight = UIManager.getInt(UIPREFIX + "rowHeight");
3845: if (uiHeight > 0) {
3846: setRowHeight(uiHeight);
3847: } else {
3848: int fontBasedHeight = getFontMetrics(getFont()).getHeight() + 2;
3849: int magicMinimum = 18;
3850: setRowHeight(Math.max(fontBasedHeight, magicMinimum));
3851: }
3852: isXTableRowHeightSet = false;
3853: }
3854:
3855: /**
3856: * Convenience to set both grid line visibility and default margin for
3857: * horizontal/vertical lines. The margin defaults to 1 or 0 if the grid
3858: * lines are drawn or not drawn.
3859: * <p>
3860: * @param showHorizontalLines boolean to decide whether to draw horizontal
3861: * grid lines.
3862: * @param showVerticalLines boolean to decide whether to draw vertical grid
3863: * lines.
3864: * @deprecated replaced by {@link #setShowGrid(boolean, boolean)}.
3865: *
3866: */
3867: public void setDefaultMargins(boolean showHorizontalLines,
3868: boolean showVerticalLines) {
3869: setShowGrid(showHorizontalLines, showVerticalLines);
3870: }
3871:
3872: /**
3873: * Convenience to set both grid line visibility and default margin for
3874: * horizontal/vertical lines. The margin defaults to 1 or 0 if the grid
3875: * lines are drawn or not drawn.
3876: * <p>
3877: * @param showHorizontalLines boolean to decide whether to draw horizontal
3878: * grid lines.
3879: * @param showVerticalLines boolean to decide whether to draw vertical grid
3880: * lines.
3881: * @see javax.swing.JTable#setShowGrid(boolean)
3882: * @see javax.swing.JTable#setIntercellSpacing(Dimension)
3883: */
3884: public void setShowGrid(boolean showHorizontalLines,
3885: boolean showVerticalLines) {
3886: int defaultRowMargin = showHorizontalLines ? 1 : 0;
3887: setRowMargin(defaultRowMargin);
3888: setShowHorizontalLines(showHorizontalLines);
3889: int defaultColumnMargin = showVerticalLines ? 1 : 0;
3890: setColumnMargin(defaultColumnMargin);
3891: setShowVerticalLines(showVerticalLines);
3892: }
3893:
3894: /**
3895: * {@inheritDoc}
3896: * <p>
3897: * Overriden to keep view/model coordinates of SizeSequence in synch. Marks
3898: * the request as client-code induced.
3899: *
3900: * @see #isXTableRowHeightSet
3901: */
3902: @Override
3903: public void setRowHeight(int rowHeight) {
3904: super .setRowHeight(rowHeight);
3905: if (rowHeight > 0) {
3906: isXTableRowHeightSet = true;
3907: }
3908: updateViewSizeSequence();
3909:
3910: }
3911:
3912: /**
3913: * {@inheritDoc}
3914: * <p>
3915: * Does nothing if support of individual rowHeights is not enabled.
3916: * Overriden to keep view/model coordinates of SizeSequence in synch.
3917: *
3918: * @see #isRowHeightEnabled()
3919: */
3920: @Override
3921: public void setRowHeight(int row, int rowHeight) {
3922: if (!isRowHeightEnabled())
3923: return;
3924: super .setRowHeight(row, rowHeight);
3925: updateViewSizeSequence();
3926: resizeAndRepaint();
3927: }
3928:
3929: /**
3930: * Sets enablement of individual rowHeight support. Enabling the support
3931: * involves reflective access to super's private field rowModel which may
3932: * fail due to security issues. If failing the support is not enabled.
3933: * <p>
3934: * The default value is <code>false</code>.
3935: *
3936: * @param enabled a boolean to indicate whether per-row heights should be
3937: * enabled.
3938: * @see #isRowHeightEnabled()
3939: * @see #setRowHeight(int, int)
3940: */
3941: public void setRowHeightEnabled(boolean enabled) {
3942: // PENDING: should we throw an Exception if the enabled fails?
3943: // Or silently fail - depends on runtime context,
3944: // can't do anything about it.
3945: boolean old = isRowHeightEnabled();
3946: if (old == enabled)
3947: return;
3948: if (enabled && !canEnableRowHeight())
3949: return;
3950: rowHeightEnabled = enabled;
3951: if (!enabled) {
3952: adminSetRowHeight(getRowHeight());
3953: }
3954: firePropertyChange("rowHeightEnabled", old, rowHeightEnabled);
3955: }
3956:
3957: /**
3958: * Returns a boolean to indicate whether individual row height is enabled.
3959: *
3960: * @return a boolean to indicate whether individual row height support is
3961: * enabled.
3962: * @see #setRowHeightEnabled(boolean)
3963: * @see #setRowHeight(int, int)
3964: */
3965: public boolean isRowHeightEnabled() {
3966: return rowHeightEnabled;
3967: }
3968:
3969: /**
3970: * Returns if it's possible to enable individual row height support.
3971: *
3972: * @return a boolean to indicate whether access of super's private
3973: * <code>rowModel</code> is allowed.
3974: */
3975: private boolean canEnableRowHeight() {
3976: return getRowModelField() != null;
3977: }
3978:
3979: /**
3980: * Returns super's private <code>rowModel</code> which holds the
3981: * individual rowHeights. This method will return <code>null</code> if the
3982: * access failed, f.i. in sandbox restricted applications.
3983: *
3984: * @return super's rowModel field or null if the access was not successful.
3985: */
3986: private SizeSequence getSuperRowModel() {
3987: try {
3988: Field field = getRowModelField();
3989: if (field != null) {
3990: return (SizeSequence) field.get(this );
3991: }
3992: } catch (SecurityException e) {
3993: LOG.fine("cannot use reflection "
3994: + " - expected behaviour in sandbox");
3995: } catch (IllegalArgumentException e) {
3996: LOG
3997: .fine("problem while accessing super's private field - private api changed?");
3998: } catch (IllegalAccessException e) {
3999: LOG
4000: .fine("cannot access private field "
4001: + " - expected behaviour in sandbox. "
4002: + "Could be program logic running wild in unrestricted contexts");
4003: }
4004: return null;
4005: }
4006:
4007: /**
4008: * Returns super's private field which holds the individual rowHeights. This
4009: * method will return <code>null</code> if the access failed, f.i. in
4010: * sandbox restricted applications.
4011: *
4012: * @return the super's field with access allowed or null if an Exception
4013: * caught while trying to access.
4014: */
4015: private Field getRowModelField() {
4016: if (rowModelField == null) {
4017: try {
4018: rowModelField = JTable.class
4019: .getDeclaredField("rowModel");
4020: rowModelField.setAccessible(true);
4021: } catch (SecurityException e) {
4022: rowModelField = null;
4023: LOG.fine("cannot access JTable private field rowModel "
4024: + "- expected behaviour in sandbox");
4025: } catch (NoSuchFieldException e) {
4026: LOG
4027: .fine("problem while accessing super's private field"
4028: + " - private api changed?");
4029: }
4030: }
4031: return rowModelField;
4032: }
4033:
4034: /**
4035: * Returns the mapper used synch individual rowHeights in view/model
4036: * coordinates.
4037: *
4038: * @return the <code>SizeSequenceMapper</code> used to synch view/model
4039: * coordinates for individual row heights
4040: * @see org.jdesktop.swingx.decorator.SizeSequenceMapper
4041: */
4042: protected SizeSequenceMapper getRowModelMapper() {
4043: if (rowModelMapper == null) {
4044: rowModelMapper = new SizeSequenceMapper(filters);
4045: }
4046: return rowModelMapper;
4047: }
4048:
4049: /**
4050: * Sets the rowHeight for all rows to the given value. Keeps the flag
4051: * <code>isXTableRowHeight</code> unchanged. This enables the distinction
4052: * between setting the height for internal reasons from doing so by client
4053: * code.
4054: *
4055: * @param rowHeight new height in pixel.
4056: * @see #setRowHeight(int)
4057: * @see #isXTableRowHeightSet
4058: */
4059: protected void adminSetRowHeight(int rowHeight) {
4060: boolean heightSet = isXTableRowHeightSet;
4061: setRowHeight(rowHeight);
4062: isXTableRowHeightSet = heightSet;
4063: }
4064:
4065: /**
4066: * Tries its best to <code>updateUI</code> of the potential
4067: * <code>TableCellEditor</code>.
4068: *
4069: * @param maybeEditor the potential editor.
4070: */
4071: private void updateEditorUI(Object maybeEditor) {
4072: // maybe null or proxyValue
4073: if (!(maybeEditor instanceof TableCellEditor))
4074: return;
4075: // super handled this
4076: if ((maybeEditor instanceof JComponent)
4077: || (maybeEditor instanceof DefaultCellEditor))
4078: return;
4079: // custom editors might balk about fake rows/columns
4080: try {
4081: Component comp = ((TableCellEditor) maybeEditor)
4082: .getTableCellEditorComponent(this , null, false, -1,
4083: -1);
4084: if (comp instanceof JComponent) {
4085: ((JComponent) comp).updateUI();
4086: }
4087: } catch (Exception e) {
4088: // ignore - can't do anything
4089: }
4090: }
4091:
4092: /**
4093: * Tries its best to <code>updateUI</code> of the potential
4094: * <code>TableCellRenderer</code>.
4095: *
4096: * @param maybeRenderer the potential renderer.
4097: */
4098: private void updateRendererUI(Object maybeRenderer) {
4099: // maybe null or proxyValue
4100: if (!(maybeRenderer instanceof TableCellRenderer))
4101: return;
4102: // super handled this
4103: if (maybeRenderer instanceof JComponent)
4104: return;
4105: // custom editors might balk about fake rows/columns
4106: try {
4107: Component comp = ((TableCellRenderer) maybeRenderer)
4108: .getTableCellRendererComponent(this , null, false,
4109: false, -1, -1);
4110: if (comp instanceof JComponent) {
4111: ((JComponent) comp).updateUI();
4112: }
4113: } catch (Exception e) {
4114: // ignore - can't do anything
4115: }
4116: }
4117:
4118: // ---------------------------- overriding super factory methods and buggy
4119: /**
4120: * {@inheritDoc}
4121: * <p>
4122: * Overridden to work around core Bug (ID #6291631): negative y is mapped to
4123: * row 0).
4124: *
4125: */
4126: @Override
4127: public int rowAtPoint(Point point) {
4128: if (point.y < 0)
4129: return -1;
4130: return super .rowAtPoint(point);
4131: }
4132:
4133: /**
4134: *
4135: * {@inheritDoc}
4136: * <p>
4137: *
4138: * Overridden to return a <code>JXTableHeader</code>.
4139: * @see JXTableHeader
4140: */
4141: @Override
4142: protected JTableHeader createDefaultTableHeader() {
4143: return new JXTableHeader(columnModel);
4144: }
4145:
4146: /**
4147: *
4148: * {@inheritDoc}
4149: * <p>
4150: *
4151: * Overridden to return a <code>DefaultTableColumnModelExt</code>.
4152: * @see org.jdesktop.swingx.table.DefaultTableColumnModelExt
4153: */
4154: @Override
4155: protected TableColumnModel createDefaultColumnModel() {
4156: return new DefaultTableColumnModelExt();
4157: }
4158:
4159: }
|