[gnome-terminal] window: Improve search



commit 6ff2df44defa586ce8110b3d5d730d1e49c13b42
Author: Christian Persch <chpe gnome org>
Date:   Tue Sep 22 14:30:54 2015 +0200

    window: Improve search
    
    Use a popover instead of a dialogue, and use a GtkSearchEntry.
    
    Note: You will need to use the 'searchbar' branch of glade to edit the .ui file
    until the branch is merged in glade.

 configure.ac                  |    2 +-
 src/Makefile.am               |    6 +-
 src/find-dialog.ui            |  222 ------------------
 src/search-popover.ui         |  249 ++++++++++++++++++++
 src/terminal-libgsystem.h     |   10 +
 src/terminal-search-dialog.c  |  372 -----------------------------
 src/terminal-search-dialog.h  |   54 -----
 src/terminal-search-popover.c |  515 +++++++++++++++++++++++++++++++++++++++++
 src/terminal-search-popover.h |   48 ++++
 src/terminal-window.c         |  119 +++++++---
 src/terminal.gresource.xml    |    2 +-
 11 files changed, 908 insertions(+), 691 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5e45759..2da8c5d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -54,7 +54,7 @@ AC_MSG_RESULT([$with_gtk])
 
 case "$with_gtk" in
   3.0) GTK_API_VERSION=3.0
-       GTK_REQUIRED=3.10.0
+       GTK_REQUIRED=3.12.0
        GTK_MIN_REQUIRED=3.8
        GTK_MAX_ALLOWED=3.14
        VTE_API_VERSION=2.91
diff --git a/src/Makefile.am b/src/Makefile.am
index 0af81d7..16766d3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -67,8 +67,8 @@ gnome_terminal_server_SOURCES = \
        terminal-screen.h \
        terminal-screen-container.c \
        terminal-screen-container.h \
-       terminal-search-dialog.c \
-       terminal-search-dialog.h \
+       terminal-search-popover.c \
+       terminal-search-popover.h \
        terminal-tab-label.c \
        terminal-tab-label.h \
        terminal-tabs-menu.c \
@@ -381,7 +381,7 @@ EXTRA_DIST = \
        terminal-type-builtins.h.template \
        org.gnome.Terminal.xml \
        nautilus.symbols \
-       find-dialog.ui \
+       search-popover.ui \
        preferences.ui \
        profile-preferences.ui \
        terminal.common.css \
diff --git a/src/search-popover.ui b/src/search-popover.ui
new file mode 100644
index 0000000..68908c7
--- /dev/null
+++ b/src/search-popover.ui
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.19.0 -->
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="TerminalSearchPopover" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <property name="transitions_enabled">False</property>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">12</property>
+        <property name="margin_right">12</property>
+        <property name="margin_top">12</property>
+        <property name="margin_bottom">12</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">18</property>
+            <child>
+              <object class="GtkBox" id="box4">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkSearchEntry" id="search_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="activates_default">True</property>
+                    <property name="width_chars">30</property>
+                    <property name="primary_icon_name">edit-find-symbolic</property>
+                    <property name="primary_icon_activatable">False</property>
+                    <property name="primary_icon_sensitive">False</property>
+                    <property name="placeholder_text" translatable="yes">Search</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="search_prev_button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="tooltip_text" translatable="yes">Search for previous 
occurrence</property>
+                    <property name="focus_on_click">False</property>
+                    <child>
+                      <object class="GtkImage" id="image2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="icon_name">go-up-symbolic</property>
+                        <property name="use_fallback">True</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="search_next_button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="can_default">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="tooltip_text" translatable="yes">Search for next occurrence</property>
+                    <property name="focus_on_click">False</property>
+                    <child>
+                      <object class="GtkImage" id="image3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="icon_name">go-down-symbolic</property>
+                        <property name="use_fallback">True</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <style>
+                  <class name="linked"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToggleButton" id="reveal_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">Toggle search options</property>
+                <property name="focus_on_click">False</property>
+                <child>
+                  <object class="GtkImage" id="image1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">view-context-menu-symbolic</property>
+                    <property name="use_fallback">True</property>
+                  </object>
+                </child>
+                <accessibility>
+                  <relation type="controller-for" target="revealer"/>
+                </accessibility>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="close_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="focus_on_click">False</property>
+                <child>
+                  <object class="GtkImage" id="image4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">window-close-symbolic</property>
+                    <property name="use_fallback">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="revealer">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="transition_type">none</property>
+            <child>
+              <object class="GtkBox" id="box3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">18</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkCheckButton" id="match_case_checkbutton">
+                    <property name="label" translatable="yes">_Match case</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0.49000000953674316</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="entire_word_checkbutton">
+                    <property name="label" translatable="yes">Match _entire word only</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="xalign">0</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="regex_checkbutton">
+                    <property name="label" translatable="yes">Match as _regular expression</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="xalign">0</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="wrap_around_checkbutton">
+                    <property name="label" translatable="yes">_Wrap around</property>
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="xalign">0</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/terminal-libgsystem.h b/src/terminal-libgsystem.h
index 3a91c7f..2d23c86 100644
--- a/src/terminal-libgsystem.h
+++ b/src/terminal-libgsystem.h
@@ -56,6 +56,7 @@ GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_
 GS_DEFINE_CLEANUP_FUNCTION0(GList*, gs_local_list_free, g_list_free)
 GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GRegex*, gs_local_regex_unref, g_regex_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free)
@@ -80,6 +81,15 @@ GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free)
 #define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref)))
 
 /**
+ * gs_unref_regex:
+ *
+ * Call g_regex_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_regex_unref(), the variable may be
+ * %NULL.
+ */
+#define gs_unref_regex __attribute__ ((cleanup(gs_local_regex_unref)))
+
+/**
  * gs_unref_variant:
  *
  * Call g_variant_unref() on a variable location when it goes out of
diff --git a/src/terminal-search-popover.c b/src/terminal-search-popover.c
new file mode 100644
index 0000000..4f286ad
--- /dev/null
+++ b/src/terminal-search-popover.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright © 2015 Christian Persch
+ * Copyright © 2005 Paolo Maggi
+ * Copyright © 2010 Red Hat (Red Hat author: Behdad Esfahbod)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "terminal-search-popover.h"
+#include "terminal-intl.h"
+#include "terminal-window.h"
+#include "terminal-app.h"
+#include "terminal-libgsystem.h"
+
+typedef struct _TerminalSearchPopoverPrivate TerminalSearchPopoverPrivate;
+
+struct _TerminalSearchPopover
+{
+  GtkPopover parent_instance;
+};
+
+struct _TerminalSearchPopoverClass
+{
+  GtkPopoverClass parent_class;
+
+  /* Signals */
+  void (* search) (TerminalSearchPopover *popover,
+                   gboolean backward);
+};
+
+struct _TerminalSearchPopoverPrivate
+{
+  GtkWidget *search_entry;
+  GtkWidget *search_prev_button;
+  GtkWidget *search_next_button;
+  GtkWidget *reveal_button;
+  GtkWidget *close_button;
+  GtkWidget *revealer;
+  GtkWidget *match_case_checkbutton;
+  GtkWidget *entire_word_checkbutton;
+  GtkWidget *regex_checkbutton;
+  GtkWidget *wrap_around_checkbutton;
+
+  gboolean search_text_changed;
+
+  /* Cached regex */
+  GRegex *regex;
+  GRegexCompileFlags regex_compile_flags;
+};
+
+enum {
+  PROP_0,
+  PROP_REGEX,
+  PROP_WRAP_AROUND,
+  LAST_PROP
+};
+
+enum {
+  SEARCH,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static GParamSpec *pspecs[LAST_PROP];
+static GtkListStore *history_store;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TerminalSearchPopover, terminal_search_popover, GTK_TYPE_POPOVER)
+
+#define PRIV(obj) ((TerminalSearchPopoverPrivate *) terminal_search_popover_get_instance_private 
((TerminalSearchPopover *)(obj)))
+
+/* history */
+
+#define HISTORY_MIN_ITEM_LEN (3)
+#define HISTORY_LENGTH (10)
+
+static gboolean
+history_enabled (void)
+{
+  gboolean enabled;
+
+  /* not quite an exact setting for this, but close enough… */
+  g_object_get (gtk_settings_get_default (), "gtk-recent-files-enabled", &enabled, NULL);
+  if (!enabled)
+    return FALSE;
+
+  if (history_store == NULL) {
+    history_store = gtk_list_store_new (1, G_TYPE_STRING);
+    g_object_set_data_full (G_OBJECT (terminal_app_get ()), "search-history-store",
+                            history_store, (GDestroyNotify) g_object_unref);
+  }
+
+  return TRUE;
+}
+
+static gboolean
+history_remove_item (const char  *text)
+{
+  GtkTreeModel *model = GTK_TREE_MODEL (history_store);
+  GtkTreeIter iter;
+
+  if (!gtk_tree_model_get_iter_first (model, &iter))
+    return FALSE;
+
+  do {
+    gs_free gchar *item_text;
+
+    gtk_tree_model_get (model, &iter, 0, &item_text, -1);
+
+    if (item_text != NULL && strcmp (item_text, text) == 0) {
+      gtk_list_store_remove (history_store, &iter);
+      return TRUE;
+    }
+  } while (gtk_tree_model_iter_next (model, &iter));
+
+  return FALSE;
+}
+
+static void
+history_clamp (int max)
+{
+  GtkTreePath *path;
+  GtkTreeIter iter;
+
+  /* -1 because TreePath counts from 0 */
+  path = gtk_tree_path_new_from_indices (max - 1, -1);
+
+  if (gtk_tree_model_get_iter (GTK_TREE_MODEL (history_store), &iter, path))
+    while (1)
+      if (!gtk_list_store_remove (history_store, &iter))
+       break;
+
+  gtk_tree_path_free (path);
+}
+
+static void
+history_insert_item (const char *text)
+{
+  GtkTreeIter iter;
+
+  if (!history_enabled () || text == NULL)
+    return;
+
+  if (g_utf8_strlen (text, -1) <= HISTORY_MIN_ITEM_LEN)
+    return;
+
+  /* remove the text from the store if it was already
+   * present. If it wasn't, clamp to max history - 1
+   * before inserting the new row, otherwise appending
+   * would not work */
+  if (!history_remove_item (text))
+    history_clamp (HISTORY_LENGTH - 1);
+
+  gtk_list_store_insert_with_values (history_store, &iter, 0,
+                                     0, text,
+                                     -1);
+}
+
+/* helper functions */
+
+static void
+update_sensitivity (TerminalSearchPopover *popover)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+  gboolean can_search;
+
+  can_search = priv->regex != NULL;
+
+  gtk_widget_set_sensitive (priv->search_prev_button, can_search);
+  gtk_widget_set_sensitive (priv->search_next_button, can_search);
+}
+
+static void
+perform_search (TerminalSearchPopover *popover,
+                gboolean backward)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+  if (priv->regex == NULL)
+    return;
+
+  /* Add to search history */
+  if (priv->search_text_changed) {
+    const char *search_text;
+
+    search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+    history_insert_item (search_text);
+
+    priv->search_text_changed = FALSE;
+  }
+
+  g_signal_emit (popover, signals[SEARCH], 0, backward);
+}
+
+static void
+previous_match_cb (GtkWidget *widget,
+                  TerminalSearchPopover *popover)
+{
+  perform_search (popover, TRUE);
+}
+
+static void
+next_match_cb (GtkWidget *widget,
+               TerminalSearchPopover *popover)
+{
+  perform_search (popover, FALSE);
+}
+
+static void
+close_clicked_cb (GtkWidget *widget,
+                  GtkWidget *popover)
+{
+  gtk_widget_hide (popover);
+}
+
+static void
+search_button_clicked_cb (GtkWidget *button,
+                          TerminalSearchPopover *popover)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+  perform_search (popover, button == priv->search_prev_button);
+}
+
+static void
+update_regex (TerminalSearchPopover *popover)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+  GRegexCompileFlags compile_flags;
+  const char *search_text;
+  gs_free char *pattern;
+
+  search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+
+  compile_flags = G_REGEX_OPTIMIZE;
+
+  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->match_case_checkbutton)))
+    compile_flags |= G_REGEX_CASELESS;
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->regex_checkbutton))) {
+    pattern = g_strdup (search_text);
+    compile_flags |= G_REGEX_MULTILINE;
+  } else {
+    pattern = g_regex_escape_string (search_text, -1);
+  }
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->entire_word_checkbutton))) {
+    char *new_pattern;
+    new_pattern = g_strdup_printf ("\\b%s\\b", pattern);
+    g_free (pattern);
+    pattern = new_pattern;
+  }
+
+  if (priv->regex != NULL &&
+      priv->regex_compile_flags == compile_flags &&
+      g_strcmp0 (pattern, g_regex_get_pattern (priv->regex)) == 0)
+    return;
+
+  priv->regex_compile_flags = compile_flags;
+  if (priv->regex)
+    g_regex_unref (priv->regex);
+
+  /* FIXME: if comping the regex fails, show the error message somewhere */
+  if (search_text[0] != '\0')
+    priv->regex = g_regex_new (pattern, compile_flags, 0, NULL);
+  else
+    priv->regex = NULL;
+
+  update_sensitivity (popover);
+
+  g_object_notify_by_pspec (G_OBJECT (popover), pspecs[PROP_REGEX]);
+}
+
+static void
+search_text_changed_cb (GtkToggleButton *button,
+                        TerminalSearchPopover *popover)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+  update_regex (popover);
+  priv->search_text_changed = TRUE;
+}
+
+static void
+search_parameters_changed_cb (GtkToggleButton *button,
+                              TerminalSearchPopover *popover)
+{
+  update_regex (popover);
+}
+
+static void
+wrap_around_toggled_cb (GtkToggleButton *button,
+                        TerminalSearchPopover *popover)
+{
+  g_object_notify_by_pspec (G_OBJECT (popover), pspecs[PROP_WRAP_AROUND]);
+}
+
+/* public functions */
+
+/* Class implementation */
+
+static void
+terminal_search_popover_init (TerminalSearchPopover *popover)
+{
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+  GtkWidget *widget = GTK_WIDGET (popover);
+
+  gtk_widget_init_template (widget);
+
+  /* Make the search entry reasonably wide */
+  gtk_widget_set_size_request (priv->search_entry, 300, -1);
+
+  /* Add entry completion with history */
+#if 0
+  g_object_set (G_OBJECT (priv->search_entry),
+               "model", history_store,
+               "entry-text-column", 0,
+               NULL);
+#endif
+
+  if (history_enabled ()) {
+    gs_unref_object GtkEntryCompletion *completion;
+
+    completion = gtk_entry_completion_new ();
+    gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (history_store));
+    gtk_entry_completion_set_text_column (completion, 0);
+    gtk_entry_completion_set_minimum_key_length (completion, HISTORY_MIN_ITEM_LEN);
+    gtk_entry_completion_set_popup_completion (completion, FALSE);
+    gtk_entry_completion_set_inline_completion (completion, TRUE);
+    gtk_entry_set_completion (GTK_ENTRY (priv->search_entry), completion);
+  }
+
+#if GTK_CHECK_VERSION (3, 17, 2)
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  gtk_popover_set_default_widget (GTK_POPOVER (popover), priv->search_next_button);
+  G_GNUC_END_IGNORE_DEPRECATIONS
+#endif
+
+  g_signal_connect (priv->search_entry, "previous-match", G_CALLBACK (previous_match_cb), popover);
+  g_signal_connect (priv->search_entry, "next-match", G_CALLBACK (next_match_cb), popover);
+
+  g_signal_connect (priv->search_prev_button, "clicked", G_CALLBACK (search_button_clicked_cb), popover);
+  g_signal_connect (priv->search_next_button, "clicked", G_CALLBACK (search_button_clicked_cb), popover);
+
+  g_signal_connect (priv->close_button, "clicked", G_CALLBACK (close_clicked_cb), popover);
+
+  g_object_bind_property (priv->reveal_button, "active",
+                          priv->revealer, "reveal-child",
+                          G_BINDING_DEFAULT);
+
+  priv->regex = NULL;
+  update_sensitivity (popover);
+
+  g_signal_connect (priv->search_entry, "search-changed", G_CALLBACK (search_text_changed_cb), popover);
+  g_signal_connect (priv->match_case_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), 
popover);
+  g_signal_connect (priv->entire_word_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), 
popover);
+  g_signal_connect (priv->regex_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), popover);
+
+  g_signal_connect (priv->wrap_around_checkbutton, "toggled", G_CALLBACK (wrap_around_toggled_cb), popover);
+}
+
+static void
+terminal_search_popover_finalize (GObject *object)
+{
+  TerminalSearchPopover *popover = TERMINAL_SEARCH_POPOVER (object);
+  TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+  if (priv->regex)
+    g_regex_unref(priv->regex);
+
+  G_OBJECT_CLASS (terminal_search_popover_parent_class)->finalize (object);
+}
+
+static void
+terminal_search_popover_get_property (GObject *object,
+                                      guint prop_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+  TerminalSearchPopover *popover = TERMINAL_SEARCH_POPOVER (object);
+
+  switch (prop_id) {
+  case PROP_REGEX:
+    g_value_set_boxed (value, terminal_search_popover_get_regex (popover));
+    break;
+  case PROP_WRAP_AROUND:
+    g_value_set_boolean (value, terminal_search_popover_get_wrap_around (popover));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+terminal_search_popover_set_property (GObject *object,
+                                      guint prop_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+  switch (prop_id) {
+  case PROP_REGEX:
+  case PROP_WRAP_AROUND:
+    /* not writable */
+    break;
+  default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+terminal_search_popover_class_init (TerminalSearchPopoverClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gobject_class->finalize = terminal_search_popover_finalize;
+  gobject_class->get_property = terminal_search_popover_get_property;
+  gobject_class->set_property = terminal_search_popover_set_property;
+
+  signals[SEARCH] =
+    g_signal_new (I_("search"),
+                  G_OBJECT_CLASS_TYPE (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (TerminalSearchPopoverClass, search),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__BOOLEAN,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_BOOLEAN);
+
+  pspecs[PROP_REGEX] =
+    g_param_spec_boxed ("regex", NULL, NULL,
+                        G_TYPE_REGEX,
+                        G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+
+  pspecs[PROP_WRAP_AROUND] =
+    g_param_spec_boolean ("wrap-around", NULL, NULL,
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
G_PARAM_STATIC_BLURB);
+
+  g_object_class_install_properties (gobject_class, G_N_ELEMENTS (pspecs), pspecs);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/terminal/ui/search-popover.ui");
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_entry);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_prev_button);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_next_button);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, reveal_button);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, close_button);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, revealer);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, match_case_checkbutton);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, 
entire_word_checkbutton);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, regex_checkbutton);
+  gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, 
wrap_around_checkbutton);
+}
+
+/* public API */
+
+/**
+ * terminal_search_popover_new:
+ *
+ * Returns: a new #TerminalSearchPopover
+ */
+TerminalSearchPopover *
+terminal_search_popover_new (GtkWidget *relative_to_widget)
+{
+  return g_object_new (TERMINAL_TYPE_SEARCH_POPOVER,
+                       "relative-to", relative_to_widget,
+                       NULL);
+}
+
+/**
+ * terminal_search_popover_get_regex:
+ * @popover: a #TerminalSearchPopover
+ *
+ * Returns: (transfer none): the search regex, or %NULL
+ */
+GRegex *
+terminal_search_popover_get_regex (TerminalSearchPopover *popover)
+{
+  g_return_val_if_fail (TERMINAL_IS_SEARCH_POPOVER (popover), NULL);
+
+  return PRIV (popover)->regex;
+}
+
+/**
+ * terminal_search_popover_get_wrap_around:
+ * @popover: a #TerminalSearchPopover
+ *
+ * Returns: (transfer none): whether search should wrap around
+ */
+gboolean
+terminal_search_popover_get_wrap_around (TerminalSearchPopover *popover)
+{
+  g_return_val_if_fail (TERMINAL_IS_SEARCH_POPOVER (popover), FALSE);
+
+  return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (PRIV (popover)->wrap_around_checkbutton));
+}
diff --git a/src/terminal-search-popover.h b/src/terminal-search-popover.h
new file mode 100644
index 0000000..8998144
--- /dev/null
+++ b/src/terminal-search-popover.h
@@ -0,0 +1,48 @@
+/*
+ *  Copyright © 2008 Christian Persch
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TERMINAL_SEARCH_POPOVER_H
+#define TERMINAL_SEARCH_POPOVER_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.h"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SEARCH_POPOVER         (terminal_search_popover_get_type ())
+#define TERMINAL_SEARCH_POPOVER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_SEARCH_POPOVER, 
TerminalSearchPopover))
+#define TERMINAL_SEARCH_POPOVER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_SEARCH_POPOVER, 
TerminalSearchPopoverClass))
+#define TERMINAL_IS_SEARCH_POPOVER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_SEARCH_POPOVER))
+#define TERMINAL_IS_SEARCH_POPOVER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_SEARCH_POPOVER))
+#define TERMINAL_SEARCH_POPOVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_SEARCH_POPOVER, 
TerminalSearchPopoverClass))
+
+typedef struct _TerminalSearchPopover        TerminalSearchPopover;
+typedef struct _TerminalSearchPopoverClass   TerminalSearchPopoverClass;
+
+GType terminal_search_popover_get_type (void);
+
+TerminalSearchPopover *terminal_search_popover_new (GtkWidget *relative_to_widget);
+
+GRegex *terminal_search_popover_get_regex (TerminalSearchPopover *popover);
+
+gboolean terminal_search_popover_get_wrap_around (TerminalSearchPopover *popover);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_SEARCH_POPOVER_H */
diff --git a/src/terminal-window.c b/src/terminal-window.c
index 9127966..f673e99 100644
--- a/src/terminal-window.c
+++ b/src/terminal-window.c
@@ -41,7 +41,7 @@
 #include "terminal-notebook.h"
 #include "terminal-schemas.h"
 #include "terminal-screen-container.h"
-#include "terminal-search-dialog.h"
+#include "terminal-search-popover.h"
 #include "terminal-tab-label.h"
 #include "terminal-tabs-menu.h"
 #include "terminal-util.h"
@@ -80,7 +80,7 @@ struct _TerminalWindowPrivate
   void *old_geometry_widget; /* only used for pointer value as it may be freed */
 
   GtkWidget *confirm_close_dialog;
-  GtkWidget *search_find_dialog;
+  TerminalSearchPopover *search_popover;
 
   guint menubar_visible : 1;
   guint use_default_menubar_visibility : 1;
@@ -867,34 +867,83 @@ action_edit_profile_cb (GSimpleAction *action,
 }
 
 static void
-find_response_cb (GtkWidget *dialog,
-                  int        response,
-                  gpointer   user_data)
+search_popover_search_cb (TerminalSearchPopover *popover,
+                          gboolean backward,
+                          TerminalWindow *window)
+{
+  TerminalWindowPrivate *priv = window->priv;
+
+  if (G_UNLIKELY (priv->active_screen == NULL))
+    return;
+
+  if (backward)
+    vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen));
+  else
+    vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+search_popover_notify_regex_cb (TerminalSearchPopover *popover,
+                                GParamSpec *pspec G_GNUC_UNUSED,
+                                TerminalWindow *window)
 {
-  TerminalWindow *window = TERMINAL_WINDOW (user_data);
   TerminalWindowPrivate *priv = window->priv;
-  TerminalSearchFlags flags;
   GRegex *regex;
 
-  if (response != GTK_RESPONSE_ACCEPT)
+  if (G_UNLIKELY (priv->active_screen == NULL))
     return;
 
-  if (G_UNLIKELY (!priv->active_screen))
+  regex = terminal_search_popover_get_regex (popover);
+  vte_terminal_search_set_gregex (VTE_TERMINAL (priv->active_screen), regex, 0);
+
+  terminal_window_update_search_sensitivity (priv->active_screen, window);
+}
+
+static void
+search_popover_notify_wrap_around_cb (TerminalSearchPopover *popover,
+                                      GParamSpec *pspec G_GNUC_UNUSED,
+                                      TerminalWindow *window)
+{
+  TerminalWindowPrivate *priv = window->priv;
+  gboolean wrap;
+
+  if (G_UNLIKELY (priv->active_screen == NULL))
     return;
 
-  regex = terminal_search_dialog_get_regex (dialog);
-  flags = terminal_search_dialog_get_search_flags (dialog);
+  wrap = terminal_search_popover_get_wrap_around (popover);
+  vte_terminal_search_set_wrap_around (VTE_TERMINAL (priv->active_screen), wrap);
+}
 
-  vte_terminal_search_set_gregex (VTE_TERMINAL (priv->active_screen), regex, 0);
-  vte_terminal_search_set_wrap_around (VTE_TERMINAL (priv->active_screen),
-                                      (flags & TERMINAL_SEARCH_FLAG_WRAP_AROUND));
+static void
+terminal_window_ensure_search_popover (TerminalWindow *window)
+{
+  TerminalWindowPrivate *priv = window->priv;
 
-  if (flags & TERMINAL_SEARCH_FLAG_BACKWARDS)
-    vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen));
-  else
-    vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen));
+  if (G_UNLIKELY(priv->active_screen == NULL))
+    return;
 
-  terminal_window_update_search_sensitivity (priv->active_screen, window);
+  if (priv->search_popover != NULL) {
+    search_popover_notify_regex_cb (priv->search_popover, NULL, window);
+    search_popover_notify_wrap_around_cb (priv->search_popover, NULL, window);
+
+    gtk_widget_show (GTK_WIDGET (priv->search_popover));
+    return;
+  }
+
+  if (priv->active_screen == NULL)
+    return;
+
+  priv->search_popover = terminal_search_popover_new (GTK_WIDGET (priv->menubar));
+
+  g_signal_connect (priv->search_popover, "search", G_CALLBACK (search_popover_search_cb), window);
+
+  search_popover_notify_regex_cb (priv->search_popover, NULL, window);
+  g_signal_connect (priv->search_popover, "notify::regex", G_CALLBACK (search_popover_notify_regex_cb), 
window);
+
+  search_popover_notify_wrap_around_cb (priv->search_popover, NULL, window);
+  g_signal_connect (priv->search_popover, "notify::wrap-around", G_CALLBACK 
(search_popover_notify_wrap_around_cb), window);
+
+  gtk_widget_show (GTK_WIDGET (priv->search_popover));
 }
 
 static void
@@ -912,20 +961,7 @@ action_find_cb (GSimpleAction *action,
   g_variant_get (parameter, "&s", &mode);
 
   if (g_str_equal (mode, "find")) {
-    if (!priv->search_find_dialog) {
-      GtkWidget *dialog;
-
-      dialog = priv->search_find_dialog = terminal_search_dialog_new (GTK_WINDOW (window));
-
-      g_signal_connect (dialog, "destroy",
-                        G_CALLBACK (gtk_widget_destroyed), &priv->search_find_dialog);
-      g_signal_connect (dialog, "response",
-                        G_CALLBACK (find_response_cb), window);
-      /* prevent destruction */
-      g_signal_connect (dialog, "delete-event", G_CALLBACK (gtk_true), NULL);
-    }
-
-    terminal_search_dialog_present (priv->search_find_dialog);
+    terminal_window_ensure_search_popover (window);
   } else if (g_str_equal (mode, "next")) {
     vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen));
   } else if (g_str_equal (mode, "previous")) {
@@ -2813,6 +2849,8 @@ terminal_window_dispose (GObject *object)
   GtkClipboard *clipboard;
   GSList *list, *l;
 
+  priv->disposed = TRUE;
+
   /* Deactivate open popup menus. This fixes a crash if the window is closed
    * while the context menu is open.
    */
@@ -2824,7 +2862,13 @@ terminal_window_dispose (GObject *object)
 
   remove_popup_info (window);
 
-  priv->disposed = TRUE;
+  if (priv->search_popover != NULL)
+    {
+      g_signal_handlers_disconnect_matched (priv->search_popover, G_SIGNAL_MATCH_DATA,
+                                            0, 0, NULL, NULL, window);
+      gtk_widget_destroy (GTK_WIDGET (priv->search_popover));
+      priv->search_popover = NULL;
+    }
 
   if (priv->tabs_menu)
     {
@@ -2876,10 +2920,6 @@ terminal_window_finalize (GObject *object)
     gtk_dialog_response (GTK_DIALOG (priv->confirm_close_dialog),
                          GTK_RESPONSE_DELETE_EVENT);
 
-  if (priv->search_find_dialog)
-    gtk_dialog_response (GTK_DIALOG (priv->search_find_dialog),
-                         GTK_RESPONSE_DELETE_EVENT);
-
   g_free (priv->uuid);
 
   G_OBJECT_CLASS (terminal_window_parent_class)->finalize (object);
@@ -3298,6 +3338,9 @@ mdi_screen_switched_cb (TerminalMdiContainer *container,
   if (screen == NULL || old_active_screen == screen)
     return;
 
+  if (priv->search_popover != NULL)
+    gtk_widget_hide (GTK_WIDGET (priv->search_popover));
+
   _terminal_debug_print (TERMINAL_DEBUG_MDI,
                          "[window %p] MDI: setting active tab to screen %p (old active screen %p)\n",
                          window, screen, priv->active_screen);
diff --git a/src/terminal.gresource.xml b/src/terminal.gresource.xml
index c54f9dd..6be1a1c 100644
--- a/src/terminal.gresource.xml
+++ b/src/terminal.gresource.xml
@@ -19,7 +19,7 @@
   <gresource prefix="/org/gnome/terminal">
     <file alias="ui/terminal.xml" compressed="true" preprocess="xml-stripblanks">terminal.xml</file>
     <file alias="ui/terminal.about" compressed="true">terminal.about</file>
-    <file alias="ui/find-dialog.ui" compressed="true" preprocess="xml-stripblanks">find-dialog.ui</file>
+    <file alias="ui/search-popover.ui" compressed="true" 
preprocess="xml-stripblanks">search-popover.ui</file>
     <file alias="ui/preferences.ui" compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
     <file alias="ui/profile-preferences.ui" compressed="true" 
preprocess="xml-stripblanks">profile-preferences.ui</file>
     <file alias="ui/window.ui" compressed="true" preprocess="xml-stripblanks">terminal-window.ui</file>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]