""" Authors: Mitja Martini and Russell Adams License: "Licensed same as original by Mitja Martini or public domain, whichever is less restrictive" Source: https://mail.python.org/pipermail/tkinter-discuss/2012-January/003041.html Edited by RedFantom for ttk and Python 2 and 3 cross-compatibility and binding """ import tkinter as tk from tkinter import ttk tk_umlauts = ['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp'] class AutocompleteEntry(ttk.Entry): """ Subclass of :class:`ttk.Entry` that features autocompletion. To enable autocompletion use :meth:`set_completion_list` to define a list of possible strings to hit. To cycle through hits use down and up arrow keys. """ def __init__(self, master=None, completevalues=None, **kwargs): """ Create an AutocompleteEntry. :param master: master widget :type master: widget :param completevalues: autocompletion values :type completevalues: list :param kwargs: keyword arguments passed to the :class:`ttk.Entry` initializer """ ttk.Entry.__init__(self, master, **kwargs) self._completion_list = completevalues self.set_completion_list(completevalues) self._hits = [] self._hit_index = 0 self.position = 0 def set_completion_list(self, completion_list): """ Set a new auto completion list :param completion_list: completion values :type completion_list: list """ self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list self._hits = [] self._hit_index = 0 self.position = 0 self.bind('', self.handle_keyrelease) def autocomplete(self, delta=0): """ Autocomplete the Entry. :param delta: 0, 1 or -1: how to cycle through possible hits :type delta: int """ if delta: # need to delete selection otherwise we would fix the current position self.delete(self.position, tk.END) else: # set position to end so selection starts where textentry ended self.position = len(self.get()) # collect hits _hits = [] for element in self._completion_list: if element.lower().startswith(self.get().lower()): # Match case-insensitively _hits.append(element) # if we have a new hit list, keep this in mind if _hits != self._hits: self._hit_index = 0 self._hits = _hits # only allow cycling if we are in a known hit list if _hits == self._hits and self._hits: self._hit_index = (self._hit_index + delta) % len(self._hits) # now finally perform the auto completion if self._hits: self.delete(0, tk.END) self.insert(0, self._hits[self._hit_index]) self.select_range(self.position, tk.END) def handle_keyrelease(self, event): """ Event handler for the keyrelease event on this widget. :param event: Tkinter event """ if event.keysym == "BackSpace": self.delete(self.index(tk.INSERT), tk.END) self.position = self.index(tk.END) if event.keysym == "Left": if self.position < self.index(tk.END): # delete the selection self.delete(self.position, tk.END) else: self.position -= 1 # delete one character self.delete(self.position, tk.END) if event.keysym == "Right": self.position = self.index(tk.END) # go to end (no selection) if event.keysym == "Down": self.autocomplete(1) # cycle to next hit if event.keysym == "Up": self.autocomplete(-1) # cycle to previous hit if event.keysym == "Return": self.handle_return(None) return if len(event.keysym) == 1 or event.keysym in tk_umlauts: self.autocomplete() def handle_return(self, event): """ Function to bind to the Enter/Return key so if Enter is pressed the selection is cleared. :param event: Tkinter event """ self.icursor(tk.END) self.selection_clear() def config(self, **kwargs): """Alias for configure""" self.configure(**kwargs) def configure(self, **kwargs): """Configure widget specific keyword arguments in addition to :class:`ttk.Entry` keyword arguments.""" if "completevalues" in kwargs: self.set_completion_list(kwargs.pop("completevalues")) return ttk.Entry.configure(self, **kwargs) def cget(self, key): """Return value for widget specific keyword arguments""" if key == "completevalues": return self._completion_list return ttk.Entry.cget(self, key) def keys(self): """Return a list of all resource names of this widget.""" keys = ttk.Entry.keys(self) keys.append("completevalues") return keys def __setitem__(self, key, value): self.configure(**{key: value}) def __getitem__(self, item): return self.cget(item)