Source for javax.swing.tree.DefaultTreeSelectionModel

   1: /* DefaultTreeSelectionModel.java 
   2:    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
   3:    
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath 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; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.tree;
  40: 
  41: import java.beans.PropertyChangeListener;
  42: import java.io.IOException;
  43: import java.io.ObjectInputStream;
  44: import java.io.ObjectOutputStream;
  45: import java.io.Serializable;
  46: import java.util.Arrays;
  47: import java.util.BitSet;
  48: import java.util.EventListener;
  49: import java.util.HashSet;
  50: import java.util.Iterator;
  51: import java.util.Vector;
  52: 
  53: import javax.swing.DefaultListSelectionModel;
  54: import javax.swing.event.EventListenerList;
  55: import javax.swing.event.SwingPropertyChangeSupport;
  56: import javax.swing.event.TreeSelectionEvent;
  57: import javax.swing.event.TreeSelectionListener;
  58: 
  59: /**
  60:  * The implementation of the default tree selection model. The installed
  61:  * listeners are notified about the path and not the row changes. If you
  62:  * specifically need to track the row changes, register the listener for the
  63:  * expansion events.
  64:  * 
  65:  * @author Andrew Selkirk
  66:  * @author Audrius Meskauskas
  67:  */
  68: public class DefaultTreeSelectionModel
  69:     implements Cloneable, Serializable, TreeSelectionModel
  70: {
  71: 
  72:   /**
  73:    * According to the API docs, the method
  74:    * {@link DefaultTreeSelectionModel#notifyPathChange} should
  75:    * expect instances of a class PathPlaceHolder in the Vector parameter.
  76:    * This seems to be a non-public class, so I can only make guesses about the
  77:    * use of it.
  78:    */
  79:   private static class PathPlaceHolder
  80:   {
  81:     /**
  82:      * The path that we wrap.
  83:      */
  84:     TreePath path;
  85: 
  86:     /**
  87:      * Indicates if the path is new or already in the selection.
  88:      */
  89:     boolean isNew;
  90: 
  91:     /**
  92:      * Creates a new instance.
  93:      *
  94:      * @param p the path to wrap
  95:      * @param n if the path is new or already in the selection
  96:      */
  97:     PathPlaceHolder(TreePath p, boolean n)
  98:     {
  99:       path = p;
 100:       isNew = n;
 101:     }
 102:   }
 103: 
 104:   /**
 105:    * Use serialVersionUID for interoperability.
 106:    */
 107:   static final long serialVersionUID = 3288129636638950196L;
 108: 
 109:   /**
 110:    * The name of the selection mode property.
 111:    */
 112:   public static final String SELECTION_MODE_PROPERTY = "selectionMode";
 113: 
 114:   /**
 115:    * Our Swing property change support.
 116:    */
 117:   protected SwingPropertyChangeSupport changeSupport;
 118: 
 119:   /**
 120:    * The current selection.
 121:    */
 122:   protected TreePath[] selection;
 123: 
 124:   /**
 125:    * Our TreeSelectionListeners.
 126:    */
 127:   protected EventListenerList listenerList;
 128: 
 129:   /**
 130:    * The current RowMapper.
 131:    */
 132:   protected transient RowMapper rowMapper;
 133: 
 134:   /**
 135:    * The current listSelectionModel.
 136:    */
 137:   protected DefaultListSelectionModel listSelectionModel;
 138: 
 139:   /**
 140:    * The current selection mode.
 141:    */
 142:   protected int selectionMode;
 143: 
 144:   /**
 145:    * The path that has been added last.
 146:    */
 147:   protected TreePath leadPath;
 148: 
 149:   /**
 150:    * The index of the last added path.
 151:    */
 152:   protected int leadIndex;
 153: 
 154:   /**
 155:    * The row of the last added path according to the RowMapper.
 156:    */
 157:   protected int leadRow = -1;
 158: 
 159:   /**
 160:    * A supporting datastructure that is used in addSelectionPaths() and
 161:    * removeSelectionPaths(). It contains currently selected paths.
 162:    *
 163:    * @see #addSelectionPaths(TreePath[])
 164:    * @see #removeSelectionPaths(TreePath[])
 165:    * @see #setSelectionPaths(TreePath[])
 166:    */
 167:   private transient HashSet selectedPaths;
 168: 
 169:   /**
 170:    * A supporting datastructure that is used in addSelectionPaths() and
 171:    * removeSelectionPaths(). It contains the paths that are added or removed.
 172:    *
 173:    * @see #addSelectionPaths(TreePath[])
 174:    * @see #removeSelectionPaths(TreePath[])
 175:    * @see #setSelectionPaths(TreePath[])
 176:    */
 177:   private transient HashSet tmpPaths;
 178: 
 179:   /**
 180:    * Constructs a new DefaultTreeSelectionModel.
 181:    */
 182:   public DefaultTreeSelectionModel()
 183:   {
 184:     setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
 185:     listSelectionModel = new DefaultListSelectionModel();
 186:     listenerList = new EventListenerList();
 187:     leadIndex = -1;
 188:     tmpPaths = new HashSet();
 189:     selectedPaths = new HashSet();
 190:   }
 191: 
 192:   /**
 193:    * Creates a clone of this DefaultTreeSelectionModel with the same selection.
 194:    * The cloned instance will have the same registered listeners, the listeners
 195:    * themselves will not be cloned. The selection will be cloned.
 196:    * 
 197:    * @exception CloneNotSupportedException should not be thrown here
 198:    * @return a copy of this DefaultTreeSelectionModel
 199:    */
 200:   public Object clone() throws CloneNotSupportedException
 201:   {
 202:     DefaultTreeSelectionModel cloned = 
 203:       (DefaultTreeSelectionModel) super.clone();
 204:     cloned.changeSupport = null;
 205:     cloned.selection = (TreePath[]) selection.clone();
 206:     cloned.listenerList = new EventListenerList();
 207:     cloned.listSelectionModel =
 208:       (DefaultListSelectionModel) listSelectionModel.clone();
 209:     cloned.selectedPaths = new HashSet();
 210:     cloned.tmpPaths = new HashSet();
 211: 
 212:     return cloned;
 213:   }
 214: 
 215:   /**
 216:    * Returns a string that shows this object's properties.
 217:    * The returned string lists the selected tree rows, if any.
 218:    * 
 219:    * @return a string that shows this object's properties
 220:    */
 221:   public String toString() 
 222:   {
 223:     if (isSelectionEmpty())
 224:       return "[selection empty]";
 225:     else
 226:       {
 227:         StringBuffer b = new StringBuffer("selected rows: [");
 228:         for (int i = 0; i < selection.length; i++)
 229:           {
 230:             b.append(getRow(selection[i]));
 231:             b.append(' ');
 232:           }
 233:         b.append(", lead " + getLeadSelectionRow());
 234:         return b.toString();
 235:       }
 236:   }
 237: 
 238:   /**
 239:    * writeObject
 240:    * 
 241:    * @param value0 TODO
 242:    * @exception IOException TODO
 243:    */
 244:   private void writeObject(ObjectOutputStream value0) throws IOException
 245:   {
 246:     // TODO
 247:   }
 248: 
 249:   /**
 250:    * readObject
 251:    * 
 252:    * @param value0 TODO
 253:    * @exception IOException TODO
 254:    * @exception ClassNotFoundException TODO
 255:    */
 256:   private void readObject(ObjectInputStream value0) throws IOException,
 257:       ClassNotFoundException
 258:   {
 259:     // TODO
 260:   }
 261: 
 262:   /**
 263:    * Sets the RowMapper that should be used to map between paths and their rows.
 264:    * 
 265:    * @param mapper the RowMapper to set
 266:    * @see RowMapper
 267:    */
 268:   public void setRowMapper(RowMapper mapper)
 269:   {
 270:     rowMapper = mapper;
 271:     resetRowSelection();
 272:   }
 273: 
 274:   /**
 275:    * Returns the RowMapper that is currently used to map between paths and their
 276:    * rows.
 277:    * 
 278:    * @return the current RowMapper
 279:    * @see RowMapper
 280:    */
 281:   public RowMapper getRowMapper()
 282:   {
 283:     return rowMapper;
 284:   }
 285: 
 286:   /**
 287:    * Sets the current selection mode. Possible values are
 288:    * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and
 289:    * {@link #DISCONTIGUOUS_TREE_SELECTION}.
 290:    * 
 291:    * @param mode the selection mode to be set
 292:    * @see #getSelectionMode
 293:    * @see #SINGLE_TREE_SELECTION
 294:    * @see #CONTIGUOUS_TREE_SELECTION
 295:    * @see #DISCONTIGUOUS_TREE_SELECTION
 296:    */
 297:   public void setSelectionMode(int mode)
 298:   {
 299:     int oldMode = selectionMode;
 300:     selectionMode = mode;
 301:     // Make sure we have a valid selection mode.
 302:     if (selectionMode != SINGLE_TREE_SELECTION
 303:         && selectionMode != CONTIGUOUS_TREE_SELECTION
 304:         && selectionMode != DISCONTIGUOUS_TREE_SELECTION)
 305:       selectionMode = DISCONTIGUOUS_TREE_SELECTION;
 306: 
 307:     // Fire property change event.
 308:     if (oldMode != selectionMode && changeSupport != null)
 309:       changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode,
 310:                                        selectionMode);
 311:   }
 312: 
 313:   /**
 314:    * Returns the current selection mode.
 315:    * 
 316:    * @return the current selection mode
 317:    * @see #setSelectionMode
 318:    * @see #SINGLE_TREE_SELECTION
 319:    * @see #CONTIGUOUS_TREE_SELECTION
 320:    * @see #DISCONTIGUOUS_TREE_SELECTION
 321:    */
 322:   public int getSelectionMode()
 323:   {
 324:     return selectionMode;
 325:   }
 326: 
 327:   /**
 328:    * Sets this path as the only selection. If this changes the selection the
 329:    * registered TreeSelectionListeners are notified.
 330:    * 
 331:    * @param path the path to set as selection
 332:    */
 333:   public void setSelectionPath(TreePath path)
 334:   {
 335:     TreePath[] paths = null;
 336:     if (path != null)
 337:       paths = new TreePath[]{ path };
 338:     setSelectionPaths(paths);
 339:   }
 340:   
 341:   /**
 342:    * Get the number of the tree row for the given path.
 343:    * 
 344:    * @param path the tree path
 345:    * @return the tree row for this path or -1 if the path is not visible.
 346:    */
 347:   int getRow(TreePath path)
 348:   {
 349:     RowMapper mapper = getRowMapper();
 350: 
 351:     if (mapper instanceof AbstractLayoutCache)
 352:       {
 353:         // The absolute majority of cases, unless the TreeUI is very
 354:         // seriously rewritten
 355:         AbstractLayoutCache ama = (AbstractLayoutCache) mapper;
 356:         return ama.getRowForPath(path);
 357:       }
 358:     else if (mapper != null)
 359:       {
 360:         // Generic non optimized implementation.
 361:         int[] rows = mapper.getRowsForPaths(new TreePath[] { path });
 362:         if (rows.length == 0)
 363:           return - 1;
 364:         else
 365:           return rows[0];
 366:       }
 367:     return -1;
 368:   }
 369: 
 370:   /**
 371:    * Sets the paths as selection. This method checks for duplicates and removes
 372:    * them. If this changes the selection the registered TreeSelectionListeners
 373:    * are notified.
 374:    * 
 375:    * @param paths the paths to set as selection
 376:    */
 377:   public void setSelectionPaths(TreePath[] paths)
 378:   {
 379:     int oldLength = 0;
 380:     if (selection != null)
 381:       oldLength = selection.length;
 382:     int newLength = 0;
 383:     if (paths != null)
 384:       newLength = paths.length;
 385:     if (newLength > 0 || oldLength > 0)
 386:       {
 387:         // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with
 388:         // a non-contiguous path, we only allow the first path element.
 389:         if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1)
 390:             || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0
 391:                 && ! arePathsContiguous(paths)))
 392:           {
 393:             paths = new TreePath[] { paths[0] };
 394:             newLength = 1;
 395:           }
 396:         // Find new paths.
 397:         Vector changedPaths = null;
 398:         tmpPaths.clear();
 399:         int validPaths = 0;
 400:         TreePath oldLeadPath = leadPath;
 401:         for (int i = 0; i < newLength; i++)
 402:           {
 403:             if (paths[i] != null && ! tmpPaths.contains(paths[i]))
 404:               {
 405:                 validPaths++;
 406:                 tmpPaths.add(paths[i]);
 407:                 if (! selectedPaths.contains(paths[i]))
 408:                   {
 409:                     if (changedPaths == null)
 410:                       changedPaths = new Vector();
 411:                     changedPaths.add(new PathPlaceHolder(paths[i], true));
 412:                   }
 413:                 leadPath = paths[i];
 414:               }
 415:           }
 416:         // Put together the new selection.
 417:         TreePath[] newSelection = null;
 418:         if (validPaths != 0)
 419:           {
 420:             if (validPaths != newLength)
 421:               {
 422:                 // Some of the paths are already selected, put together
 423:                 // the new selection carefully.
 424:                 newSelection = new TreePath[validPaths];
 425:                 Iterator newPaths = tmpPaths.iterator();
 426:                 validPaths = 0;
 427:                 for (int i = 0; newPaths.hasNext(); i++)
 428:                   newSelection[i] = (TreePath) newPaths.next();
 429:               }
 430:             else
 431:               {
 432:                 newSelection = new TreePath[paths.length];
 433:                 System.arraycopy(paths, 0, newSelection, 0, paths.length);
 434:               }
 435:           }
 436: 
 437:         // Find paths that have been selected, but are no more.
 438:         for (int i = 0; i < oldLength; i++)
 439:           {
 440:             if (selection[i] != null && ! tmpPaths.contains(selection[i]))
 441:               {
 442:                 if (changedPaths == null)
 443:                   changedPaths = new Vector();
 444:                 changedPaths.add(new PathPlaceHolder(selection[i], false));
 445:               }
 446:           }
 447: 
 448:         // Perform changes and notification.
 449:         selection = newSelection;
 450:         HashSet tmp = selectedPaths;
 451:         selectedPaths = tmpPaths;
 452:         tmpPaths = tmp;
 453:         tmpPaths.clear();
 454: 
 455:         // Not necessary, but required according to the specs and to tests.
 456:         if (selection != null)
 457:           insureUniqueness();
 458:         updateLeadIndex();
 459:         resetRowSelection();
 460:         if (changedPaths != null && changedPaths.size() > 0)
 461:           notifyPathChange(changedPaths, oldLeadPath);
 462:       }
 463:   }
 464: 
 465:   /**
 466:    * Adds a path to the list of selected paths. This method checks if the path
 467:    * is already selected and doesn't add the same path twice. If this changes
 468:    * the selection the registered TreeSelectionListeners are notified.
 469:    * 
 470:    * The lead path is changed to the added path. This also happen if the 
 471:    * passed path was already selected before.
 472:    * 
 473:    * @param path the path to add to the selection
 474:    */
 475:   public void addSelectionPath(TreePath path)
 476:   {
 477:     if (path != null)
 478:       {
 479:         TreePath[] add = new TreePath[]{ path };
 480:         addSelectionPaths(add);
 481:       }
 482:   }
 483: 
 484:   /**
 485:    * Adds the paths to the list of selected paths. This method checks if the
 486:    * paths are already selected and doesn't add the same path twice. If this
 487:    * changes the selection the registered TreeSelectionListeners are notified.
 488:    * 
 489:    * @param paths the paths to add to the selection
 490:    */
 491:   public void addSelectionPaths(TreePath[] paths)
 492:   {
 493:     int length = paths != null ? paths.length : 0;
 494:     if (length > 0)
 495:       {
 496:         if (selectionMode == SINGLE_TREE_SELECTION)
 497:           setSelectionPaths(paths);
 498:         else if (selectionMode == CONTIGUOUS_TREE_SELECTION
 499:                  &&  ! canPathsBeAdded(paths))
 500:           {
 501:             if (arePathsContiguous(paths))
 502:               setSelectionPaths(paths);
 503:             else
 504:               setSelectionPaths(new TreePath[] { paths[0] });
 505:           }
 506:         else
 507:           {
 508:             Vector changedPaths = null;
 509:             tmpPaths.clear();
 510:             int validPaths = 0;
 511:             TreePath oldLeadPath = leadPath;
 512:             int oldPaths = 0;
 513:             if (selection != null)
 514:               oldPaths = selection.length;
 515:             int i;
 516:             for (i = 0; i < length; i++)
 517:               {
 518:                 if (paths[i] != null)
 519:                   {
 520:                     if (! selectedPaths.contains(paths[i]))
 521:                       {
 522:                         validPaths++;
 523:                         if (changedPaths == null)
 524:                           changedPaths = new Vector();
 525:                         changedPaths.add(new PathPlaceHolder(paths[i], true));
 526:                         selectedPaths.add(paths[i]);
 527:                         tmpPaths.add(paths[i]);
 528:                       }
 529:                     leadPath = paths[i];
 530:                   }
 531:               }
 532:             if (validPaths > 0)
 533:               {
 534:                 TreePath[] newSelection = new TreePath[oldPaths + validPaths];
 535:                 if (oldPaths > 0)
 536:                   System.arraycopy(selection, 0, newSelection, 0, oldPaths);
 537:                 if (validPaths != paths.length)
 538:                   {
 539:                     // Some of the paths are already selected, put together
 540:                     // the new selection carefully.
 541:                     Iterator newPaths = tmpPaths.iterator();
 542:                     i = oldPaths;
 543:                     while (newPaths.hasNext())
 544:                       {
 545:                         newSelection[i] = (TreePath) newPaths.next();
 546:                         i++;
 547:                       }
 548:                   }
 549:                 else
 550:                   System.arraycopy(paths, 0, newSelection, oldPaths,
 551:                                    validPaths);
 552:                 selection = newSelection;
 553:                 insureUniqueness();
 554:                 updateLeadIndex();
 555:                 resetRowSelection();
 556:                 if (changedPaths != null && changedPaths.size() > 0)
 557:                   notifyPathChange(changedPaths, oldLeadPath);
 558:               }
 559:             else
 560:               leadPath = oldLeadPath;
 561:             tmpPaths.clear();
 562:           }
 563:       }
 564:   }
 565: 
 566:   /**
 567:    * Removes the path from the selection. If this changes the selection the
 568:    * registered TreeSelectionListeners are notified.
 569:    * 
 570:    * @param path the path to remove
 571:    */
 572:   public void removeSelectionPath(TreePath path)
 573:   {
 574:     if (path != null)
 575:       removeSelectionPaths(new TreePath[]{ path });
 576:   }
 577: 
 578:   /**
 579:    * Removes the paths from the selection. If this changes the selection the
 580:    * registered TreeSelectionListeners are notified.
 581:    * 
 582:    * @param paths the paths to remove
 583:    */
 584:   public void removeSelectionPaths(TreePath[] paths)
 585:   {
 586:     if (paths != null && selection != null && paths.length > 0)
 587:       {
 588:         if (! canPathsBeRemoved(paths))
 589:           clearSelection();
 590:         else
 591:           {
 592:             Vector pathsToRemove = null;
 593:             for (int i = paths.length - 1; i >= 0; i--)
 594:               {
 595:                 if (paths[i] != null && selectedPaths.contains(paths[i]))
 596:                   {
 597:                     if (pathsToRemove == null)
 598:                       pathsToRemove = new Vector();
 599:                     selectedPaths.remove(paths[i]);
 600:                     pathsToRemove.add(new PathPlaceHolder(paths[i],
 601:                                                           false));
 602:                   }
 603:               }
 604:             if (pathsToRemove != null)
 605:               {
 606:                 int numRemove = pathsToRemove.size();
 607:                 TreePath oldLead = leadPath;
 608:                 if (numRemove == selection.length)
 609:                   selection = null;
 610:                 else
 611:                   {
 612:                     selection = new TreePath[selection.length - numRemove];
 613:                     Iterator keep = selectedPaths.iterator();
 614:                     for (int valid = 0; keep.hasNext(); valid++)
 615:                       selection[valid] = (TreePath) keep.next();
 616:                   }
 617:                 // Update lead path.
 618:                 if (leadPath != null && ! selectedPaths.contains(leadPath))
 619:                   {
 620:                     if (selection != null)
 621:                       leadPath = selection[selection.length - 1];
 622:                     else
 623:                       leadPath = null;
 624:                   }
 625:                 else if (selection != null)
 626:                   leadPath = selection[selection.length - 1];
 627:                 else
 628:                   leadPath = null;
 629:                 updateLeadIndex();
 630:                 resetRowSelection();
 631:                 notifyPathChange(pathsToRemove, oldLead);
 632:               }
 633:           }
 634:       }
 635:   }
 636: 
 637:   /**
 638:    * Returns the first path in the selection. This is especially useful when the
 639:    * selectionMode is {@link #SINGLE_TREE_SELECTION}.
 640:    * 
 641:    * @return the first path in the selection
 642:    */
 643:   public TreePath getSelectionPath()
 644:   {
 645:     if ((selection == null) || (selection.length == 0))
 646:       return null;
 647:     else
 648:       return selection[0];
 649:   }
 650: 
 651:   /**
 652:    * Returns the complete selection.
 653:    * 
 654:    * @return the complete selection
 655:    */
 656:   public TreePath[] getSelectionPaths()
 657:   {
 658:     return selection;
 659:   }
 660: 
 661:   /**
 662:    * Returns the number of paths in the selection.
 663:    * 
 664:    * @return the number of paths in the selection
 665:    */
 666:   public int getSelectionCount()
 667:   {
 668:     if (selection == null)
 669:       return 0;
 670:     else
 671:       return selection.length;
 672:   }
 673: 
 674:   /**
 675:    * Checks if a given path is in the selection.
 676:    * 
 677:    * @param path the path to check
 678:    * @return <code>true</code> if the path is in the selection,
 679:    *         <code>false</code> otherwise
 680:    */
 681:   public boolean isPathSelected(TreePath path)
 682:   {
 683:     if (selection == null)
 684:       return false;
 685: 
 686:     for (int i = 0; i < selection.length; i++)
 687:       {
 688:         if (selection[i].equals(path))
 689:           return true;
 690:       }
 691:     return false;
 692:   }
 693: 
 694:   /**
 695:    * Checks if the selection is empty.
 696:    * 
 697:    * @return <code>true</code> if the selection is empty, <code>false</code>
 698:    *         otherwise
 699:    */
 700:   public boolean isSelectionEmpty()
 701:   {
 702:     return (selection == null) || (selection.length == 0);
 703:   }
 704: 
 705:   /**
 706:    * Removes all paths from the selection. Fire the unselection event.
 707:    */
 708:   public void clearSelection()
 709:   {
 710:     if (selection != null)
 711:       {
 712:         int selectionLength = selection.length;
 713:         boolean[] news = new boolean[selectionLength];
 714:         Arrays.fill(news, false);
 715:         TreeSelectionEvent event = new TreeSelectionEvent(this, selection,
 716:                                                           news, leadPath,
 717:                                                           null);
 718:         leadPath = null;
 719:         leadIndex = 0;
 720:         leadRow = 0;
 721:         selectedPaths.clear();
 722:         selection = null;
 723:         resetRowSelection();
 724:         fireValueChanged(event);
 725:       }
 726:   }
 727: 
 728:   /**
 729:    * Adds a <code>TreeSelectionListener</code> object to this model.
 730:    * 
 731:    * @param listener the listener to add
 732:    */
 733:   public void addTreeSelectionListener(TreeSelectionListener listener)
 734:   {
 735:     listenerList.add(TreeSelectionListener.class, listener);
 736:   }
 737: 
 738:   /**
 739:    * Removes a <code>TreeSelectionListener</code> object from this model.
 740:    * 
 741:    * @param listener the listener to remove
 742:    */
 743:   public void removeTreeSelectionListener(TreeSelectionListener listener)
 744:   {
 745:     listenerList.remove(TreeSelectionListener.class, listener);
 746:   }
 747: 
 748:   /**
 749:    * Returns all <code>TreeSelectionListener</code> added to this model.
 750:    * 
 751:    * @return an array of listeners
 752:    * @since 1.4
 753:    */
 754:   public TreeSelectionListener[] getTreeSelectionListeners()
 755:   {
 756:     return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class);
 757:   }
 758: 
 759:   /**
 760:    * fireValueChanged
 761:    * 
 762:    * @param event the event to fire.
 763:    */
 764:   protected void fireValueChanged(TreeSelectionEvent event)
 765:   {
 766:     TreeSelectionListener[] listeners = getTreeSelectionListeners();
 767: 
 768:     for (int i = 0; i < listeners.length; ++i)
 769:       listeners[i].valueChanged(event);
 770:   }
 771: 
 772:   /**
 773:    * Returns all added listeners of a special type.
 774:    * 
 775:    * @param listenerType the listener type
 776:    * @return an array of listeners
 777:    * @since 1.3
 778:    */
 779:   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
 780:   {
 781:     return listenerList.getListeners(listenerType);
 782:   }
 783: 
 784:   /**
 785:    * Returns the currently selected rows.
 786:    * 
 787:    * @return the currently selected rows
 788:    */
 789:   public int[] getSelectionRows()
 790:   {
 791:     int[] rows = null;
 792:     if (rowMapper != null && selection != null)
 793:       {
 794:         rows = rowMapper.getRowsForPaths(selection);
 795:         if (rows != null)
 796:           {
 797:             // Find invisible rows.
 798:             int invisible = 0;
 799:             for (int i = rows.length - 1; i >= 0; i--)
 800:               {
 801:                 if (rows[i] == -1)
 802:                   invisible++;
 803:                 
 804:               }
 805:             // Clean up invisible rows.
 806:             if (invisible > 0)
 807:               {
 808:                 if (invisible == rows.length)
 809:                   rows = null;
 810:                 else
 811:                   {
 812:                     int[] newRows = new int[rows.length - invisible];
 813:                     int visCount = 0;
 814:                     for (int i = rows.length - 1; i >= 0; i--)
 815:                       {
 816:                         if (rows[i] != -1)
 817:                           {
 818:                             newRows[visCount] = rows[i];
 819:                             visCount++;
 820:                           }
 821:                       }
 822:                     rows = newRows;
 823:                   }
 824:               }
 825:           }
 826:       }
 827:     return rows;
 828:   }
 829: 
 830:   /**
 831:    * Returns the smallest row index from the selection.
 832:    * 
 833:    * @return the smallest row index from the selection
 834:    */
 835:   public int getMinSelectionRow()
 836:   {
 837:     return listSelectionModel.getMinSelectionIndex();
 838:   }
 839: 
 840:   /**
 841:    * Returns the largest row index from the selection.
 842:    * 
 843:    * @return the largest row index from the selection
 844:    */
 845:   public int getMaxSelectionRow()
 846:   {
 847:     return listSelectionModel.getMaxSelectionIndex();
 848:   }
 849: 
 850:   /**
 851:    * Checks if a particular row is selected.
 852:    * 
 853:    * @param row the index of the row to check
 854:    * @return <code>true</code> if the row is in this selection,
 855:    *         <code>false</code> otherwise
 856:    * @throws NullPointerException if the row mapper is not set (can only happen
 857:    *           if the user has plugged in the custom incorrect TreeUI
 858:    *           implementation.
 859:    */
 860:   public boolean isRowSelected(int row)
 861:   {
 862:     return listSelectionModel.isSelectedIndex(row);
 863:   }
 864: 
 865:   /**
 866:    * Updates the mappings from TreePaths to row indices.
 867:    */
 868:   public void resetRowSelection()
 869:   {
 870:     listSelectionModel.clearSelection();
 871:     if (selection != null && rowMapper != null)
 872:       {
 873:         int[] rows = rowMapper.getRowsForPaths(selection);
 874:         // Update list selection model.
 875:         for (int i = 0; i < rows.length; i++)
 876:           {
 877:             int row = rows[i];
 878:             if (row != -1)
 879:               listSelectionModel.addSelectionInterval(row, row);
 880:           }
 881:         // Update lead selection.
 882:         if (leadIndex != -1 && rows != null)
 883:           leadRow = rows[leadIndex];
 884:         else if (leadPath != null)
 885:           {
 886:             TreePath[] tmp = new TreePath[]{ leadPath };
 887:             rows = rowMapper.getRowsForPaths(tmp);
 888:             leadRow = rows != null ? rows[0] : -1;
 889:           }
 890:         else
 891:           leadRow = -1;
 892:         insureRowContinuity();
 893:       }
 894:     else
 895:       leadRow = -1;
 896:   }
 897: 
 898:   /**
 899:    * getLeadSelectionRow
 900:    * 
 901:    * @return int
 902:    */
 903:   public int getLeadSelectionRow()
 904:   {
 905:     return leadRow;
 906:   }
 907: 
 908:   /**
 909:    * getLeadSelectionPath
 910:    * 
 911:    * @return TreePath
 912:    */
 913:   public TreePath getLeadSelectionPath()
 914:   {
 915:     return leadPath;
 916:   }
 917: 
 918:   /**
 919:    * Adds a <code>PropertyChangeListener</code> object to this model.
 920:    * 
 921:    * @param listener the listener to add.
 922:    */
 923:   public void addPropertyChangeListener(PropertyChangeListener listener)
 924:   {
 925:     if (changeSupport == null)
 926:       changeSupport = new SwingPropertyChangeSupport(this);
 927:     changeSupport.addPropertyChangeListener(listener);
 928:   }
 929: 
 930:   /**
 931:    * Removes a <code>PropertyChangeListener</code> object from this model.
 932:    * 
 933:    * @param listener the listener to remove.
 934:    */
 935:   public void removePropertyChangeListener(PropertyChangeListener listener)
 936:   {
 937:     if (changeSupport != null)
 938:       changeSupport.removePropertyChangeListener(listener);
 939:   }
 940: 
 941:   /**
 942:    * Returns all added <code>PropertyChangeListener</code> objects.
 943:    * 
 944:    * @return an array of listeners.
 945:    * @since 1.4
 946:    */
 947:   public PropertyChangeListener[] getPropertyChangeListeners()
 948:   {
 949:     PropertyChangeListener[] listeners = null;
 950:     if (changeSupport != null)
 951:       listeners = changeSupport.getPropertyChangeListeners();
 952:     else
 953:       listeners = new PropertyChangeListener[0];
 954:     return listeners;
 955:   }
 956: 
 957:   /**
 958:    * Makes sure the currently selected paths are valid according to the current
 959:    * selectionMode. If the selectionMode is set to
 960:    * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then
 961:    * the selection is reset to the first set of contguous paths. If the
 962:    * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection
 963:    * has more than one path, the selection is reset to the contain only the
 964:    * first path.
 965:    */
 966:   protected void insureRowContinuity()
 967:   {
 968:     if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null
 969:         && rowMapper != null)
 970:       {
 971:         int min = listSelectionModel.getMinSelectionIndex();
 972:         if (min != -1)
 973:           {
 974:             int max = listSelectionModel.getMaxSelectionIndex();
 975:             for (int i = min; i <= max; i++)
 976:               {
 977:                 if (! listSelectionModel.isSelectedIndex(i))
 978:                   {
 979:                     if (i == min)
 980:                       clearSelection();
 981:                     else
 982:                       {
 983:                         TreePath[] newSelection = new TreePath[i - min];
 984:                         int[] rows = rowMapper.getRowsForPaths(selection);
 985:                         for (int j = 0; j < rows.length; j++)
 986:                           {
 987:                             if (rows[j] < i)
 988:                               newSelection[rows[j] - min] = selection[j];
 989:                           }
 990:                         setSelectionPaths(newSelection);
 991:                         break;
 992:                       }
 993:                   }
 994:               }
 995:           }
 996:       }
 997:     else if (selectionMode == SINGLE_TREE_SELECTION && selection != null
 998:         && selection.length > 1)
 999:       setSelectionPath(selection[0]);
1000:   }
1001:   
1002:   /**
1003:    * Returns <code>true</code> if the paths are contiguous (take subsequent
1004:    * rows in the diplayed tree view. The method returns <code>true</code> if
1005:    * we have no RowMapper assigned.
1006:    * 
1007:    * @param paths the paths to check for continuity
1008:    * @return <code>true</code> if the paths are contiguous or we have no
1009:    *         RowMapper assigned
1010:    */
1011:   protected boolean arePathsContiguous(TreePath[] paths)
1012:   {
1013:     if (rowMapper == null || paths.length < 2)
1014:       return true;
1015: 
1016:     int length = paths.length;
1017:     TreePath[] tmp = new TreePath[1];
1018:     tmp[0] = paths[0];
1019:     int min = rowMapper.getRowsForPaths(tmp)[0];
1020:     BitSet selected = new BitSet();
1021:     int valid = 0;
1022:     for (int i = 0; i < length; i++)
1023:       {
1024:         if (paths[i] != null)
1025:           {
1026:             tmp[0] = paths[i];
1027:             int[] rows = rowMapper.getRowsForPaths(tmp);
1028:             if (rows == null)
1029:               return false; // No row mapping yet, can't be selected.
1030:             int row = rows[0];
1031:             if (row == -1 || row < (min - length) || row > (min + length))
1032:               return false; // Not contiguous.
1033:             min = Math.min(min, row);
1034:             if (! selected.get(row))
1035:               {
1036:                 selected.set(row);
1037:                 valid++;
1038:               }
1039:             
1040:           }
1041:       }
1042:     int max = valid + min;
1043:     for (int i = min; i < max; i++)
1044:       if (! selected.get(i))
1045:         return false; // Not contiguous.
1046:     return true;
1047:   }
1048: 
1049:   /**
1050:    * Checks if the paths can be added. This returns <code>true</code> if:
1051:    * <ul>
1052:    * <li><code>paths</code> is <code>null</code> or empty</li>
1053:    * <li>we have no RowMapper assigned</li>
1054:    * <li>nothing is currently selected</li>
1055:    * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li>
1056:    * <li>adding the paths to the selection still results in a contiguous set of
1057:    * paths</li>
1058:    * 
1059:    * @param paths the paths to check
1060:    * @return <code>true</code> if the paths can be added with respect to the
1061:    *         selectionMode
1062:    */
1063:   protected boolean canPathsBeAdded(TreePath[] paths)
1064:   {
1065:     if (paths == null || paths.length == 0 || rowMapper == null
1066:         || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1067:       return true;
1068: 
1069:     BitSet selected = new BitSet();
1070:     int min = listSelectionModel.getMinSelectionIndex();
1071:     int max = listSelectionModel.getMaxSelectionIndex();
1072:     TreePath[] tmp = new TreePath[1];
1073:     if (min != -1)
1074:       {
1075:         // Set the bitmask of selected elements.
1076:         for (int i = min; i <= max; i++)
1077:           selected.set(i);
1078:       }
1079:     else
1080:       {
1081:         tmp[0] = paths[0];
1082:         min = rowMapper.getRowsForPaths(tmp)[0];
1083:         max = min;
1084:       }
1085:     // Mark new paths as selected.
1086:     for (int i = paths.length - 1; i >= 0; i--)
1087:       {
1088:         if (paths[i] != null)
1089:           {
1090:             tmp[0] = paths[i];
1091:             int[] rows = rowMapper.getRowsForPaths(tmp);
1092:             if (rows == null)
1093:               return false; // Now row mapping yet, can't be selected.
1094:             int row = rows[0];
1095:             if (row == -1)
1096:               return false; // Now row mapping yet, can't be selected.
1097:             min = Math.min(min, row);
1098:             max = Math.max(max, row);
1099:             selected.set(row);
1100:           }
1101:       }
1102:     // Now look if the new selection would be contiguous.
1103:     for (int i = min; i <= max; i++)
1104:       if (! selected.get(i))
1105:         return false;
1106:     return true;
1107:   }
1108:   
1109:   /**
1110:    * Checks if the paths can be removed without breaking the continuity of the
1111:    * selection according to selectionMode.
1112:    * 
1113:    * @param paths the paths to check
1114:    * @return <code>true</code> if the paths can be removed with respect to the
1115:    *         selectionMode
1116:    */
1117:   protected boolean canPathsBeRemoved(TreePath[] paths)
1118:   {
1119:     if (rowMapper == null || isSelectionEmpty()
1120:         || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1121:       return true;
1122:     
1123:     HashSet set = new HashSet();
1124:     for (int i = 0; i < selection.length; i++)
1125:       set.add(selection[i]);
1126:     
1127:     for (int i = 0; i < paths.length; i++)
1128:       set.remove(paths[i]);
1129:     
1130:     TreePath[] remaining = new TreePath[set.size()];
1131:     Iterator iter = set.iterator();
1132:     
1133:     for (int i = 0; i < remaining.length; i++)
1134:       remaining[i] = (TreePath) iter.next();
1135:     
1136:     return arePathsContiguous(remaining);
1137:   }
1138: 
1139:   /**
1140:    * Notify the installed listeners that the given patches have changed. This
1141:    * method will call listeners if invoked, but it is not called from the
1142:    * implementation of this class.
1143:    * 
1144:    * @param vPaths the vector of the changed patches
1145:    * @param oldLeadSelection the old selection index
1146:    */
1147:   protected void notifyPathChange(Vector vPaths, TreePath oldLeadSelection)
1148:   {
1149: 
1150:     int numChangedPaths = vPaths.size();
1151:     boolean[] news = new boolean[numChangedPaths];
1152:     TreePath[] paths = new TreePath[numChangedPaths];
1153:     for (int i = 0; i < numChangedPaths; i++)
1154:       {
1155:         PathPlaceHolder p = (PathPlaceHolder) vPaths.get(i);
1156:         news[i] = p.isNew;
1157:         paths[i] = p.path;
1158:       }
1159: 
1160:     TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news,
1161:                                                       oldLeadSelection,
1162:                                                       leadPath);
1163:     fireValueChanged(event);
1164:   }
1165: 
1166:   /**
1167:    * Updates the lead selection row number after changing the lead selection
1168:    * path.
1169:    */
1170:   protected void updateLeadIndex()
1171:   {
1172:     leadIndex = -1;
1173:     if (leadPath != null)
1174:       {
1175:         leadRow = -1;
1176:         if (selection == null)
1177:           leadPath = null;
1178:         else
1179:           {
1180:             for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--)
1181:               {
1182:                 if (selection[i] == leadPath)
1183:                   leadIndex = i;
1184:               }
1185:           }
1186:       }
1187:   }
1188: 
1189:   /**
1190:    * This method exists due historical reasons and returns without action
1191:    * (unless overridden). For compatibility with the applications that override
1192:    * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and
1193:    * {@link #addSelectionPaths(TreePath[])}.
1194:    */
1195:   protected void insureUniqueness()
1196:   {
1197:     // Following the API 1.4, the method should return without action.
1198:   }
1199: }