# 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("", self._on_tab)
self.bind("", self._previous)
self.bind("", self._next)
self.bind('', self._next)
self.bind('', self._previous)
self.bind("", self._update_entry_from_listbox)
self.bind("", 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("", self._update_entry_from_listbox)
self._listbox.bind("", self._update_entry_from_listbox)
self._listbox.bind("", lambda event: self.unpost_listbox())
self._listbox.bind('', self._next)
self._listbox.bind('', 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()