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.

An even better solution….

  • Saturday Sep 27,2008 11:52 AM
  • By wmensah
  • In myDiary

So in my last blog the solution was to write a python script to perform the search and replace on the gtk_basepaths in the 2 files and then have a batch file call python and pass the script to it. That is great, but only as long as the user already has python installed on the computer. If not, then that could be a problem because there will be no way to execute the python script. And you don’t really want to ask the user to please go and download python before you run the installer because it depends on python…that’s kind of silly.

Ok so here’s the better solution:

Since the user is supposed to be running Windows, the system on which the installer is going to be run on should be able to execute .vbs files (visual basic script) via command prompt (ah, something we already have :) ). So if you write the search and replace algorithm in vbs you can execute this script with the batch file and the world will be safe.

The algorithm for such a visual basic script can be found here:

Here’s the script:


Const ForReading = 1
Const ForWriting = 2

strFileName = Wscript.Arguments(0)
strOldText = Wscript.Arguments(1)
strNewText = Wscript.Arguments(2)

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile(strFileName, ForReading)

strText = objFile.ReadAll
objFile.Close
strNewText = Replace(strText, strOldText, strNewText)

Set objFile = objFSO.OpenTextFile(strFileName, ForWriting)
objFile.WriteLine strNewText
objFile.Close

?

For a better understanding of how the script works, read the explanation provided at the link above. In my case I had to change a few things within the script to get it to work the way I wanted but I’ll come back to that later.?

Ok, so the script can be executed like so:

cscript replace.vbs "C:\Scripts\Text.txt" "Jim " "James "

where replace.vbs is the name of the script, C:\Scripts\Text.txt is the file which contains the text to be replaced, “Jim” is the text to find and “James” is the replacement.?

Since I wanted to replace <gtk_basepath> with the value of the GTK_BASEPATH environment variable and then change C:\GTK to C:\\GTK for instance (assuming C:\GTK is the value of the environment variable) I changed the line:

strNewText = Wscript.Arguments(2)

to read:

strNewText = Replace(Wscript.Arguments(2), "\", "\\")

and then from the batch file I called the vb script like so:

start replace.vbs <file_to_replace> %GTK_BASEPATH%

note: in command prompt, %GTK_BASEPATH% returns the value of the environment variable.

that did the trick.