pythongtkpygtkpygobjectgtkbuilder

Dialog breaks when using GtkBuilder to automatically connect signals, but works when manually connecting signals


I want to have a dialog window where some buttons close the dialog and others don't. The way I do this is by using the response signal from the Gtk.Dialog to call emit_stop_by_name('response') on the dialog. (If someone knows of a better way of doing this, that might preempt the whole rest of this question.)

This worked when I used PyGTK. I'm moving to PyGObject now.. and it seems that this technique will work only if I manually connect to the response signal rather than using Gtk.Builder.connect_signals().

But don't take my word for it. Here is a minimal example of my problem:

from gi.repository import Gtk

xml = """<interface>
  <object class="GtkDialog" id="dialog1">
    <signal name="response" handler="on_response"/>
    <child internal-child="vbox">
      <object class="GtkBox" id="dialog-vbox1">
        <child internal-child="action_area">
          <object class="GtkButtonBox" id="dialog-action_area1">
            <child>
              <object class="GtkButton" id="button1">
                <property name="label">Don't Close Dialog</property>
                <property name="visible">True</property>
              </object>
            </child>
            <child>
              <object class="GtkButton" id="button2">
                <property name="label">Close Dialog</property>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="0">button1</action-widget>
      <action-widget response="-6">button2</action-widget>
    </action-widgets>
  </object>
</interface>
"""

def on_button_clicked(widget):
    d = DummyDialog()
    d.dialog1.run()
    d.dialog1.destroy()

class DummyDialog:
    def __init__(self):
        self.builder = Gtk.Builder()
        self.builder.add_from_string(xml)
        self.dialog1 = self.builder.get_object('dialog1')
        self.builder.connect_signals(self)

    def on_response(self, widget, response, data=None):
        print 'response:', response
        if response >= 0:
            widget.emit_stop_by_name('response')

w = Gtk.Window()
w.connect('destroy', Gtk.main_quit)
b = Gtk.Button('Open Dialog')
b.connect('clicked', on_button_clicked)
w.add(b)

w.show_all()

Gtk.main()

When you run this, you get a window with a single button. When you click that button, a dialog pops up with two buttons, one labeled "Don't Close Dialog" and another labeled "Close Dialog". When running the code above, both buttons will close the dialog.

But if you change from using Gtk.Builder.connect_signals() to manually connecting the signal by replacing

        self.builder.connect_signals(self)

with

        self.dialog1.connect('response', self.on_response)

then it starts working as designed (the "Don't Close Dialog" button doesn't close the dialog).

But shouldn't those two lines be exactly functionally identical in this context? Is there any way to figure out what's different between the two scenarios?

I can tell the signals are still connected in both situations because the text is still printed to the CLI from DummyDialog.on_response. But it seems like the widget.emit_stop_by_name('response') part stops working when I use GtkBuilder.

Even more perplexing is that if you take this exact code and run it on PyGTK (change from gi.repository import Gtk to import gtk as Gtk), then it works correctly in both scenarios (using self.builder.connect_signals(self) or self.dialog1.connect('response', self.on_response)).


Solution

  • I would do it a little different. Remove the dialog1.destroy() in the button clicked callback and change the on_response callback to:

        def on_response(self, widget, response, data=None):
            print 'response:', response
            if response < 0:
                self.dialog1.destroy()