<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Adding a search bar</title> <link rel="stylesheet" type="text/css" href="style.css"> <meta name="generator" content="DocBook XSL Stylesheets V1.79.1"> <link rel="home" href="index.html" title="Programming with gtkmm 3"> <link rel="up" href="chapter-building-applications.html" title="Chapter 31. Building applications"> <link rel="prev" href="sec-buildapp-pref-dialog.html" title="A preference dialog"> <link rel="next" href="sec-buildapp-side-bar.html" title="Adding a side bar"> </head> <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> <div class="navheader"> <table width="100%" summary="Navigation header"> <tr><th colspan="3" align="center">Adding a search bar</th></tr> <tr> <td width="20%" align="left"> <a accesskey="p" href="sec-buildapp-pref-dialog.html"><img src="icons/prev.png" alt="Prev"></a> </td> <th width="60%" align="center">Chapter 31. Building applications</th> <td width="20%" align="right"> <a accesskey="n" href="sec-buildapp-side-bar.html"><img src="icons/next.png" alt="Next"></a> </td> </tr> </table> <hr> </div> <div class="sect1"> <div class="titlepage"><div><div><h2 class="title" style="clear: both"> <a name="sec-buildapp-search-bar"></a>Adding a search bar</h2></div></div></div> <p> We continue to flesh out the functionality of our application. For now, we add search. <span class="application">gtkmm</span> supports this with <code class="classname">Gtk::SearchEntry</code> and <code class="classname">Gtk::SearchBar</code>. The search bar is a widget that can slide in from the top to present a search entry. </p> <p> 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 <code class="filename">window.ui</code> file. </p> <p> 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. </p> <pre class="programlisting"> 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::TEXT_SEARCH_CASE_INSENSITIVE, match_start, match_end)) { buffer->select_range(match_start, match_end); view->scroll_to(match_start); } } </pre> <p> </p> <p> With the search bar, our application now looks like this: </p> <div class="figure"> <a name="figure-buildapp-search-bar"></a><p class="title"><b>Figure 31.6. Adding a search bar</b></p> <div class="figure-contents"><div class="screenshot"><div><img src="figures/buildapp_search_bar.png" alt="Adding a search bar"></div></div></div> </div> <br class="figure-break"><p><a class="ulink" href="http://git.gnome.org/browse/gtkmm-documentation/tree/examples/book/buildapp/step6?h=gtkmm-3-24" target="_top">Source Code</a></p> <p>File: <code class="filename">exampleappwindow.h</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #ifndef GTKMM_EXAMPLEAPPWINDOW_H_ #define GTKMM_EXAMPLEAPPWINDOW_H_ #include <gtkmm.h> 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; Gtk::ToggleButton* m_search; Gtk::SearchBar* m_searchbar; Gtk::SearchEntry* m_searchentry; Glib::RefPtr<Glib::Binding> m_prop_binding; }; #endif /* GTKMM_EXAMPLEAPPWINDOW_H */ </pre> <p>File: <code class="filename">exampleappprefs.h</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "../step5/exampleappprefs.h" // Equal to the corresponding file in step5 </pre> <p>File: <code class="filename">exampleapplication.h</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "../step4/exampleapplication.h" // Equal to the corresponding file in step4 </pre> <p>File: <code class="filename">exampleapplication.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "../step5/exampleapplication.cc" // Equal to the corresponding file in step5 </pre> <p>File: <code class="filename">main.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "../step5/main.cc" // Equal to the corresponding file in step5 </pre> <p>File: <code class="filename">exampleappwindow.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "exampleappwindow.h" #include <iostream> #include <stdexcept> ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder) : Gtk::ApplicationWindow(cobject), m_refBuilder(refBuilder), m_settings(), m_stack(nullptr), m_search(nullptr), m_searchbar(nullptr), m_searchentry(nullptr), m_prop_binding() { m_refBuilder->get_widget("stack", m_stack); if (!m_stack) throw std::runtime_error("No \"stack\" object in window.ui"); m_refBuilder->get_widget("search", m_search); if (!m_search) throw std::runtime_error("No \"search\" object in window.ui"); m_refBuilder->get_widget("searchbar", m_searchbar); if (!m_searchbar) throw std::runtime_error("No \"searchbar\" object in window.ui"); m_refBuilder->get_widget("searchentry", m_searchentry); if (!m_searchentry) throw std::runtime_error("No \"searchentry\" object in window.ui"); m_settings = Gio::Settings::create("org.gtkmm.exampleapp"); m_settings->bind("transition", m_stack->property_transition_type()); m_prop_binding = Glib::Binding::bind_property(m_search->property_active(), m_searchbar->property_search_mode_enabled(), Glib::BINDING_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)); } //static ExampleAppWindow* ExampleAppWindow::create() { // Load the Builder file and instantiate its widgets. auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui"); ExampleAppWindow* window = nullptr; refBuilder->get_widget_derived("app_window", 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 auto basename = file->get_basename(); auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>(); scrolled->set_hexpand(true); scrolled->set_vexpand(true); scrolled->show(); auto view = Gtk::make_managed<Gtk::TextView>(); view->set_editable(false); view->set_cursor_visible(false); view->show(); scrolled->add(*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::TEXT_SEARCH_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); } </pre> <p>File: <code class="filename">exampleappprefs.cc</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> #include "../step5/exampleappprefs.cc" // Equal to the corresponding file in step5 </pre> <p>File: <code class="filename">window.ui</code> (For use with gtkmm 3, not gtkmm 2) </p> <pre class="programlisting"> <?xml version="1.0" encoding="UTF-8"?> <interface> <!-- interface-requires gtk+ 3.8 --> <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> <object class="GtkBox" id="content_box"> <property name="visible">True</property> <property name="orientation">vertical</property> <child> <object class="GtkHeaderBar" id="header"> <property name="visible">True</property> <child type="title"> <object class="GtkStackSwitcher" id="tabs"> <property name="visible">True</property> <property name="stack">stack</property> </object> </child> <child> <object class="GtkToggleButton" id="search"> <property name="visible">True</property> <property name="sensitive">False</property> <style> <class name="image-button"/> </style> <child> <object class="GtkImage" id="search-icon"> <property name="visible">True</property> <property name="icon-name">edit-find-symbolic</property> <property name="icon-size">1</property> </object> </child> </object> <packing> <property name="pack-type">end</property> </packing> </child> </object> </child> <child> <object class="GtkSearchBar" id="searchbar"> <property name="visible">True</property> <child> <object class="GtkSearchEntry" id="searchentry"> <property name="visible">True</property> </object> </child> </object> </child> <child> <object class="GtkStack" id="stack"> <property name="visible">True</property> <property name="transition-duration">500</property> </object> </child> </object> </child> </object> </interface> </pre> </div> <div class="navfooter"> <hr> <table width="100%" summary="Navigation footer"> <tr> <td width="40%" align="left"> <a accesskey="p" href="sec-buildapp-pref-dialog.html"><img src="icons/prev.png" alt="Prev"></a> </td> <td width="20%" align="center"><a accesskey="u" href="chapter-building-applications.html"><img src="icons/up.png" alt="Up"></a></td> <td width="40%" align="right"> <a accesskey="n" href="sec-buildapp-side-bar.html"><img src="icons/next.png" alt="Next"></a> </td> </tr> <tr> <td width="40%" align="left" valign="top">A preference dialog </td> <td width="20%" align="center"><a accesskey="h" href="index.html"><img src="icons/home.png" alt="Home"></a></td> <td width="40%" align="right" valign="top"> Adding a side bar</td> </tr> </table> </div> </body> </html>