Signal propagation in custom widgets

Hi,

I am a bit confused about how signal propagation works. According to the documentation from this page: https://developer.gnome.org/gtk-tutorial/stable/x1810.html, signals bubble up from child to parent unless one of the signal handlers prematurely stops the chain.

In the above page, does “parent” refer to a widget’s parent container or a widget’s parent class/object?

If it refers to a widgets parent class, does that mean that a subclassed widget that defines a custom signal would require signal handlers in all parent containers in order for the custom signal to be delivered to the top level widget?

Thank you.

Note that the page describes a particular class of event signals in GTK, not any GObject signals in GTK widgets. It is how the various “*-event” signals work, which correspond to some hardware event (a key was pressed, a button was released, a pointer device was moved, …).

For example, it’s how the button-press-event and button-release-event signals work, but not the higher-level GtkButton::clicked signal which is based on those underlying hardware events. The latter is specific to the GtkButton class and doesn’t bubble up (it also has a different signature, i.e. no return value or parameters).

The parent container.

No. A GObject can only emit a signal that is defined in its own class, or in any of its ancestors. So if you define a signal in a custom widget, it can only ever be emitted on instances of that class or subclasses, but not on existing widgets. Event bubbling is something GtkWidget implements for some of its signals, it’s not a generic feature available to any classes for all signals.

So, if I understand you correctly, if I were to have the following widget tree:

GtkWindow
    GtkBox
       MyGtkWidget

the only way that GtkWindow would receive MyGtkWidget::custom_signal is to directly connect a handler to it?

Strictly speaking no. GtkWindow cannot connect to any signals it doesn’t know about.

What you probably mean is that the code that creates/manages the GtkWindow can connect to MyGtkWidget::custom-signal, and assuming that that code has access to a MyGtkWidget instance, that’ll work.

In fact, that’s exactly the same as signals from widgets GTK provides (except for some on the GtkWidget base class). If you want to handle button clicks, you have to connect to GtkButton::clicked; you cannot connect to GtkWindow::clicked to detect that some button inside the window was clicked (that signal doesn’t exist, so trying to connect to it will result in a warning).

I have the feeling that there may be some misunderstanding involved.

Subclassing an object does not always mean that a pointer chain is involved. I think most programming languages handle subclassing of objects like C handles extending structs. That is if we build a larger struct based on a existing one, we get not a chain of structs linked by pointers, but just a larger struct with additional fields. So if we allocate our new object, only one allocation is needed. I was never really interested how GTK/GObject does it, but I think there is always only one g_object_new() involved, so I would assume there is no pointer chain, but only one single memory block for each object. And for that case there really is no “bubbling up” of signals from subclass to parent class.

Nobody claimed there was. However there is event bubbling for some GtkWidget events to parent containers. That isn’t inherit to how GObject is implemented, but a feature of GtkWidget itself.

(ClutterActor is another object type that implements that form of event bubbling)

What you are saying makes sense and it certainly aligns with my experience with Gtk. However, for composite widgets, which are not a single UI element but a collection of elements, it seems that in order for the parent widget to get signals/events, it composite widget would have to connect to all signals of child and then expose it’s own. In essence, signal “bubbling” would have to be implemented artificially.

I think that in the case of composite widgets, signal bubbling might be helpful. Otherwise, it’s just an ugly chain of signal handlers that just re-emit signals.

What exactly do you have in mind?

Relaying signals is usually not a big issue for composite widgets - for signals that correspond to hardware events, GtkWidget's event bubbling just works. And more specific signals are usually not useful for the composite widget - for example, a relayed changed signal could mean some text was entered in an entry (GtkEditable::changed), or a value was picked in a combobox (GtkCombobox::changed). Not to mention that a composite widgets can have multiple buttons, entries etc., so their signals would be ambiguous on the composite widget.

One example I can think of are the various GtkFileChooser implementations, where GtkFileChooserWidget is the composite widget that implements the nuts and bolts of the interface, and GtkFileChooserDialog and GtkFileChooserButton which provide less flexible and more convenient implementations. But delegating an interface implementation goes beyond relaying signals, so even that example is probably not entirely relevant to this topic.

Suppose I have the following widget tree:

MyApplication
    GtkBox
        MyFileViewTree
        GtkNotebook1
            GtkLabel
            MyProjectEditor
                 GtkNotebook2
                    GtkLabel
                    MyFileEditor
                    GtkLabel
                    MyFileEditor

where MyFileViewTree is a custom widget that implements a GtkTreeView to show directory trees, MyProjectEditor is a custom composite widget that implements a rudimentary multi-file editor, and MyFileEditor is a custom file editor widget, which defines a custom signal MyFileEditor::file-opened, emitted when MyFileEditor opens a new file.

MyFileViewTree::row-activated signal handler is setup to create a new MyProjectEditor.

What I want is to be able to connect MyFileEditor instances to MyFileViewTree so that when MyFileEditor::file-opened is emitted, the opened file is selected in MyFileViewTree.

I am aware that in most cases, that’s not how one would design the UI (or the application). However, I wanted to illustrate that a direct connection between two widget is not always possible.

The handler for MyFileViewTree::row-activated will get a pointer to GtkNotebook1 and will append a new instance of MyProjectEditor. However, when that new instance of MyProjectEditor is created, it will create a new instance of MyFileEditor, itself, and there will be no way for MyFileViewTree to get a pointer to it in order to setup a handler for MyFileEditor::file-opened for the new instance.

What would have to happen is that each new instance of MyProjectEditor will have to setup a signal handler for each child instance of MyFileEditor and the emit it’s own MyProjectEditor::file-opened signal. This can get out of hand for deeper nesting levels and large number of signals.

But, only one signal handler is needed! All instances of MyProjectEditor would connect all instances of MyFileEditor to it. Each emitter would give that handler the info that’s needed by MyFileViewTree to select and indicate what you want to see (e.g., which file, which notebook, which row).

As usual, connections are created directly between the emitter and the consumer. “Signal bubbling” isn’t needed.

From the point of view of how many functions do I have to write, yes, only one is needed. However, when it comes to connections, as you said, they are point-to-point.

So, every instance of MyProjectEditor will have to define a signal handler. That singnal handler will have to be connected to all child instances of MyFileEditor in that instance of MyProjectEditor. Then, MyProjectEditor will have to define a signal (let’s call it MyProjectEditor::child-editor-file-opened) and emit it every time MyFileEditor::file-opened is emitted just so MyFileTreeView can connect to that.

(Just explaining it is difficult and clumsy, let alone coding it)

As you can see, it’s a log of work and MyProjectEditor has to define a signal handler just for the purposes of “re-emitting” a signal. Signal bubbling on the other hand will solve all this (just like it does for events) without the need for any of this plumbing.

MyFileTreeView (not MyProjectEditor) can define – and expose – a single signal handler. The many instances of MyProjectEditor can connect the many instances of MyFileEditor to that one handler. The connections wouldn’t be point-to-point; they would be many-to-one.

Nothing needs to be re-emitted; nothing needs to be “bubbled up”. The (many) inner-most signal emitters can be connected – directly – to the (one) outer-most callback function.

(Notice that any object’s code is allowed to connect any other object’s signal to any third object’s callback. The relationships don’t need to be parent-child.)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.