wilmens™

by William W. Mensah

Ever used Pygtk’s filechooser widget and wanted to have Back, Forward and Up navigation buttons to go with it? The filechooser widgets acts weird sometimes, for instance when you open a new folder, the folder_changed signal is received twice, sometimes once which is very inconsistent and difficult to work with. Spent hours Googling (to Google means to search for something on the internet – Oxford dictionary – soon) a solution in vain so I finally decided to put my brain to work. If you’re reading this post then chances are that you’re either looking for a solution or just a big fan of my blog :)

The script below assumes you’re designing a GTK GUI with Python as your scripting language, have a filechooser widget placed somewhere on it, and want to add some navigation buttons (basically back, forward and up, and maybe reload – just like a file browser) and need some script to get them to work. Here are a few things you’ll need before we proceed.

A stack class:

#!/usr/bin/env python

class Stack:
   def __init__(self):
       self.data = []
       self.size = 0

   def push(self,item):
      self.data.append(item)
      self.size = self.size + 1    

   def pop(self):
       if self.size > 0:
           self.size = self.size - 1
       return self.data[self.size]    

   def is_empty(self):
      return self.size == 0

   def is_full(self):
      return self.size == len(self.data)

   def top(self):
       return self.data[self.size-1]

   def item_at(self, pos):
       return self.data[pos-1]

   def count(self):
       return self.size

   def clear(self):
      self.data = []
      self.size = 0

   def elements(self):
       #displays all elements in the stack
       for x in range(0, self.size):
           print self.data[x]

- with the stack class in hand and ready to go, you’ll need to create 2 stacks (each an instance of the stack class) one for back history, and the other for the forward history (in my example I called them back_stack and fwd_stack respectively).

- note that in the code below, the filechooser widget is called, well, “filechooser”.

guru = gtk.glade.XML("your_glade_file.glade")

class WidgetsWrapper:    

    current_folder = guru.get_widget("filechooser").get_current_folder()
    back_stack = stack.Stack()
    fwd_stack = stack.Stack()
    back_size = 0 #used to determine when folder_changed occurs.
                       #if not == back_stack.count() that means back button was clicked.

    def __init__(self):
        signalDic = {"on_filechooser_current_folder_changed" : self.folder_changed,
                     "on_btnReload_clicked" : self.reload_dir,
                     "on_btnBack_clicked" : self.goBack,
                     "on_btnForward_clicked" : self.goFwd,
                     "on_btnUp_clicked" : self.goUp}

        guru.signal_autoconnect(signalDic)
	#code to show the main window goes here;
        #ie. guru.get_widget("main_window").show(), something so.

    def folder_changed(self, widget):
        filechooser = guru.get_widget("filechooser")
        if WidgetsWrapper.back_size == 0 or
            WidgetsWrapper.back_stack.count() == WidgetsWrapper.back_size:
            #the line below makes sure the current folder doesn't get added to
            #back_stack after folder is changed.
            if WidgetsWrapper.current_folder <> filechooser.get_current_folder():
                WidgetsWrapper.back_stack.push(WidgetsWrapper.current_folder)
                guru.get_widget("btnBack").set_sensitive(True)

            #should fwd_stack be cleared?
            if WidgetsWrapper.fwd_stack.count() > 0 and
                (WidgetsWrapper.back_stack.count() > WidgetsWrapper.back_size):
                WidgetsWrapper.fwd_stack.clear()
        WidgetsWrapper.back_size = WidgetsWrapper.back_stack.count()
        WidgetsWrapper.current_folder = filechooser.get_current_folder()

        #set button sensitivity
        #back button
        if WidgetsWrapper.back_stack.count() == 0:
            guru.get_widget("btnBack").set_sensitive(False)
        #forward button
        if WidgetsWrapper.fwd_stack.count() == 0:
            guru.get_widget("btnForward").set_sensitive(False)
        #up button
        if os.path.dirname(WidgetsWrapper.current_folder) == WidgetsWrapper.current_folder:
            guru.get_widget("btnUp").set_sensitive(False)
        else:
            guru.get_widget("btnUp").set_sensitive(True)

    def goBack(self, widget):
        cnt = WidgetsWrapper.back_stack.count()
        if cnt > 0:
            WidgetsWrapper.current_folder = guru.get_widget("filechooser").get_current_folder()
            setdir = WidgetsWrapper.back_stack.pop()
            WidgetsWrapper.fwd_stack.push(WidgetsWrapper.current_folder)
            try:
                guru.get_widget("filechooser").set_current_folder(setdir)
                guru.get_widget("btnForward").set_sensitive(True)
            except:
                pass
        else:
            guru.get_widget("btnBack").set_sensitive(False)

    def goFwd(self, widget):
        cnt = WidgetsWrapper.fwd_stack.count()
        if cnt > 0:
            setdir = WidgetsWrapper.fwd_stack.pop()
            WidgetsWrapper.back_stack.push(str(guru.get_widget("filechooser").get_current_folder()))
            try:
                guru.get_widget("filechooser").set_current_folder(setdir)
                guru.get_widget("btnBack").set_sensitive(True)
                WidgetsWrapper.back_size = -1 #avoid folder_changed evaluation
            except:
                pass
        else:
            guru.get_widget("btnForward").set_sensitive(False)

    def goUp(self, widget):
        WidgetsWrapper.current_folder = guru.get_widget("filechooser").get_current_folder()
        WidgetsWrapper.back_stack.push(WidgetsWrapper.current_folder)
        try:
            x = guru.get_widget("filechooser")
            x.set_current_folder(os.path.dirname(WidgetsWrapper.current_folder))
            guru.get_widget("btnBack").set_sensitive(True)
            WidgetsWrapper.back_size = -1 #avoid folder_changed evaluation
            WidgetsWrapper.fwd_stack.clear()
        except:
            pass

    def reload_dir(*args):
       x = guru.get_widget("filechooser")
       x.set_current_folder(guru.get_widget("filechooser").get_current_folder())

    def print_stacks(*args):
        print ""
        print "back_stack elements:"
        print "...................."
        WidgetsWrapper.back_stack.elements()
        print ""
        print "count: ", WidgetsWrapper.back_stack.count()
        print "fwd_stack elements:"
        print "..................."
        WidgetsWrapper.fwd_stack.elements()
        print ""

The code could use some improvement but bottom line is that it works. Just be sure to change the string in line 1 to the name of your glade file. The print_stacks function simply displays the current contents of the 2 stacks. Use it for debugging.

Hope this helps someone.

miniRCS

I’ve commenced on the development of a revision control system that supports ftp servers. Being open source, the script can be obtained from here.

To use it, you’ll have to copy and paste the file into your favorite text editor and then save it as a python file (.py). From there you can run it using Python (if you don’t already have it on your computer, download and install it).

Mess around with the code as much as you can. In addition to the source code, I’ve provided a public repository you can use for testing. You’ll have to get a user name and password from me (to do so contact me at support@wilmens.net). If you have your own ftp server, replace mine with yours in the script.

Feel free to contact me if you have any questions.

have fun :)