views:

45

answers:

2

In my PyGTK application, I am asking a user to find a file so that operations can be performed on it. The application asks the user for the file, and relays that filename to the necessary methods. Unfortunately, when calling the gtk.dispose() method on that dialog, it just hangs there until the method being called upon to perform the file-IO is complete. I have even tried placing the file manipulations inside of another thread, but that did not have any effect.

My indented purpose is to have the program display a dialog box to the user informing them that the file they selected for manipulation is taking place. With the current implementation, the dialog appears after the gtk.FileChooserDialog is disposed.

Below is my code:

def performFileManipulation(self, widget, data=None):
        # Create the file chooser dialog:
        dialog = gtk.FileChooserDialog("Open..", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)

        # Display the file selector and obtain the response back
        response = dialog.run()

        # If the user selected a file, then get the filename:
        if response == gtk.RESPONSE_OK:
            dataLocation = dialog.get_filename()

        # If the file was not chosen, then just close the window:
        else:
            print "Closed, no files selected"   # Just for now

        ########## Problem Area ########## 
        # The dialog is told to get destroyed, however, it hangs here in an
        # unresponsive state until the the file-manipulations performed in a new thread
        # below are completed.  Then, the status dialog (declared below) is displayed.
        dialog.destroy()    # Close the dialog.

        ## Show a dialog informing the user that the file manipulation is taking place:
        statusDialog = gtk.Dialog("Performing File Operations...", parent=None, flags=0, buttons=None)
        statusLabel = gtk.Label("Performing File Operations.\nPlease wait...")
        statusLabel.set_justify(gtk.JUSTIFY_CENTER)
        statusLabel.show()
        statusDialog.vbox.pack_start(statusLabel, True, True, 0)
        statusDialog.set_default_size(350, 150)
        statusDialog.show()

        # Create the thread to perform the file conversion:
        errorBucket = Queue.Queue()             # Make a bucket to catch all errors that may occur:
        theFileOperationThread = doStuffToTheFile(dataLocation, errorBucket)     # Declare the thread object.

        ## Perform the file operations:
        theFileOperationThread.start()            # Begin the thread

        # Check on the thread.  See if it's still running:
        while True:
            theFileOperationThread.join(0.1)
            if theFileOperationThread.isAlive():
                continue
            else:
                break

        # Check if there was an error in the bucket:
        try:
            errorFound = errorBucket.get(False)

        # If no errors were found, then the copy was successful!
        except Queue.Empty:
            pass

        # There was an error in the bucket!  Alert the user
        else:
            print errorFound

        statusDialog.destroy()

Please note that this code is not yet completed, for instance, it does not yet properly handle the user not selecting a file and canceling the operation.

EDIT: Upon further investigation, there appears to be a threading issue with PyGTK. The problem is occurring in the while True loop. I replaced that code with a time.sleep(15), and similarly, the file select dialog will pause. This is quite odd behavior, and everything should operate inside of a different thread. I guess the question now is to find out how to place the File Selection dialog inside of it's own thread.

+1  A: 

Mixing threads and GTK apps (from what I recall) tends to produce weird results.

The problem is that even though you call gtk.dispose, you're probably calling the methods directly, which blocks the next iteration of gtk.mainloop.

What you need to do is create another function to do the file processing and call it from a callback funciton:

def doFileStuff(filename):
   with open(filename, 'r') as f:
       for line in f:
            #do something
   return False # On success

And then change this function:

def performFileManipulation(self, widget, data=None):
        # Create the file chooser dialog:
        dialog = gtk.FileChooserDialog("Open..", 
                                       None, 
                                       gtk.FILE_CHOOSER_ACTION_OPEN, 
                                       (gtk.STOCK_CANCEL, 
                                        gtk.RESPONSE_CANCEL, 
                                        gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)

        # Display the file selector and obtain the response back
        response = dialog.run()

        # If the user selected a file, then get the filename:
        if response == gtk.RESPONSE_OK:
            dataLocation = dialog.get_filename()

        # If the file was not chosen, then just close the window:
        else:
            print "Closed, no files selected"   # Just for now

        # You'll need to import gobject
        gobject.timeout_add(100, doFileStuff, dataLocation)

That should at least let you close the dialog, and I think it will launch the file processing stuff in the background. If not it will at least give you somewhere to launch your new thread from.

HTH

Wayne Werner
I will try this out as soon as I get a chance.
Phanto
I'm using a mixture of the `gobject.timeout_add()` method, and what ptomato suggested to remove the thread. This makes the popup show. However, when just using a simple callback, how do I know when the the `doFileStuff()` method is done processing? The `gobject.timeout_add()` method returns a an integer ID of the event source. Would the `gobject.signal_query()` method do this?
Phanto
I think so: http://pygtk.org/pygtk2reference/gobject-functions.html#function-gobject--signal-query2 says that the tuple will contain `the GType of the return from the signal callback function`. I presume it will return None (or GType equivalent) if the callback hasn't finished.
Wayne Werner
+2  A: 

There's probably no need to perform the file operations in a separate thread, since you're not really doing anything in this thread while the file operations are running -- just busy-waiting. And that brings me to why the code doesn't work: GUI updates are processed within the GTK main loop. But the whole time while you're waiting for the file thread to finish, the GTK main loop isn't executing, because it's stuck waiting for your performFileManipulation function to end.

What you need to do is perform iterations of the GTK main loop during your while True loop. That looks like this:

while True:
    theFileOperationThread.join(0.1)
    if theFileOperationThread.isAlive():
        while gtk.events_pending():
            gtk.main_iteration(block=False)
    else:
        break

But again, I would consider just doing the file operations in this thread, it seems redundant to start another thread and then do nothing while it's running.

ptomato
You suggestion of removing the thread, and using the `gobject.timeout_add()` suggested by Wayne corrected the issue. Thank you.
Phanto
I don't think you need the timeout at all either - a timeout in GObject is just another way of running another thread. You could just call your file operations function in this thread, and make sure to call the `gtk.events_pending()` and `gtk.main_iteration()` pair every so often during your file operation.
ptomato
And if that's not possible, then an idle function would be better than a timeout in this case.
ptomato