Source Code Cross Referenced for JXTable.java in  » Swing-Library » swingx » org » jdesktop » swingx » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Swing Library » swingx » org.jdesktop.swingx 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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 &quot;sortable&quot; 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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.