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

8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing #172

Open
wants to merge 6 commits into
base: master
from

Conversation

@arapte
Copy link

arapte commented Apr 11, 2020

The issue occurs because the key events are consumed by the ListView in Popup, which displays the items.
This is a regression of JDK-8077916. This change aadded several KeyMappings for focus traversals to ListView, which consume the Left, Right and Ctrl+A key events.

Fix:

  1. Remove the four focus traversal arrow KeyMappings from ListViewBehavior.
  2. Add the Ctrl + A KeyMapping to ListViewBehavior only if the ListView's selection mode is set to SelectionMode.MULTIPLE. ComboBox uses the ListView with SelectionMode.SINGLE mode.

Change unrelated to fix:
ComboBoxListViewBehavior adds KeyMapping for Up and Down keys, which are not invoked when the ComboBox popup is showing. When the popup is shown, the Up and Down key events are handled by the ListView and the KeyMapping code from ComboBoxListViewBehavior does not get executed. These KeyMapping are removed.
However this change is not needed for the fix. But this seems to be dead code.

Verification:
Added new unit tests to verify the change.
Also verified that the behavior ListView behaves same before and after this change.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed

Issue

  • JDK-8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing

Download

$ git fetch https://git.openjdk.java.net/jfx pull/172/head:pull/172
$ git checkout pull/172

@bridgekeeper
Copy link

bridgekeeper bot commented Apr 11, 2020

👋 Welcome back arapte! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request.

@openjdk openjdk bot added the rfr label Apr 11, 2020
@mlbridge
Copy link

mlbridge bot commented Apr 11, 2020

@kevinrushforth
Copy link
Member

kevinrushforth commented Apr 14, 2020

/reviewers 2

@openjdk
Copy link

openjdk bot commented Apr 14, 2020

@kevinrushforth
The number of required reviews for this PR is now set to 2 (with at least 1 of role reviewers).

@kevinrushforth kevinrushforth self-requested a review Apr 21, 2020
@abhinayagarwal
Copy link
Contributor

abhinayagarwal commented Apr 22, 2020

Hi @arapte,

Great work on the PR 👍

Don't you think that all the changes in ListViewSkin can be moved to ListViewBehavior? All that we do in the skin class is to call ListViewBehavior#updateSelectionModeKeyMapping, which smells like feature envy.

Moreover, ListViewBehavior already has change listener attached to selectionModelProperty, waiting for us to re-use it 😉

@kleopatra
Copy link
Collaborator

kleopatra commented Apr 22, 2020

Don't you think that all the changes in ListViewSkin can be moved to ListViewBehavior? All that we do in the skin class is to call ListViewBehavior#updateSelectionModeKeyMapping, which smells like feature envy.

Moreover, ListViewBehavior already has change listener attached to selectionModelProperty, waiting for us to re-use it 😉

good point :) Though - I don't like listeners to control properties in behaviors, and much less listeners to path properties (they tend to not getting cleaned on dispose).

In the particular case of behaviors of controls with selectionModels they do (must?) because the selectionModel is not api complete (having no notion of anchor), so they jump in to cover up. Hopefully that design flaw will be fixed at some time in future, which would remove the existing listener, anyway. With just another responsibility - difference based on selectionMode - such cleanup would be harder.

Here the basic approach is to add/remove a keyMapping for multiple/single selection. Compared to current state, there's a subtle side-effect (the event bubbles up if the mapping if removed). We can achieve the same behavior (with the same side-effect) by making the mapping consume depending on whether it is handled (to select all) or not.

In code that would be pattern like:

// in constructor

    KeyMapping selectAllMapping;
    addDefaultMapping(listViewInputMap,
          ...
         selectAll = new KeyMapping(new KeyBinding(A).shortcut(), this:: selectAll),
         ...
     };
     selectAllMapping.setAutoConsume(false);

// selectAll with modified signature
/**
  * Calls selectAll on selectionModel and consumes the event, if 
  * the model is available and in multimode, 
  * does nothing otherwise. 
  */
private void selectAll(KeyEvent key) {
    MultipleSelectionModel<T> sm = getNode().getSelectionModel();
   // do nothing, let it bubble up
    if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) return;
   // handle and consume
    sm.selectAll();
    key.consume();
}

BTW, there are other keys that don't work as expected (from the perspective of the editor in the combo): f.i. shift-home/end is mapped to scrollToFirst/LastRow - that's hampering ux if f.i. the user has typed some input, uses them and sees her input lost because first/last row is selected. Sry to not have noticed earlier in my bug report :(

So whatever approach we choose (mappings being removed/added or their handlers not/consuming doesn't matter), we would have to do it for several keys. Plus we have the side-effect mentioned above. The mass of change for all listviews has a certain risk of breaking existing code. Think f.i. global accelerators that might (or not) get triggered depending on selection mode.

On the other hand, different mappings are needed only when the list resides in the combo's popup (and probably only if the combo is editable, didn't dig though). An alternative might be a different inputMap (or containing different mappings) when used in combo's popup (which is similar to what Swing/X does .. no wonder I would prefer it :)

@arapte
Copy link
Author

arapte commented Apr 22, 2020

ListViewBehavior already has change listener attached to selectionModelProperty, waiting for us to re-use it

Hi @abhinayagarwal, Thanks for the suggestion. This sound good to me too. I shall make this change in next commit.

@abhinayagarwal
Copy link
Contributor

abhinayagarwal commented Apr 23, 2020

Compared to current state, there's a subtle side-effect (the event bubbles up if the mapping if removed). We can achieve the same behavior (with the same side-effect) by making the mapping consume depending on whether it is handled (to select all) or not.

Changing the behavior inside selectAll is also a good option (and much cleaner as well)

So whatever approach we choose (mappings being removed/added or their handlers not/consuming doesn't matter), we would have to do it for several keys. Plus we have the side-effect mentioned above. The mass of change for all listviews has a certain risk of breaking existing code. Think f.i. global accelerators that might (or not) get triggered depending on selection mode.

It's sad that we can't easily override (InputMapping created in) ListViewBehavior :(

@arapte
Copy link
Author

arapte commented Apr 29, 2020

An alternative might be a different inputMap (or containing different mappings) when used in combo's popup (which is similar to what Swing/X does .. no wonder I would prefer it :)

Thanks for the suggestion 👍 , I shall try this approach and update the PR. I am not sure if we already do this for any other control. Do you know any, if we do ? Not actively working on this issue, Will soon get back on this :)

@kleopatra
Copy link
Collaborator

kleopatra commented Apr 29, 2020

the nearest to different input maps based on control state might be in listViewBehavior itself: it has differrent child maps for vertical/horizontal orientation. Could be possible to widen that a bit with another child map for vertical and in combo popup (provided it has a means to decide being in such a state for the sake of an interceptor, without api change that might be a simple entry in its properties)

@openjdk
Copy link

openjdk bot commented Jun 17, 2020

@arapte this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout ComboBox_Editor
git fetch https://git.openjdk.java.net/jfx master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push
@openjdk openjdk bot added the merge-conflict label Jun 17, 2020
@arapte arapte changed the title 8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing [WIP}8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing Jul 21, 2020
@arapte arapte changed the title [WIP}8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing [WIP]8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing Jul 21, 2020
@openjdk openjdk bot removed the rfr label Jul 21, 2020
Copy link
Member

kevinrushforth left a comment

Once the merge conflicts and review comments are addressed, I'll put this back on my review queue.

Ambarish Rapte
@openjdk openjdk bot removed the merge-conflict label Jul 28, 2020
Ambarish Rapte added 2 commits Jul 30, 2020
Ambarish Rapte
Ambarish Rapte
@arapte arapte changed the title [WIP]8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing 8209788: Left/Right/Ctrl+A keys not working in editor of ComboBox if popup showing Jul 30, 2020
@openjdk openjdk bot added the rfr label Jul 30, 2020
@arapte
Copy link
Author

arapte commented Jul 30, 2020

Please review the updated change:

  1. Changed the approach to add a property named removeKeyMappingsForComboBoxEditor to ListView when creating it from ComboBox. Please do suggest if this name sounds Ok.
  2. When this property is present some KeyMappings that are needed for ComboBox Editor get removed from ListViewBehavior.
  3. I have considered to remove some more KeyMappings which seem like they should be passed on to ComboBox's Editor.(method removeKeyMappingsForComboBoxEditor())
  4. There is an existing issue with BehaviorBase: JDK-8250807.
    Due to this issue, KeyMappings from child InputMap do not get removed. So the keys CTRL+SHIFT+HOME and CTRL+SHIFT+END will still not work with ComboBox editor.
  5. Updated ComboBoxTest for additional keys.
  6. Keeping the tests added for ListView, as they seem reasonable and not present already.

Change is very specific to ComboBox editor, so it should not affect any other tests.

@kevinrushforth kevinrushforth self-requested a review Jul 30, 2020
Boolean isRemoveKeyMappings = (Boolean) control.getProperties().get("removeKeyMappingsForComboBoxEditor");
if (isRemoveKeyMappings != null && isRemoveKeyMappings) {
behavior.removeKeyMappingsForComboBoxEditor();
}
Comment on lines 208 to 211

This comment has been minimized.

Copy link
@kleopatra

kleopatra Aug 3, 2020

Collaborator

hmm .. I would expect the ListViewBehavior to install the per-context mappings (vs. the ListViewSkin removing those that don't fit)

  • ComboBoxListViewSkin sets the context property to indicate that the list is part of the drop-down
  • ListViewBehavior installs (or not) the mappings that it needs in the context as appropriate (which would remove the need for the removeKeyMappingsForComboBoxEditor method to be public)
  • ListViewSkin has nothing special to do

What do I overlook?

This comment has been minimized.

Copy link
@arapte

arapte Aug 4, 2020

Author
* ComboBoxListViewSkin sets the context property to indicate that the list is part of the drop-down

Property is named such that it does not show relationship but only indicates the action that is needed. ComboBox already adds one such property named selectFirstRowByDefault. If we add a property to indicate that the ListView is part of ComboBox then we might need to remove the existing property as well, and combine the actions together under one property. So seems like naming the property to indicate actions is consistent.

ListViewBehavior to install the per-context mappings (vs. the ListViewSkin removing those that don't fit)

Installing(Not installing) specific KeyMappings has one difference compared to removing few later that, changes for installing specific mappings would be spread: 1) The KeyMappings in FocusTraversalInputMap will have to be broken into two parts like in the first commit. 2) In ListViewBehavior the default(line 82) and vertical orientation(line 130) KeyMappings will have to be broken into separate calls. Since this change is specifically for ComboBox , the regular code can be left as is and this fix can be treated as special case and kept separate packed into single call.

* remove the need for the removeKeyMappingsForComboBoxEditor method to be public

Keeping ListViewSkin untouched and making removeKeyMappingsForComboBoxEditor() private seems good to me too. I clearly overlooked. I shall move the change from ListViewSkin to end of ListViewBehavior constructor.

This comment has been minimized.

Copy link
@kleopatra

kleopatra Aug 4, 2020

Collaborator

good that we agree on where to put it :)

Will have to think about about the remove installed mappings vs. change existing code to not installing. Right now, feels a bit smelly .. but I see the constraints (and might agree :) Will come back!

This comment has been minimized.

Copy link
@kleopatra

kleopatra Aug 4, 2020

Collaborator

btw, using a key-value pair in properties is just fine with me (might have been unclear, sry) - nothing else to do short of changing api (which feels too heavy weight)

One nit-pit for checking for its existence, an alternative slightly simplified pattern might be

if (Boolean.TRUE.equals(control.getProperties().get(....)) {
    removeMappings(...);
}

This comment has been minimized.

Copy link
@kleopatra

kleopatra Aug 6, 2020

Collaborator

okay, I still favor the not-adding-if-not-needed approach - and it's not that much of a change to existing code. Basically, in the constructor of ListViewBehavior

  • in a first block, do not add the mappings that (currently) are removed again if the combo flag is set
  • in a second block, add the mappings for a stand-alone listView to all inputMaps as needed

code snippets:

    // not needed if in combo popup
    //  addDefaultMapping(listViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
    addDefaultMapping(listViewInputMap,
            // not needed if in combo popup
            // new KeyMapping(HOME, e -> selectFirstRow()),
            ...
           // add those that are always needed
    }

    // same for child input maps
    addDefaultMappings(verticalInputMap, ...

    // add those for stand-alone listView
    // handle mappings that are needed only in a stand-alone listView
    if (!Boolean.TRUE.equals(control.getProperties().get("removeKeyMappingsForComboBoxEditor"))) {
        addDefaultMapping(listViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
        addDefaultMapping(listViewInputMap, 
                new KeyMapping(HOME, e -> selectFirstRow()),
                new KeyMapping(END, e -> selectLastRow()),
                new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()),
                new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()),
                new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()),
                new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()),
                new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow())
        );

        addDefaultMapping(verticalListInputMap, 
                new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()),
                new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow())
        );
        
    }

Tests are passing as expected, and the open issue JDK-8250807 doesn't stand in the way :)

As to testing, independent of which approach is chosen at the end: I would suggest to not only test the macroscopic behavior (look if key events on combo/listView have the expected effect) but also test the base layer, that is whether or not the mappings are not/added to the inputMaps.

One open (maybe not-an) issue might be the handling of the horizontal map which currently is not touched (but will with the remove-again approach, once the deep removal of JDK-8250807 is fixed) : while not very probable, client code might change the listView in the popup to be horizontal. What would be the expected UI in that case? Anyway, could be left open for the scope of this issue, IMO.

Ambarish Rapte added 2 commits Aug 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.