1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing; 27 28 import java.util.EventListener; 29 import java.util.BitSet; 30 import java.io.Serializable; 31 import java.beans.Transient; 32 33 import javax.swing.event.*; 34 35 36 /** 37 * Default data model for list selections. 38 * <p> 39 * <strong>Warning:</strong> 40 * Serialized objects of this class will not be compatible with 41 * future Swing releases. The current serialization support is 42 * appropriate for short term storage or RMI between applications running 43 * the same version of Swing. As of 1.4, support for long term storage 44 * of all JavaBeans™ 45 * has been added to the <code>java.beans</code> package. 46 * Please see {@link java.beans.XMLEncoder}. 47 * 48 * @author Philip Milne 49 * @author Hans Muller 50 * @see ListSelectionModel 51 * @since 1.2 52 */ 53 @SuppressWarnings("serial") // Same-version serialization only 54 public class DefaultListSelectionModel implements ListSelectionModel, Cloneable, Serializable 55 { 56 private static final int MIN = -1; 57 private static final int MAX = Integer.MAX_VALUE; 58 private int selectionMode = MULTIPLE_INTERVAL_SELECTION; 59 private int minIndex = MAX; 60 private int maxIndex = MIN; 61 private int anchorIndex = -1; 62 private int leadIndex = -1; 63 private int firstAdjustedIndex = MAX; 64 private int lastAdjustedIndex = MIN; 65 private boolean isAdjusting = false; 66 67 private int firstChangedIndex = MAX; 68 private int lastChangedIndex = MIN; 69 70 private BitSet value = new BitSet(32); 71 /** 72 * The list of listeners. 73 */ 74 protected EventListenerList listenerList = new EventListenerList(); 75 76 /** 77 * Whether or not the lead anchor notification is enabled. 78 */ 79 protected boolean leadAnchorNotificationEnabled = true; 80 81 /** {@inheritDoc} */ 82 public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; } 83 84 /** {@inheritDoc} */ 85 public int getMaxSelectionIndex() { return maxIndex; } 86 87 /** {@inheritDoc} */ 88 public boolean getValueIsAdjusting() { return isAdjusting; } 89 90 /** {@inheritDoc} */ 91 public int getSelectionMode() { return selectionMode; } 92 93 /** 94 * {@inheritDoc} 95 * @throws IllegalArgumentException {@inheritDoc} 96 */ 97 public void setSelectionMode(int selectionMode) { 98 switch (selectionMode) { 99 case SINGLE_SELECTION: 100 case SINGLE_INTERVAL_SELECTION: 101 case MULTIPLE_INTERVAL_SELECTION: 102 this.selectionMode = selectionMode; 103 break; 104 default: 105 throw new IllegalArgumentException("invalid selectionMode"); 106 } 107 } 108 109 /** {@inheritDoc} */ 110 public boolean isSelectedIndex(int index) { 111 return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index); 112 } 113 114 /** {@inheritDoc} */ 115 public boolean isSelectionEmpty() { 116 return (minIndex > maxIndex); 117 } 118 119 /** {@inheritDoc} */ 120 public void addListSelectionListener(ListSelectionListener l) { 121 listenerList.add(ListSelectionListener.class, l); 122 } 123 124 /** {@inheritDoc} */ 125 public void removeListSelectionListener(ListSelectionListener l) { 126 listenerList.remove(ListSelectionListener.class, l); 127 } 128 129 /** 130 * Returns an array of all the list selection listeners 131 * registered on this <code>DefaultListSelectionModel</code>. 132 * 133 * @return all of this model's <code>ListSelectionListener</code>s 134 * or an empty 135 * array if no list selection listeners are currently registered 136 * 137 * @see #addListSelectionListener 138 * @see #removeListSelectionListener 139 * 140 * @since 1.4 141 */ 142 public ListSelectionListener[] getListSelectionListeners() { 143 return listenerList.getListeners(ListSelectionListener.class); 144 } 145 146 /** 147 * Notifies listeners that we have ended a series of adjustments. 148 * @param isAdjusting true if this is the final change in a series of 149 * adjustments 150 */ 151 protected void fireValueChanged(boolean isAdjusting) { 152 if (lastChangedIndex == MIN) { 153 return; 154 } 155 /* Change the values before sending the event to the 156 * listeners in case the event causes a listener to make 157 * another change to the selection. 158 */ 159 int oldFirstChangedIndex = firstChangedIndex; 160 int oldLastChangedIndex = lastChangedIndex; 161 firstChangedIndex = MAX; 162 lastChangedIndex = MIN; 163 fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex, isAdjusting); 164 } 165 166 167 /** 168 * Notifies <code>ListSelectionListeners</code> that the value 169 * of the selection, in the closed interval <code>firstIndex</code>, 170 * <code>lastIndex</code>, has changed. 171 * 172 * @param firstIndex the first index in the interval 173 * @param lastIndex the last index in the interval 174 */ 175 protected void fireValueChanged(int firstIndex, int lastIndex) { 176 fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting()); 177 } 178 179 /** 180 * @param firstIndex the first index in the interval 181 * @param lastIndex the last index in the interval 182 * @param isAdjusting true if this is the final change in a series of 183 * adjustments 184 * @see EventListenerList 185 */ 186 protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting) 187 { 188 Object[] listeners = listenerList.getListenerList(); 189 ListSelectionEvent e = null; 190 191 for (int i = listeners.length - 2; i >= 0; i -= 2) { 192 if (listeners[i] == ListSelectionListener.class) { 193 if (e == null) { 194 e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting); 195 } 196 ((ListSelectionListener)listeners[i+1]).valueChanged(e); 197 } 198 } 199 } 200 201 private void fireValueChanged() { 202 if (lastAdjustedIndex == MIN) { 203 return; 204 } 205 /* If getValueAdjusting() is true, (eg. during a drag opereration) 206 * record the bounds of the changes so that, when the drag finishes (and 207 * setValueAdjusting(false) is called) we can post a single event 208 * with bounds covering all of these individual adjustments. 209 */ 210 if (getValueIsAdjusting()) { 211 firstChangedIndex = Math.min(firstChangedIndex, firstAdjustedIndex); 212 lastChangedIndex = Math.max(lastChangedIndex, lastAdjustedIndex); 213 } 214 /* Change the values before sending the event to the 215 * listeners in case the event causes a listener to make 216 * another change to the selection. 217 */ 218 int oldFirstAdjustedIndex = firstAdjustedIndex; 219 int oldLastAdjustedIndex = lastAdjustedIndex; 220 firstAdjustedIndex = MAX; 221 lastAdjustedIndex = MIN; 222 223 fireValueChanged(oldFirstAdjustedIndex, oldLastAdjustedIndex); 224 } 225 226 /** 227 * Returns an array of all the objects currently registered as 228 * <code><em>Foo</em>Listener</code>s 229 * upon this model. 230 * <code><em>Foo</em>Listener</code>s 231 * are registered using the <code>add<em>Foo</em>Listener</code> method. 232 * <p> 233 * You can specify the <code>listenerType</code> argument 234 * with a class literal, such as <code><em>Foo</em>Listener.class</code>. 235 * For example, you can query a <code>DefaultListSelectionModel</code> 236 * instance <code>m</code> 237 * for its list selection listeners 238 * with the following code: 239 * 240 * <pre>ListSelectionListener[] lsls = (ListSelectionListener[])(m.getListeners(ListSelectionListener.class));</pre> 241 * 242 * If no such listeners exist, 243 * this method returns an empty array. 244 * 245 * @param <T> the type of {@code EventListener} class being requested 246 * @param listenerType the type of listeners requested; 247 * this parameter should specify an interface 248 * that descends from <code>java.util.EventListener</code> 249 * @return an array of all objects registered as 250 * <code><em>Foo</em>Listener</code>s 251 * on this model, 252 * or an empty array if no such 253 * listeners have been added 254 * @exception ClassCastException if <code>listenerType</code> doesn't 255 * specify a class or interface that implements 256 * <code>java.util.EventListener</code> 257 * 258 * @see #getListSelectionListeners 259 * 260 * @since 1.3 261 */ 262 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 263 return listenerList.getListeners(listenerType); 264 } 265 266 // Updates first and last change indices 267 private void markAsDirty(int r) { 268 if (r == -1) { 269 return; 270 } 271 272 firstAdjustedIndex = Math.min(firstAdjustedIndex, r); 273 lastAdjustedIndex = Math.max(lastAdjustedIndex, r); 274 } 275 276 // Sets the state at this index and update all relevant state. 277 private void set(int r) { 278 if (value.get(r)) { 279 return; 280 } 281 value.set(r); 282 markAsDirty(r); 283 284 // Update minimum and maximum indices 285 minIndex = Math.min(minIndex, r); 286 maxIndex = Math.max(maxIndex, r); 287 } 288 289 // Clears the state at this index and update all relevant state. 290 private void clear(int r) { 291 if (!value.get(r)) { 292 return; 293 } 294 value.clear(r); 295 markAsDirty(r); 296 297 // Update minimum and maximum indices 298 /* 299 If (r > minIndex) the minimum has not changed. 300 The case (r < minIndex) is not possible because r'th value was set. 301 We only need to check for the case when lowest entry has been cleared, 302 and in this case we need to search for the first value set above it. 303 */ 304 if (r == minIndex) { 305 for(minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) { 306 if (value.get(minIndex)) { 307 break; 308 } 309 } 310 } 311 /* 312 If (r < maxIndex) the maximum has not changed. 313 The case (r > maxIndex) is not possible because r'th value was set. 314 We only need to check for the case when highest entry has been cleared, 315 and in this case we need to search for the first value set below it. 316 */ 317 if (r == maxIndex) { 318 for(maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) { 319 if (value.get(maxIndex)) { 320 break; 321 } 322 } 323 } 324 /* Performance note: This method is called from inside a loop in 325 changeSelection() but we will only iterate in the loops 326 above on the basis of one iteration per deselected cell - in total. 327 Ie. the next time this method is called the work of the previous 328 deselection will not be repeated. 329 330 We also don't need to worry about the case when the min and max 331 values are in their unassigned states. This cannot happen because 332 this method's initial check ensures that the selection was not empty 333 and therefore that the minIndex and maxIndex had 'real' values. 334 335 If we have cleared the whole selection, set the minIndex and maxIndex 336 to their cannonical values so that the next set command always works 337 just by using Math.min and Math.max. 338 */ 339 if (isSelectionEmpty()) { 340 minIndex = MAX; 341 maxIndex = MIN; 342 } 343 } 344 345 /** 346 * Sets the value of the leadAnchorNotificationEnabled flag. 347 * 348 * @param flag boolean value for {@code leadAnchorNotificationEnabled} 349 * @see #isLeadAnchorNotificationEnabled() 350 */ 351 public void setLeadAnchorNotificationEnabled(boolean flag) { 352 leadAnchorNotificationEnabled = flag; 353 } 354 355 /** 356 * Returns the value of the <code>leadAnchorNotificationEnabled</code> flag. 357 * When <code>leadAnchorNotificationEnabled</code> is true the model 358 * generates notification events with bounds that cover all the changes to 359 * the selection plus the changes to the lead and anchor indices. 360 * Setting the flag to false causes a narrowing of the event's bounds to 361 * include only the elements that have been selected or deselected since 362 * the last change. Either way, the model continues to maintain the lead 363 * and anchor variables internally. The default is true. 364 * <p> 365 * Note: It is possible for the lead or anchor to be changed without a 366 * change to the selection. Notification of these changes is often 367 * important, such as when the new lead or anchor needs to be updated in 368 * the view. Therefore, caution is urged when changing the default value. 369 * 370 * @return the value of the <code>leadAnchorNotificationEnabled</code> flag 371 * @see #setLeadAnchorNotificationEnabled(boolean) 372 */ 373 public boolean isLeadAnchorNotificationEnabled() { 374 return leadAnchorNotificationEnabled; 375 } 376 377 private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) { 378 if (leadAnchorNotificationEnabled) { 379 if (this.anchorIndex != anchorIndex) { 380 markAsDirty(this.anchorIndex); 381 markAsDirty(anchorIndex); 382 } 383 384 if (this.leadIndex != leadIndex) { 385 markAsDirty(this.leadIndex); 386 markAsDirty(leadIndex); 387 } 388 } 389 this.anchorIndex = anchorIndex; 390 this.leadIndex = leadIndex; 391 } 392 393 private boolean contains(int a, int b, int i) { 394 return (i >= a) && (i <= b); 395 } 396 397 private void changeSelection(int clearMin, int clearMax, 398 int setMin, int setMax, boolean clearFirst) { 399 for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) { 400 401 boolean shouldClear = contains(clearMin, clearMax, i); 402 boolean shouldSet = contains(setMin, setMax, i); 403 404 if (shouldSet && shouldClear) { 405 if (clearFirst) { 406 shouldClear = false; 407 } 408 else { 409 shouldSet = false; 410 } 411 } 412 413 if (shouldSet) { 414 set(i); 415 } 416 if (shouldClear) { 417 clear(i); 418 } 419 } 420 fireValueChanged(); 421 } 422 423 /** 424 * Change the selection with the effect of first clearing the values 425 * in the inclusive range [clearMin, clearMax] then setting the values 426 * in the inclusive range [setMin, setMax]. Do this in one pass so 427 * that no values are cleared if they would later be set. 428 */ 429 private void changeSelection(int clearMin, int clearMax, int setMin, int setMax) { 430 changeSelection(clearMin, clearMax, setMin, setMax, true); 431 } 432 433 /** {@inheritDoc} */ 434 public void clearSelection() { 435 removeSelectionIntervalImpl(minIndex, maxIndex, false); 436 } 437 438 /** 439 * Changes the selection to be between {@code index0} and {@code index1} 440 * inclusive. {@code index0} doesn't have to be less than or equal to 441 * {@code index1}. 442 * <p> 443 * In {@code SINGLE_SELECTION} selection mode, only the second index 444 * is used. 445 * <p> 446 * If this represents a change to the current selection, then each 447 * {@code ListSelectionListener} is notified of the change. 448 * <p> 449 * If either index is {@code -1}, this method does nothing and returns 450 * without exception. Otherwise, if either index is less than {@code -1}, 451 * an {@code IndexOutOfBoundsException} is thrown. 452 * 453 * @param index0 one end of the interval. 454 * @param index1 other end of the interval 455 * @throws IndexOutOfBoundsException if either index is less than {@code -1} 456 * (and neither index is {@code -1}) 457 * @see #addListSelectionListener 458 */ 459 public void setSelectionInterval(int index0, int index1) { 460 if (index0 == -1 || index1 == -1) { 461 return; 462 } 463 464 if (getSelectionMode() == SINGLE_SELECTION) { 465 index0 = index1; 466 } 467 468 updateLeadAnchorIndices(index0, index1); 469 470 int clearMin = minIndex; 471 int clearMax = maxIndex; 472 int setMin = Math.min(index0, index1); 473 int setMax = Math.max(index0, index1); 474 changeSelection(clearMin, clearMax, setMin, setMax); 475 } 476 477 /** 478 * Changes the selection to be the set union of the current selection 479 * and the indices between {@code index0} and {@code index1} inclusive. 480 * <p> 481 * In {@code SINGLE_SELECTION} selection mode, this is equivalent 482 * to calling {@code setSelectionInterval}, and only the second index 483 * is used. In {@code SINGLE_INTERVAL_SELECTION} selection mode, this 484 * method behaves like {@code setSelectionInterval}, unless the given 485 * interval is immediately adjacent to or overlaps the existing selection, 486 * and can therefore be used to grow it. 487 * <p> 488 * If this represents a change to the current selection, then each 489 * {@code ListSelectionListener} is notified of the change. Note that 490 * {@code index0} doesn't have to be less than or equal to {@code index1}. 491 * <p> 492 * If either index is {@code -1}, this method does nothing and returns 493 * without exception. Otherwise, if either index is less than {@code -1}, 494 * an {@code IndexOutOfBoundsException} is thrown. 495 * 496 * @param index0 one end of the interval. 497 * @param index1 other end of the interval 498 * @throws IndexOutOfBoundsException if either index is less than {@code -1} 499 * (and neither index is {@code -1}) 500 * @see #addListSelectionListener 501 * @see #setSelectionInterval 502 */ 503 public void addSelectionInterval(int index0, int index1) 504 { 505 if (index0 == -1 || index1 == -1) { 506 return; 507 } 508 509 // If we only allow a single selection, channel through 510 // setSelectionInterval() to enforce the rule. 511 if (getSelectionMode() == SINGLE_SELECTION) { 512 setSelectionInterval(index0, index1); 513 return; 514 } 515 516 updateLeadAnchorIndices(index0, index1); 517 518 int clearMin = MAX; 519 int clearMax = MIN; 520 int setMin = Math.min(index0, index1); 521 int setMax = Math.max(index0, index1); 522 523 // If we only allow a single interval and this would result 524 // in multiple intervals, then set the selection to be just 525 // the new range. 526 if (getSelectionMode() == SINGLE_INTERVAL_SELECTION && 527 (setMax < minIndex - 1 || setMin > maxIndex + 1)) { 528 529 setSelectionInterval(index0, index1); 530 return; 531 } 532 533 changeSelection(clearMin, clearMax, setMin, setMax); 534 } 535 536 537 /** 538 * Changes the selection to be the set difference of the current selection 539 * and the indices between {@code index0} and {@code index1} inclusive. 540 * {@code index0} doesn't have to be less than or equal to {@code index1}. 541 * <p> 542 * In {@code SINGLE_INTERVAL_SELECTION} selection mode, if the removal 543 * would produce two disjoint selections, the removal is extended through 544 * the greater end of the selection. For example, if the selection is 545 * {@code 0-10} and you supply indices {@code 5,6} (in any order) the 546 * resulting selection is {@code 0-4}. 547 * <p> 548 * If this represents a change to the current selection, then each 549 * {@code ListSelectionListener} is notified of the change. 550 * <p> 551 * If either index is {@code -1}, this method does nothing and returns 552 * without exception. Otherwise, if either index is less than {@code -1}, 553 * an {@code IndexOutOfBoundsException} is thrown. 554 * 555 * @param index0 one end of the interval 556 * @param index1 other end of the interval 557 * @throws IndexOutOfBoundsException if either index is less than {@code -1} 558 * (and neither index is {@code -1}) 559 * @see #addListSelectionListener 560 */ 561 public void removeSelectionInterval(int index0, int index1) 562 { 563 removeSelectionIntervalImpl(index0, index1, true); 564 } 565 566 // private implementation allowing the selection interval 567 // to be removed without affecting the lead and anchor 568 private void removeSelectionIntervalImpl(int index0, int index1, 569 boolean changeLeadAnchor) { 570 571 if (index0 == -1 || index1 == -1) { 572 return; 573 } 574 575 if (changeLeadAnchor) { 576 updateLeadAnchorIndices(index0, index1); 577 } 578 579 int clearMin = Math.min(index0, index1); 580 int clearMax = Math.max(index0, index1); 581 int setMin = MAX; 582 int setMax = MIN; 583 584 // If the removal would produce to two disjoint selections in a mode 585 // that only allows one, extend the removal to the end of the selection. 586 if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION && 587 clearMin > minIndex && clearMax < maxIndex) { 588 clearMax = maxIndex; 589 } 590 591 changeSelection(clearMin, clearMax, setMin, setMax); 592 } 593 594 private void setState(int index, boolean state) { 595 if (state) { 596 set(index); 597 } 598 else { 599 clear(index); 600 } 601 } 602 603 /** 604 * Insert length indices beginning before/after index. If the value 605 * at index is itself selected and the selection mode is not 606 * SINGLE_SELECTION, set all of the newly inserted items as selected. 607 * Otherwise leave them unselected. This method is typically 608 * called to sync the selection model with a corresponding change 609 * in the data model. 610 */ 611 public void insertIndexInterval(int index, int length, boolean before) 612 { 613 /* The first new index will appear at insMinIndex and the last 614 * one will appear at insMaxIndex 615 */ 616 int insMinIndex = (before) ? index : index + 1; 617 int insMaxIndex = (insMinIndex + length) - 1; 618 619 /* Right shift the entire bitset by length, beginning with 620 * index-1 if before is true, index+1 if it's false (i.e. with 621 * insMinIndex). 622 */ 623 for(int i = maxIndex; i >= insMinIndex; i--) { 624 setState(i + length, value.get(i)); 625 } 626 627 /* Initialize the newly inserted indices. 628 */ 629 boolean setInsertedValues = ((getSelectionMode() == SINGLE_SELECTION) ? 630 false : value.get(index)); 631 for(int i = insMinIndex; i <= insMaxIndex; i++) { 632 setState(i, setInsertedValues); 633 } 634 635 int leadIndex = this.leadIndex; 636 if (leadIndex > index || (before && leadIndex == index)) { 637 leadIndex = this.leadIndex + length; 638 } 639 int anchorIndex = this.anchorIndex; 640 if (anchorIndex > index || (before && anchorIndex == index)) { 641 anchorIndex = this.anchorIndex + length; 642 } 643 if (leadIndex != this.leadIndex || anchorIndex != this.anchorIndex) { 644 updateLeadAnchorIndices(anchorIndex, leadIndex); 645 } 646 647 fireValueChanged(); 648 } 649 650 651 /** 652 * Remove the indices in the interval index0,index1 (inclusive) from 653 * the selection model. This is typically called to sync the selection 654 * model width a corresponding change in the data model. Note 655 * that (as always) index0 need not be <= index1. 656 */ 657 public void removeIndexInterval(int index0, int index1) 658 { 659 int rmMinIndex = Math.min(index0, index1); 660 int rmMaxIndex = Math.max(index0, index1); 661 int gapLength = (rmMaxIndex - rmMinIndex) + 1; 662 663 /* Shift the entire bitset to the left to close the index0, index1 664 * gap. 665 */ 666 for(int i = rmMinIndex; i <= maxIndex; i++) { 667 setState(i, value.get(i + gapLength)); 668 } 669 670 int leadIndex = this.leadIndex; 671 if (leadIndex == 0 && rmMinIndex == 0) { 672 // do nothing 673 } else if (leadIndex > rmMaxIndex) { 674 leadIndex = this.leadIndex - gapLength; 675 } else if (leadIndex >= rmMinIndex) { 676 leadIndex = rmMinIndex - 1; 677 } 678 679 int anchorIndex = this.anchorIndex; 680 if (anchorIndex == 0 && rmMinIndex == 0) { 681 // do nothing 682 } else if (anchorIndex > rmMaxIndex) { 683 anchorIndex = this.anchorIndex - gapLength; 684 } else if (anchorIndex >= rmMinIndex) { 685 anchorIndex = rmMinIndex - 1; 686 } 687 688 if (leadIndex != this.leadIndex || anchorIndex != this.anchorIndex) { 689 updateLeadAnchorIndices(anchorIndex, leadIndex); 690 } 691 692 fireValueChanged(); 693 } 694 695 696 /** {@inheritDoc} */ 697 public void setValueIsAdjusting(boolean isAdjusting) { 698 if (isAdjusting != this.isAdjusting) { 699 this.isAdjusting = isAdjusting; 700 this.fireValueChanged(isAdjusting); 701 } 702 } 703 704 705 /** 706 * Returns a string that displays and identifies this 707 * object's properties. 708 * 709 * @return a <code>String</code> representation of this object 710 */ 711 public String toString() { 712 String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString(); 713 return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s; 714 } 715 716 /** 717 * Returns a clone of this selection model with the same selection. 718 * <code>listenerLists</code> are not duplicated. 719 * 720 * @exception CloneNotSupportedException if the selection model does not 721 * both (a) implement the Cloneable interface and (b) define a 722 * <code>clone</code> method. 723 */ 724 public Object clone() throws CloneNotSupportedException { 725 DefaultListSelectionModel clone = (DefaultListSelectionModel)super.clone(); 726 clone.value = (BitSet)value.clone(); 727 clone.listenerList = new EventListenerList(); 728 return clone; 729 } 730 731 /** {@inheritDoc} */ 732 @Transient 733 public int getAnchorSelectionIndex() { 734 return anchorIndex; 735 } 736 737 /** {@inheritDoc} */ 738 @Transient 739 public int getLeadSelectionIndex() { 740 return leadIndex; 741 } 742 743 /** 744 * Set the anchor selection index, leaving all selection values unchanged. 745 * If leadAnchorNotificationEnabled is true, send a notification covering 746 * the old and new anchor cells. 747 * 748 * @see #getAnchorSelectionIndex 749 * @see #setLeadSelectionIndex 750 */ 751 public void setAnchorSelectionIndex(int anchorIndex) { 752 updateLeadAnchorIndices(anchorIndex, this.leadIndex); 753 fireValueChanged(); 754 } 755 756 /** 757 * Set the lead selection index, leaving all selection values unchanged. 758 * If leadAnchorNotificationEnabled is true, send a notification covering 759 * the old and new lead cells. 760 * 761 * @param leadIndex the new lead selection index 762 * 763 * @see #setAnchorSelectionIndex 764 * @see #setLeadSelectionIndex 765 * @see #getLeadSelectionIndex 766 * 767 * @since 1.5 768 */ 769 public void moveLeadSelectionIndex(int leadIndex) { 770 // disallow a -1 lead unless the anchor is already -1 771 if (leadIndex == -1) { 772 if (this.anchorIndex != -1) { 773 return; 774 } 775 776 /* PENDING(shannonh) - The following check is nice, to be consistent with 777 setLeadSelectionIndex. However, it is not absolutely 778 necessary: One could work around it by setting the anchor 779 to something valid, modifying the lead, and then moving 780 the anchor back to -1. For this reason, there's no sense 781 in adding it at this time, as that would require 782 updating the spec and officially committing to it. 783 784 // otherwise, don't do anything if the anchor is -1 785 } else if (this.anchorIndex == -1) { 786 return; 787 */ 788 789 } 790 791 updateLeadAnchorIndices(this.anchorIndex, leadIndex); 792 fireValueChanged(); 793 } 794 795 /** 796 * Sets the lead selection index, ensuring that values between the 797 * anchor and the new lead are either all selected or all deselected. 798 * If the value at the anchor index is selected, first clear all the 799 * values in the range [anchor, oldLeadIndex], then select all the values 800 * values in the range [anchor, newLeadIndex], where oldLeadIndex is the old 801 * leadIndex and newLeadIndex is the new one. 802 * <p> 803 * If the value at the anchor index is not selected, do the same thing in 804 * reverse selecting values in the old range and deselecting values in the 805 * new one. 806 * <p> 807 * Generate a single event for this change and notify all listeners. 808 * For the purposes of generating minimal bounds in this event, do the 809 * operation in a single pass; that way the first and last index inside the 810 * ListSelectionEvent that is broadcast will refer to cells that actually 811 * changed value because of this method. If, instead, this operation were 812 * done in two steps the effect on the selection state would be the same 813 * but two events would be generated and the bounds around the changed 814 * values would be wider, including cells that had been first cleared only 815 * to later be set. 816 * <p> 817 * This method can be used in the <code>mouseDragged</code> method 818 * of a UI class to extend a selection. 819 * 820 * @see #getLeadSelectionIndex 821 * @see #setAnchorSelectionIndex 822 */ 823 public void setLeadSelectionIndex(int leadIndex) { 824 int anchorIndex = this.anchorIndex; 825 826 // only allow a -1 lead if the anchor is already -1 827 if (leadIndex == -1) { 828 if (anchorIndex == -1) { 829 updateLeadAnchorIndices(anchorIndex, leadIndex); 830 fireValueChanged(); 831 } 832 833 return; 834 // otherwise, don't do anything if the anchor is -1 835 } else if (anchorIndex == -1) { 836 return; 837 } 838 839 if (this.leadIndex == -1) { 840 this.leadIndex = leadIndex; 841 } 842 843 boolean shouldSelect = value.get(this.anchorIndex); 844 845 if (getSelectionMode() == SINGLE_SELECTION) { 846 anchorIndex = leadIndex; 847 shouldSelect = true; 848 } 849 850 int oldMin = Math.min(this.anchorIndex, this.leadIndex); 851 int oldMax = Math.max(this.anchorIndex, this.leadIndex); 852 int newMin = Math.min(anchorIndex, leadIndex); 853 int newMax = Math.max(anchorIndex, leadIndex); 854 855 updateLeadAnchorIndices(anchorIndex, leadIndex); 856 857 if (shouldSelect) { 858 changeSelection(oldMin, oldMax, newMin, newMax); 859 } 860 else { 861 changeSelection(newMin, newMax, oldMin, oldMax, false); 862 } 863 } 864 }