Search code examples
pythongtkgnome

Python GTK4: CheckButton 'toggled' signal called dozens of times


Running a Python program that I wanted to add a GTK Settings menu to. The below functions as needed, but there's a couple problems with it. Here's the relevant code:

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

def gtk_on_activate(app):
    win = Gtk.ApplicationWindow(application=app)
    win.set_title('Settings')
    win.set_default_size(320,240)
    box = Gtk.ListBox()
    for k,v in config['Settings'].items():
        btn = Gtk.CheckButton(label=k,active=bool(int(v)))
        btn.connect('toggled', gtk_toggled)
        box.append(btn)
    win.set_child(box)
    win.present()   

def gtk_toggled(btn):
    global config
    print('hi')
    btn.set_active(not btn.get_active())
    config.set('Subscriptions', btn.get_label(), str(int(btn.get_active())))
    with open(os.path.join(os.path.dirname(__file__), 'Config/SavedVariables.cfg'), 'w') as cfg:
        config.write(cfg)

app = Gtk.Application(application_id='org.gdi.Settings')
app.connect('activate', gtk_on_activate)
app.run()

And here's the output:

[...]
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
Traceback (most recent call last):

RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/xyz/.local/bin/scripts/Settings/Settings.py", line 95, in gtk_toggled
    with open(os.path.join(os.path.dirname(__file__), 'Config/SavedVariables.cfg'), 'w') as cfg:
RecursionError: maximum recursion depth exceeded while calling a Python object

What I imagine the RecursionError is about is that file likely being opened dozens of times. But I don't understand:

  1. Why that print('hi') is required for the code to even work(other strings work)?
  2. Why is the signal being sent dozens and dozens of times?
  3. Am I doing something wrong here?

Thank you for any and all help!

EDIT: I've since moved the open out of that function, and that solved the RecursionError. I also have now tried with Gtk3, and noticed the same pattern of the signal being spammed. So I guess it's just normal? Gtk3 seems to have solved the print('hi') being an odd requirement. I noticed with Gtk4, that weird trick only worked if the program was run from the terminal. Don't really understand what's going on there... Perhaps I should file a bug report?


Solution

  • I've changed the code so that it works with Gtk 3 (and without error messages relating to config), and it seems to be that the problem comes down to the btn.set_active(not btn.get_active()) line. Here is the code I used:

    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    
    def gtk_on_activate(app):
        win = Gtk.ApplicationWindow(application=app)
        win.set_title('Settings')
        win.set_default_size(320,240)
        box = Gtk.ListBox()
        for k,v in {"Button1": "1", "Button2": "0", "Button3": "1"}.items():
            btn = Gtk.CheckButton(label=k,active=bool(int(v)))
            btn.connect('toggled', gtk_toggled)
            box.insert(btn, 0)
        win.add(box)
        win.show_all()
        win.present()   
    
    def gtk_toggled(btn):
        print('hi')
        btn.set_active(not btn.get_active()) # This line is causing the error message
    
    app = Gtk.Application(application_id='org.gdi.Settings')
    app.connect('activate', gtk_on_activate)
    app.run()
    

    To fix this, just remove the btn.set_active(not btn.get_active()). It's completely useless, since the checkbutton will automatically change its state by itself. And if you don't want the user to be able to change the state of the checkbutton, just use btn.set_sensitive(False) for any checkbuttons you want to be disabled.

    So, in answer to your questions:

    1. print('hi') isn't required for the code to work; the code fails whether you print('hi') or not (at least in Gtk 3).
    2. The signal is being sent so many times because the btn.set_active() function in the btn.set_active(not btn.get_active()) line sends toggled signal, which then calls gtk_toggled(), which calls btn.set_active(), which sends the toggled signal, which calls gtk_toggled(), which then calls btn.set_active(), on and on and on until the maximum recursion depth is reached.
    3. Yes, you are doing something wrong. Fortunately, it's just about the simplest and easiest error to fix, and the fix is actually less code and complication; you can let Gtk do the work for you!

    And no, thankfully it's not normal, and no, you don't need to file a bug report.