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