Welcome, guest | Sign In | My Account | Store | Cart
# 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):

History