Properties
| Chapter 31. Building applications |

## Properties

Widgets and other objects have many useful properties. Here we show some ways to use them in new and flexible ways, by wrapping them in actions with `Gio::PropertyAction` or by binding them with `Glib::Binding`.

To set this up, we add two labels to the header bar in our `window.ui` file, named `lines_label` and `lines`, and get pointers to them in the application window's constructor, as we've seen a couple of times by now. We add a new "Lines" menu item to the gears menu, which triggers the `show-lines` action.

To make this menu item do something, we create a property action for the `visible` property of the `lines` label, and add it to the actions of the window. The effect of this is that the visibility of the label gets toggled every time the action is activated. Since we want both labels to appear and disappear together, we bind the `visible` property of the `lines_label` widget to the same property of the `lines` widget. In `ExampleAppWindow`'s constructor:

    add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
    m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
      m_lines_label->property_visible());

We also need a function that counts the lines of the currently active tab, and updates the `lines` label. See the full source if you are interested in the details.

This brings our example application to this appearance:

Figure 31.8. Properties

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.h"
    // Equal to the corresponding file in step5

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    #define HAS_SEARCH_ENTRY2 GTKMM_CHECK_VERSION(4,13,2)

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      // Signal handlers
      void on_search_text_changed();
      void on_visible_child_changed();
      void on_find_word(const Gtk::Button* button);
      void on_reveal_child_changed();

      void update_words();
      void update_lines();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack {nullptr};
      Gtk::ToggleButton* m_search {nullptr};
      Gtk::SearchBar* m_searchbar {nullptr};
    #if HAS_SEARCH_ENTRY2
      Gtk::SearchEntry2* m_searchentry {nullptr};
    #else
      Gtk::SearchEntry* m_searchentry {nullptr};
    #endif
      Gtk::MenuButton* m_gears {nullptr};
      Gtk::Revealer* m_sidebar {nullptr};
      Gtk::ListBox* m_words {nullptr};
      Gtk::Label* m_lines {nullptr};
      Gtk::Label* m_lines_label {nullptr};
      Glib::RefPtr<Glib::Binding> m_binding_search_enabled;
      Glib::RefPtr<Glib::Binding> m_binding_lines_visible;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step5/exampleapplication.cc"
    // Equal to the corresponding file in step5

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.cc"
    // Equal to the corresponding file in step5

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>
    #include <set>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
      if (!m_search)
        throw std::runtime_error("No \"search\" object in window.ui");

      m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
      if (!m_searchbar)
        throw std::runtime_error("No \"searchbar\" object in window.ui");

    #if HAS_SEARCH_ENTRY2
      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry2>("searchentry");
    #else
      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
    #endif
      if (!m_searchentry)
        throw std::runtime_error("No \"searchentry\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
      if (!m_sidebar)
        throw std::runtime_error("No \"sidebar\" object in window.ui");

      m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
      if (!m_words)
        throw std::runtime_error("No \"words\" object in window.ui");

      m_lines = m_refBuilder->get_widget<Gtk::Label>("lines");
      if (!m_lines)
        throw std::runtime_error("No \"lines\" object in window.ui");

      m_lines_label = m_refBuilder->get_widget<Gtk::Label>("lines_label");
      if (!m_lines_label)
        throw std::runtime_error("No \"lines_label\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());
      m_settings->bind("show-words", m_sidebar->property_reveal_child());

      // Bind properties of the search button to the search bar.
      m_binding_search_enabled = Glib::Binding::bind_property(m_search->property_active(),
        m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);

      // Connect signal handlers.
      m_searchentry->signal_search_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
      m_stack->property_visible_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
      m_sidebar->property_reveal_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));

      // Connect the menu to the MenuButton m_gears, and bind the show-words setting
      // to the win.show-words action and the "Words" menu item.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
      add_action(m_settings->create_action("show-words"));

      // Bind the "visible" property of m_lines to the win.show-lines action, to
      // the "Lines" menu item and to the "visible" property of m_lines_label.
      add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
      m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
        m_lines_label->property_visible());
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
        return;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());

      m_search->set_sensitive(true);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      if (text.empty())
        return;

      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
        return;
      }

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
        return;
      }

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

    void ExampleAppWindow::on_visible_child_changed()
    {
      m_searchbar->set_search_mode(false);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_find_word(const Gtk::Button* button)
    {
      m_searchentry->set_text(button->get_label());
    }

    void ExampleAppWindow::on_reveal_child_changed()
    {
      update_words();
    }

    void ExampleAppWindow::update_words()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      // Find all words in the text buffer.
      std::set<Glib::ustring> words;
      auto start = buffer->begin();
      Gtk::TextIter end;
      while (start)
      {
        while (start && !start.starts_word())
          ++start;

        if (!start)
          break;

        end = start;
        end.forward_word_end();
        if (start == end)
          break;

        auto word = buffer->get_text(start, end, false);
        words.insert(word.lowercase());
        start = end;
      }

      // Remove old children from the ListBox.
      while (auto child = m_words->get_first_child())
        m_words->remove(*child);

      // Add new child buttons, one per unique word.
      for (const auto& word : words)
      {
        auto row = Gtk::make_managed<Gtk::Button>(word);
        row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
          &ExampleAppWindow::on_find_word), row));
        m_words->append(*row);
      }
    }

    void ExampleAppWindow::update_lines()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_lines(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      int count = 0;
      auto iter = buffer->begin();
      while (iter)
      {
        ++count;
        if (!iter.forward_line())
          break;
      }
      m_lines->set_text(Glib::ustring::format(count));
    }

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

    #include "../step6/main.cc"
    // Equal to the corresponding file in step6

File: `gears_menu.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <menu id="menu">
        <section>
          <item>
            <attribute name="label" translatable="yes">_Words</attribute>
            <attribute name="action">win.show-words</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Lines</attribute>
            <attribute name="action">win.show-lines</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Preferences</attribute>
            <attribute name="action">app.preferences</attribute>
          </item>
        </section>
        <section>
          <item>
            <attribute name="label" translatable="yes">_Quit</attribute>
            <attribute name="action">app.quit</attribute>
          </item>
        </section>
      </menu>
    </interface>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child>
              <object class="GtkLabel" id="lines_label">
                <property name="visible">False</property>
                <property name="label" translatable="yes">Lines:</property>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="lines">
                <property name="visible">False</property>
              </object>
            </child>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkToggleButton" id="search">
                <property name="sensitive">False</property>
                <property name="icon-name">edit-find-symbolic</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkSearchBar" id="searchbar">
                <child>
                  <object class="GtkSearchEntry" id="searchentry">
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkBox" id="hbox">
                <child>
                  <object class="GtkRevealer" id="sidebar">
                    <property name="transition-type">slide-right</property>
                    <child>
                      <object class="GtkScrolledWindow" id="sidebar-sw">
                        <property name="hscrollbar-policy">never</property>
                        <child>
                          <object class="GtkListBox" id="words">
                            <property name="selection-mode">none</property>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkStack" id="stack">
                    <property name="transition-duration">500</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

Adding a side bar |  | Header bar