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