GTK dialogs in GIMP (and updated wallpaper script) (Shallow Thoughts)

Akkana's Musings on Open Source, Science, and Nature.

Tue, 15 Sep 2009

GTK dialogs in GIMP (and updated wallpaper script)

[Grosvenor Arch] I've been getting tired of my various desktop backgrounds, and realized that I had a lot of trip photos, from fabulous places like Grosvenor Arch (at right), that I'd never added to my background collection.

There's nothing like lots of repetitions of the same task to bring out the shortcomings of a script, and the wallpaper script I threw together earlier this year was no exception. I found myself frequently irritated by not having enough information about what the script was doing or being able to change the filename. Then I could have backgrounds named grosvenor.jpg rather than img2691.jpg.

Alas, I can't use the normal GIMP Save-as dialog, since GIMP doesn't make that dialog available to plug-ins. (That's a deliberate choice, though I've never been clear on the reason behind it.) If I wanted to give that control to the user, I'd have to make my own dialogs.

It's no problem to make a GTK dialog from Python. Just create a gtk.Dialog, add a gtk.Entry to it, call dialog.run(), then check the return value and get the entry's text to see if it changed. No problem, right?

Ha! If you think that, you don't work with computers. The dialog popped up fine, it read the text entry fine ... but it wouldn't go away afterward. So after the user clicked OK, the plug-in tried to save and GIMP popped up the JPEG save dialog (the one that has a quality slider and other controls, but no indication of filename) under my text entry dialog, which remained there.

All attempts at calling dialog.hide() and dialog.destroy() and similar mathods were of no avail. A helpful person on #pygtk worked with me but ended up as baffled as I was. What was up?

The code seemed so simple -- something like this:

    response = dialog.run()
    if response == gtk.RESPONSE_OK :
        pathname = pathentry.get_text()
        dialog.hide()
        dialog.destroy()
        pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
                           run_mode=0)

In the end, GIMP guru Sven pointed me to the answer. The problem was that my dialog wasn't part of the GTK main loop. In retrospect, this makes sense: the plug-in is an entirely different process, so I shouldn't be surprised that it would have its own main loop. So when I hide() and destroy(), those events don't happen right away because there's no loop in the plug-in process that would see them.

The plug-in passes control back to GIMP to do the gimp_file_save(). GIMP's main loop doesn't have access to the hide and destroy signals I just sent. So the gimp_file_save runs, popping up its own dialog (under mine, because the JPEG save dialog is transient to the original image window while my python dialog isn't). That finishes, returns control to the plug-in, the plug-in exits and at that point GTK cleans up and finally destroys the dialog.

The solution is to loop over GTK events in the plug-in before calling gimp_file_save, like this:

    response = dialog.run()
    if response == gtk.RESPONSE_OK :
        pathname = pathentry.get_text()
        dialog.hide()
        dialog.destroy()
        while gtk.events_pending() :
            gtk.main_iteration()
        pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
                           run_mode=0)

That loop gives the Python process a chance to clean up the dialog before passing control to GIMP and its main loop. GTK in the subprocess is happy, the user is happy, and I'm happy because now I have a much more efficient way of making lots of desktop backgrounds for lots of different machines.

The updated script, along with a lot more information on how to use it and how to set up tool presets for it.

Tags: , ,
[ 22:21 Sep 15, 2009    More gimp | permalink to this entry ]