Event Propagation

Event propagation means that, when an event is emitted on a particular widget, it can be passed to its parent widget (and that widget can pass it to its parent, and so on) and, if the parent has an event handler, that handler will be called.

Contrary to other events, keyboard events are first sent to the toplevel window (Gtk::Window), where it will be checked for any keyboard shortcuts that may be set (accelerator keys and mnemonics, used for selecting menu items from the keyboard). After this (and assuming the event wasn't handled), it is sent to the widget which has focus, and the propagation begins from there.

The event will propagate until it reaches the top-level widget, or until you stop the propagation by returning true from an event handler.

Notice, that after canceling an event, no other function will be called (even if it is from the same widget).

XXI.II.I. Exemple

In this example there are three event handlers that are called after Gtk::Window's default event handler, one in the Gtk::Entry, one in the Gtk::Grid and one in the Gtk::Window.

In the Gtk::Window, we have also the default handler overridden (on_key_release_event()), and another handler being called before the default handler (windowKeyReleaseBefore()).

The purpose of this example is to show the steps the event takes when it is emitted.

When you write in the entry, a key release event will be emitted, which will go first to the toplevel window (Gtk::Window), since we have one event handler set to be called before, that's what is called first (windowKeyReleaseBefore()). Then the default handler is called (which we have overridden), and after that the event is sent to the widget that has focus, the Entry in our example and, depending on whether we let it propagate, it can reach the Grid's and the Window's event handlers. If it propagates, the text you're writing will appear in the Label above the Entry.

Figure XXI.2 Keyboard Events - Event Propagation

Source Code

File: examplewindow.h (For use with gtkmm 4)

#ifndef GTKMM_EVENT_PROPAGATION_H
#define GTKMM_EVENT_PROPAGATION_H

#include <gtkmm.h>

class ExampleWindow : public Gtk::Window
{
public:

  ExampleWindow();
  virtual ~ExampleWindow();

private:
  // Signal handlers:
  bool label2_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
  bool grid_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
  bool window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);

  bool m_first = true;
  Gtk::Box m_container;
  Gtk::Frame m_frame;
  Gtk::Label m_label1;
  Gtk::Label m_label2;
  Gtk::CheckButton m_checkbutton_can_propagate_down;
  Gtk::CheckButton m_checkbutton_can_propagate_up;
};

#endif //GTKMM_EVENT_PROPAGATION_H

File: main.cc (For use with gtkmm 4)

#include "examplewindow.h"
#include <gtkmm/application.h>

int main(int argc, char *argv[])
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  //Shows the window and returns when it is closed.
  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: examplewindow.cc (For use with gtkmm 4)

#include "examplewindow.h"
#include <iostream>

ExampleWindow::ExampleWindow()
:
m_label1("A label"),
m_label2("Write here"),
m_checkbutton_can_propagate_down("Can propagate down"),
m_checkbutton_can_propagate_up("Can propagate up")
{
  set_title("Event Propagation");
  m_container.set_margin(10);
  set_child(m_container);

  m_frame.set_child(m_label2);
  m_label2.set_selectable();
  m_checkbutton_can_propagate_down.set_active();
  m_checkbutton_can_propagate_up.set_active();

  // Main container
  m_container.set_orientation(Gtk::Orientation::VERTICAL);
  m_container.append(m_label1);
  m_container.append(m_frame);
  m_container.append(m_checkbutton_can_propagate_down);
  m_container.append(m_checkbutton_can_propagate_up);

  // Events
  const bool after = false; // Run before or after the default signal handlers.

  // Called in the capture phase of the event handling.
  auto controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "capture"), after);
  m_label2.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::grid_key_pressed), "capture"), after);
  m_container.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "capture"), after);
  add_controller(controller);

  // Called in the target phase of the event handling.
  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "target"), after);
  m_label2.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::grid_key_pressed), "target"), after);
  m_container.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "target"), after);
  add_controller(controller);

  // Called in the bubble phase of the event handling.
  // This is the default, if set_propagation_phase() is not called.
  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "bubble"), after);
  m_label2.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::grid_key_pressed), "bubble"), after);
  m_container.add_controller(controller);

  controller = Gtk::EventControllerKey::create();
  controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
  controller->signal_key_pressed().connect(
    sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "bubble"), after);
  add_controller(controller);
}

// By changing the return value we allow, or don't allow, the event to propagate to other elements.
bool ExampleWindow::label2_key_pressed(guint keyval, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
  std::cout << "Label,  " << phase << " phase" << std::endl;

  if (phase == "bubble")
  {
    const gunichar unichar = gdk_keyval_to_unicode(keyval);
    if (unichar != 0)
    {
      if (m_first)
      {
        m_label2.set_label("");
        m_first = false;
      }
      if (unichar == '\b')
        m_label2.set_label("");
      else
      {
        const Glib::ustring newchar(1, unichar);
        m_label2.set_label(m_label2.get_label() + newchar);
      }
    }

    if (!m_checkbutton_can_propagate_up.get_active())
      return true; // Don't propagate
  }
  return false;
}

bool ExampleWindow::grid_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
  std::cout << "Grid,   " << phase << " phase" << std::endl;

  // Let it propagate
  return false;
}

// This will set the second label's text in the first label every time a key is pressed.
bool ExampleWindow::window_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
  if (phase == "capture")
    std::cout << std::endl;
  std::cout << "Window, " << phase << " phase";

  // Checking if the second label is on focus, otherwise the label would get
  // changed by pressing keys on the window (when the label is not on focus),
  // even if m_checkbutton_can_propagate_up wasn't active.
  if (phase == "bubble" && m_label2.has_focus())
  {
    m_label1.set_text(m_label2.get_text());
    std::cout << ", " << m_label2.get_text();
  }
  std::cout << std::endl;

  if (phase == "capture" && !m_checkbutton_can_propagate_down.get_active())
    return true; // Don't propagate
  return false;
}

ExampleWindow::~ExampleWindow()
{
}