1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.commons.gui.table;
21
22 import java.awt.Color;
23 import java.awt.Component;
24 import java.awt.Graphics;
25 import java.awt.event.InputEvent;
26 import java.awt.event.MouseEvent;
27 import java.awt.event.MouseListener;
28 import java.awt.event.MouseMotionListener;
29 import java.beans.PropertyChangeEvent;
30 import java.beans.PropertyChangeListener;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.Hashtable;
34 import java.util.Iterator;
35 import java.util.List;
36 import javax.swing.Icon;
37 import javax.swing.JLabel;
38 import javax.swing.JPopupMenu;
39 import javax.swing.JTable;
40 import javax.swing.JTree;
41 import javax.swing.SwingUtilities;
42 import javax.swing.UIManager;
43 import javax.swing.event.ChangeEvent;
44 import javax.swing.event.ListSelectionEvent;
45 import javax.swing.event.TableColumnModelEvent;
46 import javax.swing.event.TableColumnModelListener;
47 import javax.swing.table.JTableHeader;
48 import javax.swing.table.TableCellRenderer;
49 import javax.swing.table.TableColumn;
50 import javax.swing.table.TableColumnModel;
51 import javax.swing.tree.TreePath;
52 import org.xnap.commons.gui.table.SortableModel.Order;
53 import org.xnap.commons.gui.util.GUIHelper;
54
55 /***
56 * TODO update documentation
57 * TODO squig don't make TableSorter a requirement, maybe have a general
58 * table layout and a more specific SortableTableLayout subclass?
59 * TableHeaderHandler class handles the mouse events invoked by clicking on the
60 * header portion of the JTable.
61 * TODO setter for maintain sort order
62 * TODO call stopCellEditing() whenever neccessary
63 */
64 public class TableLayout {
65
66 private List<TableColumn> columns;
67 private EventHandler eventHandler;
68 private Enumeration expandedLeafs;
69 private JTableHeader header;
70 private JPopupMenu headerPopupMenu;
71 private List<TableLayoutListener> listeners = new ArrayList<TableLayoutListener>();
72 private SortButtonRenderer renderer;
73 private TreePath[] selectedPaths;
74 private TableSorter sorter;
75 /***
76 * Set to true, when the user has pressed a button but not yet dragged it.
77 */
78 private boolean sorting = false;
79 private JTable table;
80 private JTree tree;
81
82 public TableLayout(JTable table) {
83 this(table, (TableSorter)table.getModel());
84 }
85
86 /***
87 *
88 */
89 public TableLayout(JTable table, TableSorter sorter)
90 {
91 this.table = table;
92 this.header = table.getTableHeader();
93 this.sorter = sorter;
94 this.eventHandler = new EventHandler();
95
96 TableColumnModel columnModel = table.getColumnModel();
97 columns = new ArrayList<TableColumn>(columnModel.getColumnCount());
98 for (int i = 0; i < columnModel.getColumnCount(); i++) {
99 addColumn(columnModel.getColumn(i));
100 }
101 columnModel.addColumnModelListener(eventHandler);
102 headerPopupMenu = new TableHeaderMenu(null, this).getPopupMenu();
103 renderer = new SortButtonRenderer(header.getDefaultRenderer());
104 header.setDefaultRenderer(renderer);
105 header.setReorderingAllowed(true);
106 header.setResizingAllowed(true);
107 header.addMouseListener(eventHandler);
108 header.addMouseMotionListener(eventHandler);
109 }
110
111 private void addColumn(TableColumn column) {
112 column.addPropertyChangeListener(eventHandler);
113 columns.add(column);
114 }
115
116 public void addTableLayoutListener(TableLayoutListener l) {
117 listeners.add(l);
118 }
119
120
121 private void fireColumnNameChanged(int index, String newName)
122 {
123 TableLayoutListener[] array = listeners.toArray(new TableLayoutListener[0]);
124 for (TableLayoutListener listener : array) {
125 listener.columnNameChanged(index, newName);
126 }
127 }
128
129 private void fireColumnOrderChanged()
130 {
131 TableLayoutListener[] array = listeners.toArray(new TableLayoutListener[0]);
132 for (TableLayoutListener listener : array) {
133 listener.columnOrderChanged();
134 }
135 }
136
137 private void fireColumnVisibilityChanged(int index, boolean visible)
138 {
139 TableLayoutListener[] array = listeners.toArray(new TableLayoutListener[0]);
140 for (TableLayoutListener listener : array) {
141 listener.columnVisibilityChanged(index, visible);
142 }
143 }
144
145 private void fireColumnWidthsChanged()
146 {
147 TableLayoutListener[] array = listeners.toArray(new TableLayoutListener[0]);
148 for (TableLayoutListener listener : array) {
149 listener.columnLayoutChanged();
150 }
151 }
152
153 private void fireSortedColumnChanged(int col)
154 {
155 TableLayoutListener[] array = listeners.toArray(new TableLayoutListener[0]);
156 for (TableLayoutListener listener : array) {
157 listener.sortedColumnChanged();
158 }
159 }
160
161 public TableColumn getColumnAt(int index)
162 {
163 return columns.get(index);
164 }
165
166 public int getColumnCount()
167 {
168 return columns.size();
169 }
170
171 public int getColumnIndex(String identifier)
172 {
173 for (int i = 0; i < columns.size(); i++) {
174 if (getColumnAt(i).getIdentifier().equals(identifier)) {
175 return i;
176 }
177 }
178 return -1;
179 }
180
181 public Iterator<TableColumn> getColumns()
182 {
183 return columns.iterator();
184 }
185
186 public boolean getMaintainSortOrder()
187 {
188 return sorter.getMaintainSortOrder();
189 }
190
191 public int getSortedColumn()
192 {
193 return sorter.getSortedColumn();
194 }
195
196 public JTable getTable()
197 {
198 return table;
199 }
200
201 public JPopupMenu getHeaderPopupMenu()
202 {
203 return headerPopupMenu;
204 }
205
206 public int getVisibleColumnsCount()
207 {
208 return table.getColumnModel().getColumnCount();
209 }
210
211 public boolean isColumnVisible(int index)
212 {
213 TableColumn column = getColumnAt(index);
214 TableColumnModel columnModel = table.getColumnModel();
215 for (int i = 0; i < columnModel.getColumnCount(); i++) {
216 if (columnModel.getColumn(i) == column) {
217 return true;
218 }
219 }
220 return false;
221 }
222
223 public Order getSortOrder()
224 {
225 return sorter.getSortOrder();
226 }
227
228 public void removeTableLayoutListener(TableLayoutListener l) {
229 listeners.remove(l);
230 }
231
232 public void restoreSelections()
233 {
234 if (tree != null) {
235 if (expandedLeafs != null) {
236 while (expandedLeafs.hasMoreElements()) {
237 TreePath path = (TreePath)expandedLeafs.nextElement();
238 tree.expandPath(path);
239 }
240 }
241 if (selectedPaths != null) {
242 tree.addSelectionPaths(selectedPaths);
243 }
244 }
245 }
246
247 public void setAllColumnsVisible(boolean visible)
248 {
249 for (int i = 0; i < getColumnCount(); i++) {
250 setColumnVisible(i, visible);
251 }
252 }
253
254 /***
255 * Sets the auto resize mode of the table depending on a modifier key.
256 */
257 private void setAutoResizeMode(MouseEvent e)
258 {
259 if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
260 table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
261 }
262 else if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) {
263 table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
264 }
265 else {
266 table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
267 }
268 }
269
270 public void setColumnNames(String[] columnNames)
271 {
272 if (columns.size() != columnNames.length) {
273 throw new IllegalArgumentException("Number of columns and length of columnNames must match");
274 }
275
276 for (int i = 0; i < columnNames.length; i++) {
277 columns.get(i).setHeaderValue(columnNames[i]);
278 }
279 }
280
281 public void setColumnName(int index, String name)
282 {
283 TableColumn column = columns.get(index);
284 column.setHeaderValue(name);
285 }
286
287 public void setColumnProperties(int index, String key)
288 {
289 TableColumn column = columns.get(index);
290 column.setIdentifier(key);
291 }
292
293 public void setColumnProperties(int index, String key, int width)
294 {
295 TableColumn column = columns.get(index);
296 column.setIdentifier(key);
297 column.setPreferredWidth(width);
298 column.setWidth(width);
299 }
300
301 public void setColumnVisible(int index, boolean visible)
302 {
303 boolean currentlyVisible = isColumnVisible(index);
304 if (currentlyVisible == visible) {
305 return;
306 }
307
308 if (visible) {
309
310 table.getColumnModel().addColumn(getColumnAt(index));
311 } else {
312 table.getColumnModel().removeColumn(getColumnAt(index));
313 }
314 fireColumnVisibilityChanged(index, visible);
315 }
316
317 public void setColumnsVisible(String[] columns)
318 {
319 setAllColumnsVisible(false);
320 for (int i = 0; i < columns.length; i++) {
321 int index = getColumnIndex(columns[i]);
322 if (index != -1) {
323 setColumnVisible(index, true);
324 }
325 }
326 }
327
328 public void setMaintainSortOrder(boolean maintainSortOrder)
329 {
330 sorter.setMaintainSortOrder(maintainSortOrder);
331 }
332
333 public void setTree(JTree jt)
334 {
335 this.tree = jt;
336 }
337
338 /***
339 * Displays the popup menu.
340 */
341 public void showPopupMenu(MouseEvent e)
342 {
343 if (headerPopupMenu != null) {
344 header.setDraggedColumn(null);
345 header.setResizingColumn(null);
346 GUIHelper.showPopupMenu(headerPopupMenu, e.getComponent(),
347 e.getX(), e.getY());
348 }
349 }
350
351 public void sortByColumn(int modelIndex, SortableModel.Order sortOrder, boolean revert) {
352 sortByColumn(table.convertColumnIndexToView(modelIndex), modelIndex, sortOrder, revert);
353 }
354
355 private void sortByColumn(int column, int modelIndex, SortableModel.Order sortOrder, boolean revert) {
356 stopCellEditing();
357 storeSelections();
358 sortOrder = sorter.sortByColumn(modelIndex, sortOrder, revert);
359 renderer.setSortedColumn(column, sortOrder);
360 restoreSelections();
361 }
362
363 /***
364 *
365 */
366 private void sortByColumnInternal(int column)
367 {
368 int modelIndex = table.convertColumnIndexToModel(column);
369 SortableModel.Order sortOrder;
370 if (sorter.getSortedColumn() == modelIndex) {
371
372 sortOrder = sorter.getSortOrder();
373 }
374 else if (sorter.getColumnClass(modelIndex) == String.class) {
375
376 sortOrder = SortableModel.Order.ASCENDING;
377 }
378 else {
379
380 sortOrder = SortableModel.Order.DESCENDING;
381 }
382
383 boolean revert = (sorter.getSortedColumn() == modelIndex);
384 sortByColumn(column, modelIndex, sortOrder, revert);
385 }
386
387 public void storeSelections()
388 {
389 if (tree != null) {
390 selectedPaths = tree.getSelectionPaths();
391 expandedLeafs = tree.getExpandedDescendants(new TreePath(tree
392 .getModel().getRoot()));
393 }
394 }
395
396 public void stopCellEditing()
397 {
398 if (table.getCellEditor() != null) {
399 table.getCellEditor().stopCellEditing();
400 }
401 }
402
403 /***
404 * Provides a small arrow shaped icon.
405 */
406 private static class BevelArrowIcon implements Icon {
407
408 public static final int DOWN = 1;
409 public static final int UP = 0;
410
411 private int direction;
412 private Color edge1;
413 private Color edge2;
414
415 public BevelArrowIcon(int direction, boolean isPressedView)
416 {
417 this.direction = direction;
418
419 if (isPressedView) {
420 edge1 = UIManager.getColor("controlDkShadow");
421 edge2 = UIManager.getColor("controlLtHighlight");
422 }
423 else {
424 edge1 = UIManager.getColor("controlShadow");
425 edge2 = UIManager.getColor("controlHighlight");
426 }
427 }
428
429 public int getIconHeight()
430 {
431 return 7;
432 }
433
434 public int getIconWidth()
435 {
436 return 8;
437 }
438
439 public void paintIcon(Component c, Graphics g, int x, int y)
440 {
441 switch (direction) {
442 case DOWN:
443 g.setColor(edge2);
444 g.drawLine(x + 5, y + 7, x + 8, y);
445 g.setColor(edge1);
446 g.drawLine(x, y, x + 8, y);
447 g.drawLine(x, y, x + 4, y + 7);
448 break;
449 case UP:
450 g.setColor(edge1);
451 g.drawLine(x, y + 6, x + 4, y);
452 g.setColor(edge2);
453 g.drawLine(x, y + 7, x + 8, y + 7);
454 g.drawLine(x + 5, y, x + 8, y + 7);
455 break;
456 }
457 }
458
459 }
460
461 protected class EventHandler implements
462 MouseListener, MouseMotionListener, TableColumnModelListener,
463 PropertyChangeListener {
464
465
466 public void columnAdded(TableColumnModelEvent event)
467 {
468 }
469
470 public void columnMarginChanged(ChangeEvent event)
471 {
472 }
473
474 public void columnMoved(TableColumnModelEvent event)
475 {
476 int i = table.convertColumnIndexToView(sorter.getSortedColumn());
477 if (i != -1) {
478 renderer.setSortedColumn(i, sorter.getSortOrder());
479 }
480 fireColumnOrderChanged();
481 }
482
483 public void columnRemoved(TableColumnModelEvent event)
484 {
485 }
486
487 public void columnSelectionChanged(ListSelectionEvent event)
488 {
489 }
490
491 public void mouseClicked(MouseEvent e)
492 {
493 }
494
495 public void mouseDragged(MouseEvent e)
496 {
497 if (sorting) {
498
499 sorting = false;
500 renderer.selectColumn(-1);
501 header.repaint();
502 }
503 setAutoResizeMode(e);
504 }
505
506 public void mouseEntered(MouseEvent e)
507 {
508 }
509
510 public void mouseExited(MouseEvent e)
511 {
512 }
513
514 public void mouseMoved(MouseEvent e)
515 {
516 }
517
518 public void mousePressed(MouseEvent e)
519 {
520 if (e.isPopupTrigger()) {
521 showPopupMenu(e);
522 }
523 else {
524 int col = header.columnAtPoint(e.getPoint());
525 renderer.selectColumn(col);
526 header.repaint();
527 sorting = true;
528 }
529 setAutoResizeMode(e);
530 }
531
532 public void mouseReleased(MouseEvent e)
533 {
534 if (e.isPopupTrigger()) {
535 showPopupMenu(e);
536 }
537 else if (sorting) {
538 int col = header.columnAtPoint(e.getPoint());
539 if (table.isEditing()) {
540 table.getCellEditor().stopCellEditing();
541 }
542 sortByColumnInternal(col);
543 fireSortedColumnChanged(col);
544 sorting = false;
545 renderer.selectColumn(-1);
546 header.repaint();
547 }
548 else {
549 fireColumnWidthsChanged();
550 }
551 }
552
553 public void propertyChange(PropertyChangeEvent evt)
554 {
555 if ("headerValue".equals(evt.getPropertyName())) {
556 TableColumn column = (TableColumn)evt.getSource();
557 fireColumnNameChanged(getColumnIndex(column.getIdentifier().toString()), evt.getNewValue().toString());
558 }
559 }
560
561 }
562
563 /***
564 * Don't use this class directly. Use
565 * <code>TableHeaderListener.install()</code> instead.
566 *
567 * <p>If an instance of this class is set as the header renderer for a table,
568 *
569 * @see xnap.gui.table.TableHeaderListener
570 */
571 private static class SortButtonRenderer implements TableCellRenderer
572 {
573
574 public static final Icon downIcon = new BevelArrowIcon(BevelArrowIcon.DOWN, false);
575 public static final Icon upIcon = new BevelArrowIcon(BevelArrowIcon.UP, false);
576
577 private Hashtable<Integer, Icon> icons = new Hashtable<Integer, Icon>();
578 private JLabel label;
579 private TableCellRenderer renderer;
580 private int selectedColumn = -1;
581
582 /***
583 * Constructs a SortButtonRenderer. The <code>renderer</code> is used
584 * to renderer the header. This needs to be an instance of
585 * {@link javax.swing.JLabel JLabel} for the sort arrow to be displayed.
586 *
587 * <p>This implementation uses the setIcon() method of JLabel to display
588 * the arrow icon. Make sure you do not use the icon of the renderer.
589 *
590 * @param renderer the default renderer
591 */
592 public SortButtonRenderer(TableCellRenderer renderer)
593 {
594 this.renderer = renderer;
595 if (renderer instanceof JLabel) {
596 label = (JLabel)renderer;
597 label.setHorizontalTextPosition(SwingUtilities.LEFT);
598 }
599 else {
600
601 label = new JLabel();
602 }
603 }
604
605 /***
606 * Returns the icon of col.
607 */
608 public Icon getIcon(int col)
609 {
610 return (Icon)icons.get(new Integer(col));
611 }
612
613 public Component getTableCellRendererComponent(JTable table, Object value,
614 boolean isSelected, boolean hasFocus,
615 int row, int column)
616 {
617
618 label.setIcon(getIcon(column));
619
620 Component c = renderer.getTableCellRendererComponent
621 (table, value, isSelected, hasFocus, row, column);
622
623 if (column == selectedColumn) {
624 label.setBackground(UIManager.getColor("controlShadow"));
625 }
626
627 return c;
628 }
629
630 public void selectColumn(int col)
631 {
632 selectedColumn = col;
633 }
634
635 /***
636 * Prints the arrow icon to the column at index <code>col</code>.
637 */
638 public void setSortedColumn(int col, Order sortOrder)
639 {
640 icons.clear();
641 if (sortOrder == SortableModel.Order.ASCENDING) {
642 icons.put(new Integer(col), downIcon);
643 }
644 else if (sortOrder == SortableModel.Order.DESCENDING) {
645 icons.put(new Integer(col), upIcon);
646 }
647 }
648 }
649
650 }