Skip to content

Commit 778b484

Browse files
mlouieluterryjreedy
authored andcommitted
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.
1 parent 4ebf03d commit 778b484

File tree

1 file changed

+50
-8
lines changed

1 file changed

+50
-8
lines changed

Lib/idlelib/autocomplete_w.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
"""
22
An auto-completion window for IDLE, used by the autocomplete extension
33
"""
4+
import platform
5+
46
from tkinter import *
57
from tkinter.ttk import Scrollbar
68

79
from idlelib.autocomplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES
810
from idlelib.multicall import MC_SHIFT
911

1012
HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
11-
HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
13+
HIDE_FOCUS_OUT_SEQUENCE = "<FocusOut>"
14+
HIDE_SEQUENCES = (HIDE_FOCUS_OUT_SEQUENCE, "<ButtonPress>")
1215
KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
1316
# We need to bind event beyond <Key> so that the function will be called
1417
# before the default specific IDLE function
@@ -201,10 +204,12 @@ def show_window(self, comp_lists, index, complete, mode, userWantsWin):
201204
self._selection_changed()
202205

203206
# bind events
204-
self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
205-
self.hide_event)
207+
self.hideaid = acw.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
208+
self.hidewid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
209+
acw.event_add(HIDE_VIRTUAL_EVENT_NAME, HIDE_FOCUS_OUT_SEQUENCE)
206210
for seq in HIDE_SEQUENCES:
207211
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
212+
208213
self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
209214
self.keypress_event)
210215
for seq in KEYPRESS_SEQUENCES:
@@ -240,9 +245,37 @@ def winconfig_event(self, event):
240245
new_y -= acw_height
241246
acw.wm_geometry("+%d+%d" % (new_x, new_y))
242247

248+
if platform.system().startswith('Windows'):
249+
# See issue 15786. When on windows platform, Tk will misbehaive
250+
# to call winconfig_event multiple times, we need to prevent this,
251+
# otherwise mouse button double click will not be able to used.
252+
acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
253+
self.winconfigid = None
254+
255+
def _hide_event_check(self):
256+
if not self.autocompletewindow:
257+
return
258+
259+
try:
260+
if not self.autocompletewindow.focus_get():
261+
self.hide_window()
262+
except KeyError:
263+
# See issue 734176, when user click on menu, acw.focus_get()
264+
# will get KeyError.
265+
self.hide_window()
266+
243267
def hide_event(self, event):
268+
# Hide autocomplete list if it exists and does not have focus or
269+
# mouse click on widget / text area.
244270
if self.is_active():
245-
self.hide_window()
271+
if event.type == EventType.FocusOut:
272+
# On windows platform, it will need to delay the check for
273+
# acw.focus_get() when click on acw, otherwise it will return
274+
# None and close the window
275+
self.widget.after(1, self._hide_event_check)
276+
elif event.type == EventType.ButtonPress:
277+
# ButtonPress event only bind to self.widget
278+
self.hide_window()
246279

247280
def listselect_event(self, event):
248281
if self.is_active():
@@ -391,10 +424,15 @@ def hide_window(self):
391424
return
392425

393426
# unbind events
427+
self.autocompletewindow.event_delete(HIDE_VIRTUAL_EVENT_NAME,
428+
HIDE_FOCUS_OUT_SEQUENCE)
394429
for seq in HIDE_SEQUENCES:
395430
self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
396-
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
397-
self.hideid = None
431+
432+
self.autocompletewindow.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideaid)
433+
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hidewid)
434+
self.hideaid = None
435+
self.hidewid = None
398436
for seq in KEYPRESS_SEQUENCES:
399437
self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
400438
self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
@@ -405,8 +443,12 @@ def hide_window(self):
405443
self.keyreleaseid = None
406444
self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
407445
self.listupdateid = None
408-
self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
409-
self.winconfigid = None
446+
if self.winconfigid:
447+
self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
448+
self.winconfigid = None
449+
450+
# Re-focusOn frame.text (See issue #15786)
451+
self.widget.focus_set()
410452

411453
# destroy widgets
412454
self.scrollbar.destroy()

0 commit comments

Comments
 (0)