comparison agent/src/share/classes/sun/jvm/hotspot/ui/treetable/JTreeTable.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c70a245cad3a
comparison
equal deleted inserted replaced
-1:000000000000 0:a61af66fc99e
1 /*
2 * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 *
23 */
24
25 package sun.jvm.hotspot.ui.treetable;
26
27 import java.awt.*;
28
29 import javax.swing.*;
30 import javax.swing.border.*;
31 import javax.swing.event.*;
32 import javax.swing.tree.*;
33 import javax.swing.table.*;
34
35 import java.awt.event.*;
36
37 import java.util.EventObject;
38
39 /**
40 * This example shows how to create a simple JTreeTable component,
41 * by using a JTree as a renderer (and editor) for the cells in a
42 * particular column in the JTable.
43 *
44 *
45 * @author Philip Milne
46 * @author Scott Violet
47 */
48 public class JTreeTable extends JTable {
49 /** A subclass of JTree. */
50 protected TreeTableCellRenderer tree;
51
52 //////////////////////////
53 // Convenience routines //
54 //////////////////////////
55
56 private boolean treeEditable = true;
57 private boolean showsIcons = true;
58
59 public boolean getTreeEditable() {
60 return treeEditable;
61 }
62
63 public void setTreeEditable(boolean editable) {
64 treeEditable = editable;
65 }
66
67 public boolean getShowsIcons() {
68 return showsIcons;
69 }
70
71 public void setShowsIcons(boolean show) {
72 showsIcons = show;
73 }
74
75 public void setRootVisible(boolean visible) {
76 tree.setRootVisible(visible);
77 }
78
79 public boolean getShowsRootHandles() {
80 return tree.getShowsRootHandles();
81 }
82
83 public void setShowsRootHandles(boolean newValue) {
84 tree.setShowsRootHandles(newValue);
85 }
86
87 public JTreeTable(TreeTableModel treeTableModel) {
88 super();
89
90 // Create the tree. It will be used as a renderer and editor.
91 tree = new TreeTableCellRenderer(treeTableModel);
92
93 // Install a tableModel representing the visible rows in the tree.
94 super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
95
96 // Force the JTable and JTree to share their row selection models.
97 ListToTreeSelectionModelWrapper selectionWrapper = new
98 ListToTreeSelectionModelWrapper();
99 tree.setSelectionModel(selectionWrapper);
100 setSelectionModel(selectionWrapper.getListSelectionModel());
101
102 // Install the tree editor renderer and editor.
103 setDefaultRenderer(TreeTableModel.class, tree);
104 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
105
106 // No grid.
107 setShowGrid(false);
108
109 // No intercell spacing
110 setIntercellSpacing(new Dimension(0, 0));
111
112 // And update the height of the trees row to match that of
113 // the table.
114 if (tree.getRowHeight() < 1) {
115 // Metal looks better like this.
116 setRowHeight(20);
117 }
118 }
119
120 /**
121 * Overridden to message super and forward the method to the tree.
122 * Since the tree is not actually in the component hieachy it will
123 * never receive this unless we forward it in this manner.
124 */
125 public void updateUI() {
126 super.updateUI();
127 if(tree != null) {
128 tree.updateUI();
129 // Do this so that the editor is referencing the current renderer
130 // from the tree. The renderer can potentially change each time
131 // laf changes.
132 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
133 }
134 // Use the tree's default foreground and background colors in the
135 // table.
136 LookAndFeel.installColorsAndFont(this, "Tree.background",
137 "Tree.foreground", "Tree.font");
138 }
139
140 /**
141 * Workaround for BasicTableUI anomaly. Make sure the UI never tries to
142 * resize the editor. The UI currently uses different techniques to
143 * paint the renderers and editors and overriding setBounds() below
144 * is not the right thing to do for an editor. Returning -1 for the
145 * editing row in this case, ensures the editor is never painted.
146 */
147 public int getEditingRow() {
148 return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
149 editingRow;
150 }
151
152 /**
153 * Returns the actual row that is editing as <code>getEditingRow</code>
154 * will always return -1.
155 */
156 private int realEditingRow() {
157 return editingRow;
158 }
159
160 /**
161 * This is overriden to invoke supers implementation, and then,
162 * if the receiver is editing a Tree column, the editors bounds is
163 * reset. The reason we have to do this is because JTable doesn't
164 * think the table is being edited, as <code>getEditingRow</code> returns
165 * -1, and therefore doesn't automaticly resize the editor for us.
166 */
167 public void sizeColumnsToFit(int resizingColumn) {
168 super.sizeColumnsToFit(resizingColumn);
169 if (getEditingColumn() != -1 && getColumnClass(editingColumn) ==
170 TreeTableModel.class) {
171 Rectangle cellRect = getCellRect(realEditingRow(),
172 getEditingColumn(), false);
173 Component component = getEditorComponent();
174 component.setBounds(cellRect);
175 component.validate();
176 }
177 }
178
179 /**
180 * Overridden to pass the new rowHeight to the tree.
181 */
182 public void setRowHeight(int rowHeight) {
183 super.setRowHeight(rowHeight);
184 if (tree != null && tree.getRowHeight() != rowHeight) {
185 tree.setRowHeight(getRowHeight());
186 }
187 }
188
189 /**
190 * Returns the tree that is being shared between the model.
191 */
192 public JTree getTree() {
193 return tree;
194 }
195
196 /**
197 * Overriden to invoke repaint for the particular location if
198 * the column contains the tree. This is done as the tree editor does
199 * not fill the bounds of the cell, we need the renderer to paint
200 * the tree in the background, and then draw the editor over it.
201 */
202 public boolean editCellAt(int row, int column, EventObject e){
203 boolean retValue = super.editCellAt(row, column, e);
204 if (retValue && getColumnClass(column) == TreeTableModel.class) {
205 repaint(getCellRect(row, column, false));
206 }
207 return retValue;
208 }
209
210 /** A DefaultTreeCellRenderer which can optionally skip drawing
211 all icons. */
212 class JTreeTableCellRenderer extends DefaultTreeCellRenderer {
213 public Icon getClosedIcon() { return (showsIcons ? super.getClosedIcon() : null); }
214 public Icon getDefaultClosedIcon() { return (showsIcons ? super.getDefaultClosedIcon() : null); }
215 public Icon getDefaultLeafIcon() { return (showsIcons ? super.getDefaultLeafIcon() : null); }
216 public Icon getDefaultOpenIcon() { return (showsIcons ? super.getDefaultOpenIcon() : null); }
217 public Icon getLeafIcon() { return (showsIcons ? super.getLeafIcon() : null); }
218 public Icon getOpenIcon() { return (showsIcons ? super.getOpenIcon() : null); }
219 }
220
221 /**
222 * A TreeCellRenderer that displays a JTree.
223 */
224 public class TreeTableCellRenderer extends JTree implements
225 TableCellRenderer {
226 /** Last table/tree row asked to renderer. */
227 protected int visibleRow;
228 /** Border to draw around the tree, if this is non-null, it will
229 * be painted. */
230 protected Border highlightBorder;
231
232 public TreeTableCellRenderer(TreeModel model) {
233 super(model);
234 setCellRenderer(new JTreeTableCellRenderer());
235 }
236
237 /**
238 * updateUI is overridden to set the colors of the Tree's renderer
239 * to match that of the table.
240 */
241 public void updateUI() {
242 super.updateUI();
243 // Make the tree's cell renderer use the table's cell selection
244 // colors.
245 TreeCellRenderer tcr = getCellRenderer();
246 if (tcr instanceof DefaultTreeCellRenderer) {
247 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
248 // For 1.1 uncomment this, 1.2 has a bug that will cause an
249 // exception to be thrown if the border selection color is
250 // null.
251 // dtcr.setBorderSelectionColor(null);
252 dtcr.setTextSelectionColor(UIManager.getColor
253 ("Table.selectionForeground"));
254 dtcr.setBackgroundSelectionColor(UIManager.getColor
255 ("Table.selectionBackground"));
256 }
257 }
258
259 /**
260 * Sets the row height of the tree, and forwards the row height to
261 * the table.
262 */
263 public void setRowHeight(int rowHeight) {
264 if (rowHeight > 0) {
265 super.setRowHeight(rowHeight);
266 if (JTreeTable.this != null &&
267 JTreeTable.this.getRowHeight() != rowHeight) {
268 JTreeTable.this.setRowHeight(getRowHeight());
269 }
270 }
271 }
272
273 /**
274 * This is overridden to set the height to match that of the JTable.
275 */
276 public void setBounds(int x, int y, int w, int h) {
277 super.setBounds(x, 0, w, JTreeTable.this.getHeight());
278 }
279
280 /**
281 * Sublcassed to translate the graphics such that the last visible
282 * row will be drawn at 0,0.
283 */
284 public void paint(Graphics g) {
285 g.translate(0, -visibleRow * getRowHeight());
286 super.paint(g);
287 // Draw the Table border if we have focus.
288 if (highlightBorder != null) {
289 highlightBorder.paintBorder(this, g, 0, visibleRow *
290 getRowHeight(), getWidth(),
291 getRowHeight());
292 }
293 }
294
295 /**
296 * TreeCellRenderer method. Overridden to update the visible row.
297 */
298 public Component getTableCellRendererComponent(JTable table,
299 Object value,
300 boolean isSelected,
301 boolean hasFocus,
302 int row, int column) {
303 Color background;
304 Color foreground;
305
306 if(isSelected) {
307 background = table.getSelectionBackground();
308 foreground = table.getSelectionForeground();
309 }
310 else {
311 background = table.getBackground();
312 foreground = table.getForeground();
313 }
314 highlightBorder = null;
315 if (realEditingRow() == row && getEditingColumn() == column) {
316 background = UIManager.getColor("Table.focusCellBackground");
317 foreground = UIManager.getColor("Table.focusCellForeground");
318 }
319 else if (hasFocus) {
320 highlightBorder = UIManager.getBorder
321 ("Table.focusCellHighlightBorder");
322 if (isCellEditable(row, column)) {
323 background = UIManager.getColor
324 ("Table.focusCellBackground");
325 foreground = UIManager.getColor
326 ("Table.focusCellForeground");
327 }
328 }
329
330 visibleRow = row;
331 setBackground(background);
332
333 TreeCellRenderer tcr = getCellRenderer();
334 if (tcr instanceof DefaultTreeCellRenderer) {
335 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
336 if (isSelected) {
337 dtcr.setTextSelectionColor(foreground);
338 dtcr.setBackgroundSelectionColor(background);
339 }
340 else {
341 dtcr.setTextNonSelectionColor(foreground);
342 dtcr.setBackgroundNonSelectionColor(background);
343 }
344 }
345 return this;
346 }
347 }
348
349
350 /**
351 * An editor that can be used to edit the tree column. This extends
352 * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
353 * to perform the actual editing.
354 * <p>To support editing of the tree column we can not make the tree
355 * editable. The reason this doesn't work is that you can not use
356 * the same component for editing and renderering. The table may have
357 * the need to paint cells, while a cell is being edited. If the same
358 * component were used for the rendering and editing the component would
359 * be moved around, and the contents would change. When editing, this
360 * is undesirable, the contents of the text field must stay the same,
361 * including the caret blinking, and selections persisting. For this
362 * reason the editing is done via a TableCellEditor.
363 * <p>Another interesting thing to be aware of is how tree positions
364 * its render and editor. The render/editor is responsible for drawing the
365 * icon indicating the type of node (leaf, branch...). The tree is
366 * responsible for drawing any other indicators, perhaps an additional
367 * +/- sign, or lines connecting the various nodes. So, the renderer
368 * is positioned based on depth. On the other hand, table always makes
369 * its editor fill the contents of the cell. To get the allusion
370 * that the table cell editor is part of the tree, we don't want the
371 * table cell editor to fill the cell bounds. We want it to be placed
372 * in the same manner as tree places it editor, and have table message
373 * the tree to paint any decorations the tree wants. Then, we would
374 * only have to worry about the editing part. The approach taken
375 * here is to determine where tree would place the editor, and to override
376 * the <code>reshape</code> method in the JTextField component to
377 * nudge the textfield to the location tree would place it. Since
378 * JTreeTable will paint the tree behind the editor everything should
379 * just work. So, that is what we are doing here. Determining of
380 * the icon position will only work if the TreeCellRenderer is
381 * an instance of DefaultTreeCellRenderer. If you need custom
382 * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer,
383 * and you want to support editing in JTreeTable, you will have
384 * to do something similiar.
385 */
386 public class TreeTableCellEditor extends DefaultCellEditor {
387 public TreeTableCellEditor() {
388 super(new TreeTableTextField());
389 }
390
391 /**
392 * Overriden to determine an offset that tree would place the
393 * editor at. The offset is determined from the
394 * <code>getRowBounds</code> JTree method, and additionaly
395 * from the icon DefaultTreeCellRenderer will use.
396 * <p>The offset is then set on the TreeTableTextField component
397 * created in the constructor, and returned.
398 */
399 public Component getTableCellEditorComponent(JTable table,
400 Object value,
401 boolean isSelected,
402 int r, int c) {
403 Component component = super.getTableCellEditorComponent
404 (table, value, isSelected, r, c);
405 JTree t = getTree();
406 boolean rv = t.isRootVisible();
407 int offsetRow = rv ? r : r - 1;
408 Rectangle bounds = t.getRowBounds(offsetRow);
409 int offset = bounds.x;
410 TreeCellRenderer tcr = t.getCellRenderer();
411 if (tcr instanceof DefaultTreeCellRenderer) {
412 Object node = t.getPathForRow(offsetRow).
413 getLastPathComponent();
414 Icon icon;
415 if (t.getModel().isLeaf(node))
416 icon = ((DefaultTreeCellRenderer)tcr).getLeafIcon();
417 else if (tree.isExpanded(offsetRow))
418 icon = ((DefaultTreeCellRenderer)tcr).getOpenIcon();
419 else
420 icon = ((DefaultTreeCellRenderer)tcr).getClosedIcon();
421 if (icon != null) {
422 offset += ((DefaultTreeCellRenderer)tcr).getIconTextGap() +
423 icon.getIconWidth();
424 }
425 }
426 ((TreeTableTextField)getComponent()).offset = offset;
427 return component;
428 }
429
430 /**
431 * This is overriden to forward the event to the tree. This will
432 * return true if the click count >= 3, or the event is null.
433 */
434 public boolean isCellEditable(EventObject e) {
435 if (e instanceof MouseEvent) {
436 MouseEvent me = (MouseEvent)e;
437 // If the modifiers are not 0 (or the left mouse button),
438 // tree may try and toggle the selection, and table
439 // will then try and toggle, resulting in the
440 // selection remaining the same. To avoid this, we
441 // only dispatch when the modifiers are 0 (or the left mouse
442 // button).
443 if (me.getModifiers() == 0 ||
444 me.getModifiers() == InputEvent.BUTTON1_MASK) {
445 for (int counter = getColumnCount() - 1; counter >= 0;
446 counter--) {
447 if (getColumnClass(counter) == TreeTableModel.class) {
448 MouseEvent newME = new MouseEvent
449 (JTreeTable.this.tree, me.getID(),
450 me.getWhen(), me.getModifiers(),
451 me.getX() - getCellRect(0, counter, true).x,
452 me.getY(), me.getClickCount(),
453 me.isPopupTrigger());
454 JTreeTable.this.tree.dispatchEvent(newME);
455 break;
456 }
457 }
458 }
459 if (me.getClickCount() >= 3) {
460 return treeEditable;
461 }
462 return false;
463 }
464 if (e == null) {
465 return treeEditable;
466 }
467 return false;
468 }
469 }
470
471
472 /**
473 * Component used by TreeTableCellEditor. The only thing this does
474 * is to override the <code>reshape</code> method, and to ALWAYS
475 * make the x location be <code>offset</code>.
476 */
477 static class TreeTableTextField extends JTextField {
478 public int offset;
479
480 public void reshape(int x, int y, int w, int h) {
481 int newX = Math.max(x, offset);
482 super.reshape(newX, y, w - (newX - x), h);
483 }
484 }
485
486
487 /**
488 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
489 * to listen for changes in the ListSelectionModel it maintains. Once
490 * a change in the ListSelectionModel happens, the paths are updated
491 * in the DefaultTreeSelectionModel.
492 */
493 class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
494 /** Set to true when we are updating the ListSelectionModel. */
495 protected boolean updatingListSelectionModel;
496
497 public ListToTreeSelectionModelWrapper() {
498 super();
499 getListSelectionModel().addListSelectionListener
500 (createListSelectionListener());
501 }
502
503 /**
504 * Returns the list selection model. ListToTreeSelectionModelWrapper
505 * listens for changes to this model and updates the selected paths
506 * accordingly.
507 */
508 ListSelectionModel getListSelectionModel() {
509 return listSelectionModel;
510 }
511
512 /**
513 * This is overridden to set <code>updatingListSelectionModel</code>
514 * and message super. This is the only place DefaultTreeSelectionModel
515 * alters the ListSelectionModel.
516 */
517 public void resetRowSelection() {
518 if(!updatingListSelectionModel) {
519 updatingListSelectionModel = true;
520 try {
521 super.resetRowSelection();
522 }
523 finally {
524 updatingListSelectionModel = false;
525 }
526 }
527 // Notice how we don't message super if
528 // updatingListSelectionModel is true. If
529 // updatingListSelectionModel is true, it implies the
530 // ListSelectionModel has already been updated and the
531 // paths are the only thing that needs to be updated.
532 }
533
534 /**
535 * Creates and returns an instance of ListSelectionHandler.
536 */
537 protected ListSelectionListener createListSelectionListener() {
538 return new ListSelectionHandler();
539 }
540
541 /**
542 * If <code>updatingListSelectionModel</code> is false, this will
543 * reset the selected paths from the selected rows in the list
544 * selection model.
545 */
546 protected void updateSelectedPathsFromSelectedRows() {
547 if(!updatingListSelectionModel) {
548 updatingListSelectionModel = true;
549 try {
550 // This is way expensive, ListSelectionModel needs an
551 // enumerator for iterating.
552 int min = listSelectionModel.getMinSelectionIndex();
553 int max = listSelectionModel.getMaxSelectionIndex();
554
555 clearSelection();
556 if(min != -1 && max != -1) {
557 for(int counter = min; counter <= max; counter++) {
558 if(listSelectionModel.isSelectedIndex(counter)) {
559 TreePath selPath = tree.getPathForRow
560 (counter);
561
562 if(selPath != null) {
563 addSelectionPath(selPath);
564 }
565 }
566 }
567 }
568 }
569 finally {
570 updatingListSelectionModel = false;
571 }
572 }
573 }
574
575 /**
576 * Class responsible for calling updateSelectedPathsFromSelectedRows
577 * when the selection of the list changse.
578 */
579 class ListSelectionHandler implements ListSelectionListener {
580 public void valueChanged(ListSelectionEvent e) {
581 updateSelectedPathsFromSelectedRows();
582 }
583 }
584 }
585 }