diff options
author | mlouielu <git@louie.lu> | 2017-06-14 15:13:19 (GMT) |
---|---|---|
committer | terryjreedy <tjreedy@udel.edu> | 2017-06-14 15:13:19 (GMT) |
commit | 778b484145edfd0d9b65129322d3295bed8eb71a (patch) | |
tree | 5f8895ec0e82caecc3b5ec01b626a7a212796813 | |
parent | 4ebf03d109f827c91a23256a447c1d74a203dfee (diff) | |
download | cpython-778b484145edfd0d9b65129322d3295bed8eb71a.zip cpython-778b484145edfd0d9b65129322d3295bed8eb71a.tar.gz cpython-778b484145edfd0d9b65129322d3295bed8eb71a.tar.bz2 |
bpo-15786: IDLE: Fix mouse clicks on autocompletetion window (#1811)
The root problem was non-check for hide_event. When user clicks
on autocomplete window (acw), root widget gets focusOut event, then triggers hide_window to
close the acw. It should only be hide when acw is active, and acw didn't
get focus at FocusOut event (this event bind on acw and widget), or when
widget get a ButtonPress event (this event only bind on widget).
MacOS froze after double click on acw because when
doubleclick_event try to hide window at the end, hide_window function
destory whole acw, but tkinter didn't get focus back to widget. So set focus
on widget first, then destory acw.
Windows could not respond on double click event, because of the misbehavior
of Configure event. When acw was shown, tkinter called winconfig
event multiple times. That caused tkinter to not response to double
click event. When on Windows, unbind Configure event first
time get into winconfig_event to prevent multiple call of this event.
-rw-r--r-- | Lib/idlelib/autocomplete_w.py | 58 |
1 files changed, 50 insertions, 8 deletions
diff --git a/Lib/idlelib/autocomplete_w.py b/Lib/idlelib/autocomplete_w.py index 3374c6e..e7354d0 100644 --- a/Lib/idlelib/autocomplete_w.py +++ b/Lib/idlelib/autocomplete_w.py @@ -1,6 +1,8 @@ """ An auto-completion window for IDLE, used by the autocomplete extension """ +import platform + from tkinter import * from tkinter.ttk import Scrollbar @@ -8,7 +10,8 @@ from idlelib.autocomplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES from idlelib.multicall import MC_SHIFT HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>" -HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>") +HIDE_FOCUS_OUT_SEQUENCE = "<FocusOut>" +HIDE_SEQUENCES = (HIDE_FOCUS_OUT_SEQUENCE, "<ButtonPress>") KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>" # We need to bind event beyond <Key> so that the function will be called # before the default specific IDLE function @@ -201,10 +204,12 @@ class AutoCompleteWindow: self._selection_changed() # bind events - self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, - self.hide_event) + self.hideaid = acw.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event) + self.hidewid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event) + acw.event_add(HIDE_VIRTUAL_EVENT_NAME, HIDE_FOCUS_OUT_SEQUENCE) for seq in HIDE_SEQUENCES: self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) + self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypress_event) for seq in KEYPRESS_SEQUENCES: @@ -240,9 +245,37 @@ class AutoCompleteWindow: new_y -= acw_height acw.wm_geometry("+%d+%d" % (new_x, new_y)) + if platform.system().startswith('Windows'): + # See issue 15786. When on windows platform, Tk will misbehaive + # to call winconfig_event multiple times, we need to prevent this, + # otherwise mouse button double click will not be able to used. + acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid) + self.winconfigid = None + + def _hide_event_check(self): + if not self.autocompletewindow: + return + + try: + if not self.autocompletewindow.focus_get(): + self.hide_window() + except KeyError: + # See issue 734176, when user click on menu, acw.focus_get() + # will get KeyError. + self.hide_window() + def hide_event(self, event): + # Hide autocomplete list if it exists and does not have focus or + # mouse click on widget / text area. if self.is_active(): - self.hide_window() + if event.type == EventType.FocusOut: + # On windows platform, it will need to delay the check for + # acw.focus_get() when click on acw, otherwise it will return + # None and close the window + self.widget.after(1, self._hide_event_check) + elif event.type == EventType.ButtonPress: + # ButtonPress event only bind to self.widget + self.hide_window() def listselect_event(self, event): if self.is_active(): @@ -391,10 +424,15 @@ class AutoCompleteWindow: return # unbind events + self.autocompletewindow.event_delete(HIDE_VIRTUAL_EVENT_NAME, + HIDE_FOCUS_OUT_SEQUENCE) for seq in HIDE_SEQUENCES: self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) - self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) - self.hideid = None + + self.autocompletewindow.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideaid) + self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hidewid) + self.hideaid = None + self.hidewid = None for seq in KEYPRESS_SEQUENCES: self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq) self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid) @@ -405,8 +443,12 @@ class AutoCompleteWindow: self.keyreleaseid = None self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid) self.listupdateid = None - self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid) - self.winconfigid = None + if self.winconfigid: + self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid) + self.winconfigid = None + + # Re-focusOn frame.text (See issue #15786) + self.widget.focus_set() # destroy widgets self.scrollbar.destroy() |