# Author: Miguel Martinez Lopez
# Version: 0.6
import re
import os
try:
from Tkinter import StringVar, Entry, Frame, Listbox, Button, Scrollbar
from Tkconstants import *
except ImportError:
from tkinter import StringVar, Entry, Frame, Listbox, Button, Scrollbar
from tkinter.constants import *
try:
from tkFileDialog import *
except ImportError:
from tkinter.filedialog import *
def autoscroll(sbar, first, last):
"""Hide and show scrollbar as needed."""
first, last = float(first), float(last)
if first <= 0 and last >= 1:
sbar.grid_remove()
else:
sbar.grid()
sbar.set(first, last)
class Combobox_Autocomplete(Entry, object):
def __init__(self, master, list_of_items=None, autocomplete_function=None, listbox_width=None, listbox_height=7, ignorecase_match=False, startswith_match=True, vscrollbar=True, hscrollbar=True, **kwargs):
if hasattr(self, "autocomplete_function"):
if autocomplete_function is not None:
raise ValueError("Combobox_Autocomplete subclass has 'autocomplete_function' implemented")
else:
if autocomplete_function is not None:
self.autocomplete_function = autocomplete_function
else:
if list_of_items is None:
raise ValueError("If not guiven complete function, list_of_items can't be 'None'")
if ignorecase_match:
if startswith_match:
def matches_function(entry_data, item):
return item.startswith(entry_data)
else:
def matches_function(entry_data, item):
return item in entry_data
self.autocomplete_function = lambda entry_data: [item for item in self.list_of_items if matches_function(entry_data, item)]
else:
if startswith_match:
def matches_function(escaped_entry_data, item):
if re.match(escaped_entry_data, item, re.IGNORECASE):
return True
else:
return False
else:
def matches_function(escaped_entry_data, item):
if re.search(escaped_entry_data, item, re.IGNORECASE):
return True
else:
return False
def autocomplete_function(entry_data):
escaped_entry_data = re.escape(entry_data)
return [item for item in self.list_of_items if matches_function(escaped_entry_data, item)]
self.autocomplete_function = autocomplete_function
self._listbox_height = int(listbox_height)
self._listbox_width = listbox_width
self.list_of_items = list_of_items
self._use_vscrollbar = vscrollbar
self._use_hscrollbar = hscrollbar
kwargs.setdefault("background", "white")
if "textvariable" in kwargs:
self._entry_var = kwargs["textvariable"]
else:
self._entry_var = kwargs["textvariable"] = StringVar()
Entry.__init__(self, master, **kwargs)
self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)
self._listbox = None
self.bind("<Tab>", self._on_tab)
self.bind("<Up>", self._previous)
self.bind("<Down>", self._next)
self.bind('<Control-n>', self._next)
self.bind('<Control-p>', self._previous)
self.bind("<Return>", self._update_entry_from_listbox)
self.bind("<Escape>", lambda event: self.unpost_listbox())
def _on_tab(self, event):
self.post_listbox()
return "break"
def _on_change_entry_var(self, name, index, mode):
entry_data = self._entry_var.get()
if entry_data == '':
self.unpost_listbox()
self.focus()
else:
values = self.autocomplete_function(entry_data)
if values:
if self._listbox is None:
self._build_listbox(values)
else:
self._listbox.delete(0, END)
height = min(self._listbox_height, len(values))
self._listbox.configure(height=height)
for item in values:
self._listbox.insert(END, item)
else:
self.unpost_listbox()
self.focus()
def _build_listbox(self, values):
listbox_frame = Frame()
self._listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none", exportselection=False)
self._listbox.grid(row=0, column=0,sticky = N+E+W+S)
self._listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox)
self._listbox.bind("<Return>", self._update_entry_from_listbox)
self._listbox.bind("<Escape>", lambda event: self.unpost_listbox())
self._listbox.bind('<Control-n>', self._next)
self._listbox.bind('<Control-p>', self._previous)
if self._use_vscrollbar:
vbar = Scrollbar(listbox_frame, orient=VERTICAL, command= self._listbox.yview)
vbar.grid(row=0, column=1, sticky=N+S)
self._listbox.configure(yscrollcommand= lambda f, l: autoscroll(vbar, f, l))
if self._use_hscrollbar:
hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command= self._listbox.xview)
hbar.grid(row=1, column=0, sticky=E+W)
self._listbox.configure(xscrollcommand= lambda f, l: autoscroll(hbar, f, l))
listbox_frame.grid_columnconfigure(0, weight= 1)
listbox_frame.grid_rowconfigure(0, weight= 1)
x = -self.cget("borderwidth") - self.cget("highlightthickness")
y = self.winfo_height()-self.cget("borderwidth") - self.cget("highlightthickness")
if self._listbox_width:
width = self._listbox_width
else:
width=self.winfo_width()
listbox_frame.place(in_=self, x=x, y=y, width=width)
height = min(self._listbox_height, len(values))
self._listbox.configure(height=height)
for item in values:
self._listbox.insert(END, item)
def post_listbox(self):
if self._listbox is not None: return
entry_data = self._entry_var.get()
if entry_data == '': return
values = self.autocomplete_function(entry_data)
if values:
self._build_listbox(values)
def unpost_listbox(self):
if self._listbox is not None:
self._listbox.master.destroy()
self._listbox = None
def get_value(self):
return self._entry_var.get()
def set_value(self, text, close_dialog=False):
self._set_var(text)
if close_dialog:
self.unpost_listbox()
self.icursor(END)
self.xview_moveto(1.0)
def _set_var(self, text):
self._entry_var.trace_vdelete("w", self._trace_id)
self._entry_var.set(text)
self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)
def _update_entry_from_listbox(self, event):
if self._listbox is not None:
current_selection = self._listbox.curselection()
if current_selection:
text = self._listbox.get(current_selection)
self._set_var(text)
self._listbox.master.destroy()
self._listbox = None
self.focus()
self.icursor(END)
self.xview_moveto(1.0)
return "break"
def _previous(self, event):
if self._listbox is not None:
current_selection = self._listbox.curselection()
if len(current_selection)==0:
self._listbox.selection_set(0)
self._listbox.activate(0)
else:
index = int(current_selection[0])
self._listbox.selection_clear(index)
if index == 0:
index = END
else:
index -= 1
self._listbox.see(index)
self._listbox.selection_set(first=index)
self._listbox.activate(index)
return "break"
def _next(self, event):
if self._listbox is not None:
current_selection = self._listbox.curselection()
if len(current_selection)==0:
self._listbox.selection_set(0)
self._listbox.activate(0)
else:
index = int(current_selection[0])
self._listbox.selection_clear(index)
if index == self._listbox.size() - 1:
index = 0
else:
index +=1
self._listbox.see(index)
self._listbox.selection_set(index)
self._listbox.activate(index)
return "break"
class File_Entry(Frame, object):
def __init__(self, master, ask_dialog = askopenfilename, width=30, **dialog_options):
Frame.__init__(self, master)
self._file_autocomplete = Combobox_Autocomplete(self, width=width, autocomplete_function=self._autocomplete_function)
self._file_autocomplete.pack(side=LEFT)
button_size = self._file_autocomplete.winfo_reqheight()
button_frame = Frame(self, height=button_size, width=button_size)
button_frame.pack(side=LEFT, padx=(3,0))
button_frame.pack_propagate(0)
Button(button_frame, text="...", command=self._open_dialog).pack(fill=BOTH, expand=True)
self._ask_dialog = ask_dialog
self._dialog_options = dialog_options
@property
def path(self):
return self._file_autocomplete.get_value()
def focus(self):
self._file_autocomplete.focus()
def _open_dialog(self):
filename = self._ask_dialog(**self._dialog_options)
self._file_autocomplete.set_value(filename, close_dialog=True)
def _autocomplete_function(self, base_path):
try:
base_path = os.path.normcase(base_path)
if base_path.endswith(os.path.sep) and os.path.isdir(base_path):
list_of_paths = []
for filename in os.listdir(base_path):
file_path = os.path.join(base_path, filename)
if os.path.isdir(file_path):
file_path += os.sep
list_of_paths.append(file_path)
list_of_paths.sort()
return list_of_paths
else:
current_directory, prefix = os.path.split(base_path)
if not os.path.isdir(current_directory): return None
list_of_paths = []
for filename in os.listdir(current_directory):
filename = os.path.normcase(filename)
if filename.startswith(prefix):
file_path = os.path.join(current_directory, filename)
if os.path.isdir(file_path):
file_path += os.sep
list_of_paths.append(file_path)
list_of_paths.sort()
return list_of_paths
except os.error:
return None
if __name__ == '__main__':
try:
from Tkinter import Tk
except ImportError:
from tkinter import Tk
root = Tk()
root.geometry("300x200")
file_entry = File_Entry(root)
file_entry.pack()
file_entry.focus()
root.mainloop()
Diff to Previous Revision
--- revision 8 2017-04-03 11:41:48
+++ revision 9 2017-04-03 15:18:36
@@ -1,5 +1,5 @@
# Author: Miguel Martinez Lopez
-# Version: 0.5
+# Version: 0.6
import re
import os
@@ -67,8 +67,8 @@
self.autocomplete_function = autocomplete_function
- self.listbox_height = int(listbox_height)
- self.listbox_width = listbox_width
+ self._listbox_height = int(listbox_height)
+ self._listbox_width = listbox_width
self.list_of_items = list_of_items
@@ -86,17 +86,19 @@
self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)
- self.listbox = None
+ self._listbox = None
self.bind("<Tab>", self._on_tab)
- self.bind("<Up>", self._on_move_up)
- self.bind("<Down>", self._on_move_down)
+ self.bind("<Up>", self._previous)
+ self.bind("<Down>", self._next)
+ self.bind('<Control-n>', self._next)
+ self.bind('<Control-p>', self._previous)
self.bind("<Return>", self._update_entry_from_listbox)
- self.bind("<Escape>", lambda event: self.close_listbox())
+ self.bind("<Escape>", lambda event: self.unpost_listbox())
def _on_tab(self, event):
- self.open_listbox()
+ self.post_listbox()
return "break"
def _on_change_entry_var(self, name, index, mode):
@@ -104,47 +106,50 @@
entry_data = self._entry_var.get()
if entry_data == '':
- self.close_listbox()
+ self.unpost_listbox()
self.focus()
else:
values = self.autocomplete_function(entry_data)
if values:
- if self.listbox is None:
+ if self._listbox is None:
self._build_listbox(values)
else:
- self.listbox.delete(0, END)
-
- height = min(self.listbox_height, len(values))
- self.listbox.configure(height=height)
+ self._listbox.delete(0, END)
+
+ height = min(self._listbox_height, len(values))
+ self._listbox.configure(height=height)
for item in values:
- self.listbox.insert(END, item)
-
- else:
- self.close_listbox()
+ self._listbox.insert(END, item)
+
+ else:
+ self.unpost_listbox()
self.focus()
def _build_listbox(self, values):
listbox_frame = Frame()
- self.listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none", exportselection=False)
- self.listbox.grid(row=0, column=0,sticky = N+E+W+S)
-
- self.listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox)
- self.listbox.bind("<Return>", self._update_entry_from_listbox)
- self.listbox.bind("<Escape>", lambda event: self.close_listbox())
+ self._listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none", exportselection=False)
+ self._listbox.grid(row=0, column=0,sticky = N+E+W+S)
+
+ self._listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox)
+ self._listbox.bind("<Return>", self._update_entry_from_listbox)
+ self._listbox.bind("<Escape>", lambda event: self.unpost_listbox())
+
+ self._listbox.bind('<Control-n>', self._next)
+ self._listbox.bind('<Control-p>', self._previous)
if self._use_vscrollbar:
- vbar = Scrollbar(listbox_frame, orient=VERTICAL, command= self.listbox.yview)
+ vbar = Scrollbar(listbox_frame, orient=VERTICAL, command= self._listbox.yview)
vbar.grid(row=0, column=1, sticky=N+S)
- self.listbox.configure(yscrollcommand= lambda f, l: autoscroll(vbar, f, l))
+ self._listbox.configure(yscrollcommand= lambda f, l: autoscroll(vbar, f, l))
if self._use_hscrollbar:
- hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command= self.listbox.xview)
+ hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command= self._listbox.xview)
hbar.grid(row=1, column=0, sticky=E+W)
- self.listbox.configure(xscrollcommand= lambda f, l: autoscroll(hbar, f, l))
+ self._listbox.configure(xscrollcommand= lambda f, l: autoscroll(hbar, f, l))
listbox_frame.grid_columnconfigure(0, weight= 1)
listbox_frame.grid_rowconfigure(0, weight= 1)
@@ -152,21 +157,21 @@
x = -self.cget("borderwidth") - self.cget("highlightthickness")
y = self.winfo_height()-self.cget("borderwidth") - self.cget("highlightthickness")
- if self.listbox_width:
- width = self.listbox_width
+ if self._listbox_width:
+ width = self._listbox_width
else:
width=self.winfo_width()
listbox_frame.place(in_=self, x=x, y=y, width=width)
- height = min(self.listbox_height, len(values))
- self.listbox.configure(height=height)
+ height = min(self._listbox_height, len(values))
+ self._listbox.configure(height=height)
for item in values:
- self.listbox.insert(END, item)
-
- def open_listbox(self):
- if self.listbox is not None: return
+ self._listbox.insert(END, item)
+
+ def post_listbox(self):
+ if self._listbox is not None: return
entry_data = self._entry_var.get()
if entry_data == '': return
@@ -175,10 +180,10 @@
if values:
self._build_listbox(values)
- def close_listbox(self):
- if self.listbox is not None:
- self.listbox.master.destroy()
- self.listbox = None
+ def unpost_listbox(self):
+ if self._listbox is not None:
+ self._listbox.master.destroy()
+ self._listbox = None
def get_value(self):
return self._entry_var.get()
@@ -187,7 +192,7 @@
self._set_var(text)
if close_dialog:
- self.close_listbox()
+ self.unpost_listbox()
self.icursor(END)
self.xview_moveto(1.0)
@@ -198,15 +203,15 @@
self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)
def _update_entry_from_listbox(self, event):
- if self.listbox is not None:
- current_selection = self.listbox.curselection()
+ if self._listbox is not None:
+ current_selection = self._listbox.curselection()
if current_selection:
- text = self.listbox.get(current_selection)
+ text = self._listbox.get(current_selection)
self._set_var(text)
- self.listbox.master.destroy()
- self.listbox = None
+ self._listbox.master.destroy()
+ self._listbox = None
self.focus()
self.icursor(END)
@@ -214,47 +219,47 @@
return "break"
- def _on_move_up(self, event):
- if self.listbox is not None:
- current_selection = self.listbox.curselection()
+ def _previous(self, event):
+ if self._listbox is not None:
+ current_selection = self._listbox.curselection()
if len(current_selection)==0:
- self.listbox.selection_set(0)
- self.listbox.activate(0)
+ self._listbox.selection_set(0)
+ self._listbox.activate(0)
else:
index = int(current_selection[0])
- self.listbox.selection_clear(index)
+ self._listbox.selection_clear(index)
if index == 0:
index = END
else:
index -= 1
- self.listbox.see(index)
- self.listbox.selection_set(first=index)
- self.listbox.activate(index)
+ self._listbox.see(index)
+ self._listbox.selection_set(first=index)
+ self._listbox.activate(index)
return "break"
- def _on_move_down(self, event):
- if self.listbox is not None:
-
- current_selection = self.listbox.curselection()
+ def _next(self, event):
+ if self._listbox is not None:
+
+ current_selection = self._listbox.curselection()
if len(current_selection)==0:
- self.listbox.selection_set(0)
- self.listbox.activate(0)
+ self._listbox.selection_set(0)
+ self._listbox.activate(0)
else:
index = int(current_selection[0])
- self.listbox.selection_clear(index)
-
- if index == self.listbox.size() - 1:
+ self._listbox.selection_clear(index)
+
+ if index == self._listbox.size() - 1:
index = 0
else:
index +=1
- self.listbox.see(index)
- self.listbox.selection_set(index)
- self.listbox.activate(index)
+ self._listbox.see(index)
+ self._listbox.selection_set(index)
+ self._listbox.activate(index)
return "break"
class File_Entry(Frame, object):