Create auto_complete.py

This commit is contained in:
Rajvir Singh 2020-11-09 23:06:13 +05:30 committed by GitHub
parent 23c5f93b83
commit b2a0ab58ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 155 additions and 0 deletions

View File

@ -0,0 +1,155 @@
try:
import tkinter as tk
from tkinter import ttk
except ImportError:
# Python 2
import Tkinter as tk
import ttk
__all__ = ["AutocompleteEntry"]
NO_RESULTS_MESSAGE = "No results found for '{}'"
class AutocompleteEntry(tk.Frame, object):
"""A container for `tk.Entry` and `tk.Listbox` widgets.
An instance of AutocompleteEntry is actually a `tk.Frame`,
containing the `tk.Entry` and `tk.Listbox` widgets needed
to display autocompletion entries. Thus, you can initialize
it with the usual arguments to `tk.Frame`.
Constants:
LISTBOX_HEIGHT -- Default height for the `tk.Listbox` widget
LISTBOX_WIDTH -- Default width for the `tk.Listbox` widget
ENTRY_WIDTH -- Default width for the `tk.Entry` widget
Methods:
__init__ -- Set up the `tk.Listbox` and `tk.Entry` widgets
build -- Build a list of autocompletion entries
_update_autocomplete -- Internal method
_select_entry -- Internal method
_cycle_up -- Internal method
_cycle_down -- Internal method
Other attributes:
text -- StringVar object associated with the `tk.Entry` widget
entry -- The `tk.Entry` widget (access this directly if you
need to change styling)
listbox -- The `tk.Listbox` widget (access this directly if
you need to change styling)
"""
LISTBOX_HEIGHT = 5
LISTBOX_WIDTH = 25
ENTRY_WIDTH = 25
def __init__(self, master, *args, **kwargs):
"""Constructor.
Create the `self.entry` and `self.listbox` widgets.
Note that these widgets are not yet displayed and will only
be visible when you call `self.build`.
Arguments:
master -- The master tkinter widget
Returns:
None
"""
super(AutocompleteEntry, self).__init__(*args, **kwargs)
self.text = tk.StringVar()
self.entry = tk.Entry(
self,
textvariable=self.text,
width=self.ENTRY_WIDTH
)
self.listbox = tk.Listbox(
self,
height=self.LISTBOX_HEIGHT,
width=self.LISTBOX_WIDTH
)
def build(
self,
entries,
max_entries=5,
case_sensitive=False,
no_results_message=NO_RESULTS_MESSAGE
):
"""Set up the autocompletion settings.
Binds <KeyRelease>, <<ListboxSelect>>, <Down> and <Up> for
smooth cycling between autocompletion entries.
Arguments:
entries -- An iterable containg autocompletion entries (strings)
max_entries -- [int] The maximum number of entries to display
case_sensitive -- [bool] Set to `True` to make autocompletion
case-sensitive
no_results_message -- [str] Message to display when no entries
match the current entry; you can use a
formatting identifier '{}' which will be
replaced with the entry at runtime
Returns:
None
"""
if not case_sensitive:
entries = [entry.lower() for entry in entries]
self._case_sensitive = case_sensitive
self._entries = entries
self._no_results_message = no_results_message
self._listbox_height = max_entries
self.entry.bind("<KeyRelease>", self._update_autocomplete)
self.entry.focus()
self.entry.grid(column=0, row=0)
self.listbox.bind("<<ListboxSelect>>", self._select_entry)
self.listbox.grid(column=0, row=1)
self.listbox.grid_forget()
# Initially, the listbox widget doesn't show up.
def _update_autocomplete(self, event):
"""Internal method.
Update `self.listbox` to display new matches.
"""
self.listbox.delete(0, tk.END)
self.listbox["height"] = self._listbox_height
text = self.text.get()
if not self._case_sensitive:
text = text.lower()
if not text:
self.listbox.grid_forget()
else:
for entry in self._entries:
if text in entry.strip():
self.listbox.insert(tk.END, entry)
listbox_size = self.listbox.size()
if not listbox_size:
if self._no_results_message is None:
self.listbox.grid_forget()
else:
try:
self.listbox.insert(
tk.END,
self._no_results_message.format(text)
)
except UnicodeEncodeError:
self.listbox.insert(
tk.END,
self._no_results_message.format(
text.encode("utf-8")
)
)
if listbox_size <= self.listbox["height"]:
# In case there's less entries than the maximum
# amount of entries allowed, resize the listbox.
self.listbox["height"] = listbox_size
self.listbox.grid()
else:
if listbox_size <= self.listbox["height"]:
self.listbox["height"] = listbox_size
self.listbox.grid()
def _select_entry(self, event):
"""Internal method.
Set the textvariable corresponding to `self.entry`
to the value currently selected.
"""
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
self.text.set(value)