1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  package org.xnap.gui.table;
39  
40  import java.awt.Color;
41  import java.awt.Component;
42  import java.awt.Dimension;
43  import java.awt.Graphics;
44  import java.awt.*;
45  import java.awt.event.*;
46  import java.awt.event.MouseEvent;
47  import java.util.EventObject;
48  
49  import javax.swing.AbstractCellEditor;
50  import javax.swing.JTable;
51  import javax.swing.JTree;
52  import javax.swing.ListSelectionModel;
53  import javax.swing.*;
54  import javax.swing.UIManager;
55  import javax.swing.border.Border;
56  import javax.swing.event.ListSelectionEvent;
57  import javax.swing.event.ListSelectionListener;
58  import javax.swing.table.TableCellEditor;
59  import javax.swing.table.TableCellRenderer;
60  import javax.swing.table.TableColumnModel;
61  import javax.swing.tree.DefaultTreeCellRenderer;
62  import javax.swing.tree.DefaultTreeSelectionModel;
63  import javax.swing.tree.TreeCellRenderer;
64  import javax.swing.tree.TreeModel;
65  import javax.swing.tree.TreePath;
66  
67  /***
68   * This example shows how to create a simple JTreeTable component, 
69   * by using a JTree as a renderer (and editor) for the cells in a 
70   * particular column in the JTable.  
71   *
72   * @version 1.2 10/27/98
73   *
74   * @author Philip Milne
75   * @author Scott Violet
76   */
77  public class JTreeTable extends ColoredTable {
78      /*** A subclass of JTree. */
79      protected TreeTableCellRenderer tree;
80  
81      public JTreeTable(TreeTableModel treeTableModel) {
82      	this(treeTableModel, null);
83      }
84      
85      public JTreeTable(TreeTableModel treeTableModel, TableColumnModel cm) {
86  		super(null, cm);
87  	
88  		setModel(treeTableModel);
89  
90  		
91  		setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
92  
93  		
94  		setShowGrid(false);
95  
96  		
97  		setIntercellSpacing(new Dimension(0, 0));	
98  
99  		
100 		
101 
102 
103 
104 
105 
106 		
107 		
108 		int rowHeight = UIManager.getInt("Tree.rowHeight");
109 		if (rowHeight > 0) {
110 			setRowHeight(rowHeight);
111 		} 
112 		else {
113 			if (tree.getRowHeight() < 1) {
114 				
115 				setRowHeight(20);
116 			}
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 			
130 			
131 			
132 			setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
133 		}
134 		
135 		
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      * Overrides <code>JComponent</code>'s <code>getToolTipText</code>
154      * method in order to allow the renderer's tips to be used
155      * if it has text set.
156      * <p>
157      * <bold>Note:</bold> For <code>JTable</code> to properly display
158      * tooltips of its renderers
159      * <code>JTable</code> must be a registered component with the
160      * <code>ToolTipManager</code>.
161      * This is done automatically in <code>initializeLocalVars</code>,
162      * but if at a later point <code>JTable</code> is told
163      * <code>setToolTipText(null)</code> it will unregister the table
164      * component, and no tips from renderers will display anymore.
165      *
166      * @see JComponent#getToolTipText
167      */
168     public String getToolTipText(MouseEvent event) {
169         String tip = null;
170         Point p = event.getPoint();
171 
172         
173         int hitColumnIndex = columnAtPoint(p);
174         int hitRowIndex = rowAtPoint(p);
175 
176         if ((hitColumnIndex != -1) && (hitRowIndex != -1)) {
177             TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex);
178             Component component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex);
179 
180             
181             
182             if (component instanceof TreeTableCellRenderer) {
183                 tip = ((TreeTableCellRenderer)component).getToolTipText(event);
184 			}
185             else if (component instanceof JComponent) {
186                 
187                 Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false);
188                 p.translate(-cellRect.x, -cellRect.y);
189                 MouseEvent newEvent = new MouseEvent
190 					(component, event.getID(),
191 					 event.getWhen(), event.getModifiers(),
192 					 p.x, p.y, event.getClickCount(),
193 					 event.isPopupTrigger());
194 
195                 tip = ((JComponent)component).getToolTipText(newEvent);
196             }
197         }
198 
199         
200         if (tip == null)
201             tip = getToolTipText();
202 
203         return tip;
204     }
205 
206 
207     /***
208      * Returns the actual row that is editing as
209      * <code>getEditingRow</code> will always return -1.  */
210     private int realEditingRow() 
211 	{
212 		return editingRow;
213     }
214 
215     /***
216      * This is overriden to invoke supers implementation, and then,
217      * if the receiver is editing a Tree column, the editors bounds is
218      * reset. The reason we have to do this is because JTable doesn't
219      * think the table is being edited, as <code>getEditingRow</code> returns
220      * -1, and therefore doesn't automaticly resize the editor for us.
221      */
222     public void sizeColumnsToFit(int resizingColumn) { 
223 		super.sizeColumnsToFit(resizingColumn);
224 		if (getEditingColumn() != -1 && getColumnClass(editingColumn) ==
225 			TreeTableModel.class) {
226 			Rectangle cellRect = getCellRect(realEditingRow(),
227 											 getEditingColumn(), false);
228             Component component = getEditorComponent();
229 			component.setBounds(cellRect);
230             component.validate();
231 		}
232     }
233 
234     /***
235      * Overridden to pass the new rowHeight to the tree.
236      */
237     public void setRowHeight(int rowHeight) { 
238         super.setRowHeight(rowHeight); 
239 		if (tree != null && tree.getRowHeight() != rowHeight) {
240             tree.setRowHeight(getRowHeight()); 
241 		}
242     }
243 	
244 	public void setModel(TreeTableModel treeTableModel) {
245 		boolean newTree;
246 
247 		if (tree == null) {
248 			
249 			tree = new TreeTableCellRenderer(treeTableModel);
250 			newTree = true;
251 		}
252 		else {
253 			tree.setModel(treeTableModel);
254 			newTree = false;
255 		}
256 
257 		
258 		super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
259 		
260 		if (newTree) {
261 			
262 			ListToTreeSelectionModelWrapper selectionWrapper = new 
263 				ListToTreeSelectionModelWrapper();
264 			tree.setSelectionModel(selectionWrapper);
265 			setSelectionModel(selectionWrapper.getListSelectionModel());
266 
267 			
268 			setDefaultRenderer(TreeTableModel.class, tree);
269 		}
270 	}
271 	
272 	public void setModel(TreeTableModelAdapter treeTableModelAdapter) {
273 		boolean newTree;
274 
275 		if (tree == null) {
276 			
277 			tree = new TreeTableCellRenderer(treeTableModelAdapter.treeTableModel);
278 			newTree = true;
279 		}
280 		else {
281 			tree.setModel(treeTableModelAdapter.treeTableModel);
282 			newTree = false;
283 		}
284 
285 		
286 		super.setModel(treeTableModelAdapter);
287 		
288 		if (newTree) {
289 			
290 			ListToTreeSelectionModelWrapper selectionWrapper = new 
291 				ListToTreeSelectionModelWrapper();
292 			tree.setSelectionModel(selectionWrapper);
293 			setSelectionModel(selectionWrapper.getListSelectionModel());
294 
295 			
296 			setDefaultRenderer(TreeTableModel.class, tree);
297 		}
298 	}
299 
300     /***
301      * Returns the tree that is being shared between the model.
302      */
303     public JTree getTree() {
304 		return tree;
305     }
306 
307     /***
308      * Overriden to invoke repaint for the particular location if
309      * the column contains the tree. This is done as the tree editor does
310      * not fill the bounds of the cell, we need the renderer to paint
311      * the tree in the background, and then draw the editor over it.
312      */
313     public boolean editCellAt(int row, int column, EventObject e){
314 		boolean retValue = super.editCellAt(row, column, e);
315 		if (retValue && getColumnClass(column) == TreeTableModel.class) {
316 			repaint(getCellRect(row, column, false));
317 		}
318 		return retValue;
319     }
320 
321     /***
322      * A TreeCellRenderer that displays a JTree.
323      */
324     public class TreeTableCellRenderer extends JTree implements
325 														 TableCellRenderer {
326 		/*** Last table/tree row asked to renderer. */
327 		protected int visibleRow;
328 		/*** Border to draw around the tree, if this is non-null, it will
329 		 * be painted. */
330 		protected Border highlightBorder;
331 
332 		public TreeTableCellRenderer(TreeModel model) {
333 			super(model); 
334 		}
335 
336 		/***
337 		 * updateUI is overridden to set the colors of the Tree's renderer
338 		 * to match that of the table.
339 		 */
340 		public void updateUI() {
341 			super.updateUI();
342 			
343 			
344 			TreeCellRenderer tcr = getCellRenderer();
345 			if (tcr instanceof DefaultTreeCellRenderer) {
346 				DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); 
347 				
348 				
349 				
350 				
351 				dtcr.setTextSelectionColor(UIManager.getColor
352 										   ("Table.selectionForeground"));
353 				dtcr.setBackgroundSelectionColor(UIManager.getColor
354 												 ("Table.selectionBackground"));
355 			}
356 		}
357 
358 		/***
359 		 * Sets the row height of the tree, and forwards the row height to
360 		 * the table.
361 		 */
362 		public void setRowHeight(int rowHeight) { 
363 			if (rowHeight > 0) {
364 				super.setRowHeight(rowHeight); 
365 				if (JTreeTable.this != null &&
366 					JTreeTable.this.getRowHeight() != rowHeight) {
367 					JTreeTable.this.setRowHeight(this.getRowHeight()); 
368 				}
369 			}
370 		}
371 
372 		/***
373 		 * This is overridden to set the height to match that of the JTable.
374 		 */
375 		public void setBounds(int x, int y, int w, int h) {
376 			super.setBounds(x, 0, w, JTreeTable.this.getHeight());
377 		}
378 
379 		/***
380 		 * Sublcassed to translate the graphics such that the last visible
381 		 * row will be drawn at 0,0.
382 		 */
383 		public void paint(Graphics g) {
384 			g.translate(0, -visibleRow * this.getRowHeight());
385 			super.paint(g);
386 			
387 			if (highlightBorder != null) {
388 				highlightBorder.paintBorder(this, g, 0, visibleRow *
389 											this.getRowHeight(), this.getWidth(),
390 											this.getRowHeight());
391 			}
392 		}
393 
394 		/***
395 		 * TreeCellRenderer method. Overridden to update the visible row.
396 		 */
397 		public Component getTableCellRendererComponent(JTable table,
398 													   Object value,
399 													   boolean isSelected,
400 													   boolean hasFocus,
401 													   int row, int column) {
402 			Color background;
403 			Color foreground;
404 
405 			if(isSelected) {
406 				background = table.getSelectionBackground();
407 				foreground = table.getSelectionForeground();
408 			}
409 			else {
410 				background = JTreeTable.this.getBackgroundColor(row); 
411 				foreground = table.getForeground();
412 			}
413 			highlightBorder = null;
414 			
415 
416 
417 
418 
419 
420 
421 
422 
423 
424 
425 
426 
427 
428 
429 			visibleRow = row;
430 			this.setBackground(background);
431 	    
432 			TreeCellRenderer tcr = getCellRenderer();
433 			if (tcr instanceof DefaultTreeCellRenderer) {
434 				DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); 
435 				if (isSelected) {
436 					dtcr.setTextSelectionColor(foreground);
437 					dtcr.setBackgroundSelectionColor(background);
438 				}
439 				else {
440 					dtcr.setTextNonSelectionColor(foreground);
441 					dtcr.setBackgroundNonSelectionColor(background);
442 				}
443 			}
444 			return this;
445 		}
446     }
447 
448 
449     /***
450      * An editor that can be used to edit the tree column. This extends
451      * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
452      * to perform the actual editing.
453      * <p>To support editing of the tree column we can not make the tree
454      * editable. The reason this doesn't work is that you can not use
455      * the same component for editing and renderering. The table may have
456      * the need to paint cells, while a cell is being edited. If the same
457      * component were used for the rendering and editing the component would
458      * be moved around, and the contents would change. When editing, this
459      * is undesirable, the contents of the text field must stay the same,
460      * including the caret blinking, and selections persisting. For this
461      * reason the editing is done via a TableCellEditor.
462      * <p>Another interesting thing to be aware of is how tree positions
463      * its render and editor. The render/editor is responsible for drawing the
464      * icon indicating the type of node (leaf, branch...). The tree is
465      * responsible for drawing any other indicators, perhaps an additional
466      * +/- sign, or lines connecting the various nodes. So, the renderer
467      * is positioned based on depth. On the other hand, table always makes
468      * its editor fill the contents of the cell. To get the allusion
469      * that the table cell editor is part of the tree, we don't want the
470      * table cell editor to fill the cell bounds. We want it to be placed
471      * in the same manner as tree places it editor, and have table message
472      * the tree to paint any decorations the tree wants. Then, we would
473      * only have to worry about the editing part. The approach taken
474      * here is to determine where tree would place the editor, and to override
475      * the <code>reshape</code> method in the JTextField component to
476      * nudge the textfield to the location tree would place it. Since
477      * JTreeTable will paint the tree behind the editor everything should
478      * just work. So, that is what we are doing here. Determining of
479      * the icon position will only work if the TreeCellRenderer is
480      * an instance of DefaultTreeCellRenderer. If you need custom
481      * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer, 
482      * and you want to support editing in JTreeTable, you will have
483      * to do something similiar.
484      */
485     
486 
487 
488 
489 
490 	/***
491 	 * Overriden to determine an offset that tree would place the
492 	 * editor at. The offset is determined from the
493 	 * <code>getRowBounds</code> JTree method, and additionaly
494 	 * from the icon DefaultTreeCellRenderer will use.
495 	 * <p>The offset is then set on the TreeTableTextField component
496 	 * created in the constructor, and returned.
497 	 */
498 	
499 
500 
501 
502 
503 
504 
505 
506 
507 
508 
509 
510 
511 
512 
513 
514 
515 
516 
517 
518 
519 
520 
521 
522 
523 
524 
525 
526 
527 
528 
529 	/***
530 	 * This is overriden to forward the event to the tree. This will
531 	 * return true if the click count >= 3, or the event is null.
532 	 */
533 	
534 
535 
536 
537 
538 
539 
540 
541 
542 
543 
544 
545 
546 
547 
548 
549 
550 
551 
552 
553 
554 
555 
556 
557 
558 
559 
560 
561 
562 
563 
564 
565 
566 
567 
568 
569 	
570 	/***
571      * Component used by TreeTableCellEditor. The only thing this does
572      * is to override the <code>reshape</code> method, and to ALWAYS
573      * make the x location be <code>offset</code>.
574      */
575     
576 
577 
578 
579 
580 
581 
582 
583 	
584 	/***
585      * TreeTableCellEditor implementation. Component returned is the
586      * JTree.
587      */
588     public class TreeTableCellEditor extends AbstractCellEditor implements
589 																	TableCellEditor {
590 		public Component getTableCellEditorComponent(JTable table,
591 													 Object value,
592 													 boolean isSelected,
593 													 int r, int c) {
594 			return tree;
595 		}
596 
597 		public Object getCellEditorValue() 
598 		{
599 			return null;
600 		}
601 
602 		/***
603 		 * Overridden to return false, and if the event is a mouse event
604 		 * it is forwarded to the tree.<p>
605 		 * The behavior for this is debatable, and should really be offered
606 		 * as a property. By returning false, all keyboard actions are
607 		 * implemented in terms of the table. By returning true, the
608 		 * tree would get a chance to do something with the keyboard
609 		 * events. For the most part this is ok. But for certain keys,
610 		 * such as left/right, the tree will expand/collapse where as
611 		 * the table focus should really move to a different column. Page
612 		 * up/down should also be implemented in terms of the table.
613 		 * By returning false this also has the added benefit that clicking
614 		 * outside of the bounds of the tree node, but still in the tree
615 		 * column will select the row, whereas if this returned true
616 		 * that wouldn't be the case.
617 		 * <p>By returning false we are also enforcing the policy that
618 		 * the tree will never be editable (at least by a key sequence).
619 		 */
620 		public boolean isCellEditable(EventObject e) {
621 			if (e instanceof MouseEvent) {
622 				MouseEvent me = (MouseEvent)e;
623 				
624                 
625                 
626                 
627                 
628                 
629 				if (me.getModifiers() == 0 ||
630                     me.getModifiers() == InputEvent.BUTTON1_MASK) {
631 					for (int counter = getColumnCount() - 1; counter >= 0;
632 						 counter--) {
633 						if (getColumnClass(counter) == TreeTableModel.class) {
634 							MouseEvent newME = new MouseEvent
635 								(JTreeTable.this.tree, me.getID(),
636 								 me.getWhen(), me.getModifiers(),
637 								 me.getX() - getCellRect(0, counter, true).x,
638 								 me.getY(), me.getClickCount(),
639 								 me.isPopupTrigger());
640 							JTreeTable.this.tree.dispatchEvent(newME);
641 							break;
642 						}
643 					}
644 				}
645 			}
646 			return false;
647 		}
648     }
649 
650     /***
651      * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
652      * to listen for changes in the ListSelectionModel it maintains. Once
653      * a change in the ListSelectionModel happens, the paths are updated
654      * in the DefaultTreeSelectionModel.
655      */
656     class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel { 
657 		/*** Set to true when we are updating the ListSelectionModel. */
658 		protected boolean         updatingListSelectionModel;
659 
660 		public ListToTreeSelectionModelWrapper() {
661 			super();
662 			getListSelectionModel().addListSelectionListener
663 				(createListSelectionListener());
664 		}
665 
666 		/***
667 		 * Returns the list selection model. ListToTreeSelectionModelWrapper
668 		 * listens for changes to this model and updates the selected paths
669 		 * accordingly.
670 		 */
671 		ListSelectionModel getListSelectionModel() {
672 			return listSelectionModel; 
673 		}
674 
675 		/***
676 		 * This is overridden to set <code>updatingListSelectionModel</code>
677 		 * and message super. This is the only place DefaultTreeSelectionModel
678 		 * alters the ListSelectionModel.
679 		 */
680 		public void resetRowSelection() {
681 			if(!updatingListSelectionModel) {
682 				updatingListSelectionModel = true;
683 				try {
684 					super.resetRowSelection();
685 				}
686 				finally {
687 					updatingListSelectionModel = false;
688 				}
689 			}
690 			
691 			
692 			
693 			
694 			
695 		}
696 
697 		/***
698 		 * Creates and returns an instance of ListSelectionHandler.
699 		 */
700 		protected ListSelectionListener createListSelectionListener() {
701 			return new ListSelectionHandler();
702 		}
703 
704 		/***
705 		 * If <code>updatingListSelectionModel</code> is false, this will
706 		 * reset the selected paths from the selected rows in the list
707 		 * selection model.
708 		 */
709 		protected void updateSelectedPathsFromSelectedRows() {
710 			if(!updatingListSelectionModel) {
711 				updatingListSelectionModel = true;
712 				try {
713 					
714 					
715 					int        min = listSelectionModel.getMinSelectionIndex();
716 					int        max = listSelectionModel.getMaxSelectionIndex();
717 
718 					this.clearSelection();
719 					if(min != -1 && max != -1) {
720 						for(int counter = min; counter <= max; counter++) {
721 							if(listSelectionModel.isSelectedIndex(counter)) {
722 								TreePath     selPath = tree.getPathForRow
723 									(counter);
724 
725 								if(selPath != null) {
726 									addSelectionPath(selPath);
727 								}
728 							}
729 						}
730 					}
731 				}
732 				finally {
733 					updatingListSelectionModel = false;
734 				}
735 			}
736 		}
737 
738 		/***
739 		 * Class responsible for calling updateSelectedPathsFromSelectedRows
740 		 * when the selection of the list changse.
741 		 */
742 		class ListSelectionHandler implements ListSelectionListener {
743 			public void valueChanged(ListSelectionEvent e) {
744 				updateSelectedPathsFromSelectedRows();
745 			}
746 		}
747     }
748 }