1 /* 2 * Copyright (c) 2010, 2016, 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 javafx.scene.control; 27 28 import com.sun.javafx.collections.NonIterableChange; 29 import static javafx.scene.control.SelectionMode.SINGLE; 30 31 import java.util.*; 32 import java.util.stream.Collectors; 33 import java.util.stream.IntStream; 34 35 import com.sun.javafx.scene.control.MultipleAdditionAndRemovedChange; 36 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; 37 import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList; 38 import javafx.collections.ListChangeListener; 39 import javafx.collections.ObservableList; 40 import javafx.collections.ObservableListBase; 41 import javafx.util.Callback; 42 43 import javafx.util.Pair; 44 45 46 /** 47 * An abstract class that implements more of the abstract MultipleSelectionModel 48 * abstract class. However, this class is package-protected and not intended 49 * for public use. 50 * 51 * @param <T> The type of the underlying data model for the UI control. 52 */ 53 abstract class MultipleSelectionModelBase<T> extends MultipleSelectionModel<T> { 54 55 /*********************************************************************** 56 * * 57 * Constructors * 58 * * 59 **********************************************************************/ 60 61 public MultipleSelectionModelBase() { 62 selectedIndexProperty().addListener(valueModel -> { 63 // we used to lazily retrieve the selected item, but now we just 64 // do it when the selection changes. This is hardly likely to be 65 // expensive, and we still lazily handle the multiple selection 66 // cases over in MultipleSelectionModel. 67 setSelectedItem(getModelItem(getSelectedIndex())); 68 }); 69 70 selectedIndices = new SelectedIndicesList(); 71 72 selectedItems = new SelectedItemsReadOnlyObservableList<T>(selectedIndices, () -> getItemCount()) { 73 @Override protected T getModelItem(int index) { 74 return MultipleSelectionModelBase.this.getModelItem(index); 75 } 76 }; 77 } 78 79 80 81 /*********************************************************************** 82 * * 83 * Observable properties * 84 * * 85 **********************************************************************/ 86 87 /* 88 * We only maintain the values of the selectedIndex and selectedIndices 89 * properties. The value of the selectedItem and selectedItems properties 90 * is determined on-demand. We fire the SELECTED_ITEM and SELECTED_ITEMS 91 * property change events whenever the related SELECTED_INDEX or 92 * SELECTED_INDICES properties change. 93 * 94 * This means that the cost of the ListViewSelectionModel is cheap in most 95 * cases, assuming that the end-consumer isn't calling getSelectedItems 96 * too aggressively. Of course, this is only an issue when the ListViewModel 97 * is being populated by some remote, expensive to query data source. 98 * 99 * In addition, we do not provide ObservableLists for the selected indices or the 100 * selected items properties, as this would allow the API consumer to add 101 * observers to these ObservableLists. This would make life tougher as we would 102 * then be forced to keep these ObservableLists in-sync at all times, which for 103 * the selectedItems ObservableList, would require potentially a lot of work and 104 * memory. Instead, we return a List, and allow for changes to these Lists 105 * to be observed through the SELECTED_INDICES and SELECTED_ITEMS 106 * properties. 107 */ 108 109 110 final SelectedIndicesList selectedIndices; 111 @Override public ObservableList<Integer> getSelectedIndices() { 112 return selectedIndices; 113 } 114 115 private final ObservableListBase<T> selectedItems; 116 @Override public ObservableList<T> getSelectedItems() { 117 return selectedItems; 118 } 119 120 121 122 /*********************************************************************** 123 * * 124 * Internal field * 125 * * 126 **********************************************************************/ 127 128 ListChangeListener.Change selectedItemChange; 129 130 131 132 /*********************************************************************** 133 * * 134 * Public selection API * 135 * * 136 **********************************************************************/ 137 138 /** 139 * Returns the number of items in the data model that underpins the control. 140 * An example would be that a ListView selection model would likely return 141 * <code>listView.getItems().size()</code>. The valid range of selectable 142 * indices is between 0 and whatever is returned by this method. 143 */ 144 protected abstract int getItemCount(); 145 146 /** 147 * Returns the item at the given index. An example using ListView would be 148 * <code>listView.getItems().get(index)</code>. 149 * 150 * @param index The index of the item that is requested from the underlying 151 * data model. 152 * @return Returns null if the index is out of bounds, or an element of type 153 * T that is related to the given index. 154 */ 155 protected abstract T getModelItem(int index); 156 protected abstract void focus(int index); 157 protected abstract int getFocusedIndex(); 158 159 static class ShiftParams { 160 private final int clearIndex; 161 private final int setIndex; 162 private final boolean selected; 163 164 ShiftParams(int clearIndex, int setIndex, boolean selected) { 165 this.clearIndex = clearIndex; 166 this.setIndex = setIndex; 167 this.selected = selected; 168 } 169 170 public final int getClearIndex() { 171 return clearIndex; 172 } 173 174 public final int getSetIndex() { 175 return setIndex; 176 } 177 178 public final boolean isSelected() { 179 return selected; 180 } 181 } 182 183 // package only 184 void shiftSelection(int position, int shift, final Callback<ShiftParams, Void> callback) { 185 shiftSelection(Arrays.asList(new Pair<>(position, shift)), callback); 186 } 187 188 void shiftSelection(List<Pair<Integer, Integer>> shifts, final Callback<ShiftParams, Void> callback) { 189 int selectedIndicesCardinality = selectedIndices.size(); // number of true bits 190 if (selectedIndicesCardinality == 0) return; 191 192 int selectedIndicesSize = selectedIndices.bitsetSize(); // number of bits reserved 193 194 int[] perm = new int[selectedIndicesSize]; 195 Arrays.fill(perm, -1); 196 197 // sort the list so that we iterate from highest position to lowest position 198 Collections.sort(shifts, (s1, s2) -> Integer.compare(s2.getKey(), s1.getKey())); 199 final int lowestShiftPosition = shifts.get(shifts.size() - 1).getKey(); 200 201 // make a copy of the selectedIndices before so we can compare to it afterwards 202 BitSet selectedIndicesCopy = (BitSet) selectedIndices.bitset.clone(); 203 204 startAtomic(); 205 for (Pair<Integer, Integer> shift : shifts) { 206 doShift(shift, callback, perm); 207 } 208 stopAtomic(); 209 210 // strip out all useless -1 default values from the perm array 211 final int[] prunedPerm = Arrays.stream(perm).filter(value -> value > -1).toArray(); 212 final boolean hasSelectionChanged = prunedPerm.length > 0; 213 214 // This ensure that the selection remains accurate when a shift occurs. 215 final int selectedIndex = getSelectedIndex(); 216 if (selectedIndex >= lowestShiftPosition && selectedIndex > -1) { 217 // sum up the total shift, where the position is less than or equal 218 // to the previously selected index 219 int totalShift = shifts.stream() 220 .filter(shift -> shift.getKey() <= selectedIndex) 221 .mapToInt(shift -> shift.getValue()) 222 .sum(); 223 224 // Fix for RT-38787: we used to not enter this block if 225 // selectedIndex + shift resulted in a value less than zero, whereas 226 // now we just set the newSelectionLead to zero in that instance. 227 // There exists unit tests that cover this. 228 final int newSelectionLead = Math.max(0, selectedIndex + totalShift); 229 230 setSelectedIndex(newSelectionLead); 231 232 // added the selectedIndices call for RT-30356. 233 // changed to check if hasPermutated, and to call select(..) for RT-40010. 234 // This forces the selection event to go through the system and fire 235 // the necessary events. 236 if (hasSelectionChanged) { 237 selectedIndices.set(newSelectionLead, true); 238 } else { 239 select(newSelectionLead); 240 } 241 242 // removed due to RT-27185 243 // focus(newSelectionLead); 244 } 245 246 if (hasSelectionChanged) { 247 // work out what indices were removed and added 248 BitSet removed = (BitSet) selectedIndicesCopy.clone(); 249 removed.andNot(selectedIndices.bitset); 250 251 BitSet added = (BitSet) selectedIndices.bitset.clone(); 252 added.andNot(selectedIndicesCopy); 253 254 selectedIndices.reset(); 255 selectedIndices.callObservers(new MultipleAdditionAndRemovedChange<>( 256 added.stream().boxed().collect(Collectors.toList()), 257 removed.stream().boxed().collect(Collectors.toList()), 258 selectedIndices 259 )); 260 } 261 } 262 263 private void doShift(Pair<Integer, Integer> shiftPair, final Callback<ShiftParams, Void> callback, int[] perm) { 264 final int position = shiftPair.getKey(); 265 final int shift = shiftPair.getValue(); 266 267 // with no check here, we get RT-15024 268 if (position < 0) return; 269 if (shift == 0) return; 270 271 int idx = (int) Arrays.stream(perm).filter(value -> value > -1).count(); 272 273 int selectedIndicesSize = selectedIndices.bitsetSize() - idx; // number of bits reserved 274 275 if (shift > 0) { 276 for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) { 277 boolean selected = selectedIndices.isSelected(i); 278 279 if (callback == null) { 280 selectedIndices.clear(i); 281 selectedIndices.set(i + shift, selected); 282 } else { 283 callback.call(new ShiftParams(i, i + shift, selected)); 284 } 285 286 if (selected) { 287 perm[idx++] = i + 1; 288 } 289 } 290 selectedIndices.clear(position); 291 } else if (shift < 0) { 292 for (int i = position; i < selectedIndicesSize; i++) { 293 if ((i + shift) < 0) continue; 294 if ((i + 1 + shift) < position) continue; 295 boolean selected = selectedIndices.isSelected(i + 1); 296 297 if (callback == null) { 298 selectedIndices.clear(i + 1); 299 selectedIndices.set(i + 1 + shift, selected); 300 } else { 301 callback.call(new ShiftParams(i + 1, i + 1 + shift, selected)); 302 } 303 304 if (selected) { 305 perm[idx++] = i; 306 } 307 } 308 } 309 } 310 311 void startAtomic() { 312 selectedIndices.startAtomic(); 313 } 314 315 void stopAtomic() { 316 selectedIndices.stopAtomic(); 317 } 318 319 boolean isAtomic() { 320 return selectedIndices.isAtomic(); 321 } 322 323 @Override public void clearAndSelect(int row) { 324 if (row < 0 || row >= getItemCount()) { 325 clearSelection(); 326 return; 327 } 328 329 final boolean wasSelected = isSelected(row); 330 331 // RT-33558 if this method has been called with a given row, and that 332 // row is the only selected row currently, then this method becomes a no-op. 333 if (wasSelected && getSelectedIndices().size() == 1) { 334 // before we return, we double-check that the selected item 335 // is equal to the item in the given index 336 if (getSelectedItem() == getModelItem(row)) { 337 return; 338 } 339 } 340 341 // firstly we make a copy of the selection, so that we can send out 342 // the correct details in the selection change event. 343 // We remove the new selection from the list seeing as it is not removed. 344 BitSet selectedIndicesCopy = new BitSet(); 345 selectedIndicesCopy.or(selectedIndices.bitset); 346 selectedIndicesCopy.clear(row); 347 List<Integer> previousSelectedIndices = new SelectedIndicesList(selectedIndicesCopy); 348 349 // RT-32411 We used to call quietClearSelection() here, but this 350 // resulted in the selectedItems and selectedIndices lists never 351 // reporting that they were empty. 352 // makeAtomic toggle added to resolve RT-32618 353 startAtomic(); 354 355 // then clear the current selection 356 clearSelection(); 357 358 // and select the new row 359 select(row); 360 stopAtomic(); 361 362 // fire off a single add/remove/replace notification (rather than 363 // individual remove and add notifications) - see RT-33324 364 ListChangeListener.Change<Integer> change; 365 366 /* 367 * getFrom() documentation: 368 * If wasAdded is true, the interval contains all the values that were added. 369 * If wasPermutated is true, the interval marks the values that were permutated. 370 * If wasRemoved is true and wasAdded is false, getFrom() and getTo() should 371 * return the same number - the place where the removed elements were positioned in the list. 372 */ 373 if (wasSelected) { 374 change = ControlUtils.buildClearAndSelectChange(selectedIndices, previousSelectedIndices, row); 375 } else { 376 int changeIndex = Math.max(0, selectedIndices.indexOf(row)); 377 change = new NonIterableChange.GenericAddRemoveChange<>( 378 changeIndex, changeIndex+1, previousSelectedIndices, selectedIndices); 379 } 380 381 selectedIndices.callObservers(change); 382 } 383 384 @Override public void select(int row) { 385 if (row == -1) { 386 clearSelection(); 387 return; 388 } 389 if (row < 0 || row >= getItemCount()) { 390 return; 391 } 392 393 boolean isSameRow = row == getSelectedIndex(); 394 T currentItem = getSelectedItem(); 395 T newItem = getModelItem(row); 396 boolean isSameItem = newItem != null && newItem.equals(currentItem); 397 boolean fireUpdatedItemEvent = isSameRow && ! isSameItem; 398 399 // focus must come first so that we have the anchors set appropriately 400 focus(row); 401 402 if (! selectedIndices.isSelected(row)) { 403 if (getSelectionMode() == SINGLE) { 404 startAtomic(); 405 quietClearSelection(); 406 stopAtomic(); 407 } 408 selectedIndices.set(row); 409 } 410 411 setSelectedIndex(row); 412 413 if (fireUpdatedItemEvent) { 414 setSelectedItem(newItem); 415 } 416 } 417 418 @Override public void select(T obj) { 419 // if (getItemCount() <= 0) return; 420 421 if (obj == null && getSelectionMode() == SelectionMode.SINGLE) { 422 clearSelection(); 423 return; 424 } 425 426 // We have no option but to iterate through the model and select the 427 // first occurrence of the given object. Once we find the first one, we 428 // don't proceed to select any others. 429 Object rowObj = null; 430 for (int i = 0, max = getItemCount(); i < max; i++) { 431 rowObj = getModelItem(i); 432 if (rowObj == null) continue; 433 434 if (rowObj.equals(obj)) { 435 if (isSelected(i)) { 436 return; 437 } 438 439 if (getSelectionMode() == SINGLE) { 440 quietClearSelection(); 441 } 442 443 select(i); 444 return; 445 } 446 } 447 448 // if we are here, we did not find the item in the entire data model. 449 // Even still, we allow for this item to be set to the give object. 450 // We expect that in concrete subclasses of this class we observe the 451 // data model such that we check to see if the given item exists in it, 452 // whilst SelectedIndex == -1 && SelectedItem != null. 453 setSelectedIndex(-1); 454 setSelectedItem(obj); 455 } 456 457 @Override public void selectIndices(int row, int... rows) { 458 if (rows == null || rows.length == 0) { 459 select(row); 460 return; 461 } 462 463 /* 464 * Performance optimisation - if multiple selection is disabled, only 465 * process the end-most row index. 466 */ 467 468 int rowCount = getItemCount(); 469 470 if (getSelectionMode() == SINGLE) { 471 quietClearSelection(); 472 473 for (int i = rows.length - 1; i >= 0; i--) { 474 int index = rows[i]; 475 if (index >= 0 && index < rowCount) { 476 selectedIndices.set(index); 477 select(index); 478 break; 479 } 480 } 481 482 if (selectedIndices.isEmpty()) { 483 if (row > 0 && row < rowCount) { 484 selectedIndices.set(row); 485 select(row); 486 } 487 } 488 } else { 489 selectedIndices.set(row, rows); 490 491 IntStream.concat(IntStream.of(row), IntStream.of(rows)) 492 .filter(index -> index >= 0 && index < rowCount) 493 .reduce((first, second) -> second) 494 .ifPresent(lastIndex -> { 495 setSelectedIndex(lastIndex); 496 focus(lastIndex); 497 setSelectedItem(getModelItem(lastIndex)); 498 }); 499 } 500 } 501 502 @Override public void selectAll() { 503 if (getSelectionMode() == SINGLE) return; 504 505 if (getItemCount() <= 0) return; 506 507 final int rowCount = getItemCount(); 508 final int focusedIndex = getFocusedIndex(); 509 510 // set all selected indices to true 511 clearSelection(); 512 selectedIndices.set(0, rowCount, true); 513 514 if (focusedIndex == -1) { 515 setSelectedIndex(rowCount - 1); 516 focus(rowCount - 1); 517 } else { 518 setSelectedIndex(focusedIndex); 519 focus(focusedIndex); 520 } 521 } 522 523 @Override public void selectFirst() { 524 if (getSelectionMode() == SINGLE) { 525 quietClearSelection(); 526 } 527 528 if (getItemCount() > 0) { 529 select(0); 530 } 531 } 532 533 @Override public void selectLast() { 534 if (getSelectionMode() == SINGLE) { 535 quietClearSelection(); 536 } 537 538 int numItems = getItemCount(); 539 if (numItems > 0 && getSelectedIndex() < numItems - 1) { 540 select(numItems - 1); 541 } 542 } 543 544 @Override public void clearSelection(int index) { 545 if (index < 0) return; 546 547 // TODO shouldn't directly access like this 548 // TODO might need to update focus and / or selected index/item 549 boolean wasEmpty = selectedIndices.isEmpty(); 550 selectedIndices.clear(index); 551 552 if (! wasEmpty && selectedIndices.isEmpty()) { 553 clearSelection(); 554 } 555 } 556 557 @Override public void clearSelection() { 558 quietClearSelection(); 559 560 if (! isAtomic()) { 561 setSelectedIndex(-1); 562 focus(-1); 563 } 564 } 565 566 private void quietClearSelection() { 567 selectedIndices.clear(); 568 } 569 570 @Override public boolean isSelected(int index) { 571 // Note the change in semantics here - we used to check to ensure that 572 // the index is less than the item count, but now simply ensure that 573 // it is less than the length of the selectedIndices bitset. This helps 574 // to resolve issues such as RT-26721, where isSelected(int) was being 575 // called for indices that exceeded the item count, as a TreeItem (e.g. 576 // the root) was being collapsed. 577 // if (index >= 0 && index < getItemCount()) { 578 if (index >= 0 && index < selectedIndices.bitsetSize()) { 579 return selectedIndices.isSelected(index); 580 } 581 582 return false; 583 } 584 585 @Override public boolean isEmpty() { 586 return selectedIndices.isEmpty(); 587 } 588 589 @Override public void selectPrevious() { 590 int focusIndex = getFocusedIndex(); 591 592 if (getSelectionMode() == SINGLE) { 593 quietClearSelection(); 594 } 595 596 if (focusIndex == -1) { 597 select(getItemCount() - 1); 598 } else if (focusIndex > 0) { 599 select(focusIndex - 1); 600 } 601 } 602 603 @Override public void selectNext() { 604 int focusIndex = getFocusedIndex(); 605 606 if (getSelectionMode() == SINGLE) { 607 quietClearSelection(); 608 } 609 610 if (focusIndex == -1) { 611 select(0); 612 } else if (focusIndex != getItemCount() -1) { 613 select(focusIndex + 1); 614 } 615 } 616 617 618 619 /*********************************************************************** 620 * * 621 * Private implementation * 622 * * 623 **********************************************************************/ 624 625 class SelectedIndicesList extends ReadOnlyUnbackedObservableList<Integer> { 626 private final BitSet bitset; 627 628 private int lastGetIndex = -1; 629 private int lastGetValue = -1; 630 631 // Fix for RT-20945 (and numerous other issues!) 632 private int atomicityCount = 0; 633 634 // @Override 635 // public void callObservers(Change<Integer> c) { 636 // throw new RuntimeException("callObservers unavailable"); 637 // } 638 639 public SelectedIndicesList() { 640 this(new BitSet()); 641 } 642 643 public SelectedIndicesList(BitSet bitset) { 644 this.bitset = bitset; 645 } 646 647 boolean isAtomic() { 648 return atomicityCount > 0; 649 } 650 void startAtomic() { 651 atomicityCount++; 652 } 653 void stopAtomic() { 654 atomicityCount = Math.max(0, atomicityCount - 1); 655 } 656 657 // Returns the selected index at the given index. 658 // e.g. if our selectedIndices are [1,3,5], then an index of 2 will return 5 here. 659 @Override public Integer get(int index) { 660 final int itemCount = size(); 661 if (index < 0 || index >= itemCount) { 662 throw new IndexOutOfBoundsException(index + " >= " + itemCount); 663 } 664 665 if (index == (lastGetIndex + 1) && lastGetValue < itemCount) { 666 // we're iterating forward in order, short circuit for 667 // performance reasons (RT-39776) 668 lastGetIndex++; 669 lastGetValue = bitset.nextSetBit(lastGetValue + 1); 670 return lastGetValue; 671 } else if (index == (lastGetIndex - 1) && lastGetValue > 0) { 672 // we're iterating backward in order, short circuit for 673 // performance reasons (RT-39776) 674 lastGetIndex--; 675 lastGetValue = bitset.previousSetBit(lastGetValue - 1); 676 return lastGetValue; 677 } else { 678 for (lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0); 679 lastGetValue >= 0 || lastGetIndex == index; 680 lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) { 681 if (lastGetIndex == index) { 682 return lastGetValue; 683 } 684 } 685 } 686 687 return -1; 688 } 689 690 public void set(int index) { 691 if (!isValidIndex(index) || isSelected(index)) { 692 return; 693 } 694 695 _beginChange(); 696 bitset.set(index); 697 int indicesIndex = indexOf(index); 698 _nextAdd(indicesIndex, indicesIndex + 1); 699 _endChange(); 700 } 701 702 private boolean isValidIndex(int index) { 703 return index >= 0 && index < getItemCount(); 704 } 705 706 public void set(int index, boolean isSet) { 707 if (isSet) { 708 set(index); 709 } else { 710 clear(index); 711 } 712 } 713 714 public void set(int index, int end, boolean isSet) { 715 _beginChange(); 716 if (isSet) { 717 bitset.set(index, end, isSet); 718 int indicesIndex = indexOf(index); 719 int span = end - index; 720 _nextAdd(indicesIndex, indicesIndex + span); 721 } else { 722 // TODO handle remove 723 bitset.set(index, end, isSet); 724 } 725 _endChange(); 726 } 727 728 public void set(int index, int... indices) { 729 if (indices == null || indices.length == 0) { 730 set(index); 731 } else { 732 // we reduce down to the minimal number of changes possible 733 // by finding all contiguous indices, of all indices that are 734 // not already selected, and which are in the valid range 735 startAtomic(); 736 List<Integer> sortedNewIndices = 737 IntStream.concat(IntStream.of(index), IntStream.of(indices)) 738 .distinct() 739 .filter(this::isValidIndex) 740 .filter(this::isNotSelected) 741 .sorted() 742 .boxed() 743 .peek(this::set) // we also set here, but it's atomic! 744 .collect(Collectors.toList()); 745 stopAtomic(); 746 747 final int size = sortedNewIndices.size(); 748 if (size == 0) { 749 // no-op 750 } else if (size == 1) { 751 _beginChange(); 752 int _index = sortedNewIndices.get(0); 753 _nextAdd(_index, _index + 1); 754 _endChange(); 755 } else { 756 _beginChange(); 757 int pos = 0; 758 int start = 0; 759 int end = 0; 760 761 // starting from pos, we keep going until the value is 762 // not the next value 763 int startValue = sortedNewIndices.get(pos++); 764 start = indexOf(startValue); 765 end = start + 1; 766 int endValue = startValue; 767 while (pos < size) { 768 int previousEndValue = endValue; 769 endValue = sortedNewIndices.get(pos++); 770 ++end; 771 if (previousEndValue != (endValue - 1)) { 772 _nextAdd(start, end); 773 start = end; 774 continue; 775 } 776 777 // special case for when we get to the point where the loop is about to end 778 // and we have uncommitted changes to fire. 779 if (pos == size) { 780 _nextAdd(start, start + pos); 781 } 782 } 783 784 _endChange(); 785 } 786 } 787 } 788 789 public void clear() { 790 _beginChange(); 791 List<Integer> removed = bitset.stream().boxed().collect(Collectors.toList()); 792 bitset.clear(); 793 _nextRemove(0, removed); 794 _endChange(); 795 } 796 797 public void clear(int index) { 798 if (!bitset.get(index)) return; 799 800 _beginChange(); 801 bitset.clear(index); 802 _nextRemove(index, index); 803 _endChange(); 804 } 805 806 // public void clearAndSelect(int index) { 807 // if (index < 0 || index >= getItemCount()) { 808 // clearSelection(); 809 // return; 810 // } 811 // 812 // final boolean wasSelected = isSelected(index); 813 // 814 // // RT-33558 if this method has been called with a given row, and that 815 // // row is the only selected row currently, then this method becomes a no-op. 816 // if (wasSelected && getSelectedIndices().size() == 1) { 817 // // before we return, we double-check that the selected item 818 // // is equal to the item in the given index 819 // if (getSelectedItem() == getModelItem(index)) { 820 // return; 821 // } 822 // } 823 // 824 // List<Integer> removed = bitset.stream().boxed().collect(Collectors.toList()); 825 // boolean isSelected = removed.contains(index); 826 // if (isSelected) { 827 // removed.remove((Object)index); 828 // } 829 // 830 // if (removed.isEmpty()) { 831 // set(index); 832 // } 833 // 834 // bitset.clear(); 835 // bitset.set(index); 836 // _beginChange(); 837 // if (isSelected) { 838 // _nextRemove(0, removed); 839 // } else { 840 // _nextAdd(0, 1); 841 // _nextRemove(0, removed); 842 // } 843 // _endChange(); 844 // } 845 846 public boolean isSelected(int index) { 847 return bitset.get(index); 848 } 849 850 public boolean isNotSelected(int index) { 851 return !isSelected(index); 852 } 853 854 /** Returns number of true bits in BitSet */ 855 @Override public int size() { 856 return bitset.cardinality(); 857 } 858 859 /** Returns the number of bits reserved in the BitSet */ 860 public int bitsetSize() { 861 return bitset.size(); 862 } 863 864 @Override public int indexOf(Object obj) { 865 reset(); 866 return super.indexOf(obj); 867 } 868 869 @Override public boolean contains(Object o) { 870 if (o instanceof Number) { 871 Number n = (Number) o; 872 int index = n.intValue(); 873 874 return index >= 0 && index < bitset.length() && 875 bitset.get(index); 876 } 877 878 return false; 879 } 880 881 public void reset() { 882 this.lastGetIndex = -1; 883 this.lastGetValue = -1; 884 } 885 886 @Override public void _beginChange() { 887 if (!isAtomic()) { 888 super._beginChange(); 889 } 890 } 891 892 @Override public void _endChange() { 893 if (!isAtomic()) { 894 super._endChange(); 895 } 896 } 897 898 @Override public final void _nextUpdate(int pos) { 899 if (!isAtomic()) { 900 nextUpdate(pos); 901 } 902 } 903 904 @Override public final void _nextSet(int idx, Integer old) { 905 if (!isAtomic()) { 906 nextSet(idx, old); 907 } 908 } 909 910 @Override public final void _nextReplace(int from, int to, List<? extends Integer> removed) { 911 if (!isAtomic()) { 912 nextReplace(from, to, removed); 913 } 914 } 915 916 @Override public final void _nextRemove(int idx, List<? extends Integer> removed) { 917 if (!isAtomic()) { 918 nextRemove(idx, removed); 919 } 920 } 921 922 @Override public final void _nextRemove(int idx, Integer removed) { 923 if (!isAtomic()) { 924 nextRemove(idx, removed); 925 } 926 } 927 928 @Override public final void _nextPermutation(int from, int to, int[] perm) { 929 if (!isAtomic()) { 930 nextPermutation(from, to, perm); 931 } 932 } 933 934 @Override public final void _nextAdd(int from, int to) { 935 if (!isAtomic()) { 936 nextAdd(from, to); 937 } 938 } 939 } 940 }