Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Introduce MouseButton enum for MouseEvent. #12640

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

Merged
merged 1 commit into from
Nov 27, 2018

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Oct 26, 2018

MouseButton.LEFT/MIDDLE/RIGHT is a bit less cryptic than 1/2/3 (which
remain available as MouseButton is an IntEnum).

The names LEFT/MIDDLE/RIGHT are used by both Qt and wx, with the same
inversion for left-handed mode as noted in the docstring. (Gtk uses
PRIMARY/MIDDLE/SECONDARY, which works better for left-handed mode but
seems otherwise less readable; tk just uses 1/2/3).

Changed one example to demonstrate the use; I can change the other
examples if we agree on the general approach. (That specific example
printed mouse coordinates both on move and on click, which seems a bit
redundant, so I moved the "disconnect" part to the click handler.)

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

MouseButton.LEFT/MIDDLE/RIGHT is a bit less cryptic than 1/2/3 (which
remain available as MouseButton is an IntEnum).

The names LEFT/MIDDLE/RIGHT are used by both Qt and wx, with the same
inversion for left-handed mode as noted in the docstring.  (Gtk uses
PRIMARY/MIDDLE/SECONDARY, which works better for left-handed mode but
seems otherwise less readable; tk just uses 1/2/3).

Changed one example to demonstrate the use; I can change the other
examples if we agree on the general approach.  (That specific example
printed mouse coordinates both on move and on click, which seems a bit
redundant, so I moved the "disconnect" part to the click handler.)
@anntzer anntzer added this to the v3.1 milestone Oct 26, 2018
@timhoffm
Copy link
Member

Unsure if an IntEnum is better than supporting plain strings “left” etc.

Pro:

  • Supports completion

Con:

  • quite verbose because of the qualified name
  • everywhere else we currently use strings for enums (API consistency)

@anntzer
Copy link
Contributor Author

anntzer commented Oct 26, 2018

While we do use strings everywhere, I find them typo-prone: you get an error if you write event.button is MouseButton.lef, but nothing if you write event.button == "lef". (I don't personally care much about completion, but I guess it's important for some.)
It would also be a pretty gratuitous API break to switch to strings...

@timhoffm
Copy link
Member

With „get“ you mean your IDE or a linter? Both should fail at runtime.

Of course, the plain ints have to stay. Similar to the loc parameter.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 26, 2018

With „get“ you mean your IDE or a linter? Both should fail at runtime.

I mean at runtime. is MouseEvent.lef will get you an AttributeError, == "lef" will silently return false.

Of course, the plain ints have to stay. Similar to the loc parameter.

Then it's not clear what the proposed alternative change is.

@QuLogic
Copy link
Member

QuLogic commented Oct 27, 2018

On Linux scrolling is button 4/5, though I don't know how much that translates to the toolkits.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 28, 2018

@timhoffm
Copy link
Member

is MouseEvent.lef will get you an AttributeError, == "lef" will silently return false.

Yes that's true and I agree that's a benefit. However, in this particular case I propose that it's not worth the additional verbosity and API inconsistency (in particular since we already have "up" and "down" strings).

Then it's not clear what the proposed alternative change is.

Current solution:

button : {None, 1, 2, 3, 'up', 'down'}

Alternative proposed in this PR:

button : {None, MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, \
'up', 'down'}

My preferred alternative:

button : {None, 1, 2, 3, 'left', 'middle', 'right', 'up', 'down'}

@anntzer
Copy link
Contributor Author

anntzer commented Oct 29, 2018

I think I see the source of the confusion. You are (I believe?) thinking about the signature of the Event constructor, but that doesn't really matter -- in practice it must be exceptionally rare for end users to construct Events themselves (except for some mocking purposes, which I've done, but again that's not the main point). The main place where end users see events is the instance that's passed to them in eventhandlers (canvas.mpl_connect("button_press_event", lambda event: <do-something-with-event>)).
Now that event has a button attribute, and we need to decide whether it's 1 (current status) or MouseButton.LEFT (this PR) or "left" (as you propose). Changing it from 1 to MouseButton.LEFT doesn't break any API, because MouseButton is an IntEnum, so behaves like an int in any reasonable way (except explicit type equality checks); changing it to "left" would break any event handler that starts with if event.button == 1: ....

@timhoffm
Copy link
Member

I see. In that context it's ok to use an enum. Still, it's unfortunate that we currently already have an inconsistent API.

@@ -1425,6 +1426,12 @@ def _update_enter_leave(self):
LocationEvent.lastevent = self


class MouseButton(IntEnum):
Copy link
Member

Choose a reason for hiding this comment

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

could we change this to an Enum and add

UP = 'up'
DOWN = 'down'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, this will break uses like event.button == 1.
(I guess we could override __eq__ if you really want to...).

@dopplershift
Copy link
Contributor

dopplershift commented Oct 30, 2018

I’m pretty sure another point in favor of using ints/IntEnum is that equality is faster for ints/enums than strings.

Personally, I’m not a huge fan of using a set of strings for input parameters.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 30, 2018

Actually I'm warming up to the idea of making MouseButton an Enum and override __eq__ to work for with 1/2/3 and "up"/"down". That means that really funky use cases such as event.button + 1 or event.button.upper() won't work anymore but heh... Thoughts?

@timhoffm
Copy link
Member

I’m pretty sure another point in favor of using ints/IntEnum is that equality is faster for ints/enums than strings.

The difference in string vs int equality comparison should be absolutely negligible in this context.

Just to prove:

In [1]: def foo(a):
   ...:     return a == 1
   ...: 

In [2]: %timeit foo(2)
62.8 ns ± 0.136 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def bar(a):
   ...:     return a == "left"
   ...: 
   ...: 

In [4]: %timeit bar('left')
65.5 ns ± 0.162 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

@timhoffm
Copy link
Member

I'm fine with an IntEnum. It's not making it worse compared to now.

Not sure if an Enum with an overloaded __eq__ is too clever. OTOH it would unify the API while maintaining basic compatibility. The funky usecases are rather an abuse and already now each would only work for a subset of current MouseButton values. I wouldn't worry about them.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 30, 2018

The funky usecases are rather an abuse and already now each would only work for a subset of current MouseButton values.

Well, you don't seem them in the same event handlers: buttons go to the button_press_event and scrolls to the scroll_event.

@timhoffm
Copy link
Member

You could of course overload __str__ and __add__ as well 😄 . But no, let's keep it simple and just introduce the IntEnum unless somebody else advocates the more sophisticated solution.

@@ -1470,6 +1482,8 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
button pressed None, 1, 2, 3, 'up', 'down'
"""
LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
if button in MouseButton.__members__.values():
button = MouseButton(button)
Copy link
Member

Choose a reason for hiding this comment

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

do we want to add a check that we did not get something invalid?

Copy link
Contributor Author

@anntzer anntzer Nov 4, 2018

Choose a reason for hiding this comment

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

Well, there wasn't a check before, and I guess someone can always have a 25-button mouse and use MouseEvent for their own thing...

@tacaswell
Copy link
Member

  • everywhere else we currently use strings for enums (API consistency)

This is something I am in favor of addressing over time (and moving to more enums).

@anntzer
Copy link
Contributor Author

anntzer commented Nov 4, 2018

Thoughts about just moving to Enum and override __eq__? I guess then we could make up/down be 4/5 to match the X model (while still being "equal" to "up"/"down", and they get a nice repr so no one gets confused...).

@timhoffm
Copy link
Member

timhoffm commented Nov 4, 2018

Thoughts about just moving to Enum and override eq?

I think we have to use something like this. Also, when we think about moving to enums in other places that accept strings so far, this would be the way to keep backward compatibility.

@tacaswell "moving to more enums". Is that intended

  • mainly for internal use?
  • an addition to the API and enum values are equally supported as strings.
  • a replacement for string parameters, which would be deprecated and removed in the long term?

@anntzer
Copy link
Contributor Author

anntzer commented Nov 24, 2018

@tacaswell or @dopplershift either of you want to do the second review?

Copy link
Contributor

@dopplershift dopplershift left a comment

Choose a reason for hiding this comment

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

This makes things better, and I'm not passionate about adding a validity check.

IMO, using Enum and adding an __eq__ override just continues our poor API design in accepting too many different types for things. If anything, we should eliminate deprecate up and down and move them to integer values.

@anntzer
Copy link
Contributor Author

anntzer commented Nov 27, 2018

Ready to merge it? :)

@dopplershift dopplershift merged commit cb8969f into matplotlib:master Nov 27, 2018
@anntzer anntzer deleted the mousebutton branch November 27, 2018 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants