View Javadoc
1   package net.logAnalyzer.utils.gui;
2   
3   import java.awt.Component;
4   import java.awt.Point;
5   import java.awt.Rectangle;
6   import java.awt.event.MouseAdapter;
7   import java.awt.event.MouseEvent;
8   
9   import javax.swing.JTable;
10  import javax.swing.event.TableModelEvent;
11  import javax.swing.event.TableModelListener;
12  import javax.swing.table.JTableHeader;
13  import javax.swing.table.TableCellRenderer;
14  import javax.swing.table.TableColumn;
15  import javax.swing.table.TableColumnModel;
16  
17  /***
18   * This subclass of <code>JTableHeader</code> extends a
19   * <code>JTableHeader</code> with the ability to resize a column to fit all
20   * its content on double click on the right end of the column's header cell.
21   * <p>
22   * Note that you have to set the tableheader's columnmodel to the columnmodel of
23   * the table. See example for usage:
24   * 
25   * <pre>
26   * JTable table;
27   * ResizeableTableHeader tableHeader;
28   * 
29   * // create new table
30   * table = new JTable();
31   * // create new ResizableTableHeader with the table's ColumnModel
32   * tableHeader = new ResizeableTableHeader(table.getColumnModel());
33   * // set the new header for the table
34   * table.setTableHeader(tableHeader);
35   * </pre>
36   * 
37   * Or for the one line fans
38   * 
39   * <pre>
40   * // create new table
41   * JTable table = new JTable();
42   * table.setTableHeader(new ResizeableTableHeader(table.getColumnModel()));
43   * </pre>
44   * 
45   */
46  public class ResizableTableHeader extends JTableHeader implements
47          TableModelListener {
48      private static final long serialVersionUID = 1L;
49  
50      /***
51       * If true, auto resizing of columns when the TableModel invokes a
52       * tableChanged event is enabled. The default is false.
53       */
54      private boolean autoResizingEnabled;
55  
56      /***
57       * If true, resizing of columns will also take the width of header cells
58       * into account. The default is false.
59       */
60      private boolean includeHeaderWidth;
61  
62      /***
63       * Constructs a <code>ResizeableTableHeader</code> with a default
64       * <code>TableColumnModel</code>.
65       * 
66       */
67      public ResizableTableHeader() {
68          this(null);
69      }
70  
71      /***
72       * Constructs a <code>ResizeableTableHeader</code> which is initialized
73       * with <code>cm</code> as the column model. If <code>cm</code> is
74       * <code>null</code> this method will initialize the table header with a
75       * default <code>TableColumnModel</code>.
76       * 
77       * @param cm
78       *            the column model for the table
79       */
80      public ResizableTableHeader(TableColumnModel cm) {
81          super(cm);
82          autoResizingEnabled = false;
83          includeHeaderWidth = false;
84          addMouseListener(new ResizingMouseAdapter());
85      }
86  
87      /***
88       * Sets the table associated with this header. Also adds a
89       * TableModelListener to the table's TableModel to allow resizing of columns
90       * if data of table changed.
91       * 
92       * @param table
93       *            the new table
94       */
95      public void setTable(JTable table) {
96          JTable old = this.table;
97          if (table != old) {
98              if (old != null && old.getModel() != null) {
99                  old.getModel().removeTableModelListener(this);
100             }
101             if (table != null && table.getModel() != null) {
102                 table.getModel().addTableModelListener(this);
103             }
104         }
105         this.table = table;
106         firePropertyChange("table", old, table);
107     }
108 
109     /***
110      * Sets whether columns are resized on table model events.
111      * 
112      * @param autoResizingEnabled
113      *            true if columns are resized automatically
114      * @see #getAutoResizingEnabled
115      */
116     public void setAutoResizingEnabled(boolean autoResizingEnabled) {
117         boolean old = this.autoResizingEnabled;
118         this.autoResizingEnabled = autoResizingEnabled;
119         firePropertyChange("autoResizingEnabled", old, autoResizingEnabled);
120     }
121 
122     /***
123      * Returns true if auto resizing is enabled.
124      * 
125      * @return the <code>autoResizingEnabled</code> property
126      * @see #setAutoResizingEnabled
127      */
128     public boolean getAutoResizingEnabled() {
129         return autoResizingEnabled;
130     }
131 
132     /***
133      * Sets whether the header's width are included on calculation.
134      * 
135      * @param includeHeaderWidth
136      *            true if the headers are included
137      * @see #getIncludeHeaderWidth
138      */
139     public void setIncludeHeaderWidth(boolean includeHeaderWidth) {
140         boolean old = this.includeHeaderWidth;
141         this.includeHeaderWidth = includeHeaderWidth;
142         firePropertyChange("includeHeaderWidth", old, autoResizingEnabled);
143     }
144 
145     /***
146      * Returns true, if the header's width are.
147      * 
148      * @return the <code>setIncludeHeaderWidth</code> property
149      * @see #setIncludeHeaderWidth
150      */
151     public boolean getIncludeHeaderWidth() {
152         return includeHeaderWidth;
153     }
154 
155     /***
156      * Resizes the given column to fit all its content.
157      * 
158      * @param column
159      *            The <code> TableColumn</code> to resize.
160      */
161     public void resizeColumn(TableColumn column) {
162         if (column != null) {
163             adjustColumnWidth(column);
164         }
165     }
166 
167     /***
168      * Resizes all columns to fit all their content.
169      */
170     public void resizeAllColumns() {
171         for (int col = 0; col < table.getColumnCount(); col++) {
172             TableColumn column = table.getColumnModel().getColumn(col);
173             adjustColumnWidth(column);
174         }
175     }
176 
177     /***
178      * Sets preferred width, the minimum and maximum width for a given column.
179      * The minimum and maximum widths are the bounds for the preferred width. If
180      * the preferred width is not in the region of minmum and maximum, it will
181      * be adjusted. Also the user cannot resize columns out of this bounds by
182      * moving the edge or double clicking it. If a width is set to -1, no change
183      * is made to this value.
184      * 
185      * @param column
186      *            The <code>TableColumn</code> to change.
187      * @param preferredWidth
188      *            The preferred width of the column.
189      * @param minWidth
190      *            The minimum width of the column.
191      * @param maxWidth
192      *            The maximum width of the column.
193      * 
194      * @see #setAllColumnWidths(int preferredWidth, int minWidth, int maxWidth)
195      */
196     public void setColumnWidths(TableColumn column, int preferredWidth,
197             int minWidth, int maxWidth) {
198         if (column != null) {
199             if (preferredWidth != -1){
200                 column.setPreferredWidth(preferredWidth);
201             }
202             if (minWidth != -1) {
203                 column.setMinWidth(minWidth);
204             }
205             if (maxWidth != -1) {
206                 column.setMaxWidth(maxWidth);
207             }
208         }
209     }
210 
211     /***
212      * Sets preferred width, the minimum and maximum width for all columns. See
213      * setColumnWidth() for further details.
214      * 
215      * @param preferredWidth
216      *            The preferred width of the column.
217      * @param minWidth
218      *            The minimum width of the column.
219      * @param maxWidth
220      *            The maximum width of the column.
221      * 
222      * @see #setColumnWidths(javax.swing.table.TableColumn column,int
223      *      preferredWidth, int minWidth, int maxWidth)
224      */
225     public void setAllColumnWidths(int preferredWidth, int minWidth,
226             int maxWidth) {
227         for (int col = 0; col < table.getColumnCount(); col++) {
228             TableColumn column = table.getColumnModel().getColumn(col);
229             setColumnWidths(column, preferredWidth, minWidth, maxWidth);
230         }
231     }
232 
233     /***
234      * Listen for table model events from tablemodel. If new data is inserted,
235      * only determine the width of new cell and adjust column width, if
236      * necessary. If data is deleted or updated, call
237      * <code>resizeAllColumns()</code>.
238      * 
239      * @param e
240      *            The <code>TableModelEvent</code>
241      */
242     public void tableChanged(TableModelEvent e) {
243         if (getAutoResizingEnabled()) {
244             if (e.getType() == TableModelEvent.DELETE) {
245                 resizeAllColumns();
246             } else {
247                 // for performance reason only adjust width, if
248                 // one of the new cell is greater than the column width.
249                 for (int col = 0; col < table.getColumnCount(); col++) {
250                     TableColumn column = table.getColumnModel().getColumn(col);
251                     if (canResize(column)) {
252                         int width = column.getPreferredWidth();
253                         for (int row = e.getFirstRow(); row <= e.getLastRow(); row++) {
254                             TableCellRenderer renderer = table.getCellRenderer(
255                                     row, col);
256                             Component comp = renderer
257                                     .getTableCellRendererComponent(table, table
258                                             .getValueAt(row, col), false,
259                                             false, row, col);
260                             width = Math.max(width,
261                                     comp.getPreferredSize().width
262                                             + table.getColumnModel()
263                                                     .getColumnMargin());
264                         }
265                         column.setPreferredWidth(width);
266                     }
267                 }
268             }
269 
270         }
271 
272     }
273 
274     /***
275      * Adjust the width of a column to fit all cells.
276      * 
277      * @param column
278      *            The column to adjust.
279      */
280     private void adjustColumnWidth(TableColumn column) {
281 
282         int width = 0;
283         int col = table.convertColumnIndexToView(column.getModelIndex());
284 
285         // Determine width of header.
286         if (includeHeaderWidth) {
287             TableCellRenderer headerRenderer = column.getHeaderRenderer();
288             if (headerRenderer == null) {
289                 headerRenderer = table.getTableHeader().getDefaultRenderer();
290             }
291             Component headerComp = headerRenderer
292                     .getTableCellRendererComponent(table, column
293                             .getHeaderValue(), false, false, 0, col);
294             width = Math.max(width, headerComp.getPreferredSize().width);
295         }
296 
297         // Determine max width of cells.
298         Rectangle visibleRect = this.getTable().getVisibleRect();
299         int firstRow = this.getTable().rowAtPoint(
300                 new Point((int) visibleRect.getMinX(), (int) visibleRect
301                         .getMinY()));
302         int lastRow = this.getTable().rowAtPoint(
303                 new Point((int) visibleRect.getMaxX(), (int) visibleRect
304                         .getMaxY()));
305         
306         for (int row = firstRow; row <= lastRow; row++) {
307             TableCellRenderer renderer = table.getCellRenderer(row, col);
308             Component comp = renderer.getTableCellRendererComponent(table,
309                     table.getValueAt(row, col), false, false, row, col);
310             width = Math.max(width, comp.getPreferredSize().width);
311         }
312         // add columnmargins
313         width += table.getColumnModel().getColumnMargin();
314 
315         column.setPreferredWidth(width);
316     }
317 
318     /***
319      * Returns true, if resizing for the tableheader is allowed and if the
320      * column is resizable.
321      * 
322      * @param column
323      *            The column to check
324      * @return True, if the column is resizeable, otherwise false.
325      */
326     private boolean canResize(TableColumn column) {
327         return (column != null) && getResizingAllowed()
328                 && column.getResizable();
329     }
330 
331     /***
332      * Gets the the column that will be resized for a specific point. This
333      * method only return a column, if the point is within the last 3 pixels + 3
334      * pixels of next column. Otherwise it returns null.
335      * 
336      * @param p
337      *            The point to check, if we are in the resizing area.
338      * 
339      * @return The resizing column, if the point is in the resizing area,
340      *         otherwise null.
341      * 
342      * @see #getResizingColumn(java.awt.Point p,int column)
343      */
344     private TableColumn getResizingColumn(Point p) {
345         return getResizingColumn(p, columnAtPoint(p));
346     }
347 
348     /***
349      * @see #getResizingColumn(java.awt.Point p)
350      */
351     private TableColumn getResizingColumn(Point p, int column) {
352         if (column == -1) {
353             return null;
354         }
355         Rectangle r = getHeaderRect(column);
356         r.grow(-3, 0);
357         if (r.contains(p)) {
358             return null;
359         }
360         int midPoint = r.x + r.width / 2;
361         int columnIndex;
362         if (getComponentOrientation().isLeftToRight()) {
363             if (p.x < midPoint) {
364                 columnIndex = column - 1;
365             } else {
366                 columnIndex = column;
367             }
368         } else {
369             if (p.x < midPoint) {
370                 columnIndex = column;
371             } else {
372                 columnIndex = column - 1;
373             }
374         }
375         if (columnIndex == -1) {
376             return null;
377         }
378         return getColumnModel().getColumn(columnIndex);
379     }
380 
381     /***
382      * Listener that will resize a column, if a double click in a resizing area
383      * is performed.
384      */
385     private class ResizingMouseAdapter extends MouseAdapter {
386         public void mouseClicked(MouseEvent me) {
387             if (me.getClickCount() == 2 && me.getButton() == MouseEvent.BUTTON1) {
388                 TableColumn resizingColumn = getResizingColumn(me.getPoint());
389                 if (canResize(resizingColumn)) {
390                     adjustColumnWidth(resizingColumn);
391                 }
392             }
393         }
394     }
395 
396     public String getToolTipText(MouseEvent event) {
397         String tip = null;
398         Point p = event.getPoint();
399         int index = columnModel.getColumnIndexAtX(p.x);
400         Object value = columnModel.getColumn(index).getHeaderValue();
401         return value.toString();
402     }
403     
404     
405 }