Adding a search bar
| Chapter 31. Building applications |

## Adding a search bar

We continue to flesh out the functionality of our application. For now, we add search. gtkmm supports this with `Gtk::SearchEntry` and `Gtk::SearchBar`. The search bar is a widget that can slide in from the top to present a search entry.

We add a toggle button to the header bar, which can be used to slide out the search bar below the header bar. The new widgets are added in the `window.ui` file.

Implementing the search needs quite a few code changes that we are not going to completely go over here. The central piece of the search implementation is a signal handler that listens for text changes in the search entry, shown here without error handling.

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());

      // 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);
      }
    }

With the search bar, our application now looks like this:

Figure 31.6. Adding a search bar

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();

      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};
      Glib::RefPtr<Glib::Binding> m_prop_binding;
    };

    #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>

    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");

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

      // Bind properties.
      m_prop_binding = 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));

      // Connect the menu to the MenuButton m_gears.
      // (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);
    }

    //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);
    }

    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);
    }

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

    #include "exampleapplication.h"
    #include "exampleappwindow.h"

    int main(int argc, char* argv[])
    {
    #if HAS_SEARCH_ENTRY2
      // A GtkSearchEntry (e.g. from window.ui) shall be wrapped in
      // a Gtk::SearhcEntry2 and not in a deprecated Gtk::SearchEntry.
      Gtk::Application::wrap_in_search_entry2(true);
    #endif

      // Since this example is running uninstalled, we have to help it find its
      // schema. This is *not* necessary in a properly installed application.
      Glib::setenv ("GSETTINGS_SCHEMA_DIR", ".", false);

      auto application = ExampleApplication::create();

      // Start the application, showing the initial window,
      // and opening extra views for any files that it is asked to open,
      // for instance as a command-line parameter.
      // run() will return when the last window has been closed.
      return application->run(argc, argv);
    }

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 type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</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>
          </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="GtkStack" id="stack">
                <property name="transition-duration">500</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

A preference dialog |  | Adding a side bar