Propagace události

Propagace událostí znamená, že když je událost vyslána na konkrétní widget, může být předána do rodičovského widgetu (a ten ji může předat do svého rodičovského widgetu atd.) a pokud má rodič obsluhu události, je tato obsluha zavolána.

Na rozdíl od jiných událostí, události klávesnice jsou nejprve zaslány do okna nejvyšší úrovně (Gtk::Window), kde jsou zkontrolovány vůči případným klávesovým zkratkám (akcelerátory a horké klávesy používané k výběru položek v nabídkách pomocí klávesnice). Po té (za předpokladu, že událost nebyla obsloužena) jsou zaslány do widgetu, který má zaměření a z něj jsou pak propagovány.

Událost se bude propagovat, dokud nedosáhne widgetu v nejvyšší úrovni, nebo dokud propagaci nezastavíte vrácením true z obsluhy události.

Dejte pozor na to, že po zastavení události nebude už zavolána žádná funkce (i kdyby byla z toho stejného widgetu).

21.2.1. Příklad

V tomto příkladu jsou tři obsluhy událostí, které se nazývají podle výchozích obsluh událostí v Gtk::Window. Jedna je Gtk::Entry, druhá Gtk::Grid a třetí Gtk::Window.

V Gtk::Window máme také přepsanou výchozí obsluhu (on_key_release_event()) a další obsluha je volaná před výchozí obsluhou (windowKeyReleaseBefore()).

Účelem tohoto příkladu je ukázat kroky, které událost podstoupí, když je vyslána.

Když píšete do vstupního pole, vyšle se událost o uvolnění klávesy, která jde nejprve do okna nejvyšší úrovně (Gtk::Window), protože máme nastavenou jednu obsluhu události, aby byla zavolaná napřed, bude to ona, co bude zavolána první (windowKeyReleaseBefore()). Pak je zavolána výchozí obsluha (kterou máme přepsanou) a následně je událost zaslána widgetu, který má zaměření, což je v našem příkladu Entry. Podle toho, jak budeme událost propagovat, může dorazit až k obsluhám událostí objektů Grid a Window. Pokud bude propagována, bude se text, který píšete objevovat v popisku Label nad vstupním polem Entry.

Obrázek 21-2Události klávesnice – propagace událostí

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()
{
}