1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.swing; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.awt.event.*; |
|
30 import java.util.*; |
|
31 import java.util.List; |
|
32 import javax.swing.*; |
|
33 import javax.swing.border.Border; |
|
34 import javax.swing.event.*; |
|
35 import javax.swing.plaf.TableUI; |
|
36 import javax.swing.table.*; |
|
37 |
|
38 /** |
|
39 * The {@code ExtTable} class is a {@code JTable} with the following extras: |
|
40 * |
|
41 * <h4>Striping</h4> |
|
42 * |
|
43 * The {@code ExtTable} is decorated by default with alternating colored rows: |
|
44 * 1) the table's background, and 2) {@link #setStripeColor the stripe color}. |
|
45 * To turn this behavior off, call {@link #setStripeColor setStripeColor}{@code |
|
46 * (null)}. |
|
47 * |
|
48 * <h4>Caching</h4> |
|
49 * |
|
50 * The {@code ExtTable} can optionally cache the values it finds in its |
|
51 * {@code TableModel}. This can speed performance when the model's {@code |
|
52 * getValueAt} method is time-consuming. This feature is off by default, but |
|
53 * can be enabled by {@link #setCacheSize setting the size of the cache} to a |
|
54 * non-zero number of rows. |
|
55 * |
|
56 * <h4>Specifiable visible row count</h4> |
|
57 * |
|
58 * This feature, found in the {@code JList} API, {@link #setVisibleRowCount |
|
59 * sets} the preferred number of rows to display without requiring |
|
60 * scrolling. This feature is off by default. |
|
61 * |
|
62 * <h4>Automatically-sized column widths</h4> |
|
63 * |
|
64 * Columns in an {@code ExtTable} can be easily sized to fit their content, |
|
65 * overriding the constant column widths set by {@code JTable}. This can be |
|
66 * done {@link #fitColumns on demand} or {@link #setAutoFitColumns |
|
67 * automatically} whenever the table changes. This feature is off by default. |
|
68 */ |
|
69 @SuppressWarnings({"serial"}) |
|
70 public class ExtTable extends JTable { |
|
71 // |
|
72 // Enums |
|
73 // |
|
74 |
|
75 public enum ColumnWidthPolicy { |
|
76 /** |
|
77 * Column width is the maximum preferred width of each cell and |
|
78 * header in the column. |
|
79 */ |
|
80 PREFERRED, |
|
81 |
|
82 /** |
|
83 * Columns widths are sized equally, based on the maximum value of all |
|
84 * columns' preferred widths. |
|
85 */ |
|
86 EQUAL |
|
87 } |
|
88 |
|
89 // |
|
90 // Inner classes |
|
91 // |
|
92 |
|
93 private static class CachedColumn<T> { |
|
94 // |
|
95 // Instance data |
|
96 // |
|
97 |
|
98 public int col; |
|
99 public T value; |
|
100 |
|
101 // |
|
102 // Constructors |
|
103 // |
|
104 |
|
105 public CachedColumn(int col, T value) { |
|
106 this.col = col; |
|
107 this.value = value; |
|
108 } |
|
109 } |
|
110 |
|
111 private static class CachedRow<T> { |
|
112 // |
|
113 // Instance data |
|
114 // |
|
115 |
|
116 public int row; |
|
117 public List<CachedColumn<T>> cols = new LinkedList<CachedColumn<T>>(); |
|
118 |
|
119 // |
|
120 // Constructors |
|
121 // |
|
122 |
|
123 public CachedRow(int row) { |
|
124 this.row = row; |
|
125 } |
|
126 } |
|
127 |
|
128 // |
|
129 // Static data |
|
130 // |
|
131 |
|
132 public static final Color DEFAULT_STRIPE_COLOR = |
|
133 new Color(242, 242, 242); |
|
134 // new Color(237, 243, 254); |
|
135 |
|
136 // |
|
137 // Instance data |
|
138 // |
|
139 |
|
140 private int visibleRowCount = -1; |
|
141 private Color stripeColor = DEFAULT_STRIPE_COLOR; |
|
142 |
|
143 private List<CachedRow<Object>> valueCache = |
|
144 new LinkedList<CachedRow<Object>>(); |
|
145 private int valueCacheSize = 0; |
|
146 |
|
147 private Map<TableColumn, ArrayList<Integer>> widthCache = |
|
148 new HashMap<TableColumn, ArrayList<Integer>>(); |
|
149 private ColumnWidthPolicy colWidthPolicy; |
|
150 |
|
151 private TableModelListener modelListener; |
|
152 private TableColumnModelListener columnListener; |
|
153 private RowSorterListener sorterListener; |
|
154 |
|
155 // |
|
156 // Constructors |
|
157 // |
|
158 |
|
159 public ExtTable() { |
|
160 init(); |
|
161 } |
|
162 |
|
163 public ExtTable(int numRows, int numColumns) { |
|
164 super(numRows, numColumns); |
|
165 init(); |
|
166 } |
|
167 |
|
168 public ExtTable(Object[][] rowData, Object[] columnNames) { |
|
169 super(rowData, columnNames); |
|
170 init(); |
|
171 } |
|
172 |
|
173 public ExtTable(TableModel model) { |
|
174 super(model); |
|
175 init(); |
|
176 } |
|
177 |
|
178 public ExtTable(TableModel model, TableColumnModel cModel) { |
|
179 super(model, cModel); |
|
180 init(); |
|
181 } |
|
182 |
|
183 public ExtTable(TableModel model, TableColumnModel cModel, |
|
184 ListSelectionModel sModel) { |
|
185 |
|
186 super(model, cModel, sModel); |
|
187 init(); |
|
188 } |
|
189 |
|
190 public ExtTable(Vector rowData, Vector columnNames) { |
|
191 super(rowData, columnNames); |
|
192 init(); |
|
193 } |
|
194 |
|
195 // |
|
196 // JComponent methods |
|
197 // |
|
198 |
|
199 @Override |
|
200 protected void paintComponent(Graphics g) { |
|
201 Rectangle clip = g.getClipBounds(); |
|
202 int rHeight = getRowHeight(); |
|
203 int fRow = clip.y / rHeight; |
|
204 int lRow = (clip.y + clip.height) / rHeight + 1; |
|
205 |
|
206 if (isOpaque()) { |
|
207 g.setColor(getBackground()); |
|
208 g.fillRect(clip.x, clip.y, clip.width, clip.height); |
|
209 } |
|
210 |
|
211 if (stripeColor != null) { |
|
212 g.setColor(stripeColor); |
|
213 if ((fRow & 1) == 1) { |
|
214 fRow++; |
|
215 } |
|
216 |
|
217 for (int row = fRow; row < lRow; row += 2) { |
|
218 g.fillRect(clip.x, rHeight * row, clip.width, rHeight); |
|
219 } |
|
220 } |
|
221 |
|
222 if (getShowHorizontalLines()) { |
|
223 g.setColor(getGridColor()); |
|
224 for (int row = fRow; row < lRow; row++) { |
|
225 int y = rHeight * row - 1; |
|
226 g.drawLine(clip.x, y, clip.width - 1, y); |
|
227 } |
|
228 } |
|
229 |
|
230 if (getShowVerticalLines()) { |
|
231 g.setColor(getGridColor()); |
|
232 |
|
233 TableColumnModel model = getColumnModel(); |
|
234 int x = -1; |
|
235 for (int col = 0, n = model.getColumnCount(); col < n; col++) { |
|
236 x += model.getColumn(col).getWidth(); |
|
237 |
|
238 if (x >= clip.x) { |
|
239 if (x > clip.x + clip.width) { |
|
240 break; |
|
241 } |
|
242 g.drawLine(x, clip.y, x, clip.y + clip.height - 1); |
|
243 } |
|
244 } |
|
245 } |
|
246 |
|
247 getUI().paint(g, this); |
|
248 } |
|
249 |
|
250 // |
|
251 // JTable methods |
|
252 // |
|
253 |
|
254 @Override |
|
255 public Dimension getPreferredScrollableViewportSize() { |
|
256 if (visibleRowCount < 0) { |
|
257 return super.getPreferredScrollableViewportSize(); |
|
258 } |
|
259 |
|
260 Insets insets = getInsets(); |
|
261 Dimension d = getPreferredSize(); |
|
262 d.height = visibleRowCount * getRowHeight() + insets.top + |
|
263 insets.bottom; |
|
264 |
|
265 return d; |
|
266 } |
|
267 |
|
268 @Override |
|
269 public Object getValueAt(int vRow, int vCol) { |
|
270 if (valueCacheSize > 0) { |
|
271 CachedRow<Object> row = null; |
|
272 ListIterator<CachedRow<Object>> rowIter = valueCache.listIterator(); |
|
273 |
|
274 while (rowIter.hasNext()) { |
|
275 CachedRow<Object> r = rowIter.next(); |
|
276 |
|
277 if (r.row == vRow) { |
|
278 row = r; |
|
279 break; |
|
280 } |
|
281 |
|
282 if (r.row > vRow) { |
|
283 rowIter.previous(); |
|
284 break; |
|
285 } |
|
286 } |
|
287 |
|
288 if (row == null) { |
|
289 row = new CachedRow<Object>(vRow); |
|
290 |
|
291 rowIter.add(row); |
|
292 |
|
293 if (valueCache.size() > valueCacheSize) { |
|
294 // Trim cached row farthest from the new one |
|
295 int index = rowIter.previousIndex(); |
|
296 int lastIndex = valueCache.size() - 1; |
|
297 valueCache.remove(lastIndex - index > index - 0 ? |
|
298 lastIndex : 0); |
|
299 } |
|
300 } |
|
301 |
|
302 CachedColumn<Object> col = null; |
|
303 ListIterator<CachedColumn<Object>> colIter = |
|
304 row.cols.listIterator(); |
|
305 |
|
306 while (colIter.hasNext()) { |
|
307 CachedColumn<Object> c = colIter.next(); |
|
308 |
|
309 if (c.col == vCol) { |
|
310 col = c; |
|
311 break; |
|
312 } |
|
313 |
|
314 if (c.col > vCol) { |
|
315 colIter.previous(); |
|
316 break; |
|
317 } |
|
318 } |
|
319 |
|
320 if (col == null) { |
|
321 Object value = super.getValueAt(vRow, vCol); |
|
322 col = new CachedColumn<Object>(vCol, value); |
|
323 colIter.add(col); |
|
324 } |
|
325 |
|
326 return col.value; |
|
327 } |
|
328 |
|
329 return super.getValueAt(vRow, vCol); |
|
330 } |
|
331 |
|
332 @Override |
|
333 public Component prepareRenderer(TableCellRenderer renderer, int row, |
|
334 int col) { |
|
335 |
|
336 Component comp = super.prepareRenderer(renderer, row, col); |
|
337 if (comp instanceof JComponent) { |
|
338 ((JComponent)comp).setOpaque(isCellSelected(row, col)); |
|
339 } |
|
340 |
|
341 return comp; |
|
342 } |
|
343 |
|
344 @Override |
|
345 public void setColumnModel(TableColumnModel columnModel) { |
|
346 TableColumnModel old = super.getColumnModel(); |
|
347 if (old != columnModel) { |
|
348 if (columnListener == null) { |
|
349 // Create this here since object members haven't yet been |
|
350 // initialized if this method is being called from a superclass |
|
351 // constructor |
|
352 columnListener = new TableColumnModelListener() { |
|
353 @Override |
|
354 public void columnAdded(TableColumnModelEvent e) { |
|
355 fitColumnsIfRequested(); |
|
356 } |
|
357 |
|
358 @Override |
|
359 public void columnMarginChanged(ChangeEvent e) { |
|
360 } |
|
361 |
|
362 @Override |
|
363 public void columnMoved(TableColumnModelEvent e) { |
|
364 } |
|
365 |
|
366 @Override |
|
367 public void columnRemoved(TableColumnModelEvent e) { |
|
368 deleteUnusedColumnsFromWidthCache(); |
|
369 } |
|
370 |
|
371 @Override |
|
372 public void columnSelectionChanged(ListSelectionEvent e) { |
|
373 } |
|
374 }; |
|
375 } |
|
376 |
|
377 if (old != null) { |
|
378 old.removeColumnModelListener(columnListener); |
|
379 } |
|
380 |
|
381 if (columnModel != null) { |
|
382 columnModel.addColumnModelListener(columnListener); |
|
383 } |
|
384 |
|
385 super.setColumnModel(columnModel); |
|
386 |
|
387 clearWidthCache(); |
|
388 fitColumnsIfRequested(); |
|
389 } |
|
390 } |
|
391 |
|
392 @Override |
|
393 public void setTableHeader(JTableHeader header) { |
|
394 JTableHeader old = super.getTableHeader(); |
|
395 if (old != header) { |
|
396 super.setTableHeader(header); |
|
397 |
|
398 clearWidthCache(); |
|
399 fitColumnsIfRequested(); |
|
400 } |
|
401 } |
|
402 |
|
403 @Override |
|
404 public void setModel(TableModel model) { |
|
405 TableModel old = super.getModel(); |
|
406 if (old != model) { |
|
407 if (modelListener == null) { |
|
408 // Create this here since object members haven't yet been |
|
409 // initialized if this method is being called from a superclass |
|
410 // constructor |
|
411 modelListener = new TableModelListener() { |
|
412 @Override |
|
413 public void tableChanged(TableModelEvent e) { |
|
414 valueCache.clear(); |
|
415 updateWidthCache(e); |
|
416 } |
|
417 }; |
|
418 } |
|
419 |
|
420 if (old != null) { |
|
421 old.removeTableModelListener(modelListener); |
|
422 } |
|
423 |
|
424 if (model != null) { |
|
425 model.addTableModelListener(modelListener); |
|
426 } |
|
427 |
|
428 super.setModel(model); |
|
429 |
|
430 clearWidthCache(); |
|
431 fitColumnsIfRequested(); |
|
432 } |
|
433 } |
|
434 |
|
435 @Override |
|
436 public void setRowSorter(RowSorter<? extends TableModel> sorter) { |
|
437 RowSorter<? extends TableModel> oldSorter = getRowSorter(); |
|
438 if (sorter != oldSorter) { |
|
439 if (sorterListener == null) { |
|
440 // Create this here since object members haven't yet been |
|
441 // initialized if this method is being called from a superclass |
|
442 // constructor |
|
443 sorterListener = new RowSorterListener() { |
|
444 @Override |
|
445 public void sorterChanged(RowSorterEvent e) { |
|
446 valueCache.clear(); |
|
447 |
|
448 switch (e.getType()) { |
|
449 case SORT_ORDER_CHANGED: |
|
450 break; |
|
451 |
|
452 default: |
|
453 case SORTED: |
|
454 fitColumnsIfRequested(); |
|
455 } |
|
456 } |
|
457 }; |
|
458 |
|
459 if (oldSorter != null) { |
|
460 oldSorter.removeRowSorterListener(sorterListener); |
|
461 } |
|
462 |
|
463 if (sorter != null) { |
|
464 sorter.addRowSorterListener(sorterListener); |
|
465 } |
|
466 } |
|
467 |
|
468 super.setRowSorter(sorter); |
|
469 fitColumnsIfRequested(); |
|
470 } |
|
471 } |
|
472 |
|
473 // |
|
474 // ExtTable methods |
|
475 // |
|
476 |
|
477 /** |
|
478 * Calculates and sets the preferred widths for each {@code TableColumn} in |
|
479 * the {@code TableColumnModel}. |
|
480 * <p/> |
|
481 * This operation has a performance cost that increases with the number of |
|
482 * rows or columns in the table, since each cell must be examined to |
|
483 * determine its column's preferred width. However, this should be a |
|
484 * one-time cost, since these values can be cached for subsequent or {@link |
|
485 * #setAutoFitColumns automatic} calls to this routine. |
|
486 * |
|
487 * @param colWidthPolicy |
|
488 * how to automatically fit columns |
|
489 * |
|
490 * @param useCache |
|
491 * {@code true} to cache and reuse the values, {@code false} |
|
492 * if this is a one-time operation |
|
493 * |
|
494 * @see #setAutoFitColumns |
|
495 */ |
|
496 public void fitColumns(ColumnWidthPolicy colWidthPolicy, boolean useCache) { |
|
497 if (colWidthPolicy == null) { |
|
498 return; |
|
499 } |
|
500 |
|
501 synchronized (widthCache) { |
|
502 TableModel model = getModel(); |
|
503 TableColumnModel cModel = getColumnModel(); |
|
504 int cMargin = getColumnModel().getColumnMargin(); |
|
505 int nvRows = getRowCount(); |
|
506 int maxColWidth = 0; |
|
507 int n = getColumnCount(); |
|
508 |
|
509 for (int vCol = 0; vCol < n; vCol++) { |
|
510 int mCol = convertColumnIndexToModel(vCol); |
|
511 TableColumn column = cModel.getColumn(vCol); |
|
512 |
|
513 TableCellRenderer renderer = column.getCellRenderer(); |
|
514 if (renderer == null) { |
|
515 Class<?> cClass = model.getColumnClass(mCol); |
|
516 renderer = getDefaultRenderer(cClass); |
|
517 } |
|
518 |
|
519 ArrayList<Integer> colWidthCache = null; |
|
520 |
|
521 if (useCache) { |
|
522 colWidthCache = widthCache.get(column); |
|
523 if (colWidthCache == null) { |
|
524 colWidthCache = new ArrayList<Integer>(0); |
|
525 colWidthCache.ensureCapacity(nvRows); |
|
526 for (int i = 0; i < nvRows; i++) { |
|
527 colWidthCache.add(-1); |
|
528 } |
|
529 widthCache.put(column, colWidthCache); |
|
530 } |
|
531 } |
|
532 |
|
533 int width = 0; |
|
534 |
|
535 for (int vRow = 0; vRow < nvRows; vRow++) { |
|
536 int mRow = convertRowIndexToModel(vRow); |
|
537 int cellWidth = 0; |
|
538 try { |
|
539 cellWidth = colWidthCache == null ? |
|
540 -1 : colWidthCache.get(mRow); |
|
541 } catch (IndexOutOfBoundsException ignore) { |
|
542 // This method has probably been called (perhaps by the |
|
543 // RowSorter) before insertWidthCache could react to a |
|
544 // tableChange in the TableModel. That same change will |
|
545 // ultimately make its way to our own modelListener, and |
|
546 // this method will be called again (after |
|
547 // insertWidthCache has been run) so we can abort now. |
|
548 return; |
|
549 } |
|
550 |
|
551 if (cellWidth == -1) { |
|
552 Object value = getValueAt(vRow, vCol); |
|
553 |
|
554 Component comp = renderer.getTableCellRendererComponent( |
|
555 this, value, true, true, vRow, vCol); |
|
556 |
|
557 cellWidth = comp.getPreferredSize().width; |
|
558 |
|
559 if (colWidthCache != null) { |
|
560 colWidthCache.set(mRow, cellWidth); |
|
561 } |
|
562 } |
|
563 |
|
564 width = Math.max(width, cellWidth); |
|
565 } |
|
566 |
|
567 // Account for header |
|
568 renderer = column.getHeaderRenderer(); |
|
569 if (renderer == null) { |
|
570 JTableHeader header = getTableHeader(); |
|
571 if (header != null) { |
|
572 renderer = header.getDefaultRenderer(); |
|
573 } |
|
574 } |
|
575 |
|
576 if (renderer != null) { |
|
577 Object value = column.getHeaderValue(); |
|
578 |
|
579 Component comp = renderer.getTableCellRendererComponent( |
|
580 this, value, true, true, -1, vCol); |
|
581 |
|
582 int cellWidth = comp.getPreferredSize().width; |
|
583 width = Math.max(width, cellWidth); |
|
584 } |
|
585 |
|
586 width += cMargin; |
|
587 |
|
588 maxColWidth = Math.max(maxColWidth, width); |
|
589 setPreferredWidth(column, width); |
|
590 } |
|
591 |
|
592 if (colWidthPolicy == ColumnWidthPolicy.EQUAL) { |
|
593 for (int vCol = 0; vCol < n; vCol++) { |
|
594 int mCol = convertColumnIndexToModel(vCol); |
|
595 TableColumn column = cModel.getColumn(vCol); |
|
596 setPreferredWidth(column, maxColWidth); |
|
597 } |
|
598 } |
|
599 } |
|
600 } |
|
601 |
|
602 /** |
|
603 * Indicates whether {@link #fitColumns} is called automatically whenever |
|
604 * the table is changed in a significant way. |
|
605 * |
|
606 * @return how to automatically fit columns, or {@code null} to not |
|
607 * automatically call {@link #fitColumns} |
|
608 */ |
|
609 public ColumnWidthPolicy getAutoFitColumns() { |
|
610 return colWidthPolicy; |
|
611 } |
|
612 |
|
613 /** |
|
614 * Gets the number of rows in the cache of cell values. A higher value may |
|
615 * speed up rendering of the table if the table model must do expensive |
|
616 * calculations to determine the value in each cell. |
|
617 * <p> |
|
618 * The default value is 0, effectively disabling the cache. |
|
619 */ |
|
620 public int getCacheSize() { |
|
621 return valueCacheSize; |
|
622 } |
|
623 |
|
624 public Color getStripeColor() { |
|
625 return stripeColor; |
|
626 } |
|
627 |
|
628 /** |
|
629 * Gets the number of rows used to calculate the preferred size of an |
|
630 * enclosing viewport. |
|
631 * |
|
632 * @return the number of rows to show in an enclosing viewport, or -1 |
|
633 * to rely on the default calculation for preferred viewport |
|
634 * size |
|
635 */ |
|
636 public int getVisibleRowCount() { |
|
637 return visibleRowCount; |
|
638 } |
|
639 |
|
640 /** |
|
641 * Indicates whether this table has a non-{@code null} {@link |
|
642 * #getStripeColor stripe color}. |
|
643 */ |
|
644 public boolean isStriped() { |
|
645 return getStripeColor() != null; |
|
646 } |
|
647 |
|
648 /** |
|
649 * Sets whether {@link #fitColumns} is called automatically whenever the |
|
650 * table is changed in a significant way. |
|
651 * |
|
652 * @param colWidthPolicy |
|
653 * how to automatically fit columns, or {@code null} to not |
|
654 * automatically call {@link #fitColumns} |
|
655 */ |
|
656 public void setAutoFitColumns(ColumnWidthPolicy colWidthPolicy) { |
|
657 if (this.colWidthPolicy != colWidthPolicy) { |
|
658 this.colWidthPolicy = colWidthPolicy; |
|
659 fitColumnsIfRequested(); |
|
660 } |
|
661 } |
|
662 |
|
663 /** |
|
664 * Sets the number of rows in the cache of cell values. See {@link |
|
665 * #getCacheSize}. |
|
666 */ |
|
667 public void setCacheSize(int valueCacheSize) { |
|
668 if (this.valueCacheSize != valueCacheSize) { |
|
669 if (this.valueCacheSize > valueCacheSize) { |
|
670 try { |
|
671 // Trim if necessary |
|
672 ListIterator<CachedRow<Object>> rowIter = |
|
673 valueCache.listIterator(valueCacheSize); |
|
674 |
|
675 while (rowIter.hasNext()) { |
|
676 rowIter.next(); |
|
677 rowIter.remove(); |
|
678 } |
|
679 } catch (IndexOutOfBoundsException ignore) { |
|
680 } |
|
681 } |
|
682 |
|
683 this.valueCacheSize = valueCacheSize; |
|
684 } |
|
685 } |
|
686 |
|
687 public void setStripeColor(Color stripeColor) { |
|
688 if (this.stripeColor != stripeColor) { |
|
689 this.stripeColor = stripeColor; |
|
690 repaint(); |
|
691 } |
|
692 } |
|
693 |
|
694 /** |
|
695 * Sets the number of rows used to calculate the preferred size of an |
|
696 * enclosing viewport. |
|
697 * |
|
698 * @param visibleRowCount |
|
699 * the number of rows to show in an enclosing viewport, or -1 |
|
700 * to rely on the default calculation for preferred viewport |
|
701 * size |
|
702 */ |
|
703 public void setVisibleRowCount(int visibleRowCount) { |
|
704 this.visibleRowCount = visibleRowCount; |
|
705 } |
|
706 |
|
707 // |
|
708 // Private methods |
|
709 // |
|
710 |
|
711 private void init() { |
|
712 setTableHeader(new ExtTableHeader(getColumnModel())); |
|
713 setFillsViewportHeight(true); |
|
714 |
|
715 addMouseListener( |
|
716 new MouseAdapter() { |
|
717 @Override |
|
718 public void mousePressed(MouseEvent e) { |
|
719 int vRow = rowAtPoint(e.getPoint()); |
|
720 if (vRow == -1) { |
|
721 clearSelection(); |
|
722 } |
|
723 } |
|
724 }); |
|
725 |
|
726 setShowHorizontalLines(false); |
|
727 setShowVerticalLines(true); |
|
728 |
|
729 // Accommodate a 16-pixel icon + a 1-pixel focus border |
|
730 setRowHeight(18); |
|
731 } |
|
732 |
|
733 private void clearWidthCache() { |
|
734 if (widthCache != null) { |
|
735 synchronized (widthCache) { |
|
736 widthCache.clear(); |
|
737 } |
|
738 } |
|
739 } |
|
740 |
|
741 private void deleteUnusedColumnsFromWidthCache() { |
|
742 TableColumnModel cModel = getColumnModel(); |
|
743 synchronized (widthCache) { |
|
744 CACHED: for (Iterator<TableColumn> i = |
|
745 widthCache.keySet().iterator(); i.hasNext();) { |
|
746 |
|
747 TableColumn column = i.next(); |
|
748 for (int j = 0, n = cModel.getColumnCount(); j < n; j++) { |
|
749 if (cModel.getColumn(j) == column) { |
|
750 continue CACHED; |
|
751 } |
|
752 } |
|
753 i.remove(); |
|
754 } |
|
755 } |
|
756 } |
|
757 |
|
758 private void deleteWidthCache(int fmRow, int lmRow) { |
|
759 synchronized (widthCache) { |
|
760 for (TableColumn column : widthCache.keySet()) { |
|
761 ArrayList<Integer> colWidthCache = widthCache.get(column); |
|
762 for (int mRow = lmRow; mRow >= fmRow; mRow--) { |
|
763 colWidthCache.remove(mRow); |
|
764 } |
|
765 } |
|
766 } |
|
767 } |
|
768 |
|
769 private void fitColumnsIfRequested() { |
|
770 if (colWidthPolicy != null) { |
|
771 fitColumns(colWidthPolicy, true); |
|
772 } |
|
773 } |
|
774 |
|
775 private void insertWidthCache(int fmRow, int lmRow) { |
|
776 synchronized (widthCache) { |
|
777 for (TableColumn column : widthCache.keySet()) { |
|
778 ArrayList<Integer> colWidthCache = widthCache.get(column); |
|
779 for (int mRow = fmRow; mRow <= lmRow; mRow++) { |
|
780 colWidthCache.add(mRow, -1); |
|
781 } |
|
782 } |
|
783 } |
|
784 } |
|
785 |
|
786 private void resetWidthCache(int fmRow, int lmRow, int fmCol, int lmCol) { |
|
787 synchronized (widthCache) { |
|
788 TableColumnModel cModel = getColumnModel(); |
|
789 |
|
790 for (int mCol = fmCol; mCol <= lmCol; mCol++) { |
|
791 int vCol = convertColumnIndexToView(mCol); |
|
792 TableColumn column = cModel.getColumn(vCol); |
|
793 |
|
794 ArrayList<Integer> colWidthCache = widthCache.get(column); |
|
795 if (colWidthCache != null) { |
|
796 for (int mRow = fmRow; mRow <= lmRow; mRow++) { |
|
797 try { |
|
798 colWidthCache.set(mRow, -1); |
|
799 } catch (IndexOutOfBoundsException ignore) { |
|
800 } |
|
801 } |
|
802 } |
|
803 } |
|
804 } |
|
805 } |
|
806 |
|
807 private void setPreferredWidth(TableColumn column, int width) { |
|
808 column.setPreferredWidth(width); |
|
809 column.setMinWidth(Math.min(column.getMinWidth(), width)); |
|
810 } |
|
811 |
|
812 private void updateWidthCache(TableModelEvent e) { |
|
813 synchronized (widthCache) { |
|
814 int fmRow = e.getFirstRow(); |
|
815 int lmRow = e.getLastRow(); |
|
816 |
|
817 int nRows = getModel().getRowCount(); |
|
818 if (lmRow >= nRows) { |
|
819 lmRow = nRows - 1; |
|
820 } |
|
821 |
|
822 switch (e.getType()) { |
|
823 case TableModelEvent.UPDATE: |
|
824 int fmCol = e.getColumn(); |
|
825 int lmCol = fmCol; |
|
826 |
|
827 if (fmCol == TableModelEvent.ALL_COLUMNS) { |
|
828 fmCol = 0; |
|
829 lmCol = getColumnModel().getColumnCount() - 1; |
|
830 } |
|
831 |
|
832 resetWidthCache(fmRow, lmRow, fmCol, lmCol); |
|
833 break; |
|
834 |
|
835 |
|
836 case TableModelEvent.DELETE: |
|
837 deleteWidthCache(fmRow, lmRow); |
|
838 break; |
|
839 |
|
840 case TableModelEvent.INSERT: |
|
841 insertWidthCache(fmRow, lmRow); |
|
842 break; |
|
843 } |
|
844 |
|
845 fitColumnsIfRequested(); |
|
846 } |
|
847 } |
|
848 |
|
849 // |
|
850 // Static methods |
|
851 // |
|
852 |
|
853 // XXX Unit test -- remove |
|
854 public static void main(String[] args) { |
|
855 JFrame f = new JFrame(); |
|
856 |
|
857 JPanel c = (JPanel)f.getContentPane(); |
|
858 c.setLayout(new BorderLayout()); |
|
859 c.setBorder(GUIUtil.getEmptyBorder()); |
|
860 |
|
861 Object[][] data = { |
|
862 new Object[] {"0 zero", "0 zero", "blah"}, |
|
863 new Object[] {"1 one", "1 one", "blah"}, |
|
864 new Object[] {"2 two", "2 two", "blah"}, |
|
865 new Object[] {"3 three", "3 three", "blah"}, |
|
866 new Object[] {"4 four", "4 four", "blah"}, |
|
867 new Object[] {"5 five", "5 five", "blah"}, |
|
868 }; |
|
869 |
|
870 final DefaultTableModel model = new DefaultTableModel( |
|
871 data, new String[] {"Column 0", "Column 1", "Column 2"}); |
|
872 |
|
873 final ExtTable t = new ExtTable(model); |
|
874 t.setAutoFitColumns(ColumnWidthPolicy.PREFERRED); |
|
875 // t.setOpaque(false); |
|
876 // t.setBackground(Color.green); |
|
877 // t.setStripeColor(null); |
|
878 // t.setShowGrid(true); |
|
879 |
|
880 t.addMouseListener( |
|
881 new MouseAdapter() { |
|
882 @Override |
|
883 public void mouseClicked(MouseEvent e) { |
|
884 if (e.getButton() == 3) { |
|
885 Point p = e.getPoint(); |
|
886 int vCol = t.columnAtPoint(p); |
|
887 int mCol = t.convertColumnIndexToModel(vCol); |
|
888 |
|
889 Object[] row = {"new", "new", "new"}; |
|
890 row[mCol] = "loooooooooooooooooooooooooong"; |
|
891 model.addRow(row); |
|
892 } else |
|
893 |
|
894 if (e.getButton() == 2) { |
|
895 Point p = e.getPoint(); |
|
896 int vRow = t.rowAtPoint(p); |
|
897 int mRow = t.convertRowIndexToModel(vRow); |
|
898 model.removeRow(mRow); |
|
899 } |
|
900 } |
|
901 }); |
|
902 |
|
903 JScrollPane scroll = new ExtScrollPane(t); |
|
904 c.add(scroll, BorderLayout.CENTER); |
|
905 |
|
906 JButton b = new JButton("Remove column"); |
|
907 b.addActionListener( |
|
908 new ActionListener() { |
|
909 @Override |
|
910 public void actionPerformed(ActionEvent e) { |
|
911 TableColumn column = t.getColumnModel().getColumn(1); |
|
912 System.out.printf("Removing column: %s\n", |
|
913 column.getIdentifier()); |
|
914 t.removeColumn(column); |
|
915 } |
|
916 }); |
|
917 c.add(b, BorderLayout.SOUTH); |
|
918 |
|
919 f.pack(); |
|
920 f.setVisible(true); |
|
921 } |
|
922 } |
|