14adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao""" 24adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoAn auto-completion window for IDLE, used by the AutoComplete extension 34adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao""" 44adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaofrom Tkinter import * 54adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaofrom idlelib.MultiCall import MC_SHIFT 64adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaofrom idlelib.AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES 74adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 84adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoHIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>" 94adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoHIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>") 104adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoKEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>" 114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao# We need to bind event beyond <Key> so that the function will be called 124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao# before the default specific IDLE function 134adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoKEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>", 144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>", 154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "<Key-Prior>", "<Key-Next>") 164adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoKEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>" 174adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoKEYRELEASE_SEQUENCE = "<KeyRelease>" 184adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoLISTUPDATE_SEQUENCE = "<B1-ButtonRelease>" 194adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoWINCONFIG_SEQUENCE = "<Configure>" 204adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoDOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>" 214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass AutoCompleteWindow: 234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def __init__(self, widget): 254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The widget (Text) on which we place the AutoCompleteWindow 264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget = widget 274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The widgets we create 284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.autocompletewindow = self.listbox = self.scrollbar = None 294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The default foreground and background of a selection. Saved because 304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # they are changed to the regular colors of list items when the 314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # completion start is not a prefix of the selected completion 324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.origselforeground = self.origselbackground = None 334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The list of completions 344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.completions = None 354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # A list with more completions, or None 364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.morecompletions = None 374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or 384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # AutoComplete.COMPLETE_FILES 394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.mode = None 404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The current completion start, on the text box (a string) 414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.start = None 424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The index of the start of the completion 434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.startindex = None 444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The last typed start, used so that when the selection changes, 454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # the new start will be as close as possible to the last typed one. 464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lasttypedstart = None 474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Do we have an indication that the user wants the completion window 484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # (for example, he clicked the list) 494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.userwantswindow = None 504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # event ids 514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hideid = self.keypressid = self.listupdateid = self.winconfigid \ 524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao = self.keyreleaseid = self.doubleclickid = None 534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Flag set if last keypress was a tab 544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lastkey_was_tab = False 554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def _change_start(self, newstart): 574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao min_len = min(len(self.start), len(newstart)) 584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = 0 594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao while i < min_len and self.start[i] == newstart[i]: 604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i += 1 614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if i < len(self.start): 624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.delete("%s+%dc" % (self.startindex, i), 634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "%s+%dc" % (self.startindex, len(self.start))) 644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if i < len(newstart): 654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.insert("%s+%dc" % (self.startindex, i), 664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newstart[i:]) 674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.start = newstart 684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def _binary_search(self, s): 704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao """Find the first index in self.completions where completions[i] is 714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao greater or equal to s, or the last index if there is no such 724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao one.""" 734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = 0; j = len(self.completions) 744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao while j > i: 754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao m = (i + j) // 2 764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[m] >= s: 774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao j = m 784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = m + 1 804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return min(i, len(self.completions)-1) 814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def _complete_string(self, s): 834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao """Assuming that s is the prefix of a string in self.completions, 844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return the longest string which is a prefix of all the strings which 854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao s is a prefix of them. If s is not a prefix of a string, return s.""" 864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao first = self._binary_search(s) 874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[first][:len(s)] != s: 884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # There is not even one completion which s is a prefix of. 894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return s 904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Find the end of the range of completions where s is a prefix of. 914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = first + 1 924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao j = len(self.completions) 934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao while j > i: 944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao m = (i + j) // 2 954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[m][:len(s)] != s: 964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao j = m 974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = m + 1 994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao last = i-1 1004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if first == last: # only one possible completion 1024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return self.completions[first] 1034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # We should return the maximum prefix of first and last 1054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao first_comp = self.completions[first] 1064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao last_comp = self.completions[last] 1074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao min_len = min(len(first_comp), len(last_comp)) 1084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = len(s) 1094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao while i < min_len and first_comp[i] == last_comp[i]: 1104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i += 1 1114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return first_comp[:i] 1124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def _selection_changed(self): 1144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao """Should be called when the selection of the Listbox has changed. 1154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao Updates the Listbox display and calls _change_start.""" 1164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 1174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.see(cursel) 1194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao lts = self.lasttypedstart 1214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao selstart = self.completions[cursel] 1224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self._binary_search(lts) == cursel: 1234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newstart = lts 1244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 1254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao min_len = min(len(lts), len(selstart)) 1264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = 0 1274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao while i < min_len and lts[i] == selstart[i]: 1284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i += 1 1294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newstart = selstart[:i] 1304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(newstart) 1314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[cursel][:len(self.start)] == self.start: 1334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # start is a prefix of the selected completion 1344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.configure(selectbackground=self.origselbackground, 1354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao selectforeground=self.origselforeground) 1364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 1374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.configure(selectbackground=self.listbox.cget("bg"), 1384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao selectforeground=self.listbox.cget("fg")) 1394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # If there are more completions, show them, and call me again. 1404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.morecompletions: 1414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.completions = self.morecompletions 1424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.morecompletions = None 1434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.delete(0, END) 1444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for item in self.completions: 1454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.insert(END, item) 1464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_set(self._binary_search(self.start)) 1474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._selection_changed() 1484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def show_window(self, comp_lists, index, complete, mode, userWantsWin): 1504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao """Show the autocomplete list, bind events. 1514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao If complete is True, complete the text, and if there is exactly one 1524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao matching completion, don't open a list.""" 1534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Handle the start we already have 1544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.completions, self.morecompletions = comp_lists 1554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.mode = mode 1564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.startindex = self.widget.index(index) 1574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.start = self.widget.get(self.startindex, "insert") 1584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if complete: 1594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao completed = self._complete_string(self.start) 1604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(completed) 1614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao i = self._binary_search(completed) 1624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[i] == completed and \ 1634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao (i == len(self.completions)-1 or 1644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.completions[i+1][:len(completed)] != completed): 1654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # There is exactly one matching completion 1664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 1674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.userwantswindow = userWantsWin 1684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lasttypedstart = self.start 1694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Put widgets in place 1714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.autocompletewindow = acw = Toplevel(self.widget) 1724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Put it in a position so that it is not seen. 1734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw.wm_geometry("+10000+10000") 1744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Make it float 1754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw.wm_overrideredirect(1) 1764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao try: 1774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # This command is only needed and available on Tk >= 8.4.0 for OSX 1784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Without it, call tips intrude on the typing process by grabbing 1794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # the focus. 1804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w, 1814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "help", "noActivates") 1824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao except TclError: 1834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao pass 1844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL) 1854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set, 1864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao exportselection=False, bg="white") 1874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for item in self.completions: 1884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao listbox.insert(END, item) 1894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.origselforeground = listbox.cget("selectforeground") 1904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.origselbackground = listbox.cget("selectbackground") 1914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao scrollbar.config(command=listbox.yview) 1924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao scrollbar.pack(side=RIGHT, fill=Y) 1934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao listbox.pack(side=LEFT, fill=BOTH, expand=True) 1944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Initialize the listbox selection 1964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_set(self._binary_search(self.start)) 1974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._selection_changed() 1984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 1994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # bind events 2004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, 2014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_event) 2024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for seq in HIDE_SEQUENCES: 2034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) 2044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME, 2054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keypress_event) 2064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for seq in KEYPRESS_SEQUENCES: 2074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq) 2084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME, 2094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keyrelease_event) 2104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE) 2114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE, 2124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listselect_event) 2134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event) 2144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE, 2154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.doubleclick_event) 2164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def winconfig_event(self, event): 2184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 2194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Position the completion list window 2214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao text = self.widget 2224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao text.see(self.startindex) 2234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao x, y, cx, cy = text.bbox(self.startindex) 2244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw = self.autocompletewindow 2254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw_width, acw_height = acw.winfo_width(), acw.winfo_height() 2264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao text_width, text_height = text.winfo_width(), text.winfo_height() 2274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width)) 2284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao new_y = text.winfo_rooty() + y 2294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if (text_height - (y + cy) >= acw_height # enough height below 2304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao or y < acw_height): # not enough height above 2314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # place acw below current line 2324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao new_y += cy 2334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 2344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # place acw above current line 2354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao new_y -= acw_height 2364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao acw.wm_geometry("+%d+%d" % (new_x, new_y)) 2374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def hide_event(self, event): 2394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 2404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 2424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def listselect_event(self, event): 2444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 2454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.userwantswindow = True 2474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 2484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.completions[cursel]) 2494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def doubleclick_event(self, event): 2514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Put the selected completion in the text, and close the list 2524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 2534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.completions[cursel]) 2544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 2554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def keypress_event(self, event): 2574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 2584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao keysym = event.keysym 2604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if hasattr(event, "mc_state"): 2614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao state = event.mc_state 2624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 2634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao state = 0 2644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if keysym != "Tab": 2654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lastkey_was_tab = False 2664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if (len(keysym) == 1 or keysym in ("underscore", "BackSpace") 2674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao or (self.mode == COMPLETE_FILES and keysym in 2684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao ("period", "minus"))) \ 2694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao and not (state & ~MC_SHIFT): 2704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Normal editing of text 2714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if len(keysym) == 1: 2724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.start + keysym) 2734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "underscore": 2744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.start + '_') 2754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "period": 2764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.start + '.') 2774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "minus": 2784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.start + '-') 2794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 2804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # keysym == "BackSpace" 2814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if len(self.start) == 0: 2824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 2834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.start[:-1]) 2854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lasttypedstart = self.start 2864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_clear(0, int(self.listbox.curselection()[0])) 2874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_set(self._binary_search(self.start)) 2884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._selection_changed() 2894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return "break" 2904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "Return": 2924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 2934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 2944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 2954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif (self.mode == COMPLETE_ATTRIBUTES and keysym in 2964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao ("period", "space", "parenleft", "parenright", "bracketleft", 2974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "bracketright")) or \ 2984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao (self.mode == COMPLETE_FILES and keysym in 2994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao ("slash", "backslash", "quotedbl", "apostrophe")) \ 3004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao and not (state & ~MC_SHIFT): 3014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # If start is a prefix of the selection, but is not '' when 3024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # completing file names, put the whole 3034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # selected completion. Anyway, close the list. 3044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 3054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.completions[cursel][:len(self.start)] == self.start \ 3064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao and (self.mode == COMPLETE_ATTRIBUTES or self.start): 3074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.completions[cursel]) 3084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 3094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \ 3124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao not state: 3134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Move the selection in the listbox 3144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.userwantswindow = True 3154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 3164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if keysym == "Home": 3174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = 0 3184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "End": 3194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = len(self.completions)-1 3204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym in ("Prior", "Next"): 3214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao jump = self.listbox.nearest(self.listbox.winfo_height()) - \ 3224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.nearest(0) 3234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if keysym == "Prior": 3244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = max(0, cursel-jump) 3254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 3264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao assert keysym == "Next" 3274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = min(len(self.completions)-1, cursel+jump) 3284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif keysym == "Up": 3294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = max(0, cursel-1) 3304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 3314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao assert keysym == "Down" 3324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao newsel = min(len(self.completions)-1, cursel+1) 3334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_clear(cursel) 3344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.select_set(newsel) 3354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._selection_changed() 3364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.completions[newsel]) 3374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return "break" 3384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif (keysym == "Tab" and not state): 3404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.lastkey_was_tab: 3414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # two tabs in a row; insert current selection and close acw 3424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao cursel = int(self.listbox.curselection()[0]) 3434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self.completions[cursel]) 3444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 3454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return "break" 3464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 3474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # first tab; let AutoComplete handle the completion 3484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.userwantswindow = True 3494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.lastkey_was_tab = True 3504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao elif any(s in keysym for s in ("Shift", "Control", "Alt", 3534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao "Meta", "Command", "Option")): 3544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # A modifier key, so ignore 3554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao else: 3584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # Unknown event, close the window and let it through. 3594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 3604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def keyrelease_event(self, event): 3634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 3644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if self.widget.index("insert") != \ 3664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.index("%s+%dc" % (self.startindex, len(self.start))): 3674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # If we didn't catch an event which moved the insert, close window 3684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hide_window() 3694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def is_active(self): 3714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return self.autocompletewindow is not None 3724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def complete(self): 3744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self._change_start(self._complete_string(self.start)) 3754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # The selection doesn't change. 3764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao def hide_window(self): 3784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao if not self.is_active(): 3794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao return 3804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # unbind events 3824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for seq in HIDE_SEQUENCES: 3834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) 3844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) 3854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.hideid = None 3864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao for seq in KEYPRESS_SEQUENCES: 3874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq) 3884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid) 3894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keypressid = None 3904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME, 3914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao KEYRELEASE_SEQUENCE) 3924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid) 3934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.keyreleaseid = None 3944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid) 3954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listupdateid = None 3964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid) 3974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.winconfigid = None 3984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao 3994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao # destroy widgets 4004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.scrollbar.destroy() 4014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.scrollbar = None 4024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox.destroy() 4034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.listbox = None 4044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.autocompletewindow.destroy() 4054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao self.autocompletewindow = None 406