2

... in particular when modifying the underlying items?

Below is a quick example that selects a range and adds an item above the selection for TableView and ListView. The selectedIndices before/after adding:

indices before modification: [2, 3]
indices after modification: [3, 4]

Expected options:

  • a single wasReplaced for the whole range
  • a wasRemoved for the "2" and a wasAdded for the "4"
  • ??

Actual:

  • table's selection fires two events, each with a single wasAdded
  • list's selection fires a wasPermutated (that's nuts, or what am I missing?)

Output (jdk8u40b12):

Change #0 on TableView indices 
list = [3]
    Change event data:

 class javafx.scene.control.MultipleSelectionModelBase$3
 javafx.scene.control.MultipleSelectionModelBase$3@4ececa
    cursor = 0
        Kind of change: added
        Affected range: [0, 1]
        Added size: 1
        Added sublist: [3]

Change #1 on TableView indices 
list = [3, 4]
    Change event data:

 class javafx.scene.control.MultipleSelectionModelBase$3
 javafx.scene.control.MultipleSelectionModelBase$3@b0161d
    cursor = 0
        Kind of change: added
        Affected range: [1, 2]
        Added size: 1
        Added sublist: [4]

Change #0 on ListView indices 
list = [3, 4]
    Change event data:

 class com.sun.javafx.collections.NonIterableChange$SimplePermutationChange
 { permutated by [4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0] }
    cursor = 0
        Kind of change: permutated
        Affected range: [0, 2]
        Permutation: [0->4, 1->3]

The producing code:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ListChangeListener.Change;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class SelectedIndicesOnItemsModified extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<Integer> items = FXCollections.observableArrayList(1, 2, 3, 4);
        TableView<Integer> table = new TableView<>(items);
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        table.getSelectionModel().selectRange(2, 4);
        System.out.println("indices before modification: " + 
            table.getSelectionModel().getSelectedIndices());
        ListView<Integer> list = new ListView<>(items);
        list.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        list.getSelectionModel().selectRange(2, 4);

        new PrintingListChangeListener("TableView indices ", 
             table.getSelectionModel().getSelectedIndices());        
        new PrintingListChangeListener("ListView indices ", 
             list.getSelectionModel().getSelectedIndices());  
        items.add(0, 111);
    }

    public static void main(String[] args) {
        launch(args);
    }

    public static <T> void prettyPrint(Change<? extends T> change) {
        StringBuilder sb = new StringBuilder("\tChange event data:\n");
        sb.append("\n " + change.getClass() + "\n " + change);
        int i = 0;
        change.reset();
        while (change.next()) {
            sb.append("\n\tcursor = ").append(i++).append("\n");

            final String kind = change.wasPermutated() ? "permutated" : change
                    .wasReplaced() ? "replaced"
                    : change.wasRemoved() ? "removed"
                            : change.wasAdded() ? "added"
                                    : change.wasUpdated() ? "updated" : "none";
            sb.append("\t\tKind of change: ").append(kind).append("\n");

            sb.append("\t\tAffected range: [").append(change.getFrom())
                    .append(", ").append(change.getTo()).append("]\n");

            if (kind.equals("added") || kind.equals("replaced")) {
                sb.append("\t\tAdded size: ").append(change.getAddedSize())
                        .append("\n");
                sb.append("\t\tAdded sublist: ")
                        .append(change.getAddedSubList()).append("\n");
            }

            if (kind.equals("removed") || kind.equals("replaced")) {
                sb.append("\t\tRemoved size: ").append(change.getRemovedSize())
                        .append("\n");
                sb.append("\t\tRemoved: ").append(change.getRemoved())
                        .append("\n");
            }

            if (kind.equals("permutated")) {
                StringBuilder permutationStringBuilder = new StringBuilder("[");
                for (int k = change.getFrom(); k < change.getTo(); k++) {
                    permutationStringBuilder.append(k).append("->")
                            .append(change.getPermutation(k));
                    if (k < change.getTo() - 1) {
                        permutationStringBuilder.append(", ");
                    }
                }
                permutationStringBuilder.append("]");
                String permutation = permutationStringBuilder.toString();
                sb.append("\t\tPermutation: ").append(permutation).append("\n");
            }
        }
        System.out.println(sb.toString());
    };

    public static class PrintingListChangeListener implements ListChangeListener {
        String source;
        int counter;
        public PrintingListChangeListener() {
        }

        public PrintingListChangeListener(String message, ObservableList<?> list) {
            list.addListener(this);
            source = message;
        }
        @Override
        public void onChanged(Change change) {
            System.out.println("Change #" + counter++ + " on " +source + 
                 "\nlist = " + change.getList());
            prettyPrint(change);
        }
    }

}

Filed two issues, RT-39393 for ListView, RT-39394 for TableView

kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • com.sun.javafx.collections ??? :-) – mKorbel Nov 13 '14 at 16:52
  • @mKorbel that's just the type of change which is created by MultipleSelectionModelBase, nothing in my code :-) – kleopatra Nov 13 '14 at 16:57
  • aaach I can see that, anyway thanks for explanation – mKorbel Nov 13 '14 at 16:58
  • Yes, that's nuts. What's with the permutation? All my JDK versions (8u40, 8u25, 7u67) show a "permutation". Every version prior to 8u40 at least shows a "removed" change. (So I guess it's a regression from that perspective.) – James_D Nov 13 '14 at 17:03
  • @James_D thanks for checking :-) Will wait a day or two to get more feedback, and then report a bug – kleopatra Nov 13 '14 at 17:10

1 Answers1

1

A tentative partly answer to my own question

Notification count

Number of notification (aka: calls to changed(Change c)) should be the same as number in underlying data, in pseudo-code

itemsChanges = 0; 
itemsListener = c -> itemsChanges++;
getItems().addListener(itemsListener);
selectedChanges = 0; 
selectedListener = c -> selectedChanges++; 
getSelectedIndices().addListener(selectedListener);
getItems().modifySomehow(...); 
assertEquals(itemsChanges, selectedChanges);

Change Types

Thinking about it, most changes in the underlying items seem to map to a replaced in the selectedIndices ("value" below denotes the elements in the selectedIndices):

"real" wasAdded: all values greater than the insertion location must be increased by addedSize

// selectedIndices before
[2, 4]
items.add(0, something);
// selectedIndices after
[3, 5]

-> net effect: two values are set (== replaced by) to a new value

"real" wasRemoved: all values pointing to removed items must be removed as well, values greater than the remove location must be decreased by removedSize

// selectedIndices before
[2, 4, 5, 8]
items.removeAll(items.get(3), items.get(5))
// selectedIndices after
[2, 3, 6]

-> net effect: replace [4, 5, 8] at pos 1 by [3, 6]

wasUpdated: indices are unchanged, though the underlying items did change somehow. Depending on context, it might be a good idea to pass those updates through to its listener or not.

Still open: replaced/permutation in items

To get those expected (at least by my paper coding :-) notifications correct, isn't quite trivial - and went wrong in MultipleSelectionModelBase and subclasses. Currently playing with moving all the nasty details into a dedicated IndicesList (which is-a TransformList with items as sourceList)

kleopatra
  • 51,061
  • 28
  • 99
  • 211