View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  /*
21   * Original copyright notice below. Class was slightly adopted for XNap:
22   *
23   *  - Changed the package to org.xnap.gui.table
24   *  - Copied getToolTipText() from JDK sources to fix rendering of tooltips
25   *    for columns that are handled by TreeTableCellRenderer
26   */
27  /*
28   * Copyright 1997-2000 by Sun Microsystems, Inc.,
29   * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
30   * All rights reserved.
31   *
32   * This software is the confidential and proprietary information
33   * of Sun Microsystems, Inc. ("Confidential Information").  You
34   * shall not disclose such Confidential Information and shall use
35   * it only in accordance with the terms of the license agreement
36   * you entered into with Sun.
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  		// Install the tree editor. 
91  		setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
92  
93  		// No grid.
94  		setShowGrid(false);
95  
96  		// No intercell spacing
97  		setIntercellSpacing(new Dimension(0, 0));	
98  
99  		// And update the height of the trees row to match that of
100 		// the table.
101 //  		if (tree.getRowHeight() < 1) {
102 //  			// Metal looks better like this.
103 //  			setRowHeight(20);
104 //  		}
105 
106 		// And update the height of the table row to match that of the tree.
107 		// See http://forum.java.sun.com/thread.jsp?forum=57&thread=258373
108 		int rowHeight = UIManager.getInt("Tree.rowHeight");
109 		if (rowHeight > 0) {
110 			setRowHeight(rowHeight);
111 		} 
112 		else {
113 			if (tree.getRowHeight() < 1) {
114 				// Metal looks better like this.
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 			// 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      * 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         // Locate the renderer under the event location
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             // Now have to see if the component is a JComponent before
181             // getting the tip
182             if (component instanceof TreeTableCellRenderer) {
183                 tip = ((TreeTableCellRenderer)component).getToolTipText(event);
184 			}
185             else if (component instanceof JComponent) {
186                 // Convert the event to the renderer's coordinate system
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         // No tip from the renderer get our own tip
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 			// Create the tree. It will be used as a renderer and editor. 
249 			tree = new TreeTableCellRenderer(treeTableModel);
250 			newTree = true;
251 		}
252 		else {
253 			tree.setModel(treeTableModel);
254 			newTree = false;
255 		}
256 
257 		// Install a tableModel representing the visible rows in the tree. 
258 		super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
259 		
260 		if (newTree) {
261 			// Force the JTable and JTree to share their row selection models. 
262 			ListToTreeSelectionModelWrapper selectionWrapper = new 
263 				ListToTreeSelectionModelWrapper();
264 			tree.setSelectionModel(selectionWrapper);
265 			setSelectionModel(selectionWrapper.getListSelectionModel());
266 
267 			// Install the tree editor renderer and editor. 
268 			setDefaultRenderer(TreeTableModel.class, tree);
269 		}
270 	}
271 	
272 	public void setModel(TreeTableModelAdapter treeTableModelAdapter) {
273 		boolean newTree;
274 
275 		if (tree == null) {
276 			// Create the tree. It will be used as a renderer and editor. 
277 			tree = new TreeTableCellRenderer(treeTableModelAdapter.treeTableModel);
278 			newTree = true;
279 		}
280 		else {
281 			tree.setModel(treeTableModelAdapter.treeTableModel);
282 			newTree = false;
283 		}
284 
285 		// Install a tableModel representing the visible rows in the tree. 
286 		super.setModel(treeTableModelAdapter);
287 		
288 		if (newTree) {
289 			// Force the JTable and JTree to share their row selection models. 
290 			ListToTreeSelectionModelWrapper selectionWrapper = new 
291 				ListToTreeSelectionModelWrapper();
292 			tree.setSelectionModel(selectionWrapper);
293 			setSelectionModel(selectionWrapper.getListSelectionModel());
294 
295 			// Install the tree editor renderer and editor. 
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 			// Make the tree's cell renderer use the table's cell selection
343 			// colors. 
344 			TreeCellRenderer tcr = getCellRenderer();
345 			if (tcr instanceof DefaultTreeCellRenderer) {
346 				DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); 
347 				// For 1.1 uncomment this, 1.2 has a bug that will cause an
348 				// exception to be thrown if the border selection color is
349 				// null.
350 				// dtcr.setBorderSelectionColor(null);
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 			// Draw the Table border if we have focus.
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 			/*if (realEditingRow() == row && getEditingColumn() == column) {
415 			  background = UIManager.getColor("Table.focusCellBackground");
416 			  foreground = UIManager.getColor("Table.focusCellForeground");
417 			  }
418 			  else if (hasFocus) {
419 			  highlightBorder = UIManager.getBorder
420 			  ("Table.focusCellHighlightBorder");
421 			  if (isCellEditable(row, column)) {
422 			  background = UIManager.getColor
423 			  ("Table.focusCellBackground");
424 			  foreground = UIManager.getColor
425 			  ("Table.focusCellForeground");
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     /*public class TreeTableCellEditor extends DefaultCellEditor {
486 	  public TreeTableCellEditor() {
487 	  super(new TreeTableTextField());
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 	/*public Component getTableCellEditorComponent(JTable table,
499 	  Object value,
500 	  boolean isSelected,
501 	  int r, int c) {
502 	  Component component = super.getTableCellEditorComponent
503 	  (table, value, isSelected, r, c);
504 	  JTree t = getTree();
505 	  boolean rv = t.isRootVisible();
506 	  int offsetRow = rv ? r : r - 1;
507 	  Rectangle bounds = t.getRowBounds(offsetRow);
508 	  int offset = bounds.x;
509 	  TreeCellRenderer tcr = t.getCellRenderer();
510 	  if (tcr instanceof DefaultTreeCellRenderer) {
511 	  Object node = t.getPathForRow(offsetRow).
512 	  getLastPathComponent();
513 	  Icon icon;
514 	  if (t.getModel().isLeaf(node))
515 	  icon = ((DefaultTreeCellRenderer)tcr).getLeafIcon();
516 	  else if (tree.isExpanded(offsetRow))
517 	  icon = ((DefaultTreeCellRenderer)tcr).getOpenIcon();
518 	  else
519 	  icon = ((DefaultTreeCellRenderer)tcr).getClosedIcon();
520 	  if (icon != null) {
521 	  offset += ((DefaultTreeCellRenderer)tcr).getIconTextGap() +
522 	  icon.getIconWidth();
523 	  }
524 	  }
525 	  ((TreeTableTextField)getComponent()).offset = offset;
526 	  return component;
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 	/*public boolean isCellEditable(EventObject e) {
534 	  if (e instanceof MouseEvent) {
535 	  MouseEvent me = (MouseEvent)e;
536 	  // If the modifiers are not 0 (or the left mouse button),
537 	  // tree may try and toggle the selection, and table
538 	  // will then try and toggle, resulting in the
539 	  // selection remaining the same. To avoid this, we
540 	  // only dispatch when the modifiers are 0 (or the left mouse
541 	  // button).
542 	  if (me.getModifiers() == 0 ||
543 	  me.getModifiers() == InputEvent.BUTTON1_MASK) {
544 	  for (int counter = getColumnCount() - 1; counter >= 0;
545 	  counter--) {
546 	  if (getColumnClass(counter) == TreeTableModel.class) {
547 	  MouseEvent newME = new MouseEvent
548 	  (JTreeTable.this.tree, me.getID(),
549 	  me.getWhen(), me.getModifiers(),
550 	  me.getX() - getCellRect(0, counter, true).x,
551 	  me.getY(), me.getClickCount(),
552 	  me.isPopupTrigger());
553 	  JTreeTable.this.tree.dispatchEvent(newME);
554 	  break;
555 	  }
556 	  }
557 	  }
558 	  if (me.getClickCount() >= 3) {
559 	  return super.isCellEditable(e);
560 	  }
561 	  return false;
562 	  }
563 	  if (e == null) {
564 	  return super.isCellEditable(e);
565 	  }
566 	  return false;
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     /*static class TreeTableTextField extends JTextField {
576 	  public int offset;
577 
578 	  public void reshape(int x, int y, int w, int h) {
579 	  int newX = Math.max(x, offset);
580 	  super.reshape(newX, y, w - (newX - x), h);
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 				// If the modifiers are not 0 (or the left mouse button),
624                 // tree may try and toggle the selection, and table
625                 // will then try and toggle, resulting in the
626                 // selection remaining the same. To avoid this, we
627                 // only dispatch when the modifiers are 0 (or the left mouse
628                 // button).
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 			// Notice how we don't message super if
691 			// updatingListSelectionModel is true. If
692 			// updatingListSelectionModel is true, it implies the
693 			// ListSelectionModel has already been updated and the
694 			// paths are the only thing that needs to be updated.
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 					// This is way expensive, ListSelectionModel needs an
714 					// enumerator for iterating.
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 }