Ubuntu – How to change the cursor to hourglass in a python gtk3 application

cursorgtk3python

I've a function taking quite a long time to perform and I'm trying to change the cursor to an hour glass during the time the function is executed. But it only works the very first time the function is called.
I'm doing this (it's in an on_click event handler of a button):

from gi.repository import Gtk, Gdk, GObject
import time

def on_grabbtn_clicked(self, button):
    # Change the cursor to hour Glass
    cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)
    self.window.get_root_window().set_cursor(cursor)

    # lenghty process here
    time.sleep(10)

     # Set the cursor to normal Arrow
    cursor = Gdk.Cursor.new(Gdk.CursorType.ARROW)
    self.window.get_root_window().set_cursor(cursor)

window is a window build with Glade/GtkBuilder and named…window.
I get a handle on it in the __init__() of the window class like this:

self.window = self.builder.get_object('window')

As I said, the hour glass appears only the first time I click on the button. The second time it doesn't work anymore. So I'm doing it wrong. But what Am I doing wrong ?

Best Answer

Here's a working example of what you want.

You need to grasp the concept of what the GTK main loop does to understand why it is important to separate GUI manipulation code from long blocking code. Hopefully, the comments and the debugging statements in my example will help you understand. This PyGTK FAQ entry is useful, and the concept applies to Python with GTK3 and GObject introspection as well.

Example code:

This code creates a window with a simple button labelled "Click me". Once you click it, it will change to "Working", the cursor will become an hourglass, and the GUI will remain responsive. Once ten seconds have elapsed, the button label will change to "Done" and the cursor will return to normal.

import time
import threading

from gi.repository import Gtk, Gdk, GObject

window = None

def main():
    GObject.threads_init()
    Gdk.threads_init()

    # Build GUI:
    global window
    window = Gtk.Window()
    button = Gtk.Button(label="Click me")
    window.add(button)
    window.set_default_size(200, 200)
    window.show_all()

    # Connect signals:
    window.connect("delete-event", Gtk.main_quit)
    button.connect("clicked", on_button_click)

    Gtk.main()

def on_button_click(button):
    print "Debug on_button_click: current_thread name:", threading.current_thread().name

    # This is a callback called by the main loop, so it's safe to
    # manipulate GTK objects:
    watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
    window.get_window().set_cursor(watch_cursor)
    button.set_label("Working...")
    button.set_sensitive(False)

    def lengthy_process():
        print "Debug lengthy_process: current_thread name:", threading.current_thread().name
        # We're in a new thread, so we can run lengthy processes without
        # freezing the GUI, but we can't manipulate GTK objects except
        # through GObject.idle_add
        time.sleep(10)
        def done():
            print "Debug done: current_thread name:", threading.current_thread().name
            window.get_window().set_cursor(None)
            button.set_label("Done!")
            return False
        GObject.idle_add(done)

    thread = threading.Thread(target=lengthy_process)
    thread.start()
        
if __name__ == "__main__":
    main()
Related Question