Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8235491: Tree/TableView: implementation of isSelected(int) violates contract #839

Conversation

andy-goryachev-oracle
Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle commented Jul 19, 2022

  1. reword SelectionModel.isSelected(int) javadoc, removing incorrect statement "Is functionally equivalent to calling getSelectedIndices().contains(index)."
  2. reimplement TableView.TableViewSelectionModel.isSelected(int) method to return true when at least one cell in any column is selected on the given row (was: all columns)
  3. modified TreeTableRow.updateSelection() to use the right isSelected() method
  4. updated tests for Tree/TableView

NOTE: proposed change alters semantics of isSelected(int) method (in the right direction, in my opinion).


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires a CSR request to be approved
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issues

  • JDK-8235491: Tree/TableView: implementation of isSelected(int) violates contract
  • JDK-8290741: Tree/TableView: implementation of isSelected(int) violates contract (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx pull/839/head:pull/839
$ git checkout pull/839

Update a local copy of the PR:
$ git checkout pull/839
$ git pull https://git.openjdk.org/jfx pull/839/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 839

View PR using the GUI difftool:
$ git pr show -t 839

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/839.diff

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 19, 2022

👋 Welcome back angorya! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk openjdk bot changed the title 8235491: Tree/TableView: implementation of isSelected(int) violates c… 8235491: Tree/TableView: implementation of isSelected(int) violates contract Jul 19, 2022
@openjdk openjdk bot added the rfr Ready for review label Jul 19, 2022
@mlbridge
Copy link

mlbridge bot commented Jul 19, 2022

@kevinrushforth
Copy link
Member

Because there is a spec change, and a behavioral change, this needs a CSR. What are the implications of this change on applications?

/reviewers 2
/csr

@openjdk
Copy link

openjdk bot commented Jul 19, 2022

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Jul 19, 2022
@openjdk
Copy link

openjdk bot commented Jul 19, 2022

@kevinrushforth has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@andy-goryachev-oracle please create a CSR request for issue JDK-8235491 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@andy-goryachev-oracle
Copy link
Contributor Author

I think the behavior of utility method SelectionModel.isSelected(int) was poorly defined in the context of TableView, and/or incorrect in case of cell selection enabled.

People who used TableView.isSelected(int, TableColumn|TableColumnBase) will be ok, but those who assumed (arguable incorrect) implementation may need to revisit their code, and only in the case when cell selection is enabled.

@kevinrushforth
Copy link
Member

OK. When you create the CSR, you can add a note about possible compatibility impact. Seems it should be low.

@kevinrushforth
Copy link
Member

@kleopatra Would you have time / interest to review this?

@andy-goryachev-oracle
Copy link
Contributor Author

Kevin:
Proposed CSR - JDK-8290741
what would be the "correct fix version"?

@openjdk openjdk bot removed the rfr Ready for review label Jul 21, 2022
@openjdk openjdk bot added the rfr Ready for review label Jul 21, 2022
@kevinrushforth
Copy link
Member

Kevin: Proposed CSR - JDK-8290741 what would be the "correct fix version"?

openjfx20

Copy link
Collaborator

@aghaisas aghaisas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I see that you have made below change for cell selection mode -
  • Before : isSelected(int) method used to return True only if all of the cells in that row were selected.

  • After : isSelected(int) method returns True if any of the cells in that row are selected.

    Now, if someone wants to know - while in Cell selection mode, whether all of the cells in a particular row are selected? then they must use another already existing API isSelected(int row, TableColumn<S,?> column) with column parameter as null.
    This is important and should be captured somewhere as recommendation.

  1. Similar change needs to be made for TreeTableView as well
  2. I have listed a minor comment on coding style

@Override
public boolean isSelected(int index) {
final boolean isCellSelectionEnabled = isCellSelectionEnabled();
if (isCellSelectionEnabled) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Style : Why to create a local variable with the same name as method it gets its value from?
In this case changing to if (isCellSelectionEnabled()) condition check looks simple enough.

Copy link
Contributor Author

@andy-goryachev-oracle andy-goryachev-oracle Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for your comments, Ajit! below are responses, please let me know if you agree or not:

  1. javadoc for TableSelectionModel.isSelected(int, TablecolumnBase) already describes the logic:
    `
    /**

    • Convenience function which tests whether the given row and column index
    • is currently selected in this table instance. If the table control is in its
    • 'cell selection' mode (where individual cells can be selected, rather than
    • entire rows), and if the column argument is null, this method should return
    • true only if all cells in the given row are selected.
    • @param row the row
    • @param column the column
    • @return true if the given row and column index is currently selected in
    • this table instance
      */
      public abstract boolean isSelected(int row, TableColumnBase<T,?> column);
      `
  2. TreeTableView.TreeTableViewSelectionModel extends TableSelectionModel, so the changes affect both.

  3. good point, fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. javadoc for TableSelectionModel.isSelected(int, TablecolumnBase) already describes the logic:

Yes. I am aware of this documentation. I was thinking of making it clear for the users if they see their application breaks due to this change. I see that you have captured this in "Compatibility Impact" section of the CSR.

2. TreeTableView.TreeTableViewSelectionModel extends TableSelectionModel, so the changes affect both.

I see that there are overridden methods public boolean isSelected(int index) and public boolean isSelected(int row, TableColumnBase<TreeItem<S>,?> column) in class TreeTableViewArrayListSelectionModel present in TreeTableView.java. Hence, I think that the change that you have made in TableView.java - isSelected(int index) method should also be made in TreeTableView.java - isSelected(int index) as well.

3. good point, fixed.

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Would you suggest possible clarification please? The javadoc for SelectionModel.isSelected(int) is changed to say
Convenience method to inform if the given index is currently selected
     * in this SelectionModel.  It will return true when at least one cell
     * is selected in the specified row index.
  1. you are right! fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aghaisas

Yes. I am aware of this documentation. I was thinking of making it clear for the users if they see their application breaks due to this change.

If any application breaks, it's its own fault: they coded against the implementation of TableXXSelectionModels which was (incorrectly) isSelected(int) == isSelected(int, null).

That might be due to the specification mess, though:

  • the old spec on SelectionModel.isSelected(int) was incorrectly arguing with api on MultipleSelectionModel, that is Is functionally equivalent to calling getSelectedIndices().contains(index)
  • that invariant actually belongs into MultipleSelectionModel.isSelected(int) which has no specialized spec
  • the method is first implemented in the (unfortunately hidden) MultipleSelectionModelBase.isSelected(int) fulfilling the constraint but also without spec.

To clean this up completely, we could also change the of MultipleSelectionModel and move the old isSelected(int) == getSelectedIndices().contains(int) into the spec of its isSelected. Probably could be done in a follow-up issue (or added to one of the open doc errors around selection models).

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clean this up completely, we could also change the of MultipleSelectionModel and move the old isSelected(int) == getSelectedIndices().contains(int) into the spec of its isSelected. Probably could be done in a follow-up issue (or added to one of the open doc errors around selection models).

What do you think?

I like this idea. Either filing a new bug or fold into an existing one seems fine.

@aghaisas
Copy link
Collaborator

  1. Would you suggest possible clarification please?

As you have mentioned the impact of this change & using alternate API in "Compatibility Impact" section of the CSR - we are good! No additional change is required.

One minor issue - the description of this PR starts with ...ontract - looks like some typo.

@andy-goryachev-oracle
Copy link
Contributor Author

One minor issue - the description of this PR starts with ...ontract - looks like some typo.

github truncated the title and placed the remainder into PR description, I just left it there. fixed.

Copy link
Collaborator

@kleopatra kleopatra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically good fix, left a couple of change suggestions/questions inline :)

Comment on lines 2816 to 2829
@Override
public boolean isSelected(int index) {
if (isCellSelectionEnabled()) {
int columnCount = tableView.getVisibleLeafColumns().size();
for (int col = 0; col < columnCount; col++) {
if (selectedCellsMap.isSelected(index, col)) {
return true;
}
}
return false;
} else {
return selectedCellsMap.isSelected(index, -1);
}
}
Copy link
Collaborator

@kleopatra kleopatra Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering why you distinguish between not/ cellSelection? This method is all about row selection (that is the dimension available in base/multiple selection) - shouldn't it behave the same way, independent of whether the second - cell - dimension is turned on? If so, we could simply delegate to super - or not override it at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question!
The implementation is lifted from its isSelected(int,Tree/TableColumn) sibling with the logic altered for the case of enabled cell selection.
Let me check if the logic can be delegated to superclass.

Copy link
Collaborator

@kleopatra kleopatra Aug 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think that I found a case where this implementation violates the invariant getSelectedIndices().contains(row) == isSelected(row) - happens in cell selection mode when we select the last column and then hide that column:

sm.select(row, column);
assertTrue("sanity: row " + row + "contained in selectedIndices", sm.getSelectedIndices().contains(row));
assertTrue("sanity: row must be selected" , sm.isSelected(row));
column.setVisible(false);
assertTrue("after hiding column: row " + row + "contained in selectedIndices", sm.getSelectedIndices().contains(row));
assertTrue("after hiding column: row must be selected" , sm.isSelected(row));

the last line fails for cell selection enabled with this implementation and passes when delegating to super (or not override isSelected(int) at all)

Aside: there is a general issue with cell selection not updated on columns modification (the selection visual is kept on the same column index without changing selectedCells accordingly - technically due to looping across the visibleLeafCells vs. all leaf columns. Might or might not be what ux requires, but the selectedCells must be in sync with the visuals always). Don't remember if we have it covered in JBS?

The complete test set:

@Test
public void testRowSelectionAfterSelectAndHideLastColumnMultipleCellEnabled() {
    assertRowSelectionAfterSelectAndHideLastColumn(SelectionMode.MULTIPLE, true);
}

@Test
public void testRowSelectionAfterSelectAndHideLastColumnMultipleNotCellEnabled() {
    assertRowSelectionAfterSelectAndHideLastColumn(SelectionMode.MULTIPLE, false);
}

@Test
public void testRowSelectionAfterSelectAndHideLastColumnSingleCellEnabled() {
    assertRowSelectionAfterSelectAndHideLastColumn(SelectionMode.SINGLE, true);
}

@Test
public void testRowSelectionAfterSelectAndHideLastColumnSingleNotCellEnabled() {
    assertRowSelectionAfterSelectAndHideLastColumn(SelectionMode.SINGLE, false);
}

public void assertRowSelectionAfterSelectAndHideLastColumn(SelectionMode mode, boolean cellEnabled) {
    TableView<Person> table = createPersonTableView();

    TableView.TableViewSelectionModel<Person> sm = table.getSelectionModel();
    sm.setCellSelectionEnabled(cellEnabled);
    sm.setSelectionMode(mode);
    int row = 1;
    int col = table.getColumns().size() - 1;
    assertRowSelectionAfterSelectAndHideColumn(table, row, col);
}

private void assertRowSelectionAfterSelectAndHideColumn(TableView<Person> table, int row, int col) {
    TableViewSelectionModel<Person> sm = table.getSelectionModel();
    TableColumn<Person, ?> column = table.getColumns().get(col);
    TablePosition<Person, ?> pos = new TablePosition<>(table, row, column);

    sm.select(row, column);
    assertTrue("sanity: row " + row + "contained in selectedIndices", sm.getSelectedIndices().contains(row));
    assertTrue("sanity: row must be selected" , sm.isSelected(row));
    column.setVisible(false);
    assertTrue("after hiding column: row " + row + "contained in selectedIndices", sm.getSelectedIndices().contains(row));
    assertTrue("after hiding column: row must be selected" , sm.isSelected(row));
}

/**
 * Creates and returns a TableView with Persons and columns for all their properties.
 */
private TableView<Person> createPersonTableView() {
    final ObservableList<Person> data =
            FXCollections.observableArrayList(
                    new Person("Jacob", "Smith", "jacob.smith@example.com"),
                    new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                    new Person("Ethan", "Williams", "ethan.williams@example.com"),
                    new Person("Emma", "Jones", "emma.jones@example.com"),
                    new Person("Michael", "Brown", "michael.brown@example.com"));

    TableView<Person> table = new TableView<>();
    table.setItems(data);

    TableColumn<Person, String> firstNameCol = new TableColumn("First Name");
    firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));

    TableColumn<Person, String> lastNameCol = new TableColumn("Last Name");
    lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));

    TableColumn<Person, String> emailCol = new TableColumn("Email");
    emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));

    table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);

    return table;
}

arrgg .. this site is killing me again - no idea why this comment is duplicated .. sry

return false;
} else {
return selectedCellsMap.isSelected(index, -1);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as tableView - just to not forget :)

Comment on lines -175 to 178
assertFalse(model.isSelected(3));
assertTrue(model.isSelected(3));
assertEquals(1, model.getSelectedCells().size());
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have tests isSelected(index) for all combinations of single/multiple/cellSelection? If not, now might be a good time to add them :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a very good point!
should definitely cover all the scenarios.

Comment on lines 136 to 139
assertFalse(cells(model), model.isSelected(3, null));
assertFalse(model.isSelected(3));
assertTrue(model.isSelected(3));
assertEquals(1, model.getSelectedCells().size());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as TableView :)

@kleopatra
Copy link
Collaborator

forgot: will not be near my IDE until next Monday - then I'll run the tests and see for myself :)

@kleopatra
Copy link
Collaborator

the tests - they deserve to be integrated into TableViewSelectionModelImplTest, with your permission.

similar tests (along with that in the bug report) adapted to treeTable should be added to the treeTable test - we should keep them as sync'ed as possible, to not make future maintainers wonder why they aren't included :)

@andy-goryachev-oracle
Copy link
Contributor Author

similar tests (along with that in the bug report) adapted to treeTable should be added to the treeTable test - we should keep them as sync'ed as possible, to not make future maintainers wonder why they aren't included :)

The tests were added to both TableViewSelectionModelImplTest and TreeTableViewSelectionModelImplTest.

@kleopatra
Copy link
Collaborator

The tests were added to both TableViewSelectionModelImplTest and TreeTableViewSelectionModelImplTest.

hmm .. in TreeTableViewSelectionModelImplTest, I don't see any of the testRowSelectionAfterSelectAndHideLastColumnXX nor testSelectRowWhenInSingleCellSelectionModeIsSelected, what am I missing?

@andy-goryachev-oracle
Copy link
Contributor Author

hmm .. in TreeTableViewSelectionModelImplTest, I don't see any of the testRowSelectionAfterSelectAndHideLastColumnXX nor testSelectRowWhenInSingleCellSelectionModeIsSelected, what am I missing?

you are right... I am so sorry. missed this one while cherry picking from another branch. will fix it soon.

would it be possible to review #858 so there is no need to cherry pick, please?

@kleopatra
Copy link
Collaborator

you are right... I am so sorry. missed this one while cherry picking from another branch. will fix it soon.

no problem, shit happens :)

Copy link
Collaborator

@kleopatra kleopatra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long journey with a good end :)

Looks fine, verified tests:

  • some assertions needed to be adjusted to the fix, so they were passing before and still passing after the fix
  • some where failing/passing before/after the fix

@aghaisas
Copy link
Collaborator

I see a very good progress on this PR. Thanks @andy-goryachev-oracle and @kleopatra !
Only thing that needs to be corrected is - the PR description has become out of date with all the review change fixes.

@andy-goryachev-oracle
Copy link
Contributor Author

updated PR description. thank you, @aghaisas

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks good to me now. I left one comment on the CSR, and I'll review that once that is done.

This will give @aghaisas a chance to review as well.

Copy link
Collaborator

@aghaisas aghaisas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks good to me!

@openjdk openjdk bot removed the csr Need approved CSR to integrate pull request label Aug 18, 2022
@openjdk
Copy link

openjdk bot commented Aug 18, 2022

@andy-goryachev-oracle This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8235491: Tree/TableView: implementation of isSelected(int) violates contract

Reviewed-by: aghaisas, fastegal, kcr

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 10 new commits pushed to the master branch:

  • dee34b4: 8292391: Add support for optional signing of native libraries
  • b951503: 8087557: [Win] [Accessibility, Dialogs] Alert Dialog content is not fully read by Screen Reader
  • bee2dfb: 8291625: DialogPane without header nor headerText nor graphic node adds padding to the left of the content pane
  • d4ff45a: 8089009: TableView with CONSTRAINED_RESIZE_POLICY incorrectly displays a horizontal scroll bar.
  • 8815981: 8271395: Crash during printing when disposing textures
  • cedc17c: 8292549: GitHub actions: intermittent build failure on macOS while downloading ant
  • 5c47295: 8290473: update Eclipse .classpath in apps, buildSrc
  • 2618bf8: 8181084: JavaFX show big icons in system menu on macOS with Retina display
  • eaddb0f: 8291908: VirtualFlow creates unneeded empty cells
  • 33534cb: 8291630: Update attribution in webkit.md file

Please see this link for an up-to-date comparison between the source branch of this pull request and the master branch.
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@aghaisas, @kleopatra, @kevinrushforth) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Ready to be integrated label Aug 18, 2022
@kleopatra
Copy link
Collaborator

hmm .. isn't this ready for integration? CSR being approved was the last step, or not?

@andy-goryachev-oracle
Copy link
Contributor Author

/integrate

@openjdk openjdk bot added the sponsor Ready to sponsor label Aug 23, 2022
@openjdk
Copy link

openjdk bot commented Aug 23, 2022

@andy-goryachev-oracle
Your change (at version 9ad9352) is now ready to be sponsored by a Committer.

@kevinrushforth
Copy link
Member

/sponsor

@openjdk
Copy link

openjdk bot commented Aug 23, 2022

Going to push as commit 7cb8d67.
Since your change was applied there have been 10 commits pushed to the master branch:

  • dee34b4: 8292391: Add support for optional signing of native libraries
  • b951503: 8087557: [Win] [Accessibility, Dialogs] Alert Dialog content is not fully read by Screen Reader
  • bee2dfb: 8291625: DialogPane without header nor headerText nor graphic node adds padding to the left of the content pane
  • d4ff45a: 8089009: TableView with CONSTRAINED_RESIZE_POLICY incorrectly displays a horizontal scroll bar.
  • 8815981: 8271395: Crash during printing when disposing textures
  • cedc17c: 8292549: GitHub actions: intermittent build failure on macOS while downloading ant
  • 5c47295: 8290473: update Eclipse .classpath in apps, buildSrc
  • 2618bf8: 8181084: JavaFX show big icons in system menu on macOS with Retina display
  • eaddb0f: 8291908: VirtualFlow creates unneeded empty cells
  • 33534cb: 8291630: Update attribution in webkit.md file

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Aug 23, 2022
@openjdk openjdk bot closed this Aug 23, 2022
@openjdk openjdk bot removed ready Ready to be integrated rfr Ready for review sponsor Ready to sponsor labels Aug 23, 2022
@openjdk
Copy link

openjdk bot commented Aug 23, 2022

@kevinrushforth @andy-goryachev-oracle Pushed as commit 7cb8d67.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@andy-goryachev-oracle andy-goryachev-oracle deleted the 8235491.isselected branch August 23, 2022 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

4 participants