-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.py
165 lines (142 loc) · 5.48 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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)