summaryrefslogtreecommitdiffstats
path: root/gedit/gedit-view-frame.c
diff options
context:
space:
mode:
Diffstat (limited to 'gedit/gedit-view-frame.c')
-rw-r--r--gedit/gedit-view-frame.c1592
1 files changed, 1592 insertions, 0 deletions
diff --git a/gedit/gedit-view-frame.c b/gedit/gedit-view-frame.c
new file mode 100644
index 0000000..f41734c
--- /dev/null
+++ b/gedit/gedit-view-frame.c
@@ -0,0 +1,1592 @@
+/*
+ * gedit-view-frame.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2013, 2019 - Sébastien Wilmet
+ *
+ * gedit 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit 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 gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "gedit-view-frame.h"
+
+#include <gtksourceview/gtksource.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "gedit-debug.h"
+#include "gedit-utils.h"
+#include "gedit-settings.h"
+#include "libgd/gd.h"
+
+#define FLUSH_TIMEOUT_DURATION 30 /* in seconds */
+
+#define SEARCH_POPUP_MARGIN 12
+
+typedef enum
+{
+ GOTO_LINE,
+ SEARCH
+} SearchMode;
+
+typedef enum
+{
+ SEARCH_STATE_NORMAL,
+ SEARCH_STATE_NOT_FOUND
+} SearchState;
+
+struct _GeditViewFrame
+{
+ GtkOverlay parent_instance;
+
+ GeditView *view;
+
+ SearchMode search_mode;
+
+ /* Where the search has started. When the user presses escape in the
+ * search entry (to cancel the search), we return to the start_mark.
+ */
+ GtkTextMark *start_mark;
+
+ GtkRevealer *revealer;
+ GdTaggedEntry *search_entry;
+ GdTaggedEntryTag *entry_tag;
+ GtkWidget *go_up_button;
+ GtkWidget *go_down_button;
+
+ guint flush_timeout_id;
+ guint idle_update_entry_tag_id;
+ guint remove_entry_tag_timeout_id;
+ gulong view_scroll_event_id;
+ gulong search_entry_focus_out_id;
+ gulong search_entry_changed_id;
+
+ GtkSourceSearchSettings *search_settings;
+
+ /* Used to restore the search state if an incremental search is
+ * cancelled.
+ */
+ GtkSourceSearchSettings *old_search_settings;
+
+ /* The original search texts. In search_settings and
+ * old_search_settings, the search text is unescaped. Since the escape
+ * function is not reciprocal, we need to store the original search
+ * texts.
+ */
+ gchar *search_text;
+ gchar *old_search_text;
+};
+
+G_DEFINE_TYPE (GeditViewFrame, gedit_view_frame, GTK_TYPE_OVERLAY)
+
+static GeditDocument *
+get_document (GeditViewFrame *frame)
+{
+ return GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view)));
+}
+
+static void
+get_iter_at_start_mark (GeditViewFrame *frame,
+ GtkTextIter *iter)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_get_iter_at_mark (buffer, iter, frame->start_mark);
+ }
+ else
+ {
+ g_warn_if_reached ();
+ gtk_text_buffer_get_start_iter (buffer, iter);
+ }
+}
+
+static void
+gedit_view_frame_dispose (GObject *object)
+{
+ GeditViewFrame *frame = GEDIT_VIEW_FRAME (object);
+ GtkTextBuffer *buffer = NULL;
+
+ if (frame->view != NULL)
+ {
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ }
+
+ if (frame->start_mark != NULL && buffer != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ frame->start_mark = NULL;
+ }
+
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ if (frame->idle_update_entry_tag_id != 0)
+ {
+ g_source_remove (frame->idle_update_entry_tag_id);
+ frame->idle_update_entry_tag_id = 0;
+ }
+
+ if (frame->remove_entry_tag_timeout_id != 0)
+ {
+ g_source_remove (frame->remove_entry_tag_timeout_id);
+ frame->remove_entry_tag_timeout_id = 0;
+ }
+
+ if (buffer != NULL)
+ {
+ GtkSourceFile *file = gedit_document_get_file (GEDIT_DOCUMENT (buffer));
+ gtk_source_file_set_mount_operation_factory (file, NULL, NULL, NULL);
+ }
+
+ g_clear_object (&frame->entry_tag);
+ g_clear_object (&frame->search_settings);
+ g_clear_object (&frame->old_search_settings);
+
+ G_OBJECT_CLASS (gedit_view_frame_parent_class)->dispose (object);
+}
+
+static void
+gedit_view_frame_finalize (GObject *object)
+{
+ GeditViewFrame *frame = GEDIT_VIEW_FRAME (object);
+
+ g_free (frame->search_text);
+ g_free (frame->old_search_text);
+
+ G_OBJECT_CLASS (gedit_view_frame_parent_class)->finalize (object);
+}
+
+static void
+hide_search_widget (GeditViewFrame *frame,
+ gboolean cancel)
+{
+ GtkTextBuffer *buffer;
+
+ if (!gtk_revealer_get_reveal_child (frame->revealer))
+ {
+ return;
+ }
+
+ if (frame->view_scroll_event_id != 0)
+ {
+ g_signal_handler_disconnect (frame->view,
+ frame->view_scroll_event_id);
+ frame->view_scroll_event_id = 0;
+ }
+
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ gtk_revealer_set_reveal_child (frame->revealer, FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (cancel && frame->start_mark != NULL)
+ {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ frame->start_mark);
+ gtk_text_buffer_place_cursor (buffer, &iter);
+
+ tepl_view_scroll_to_cursor (TEPL_VIEW (frame->view));
+ }
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ frame->start_mark = NULL;
+ }
+}
+
+static gboolean
+search_entry_flush_timeout (GeditViewFrame *frame)
+{
+ frame->flush_timeout_id = 0;
+ hide_search_widget (frame, FALSE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+renew_flush_timeout (GeditViewFrame *frame)
+{
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ }
+
+ frame->flush_timeout_id =
+ g_timeout_add_seconds (FLUSH_TIMEOUT_DURATION,
+ (GSourceFunc)search_entry_flush_timeout,
+ frame);
+}
+
+static GtkSourceSearchContext *
+get_search_context (GeditViewFrame *frame)
+{
+ GeditDocument *doc;
+ GtkSourceSearchContext *search_context;
+ GtkSourceSearchSettings *search_settings;
+
+ doc = get_document (frame);
+ search_context = gedit_document_get_search_context (doc);
+
+ if (search_context == NULL)
+ {
+ return NULL;
+ }
+
+ search_settings = gtk_source_search_context_get_settings (search_context);
+
+ if (search_settings == frame->search_settings)
+ {
+ return search_context;
+ }
+
+ return NULL;
+}
+
+static void
+set_search_state (GeditViewFrame *frame,
+ SearchState state)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (frame->search_entry));
+
+ if (state == SEARCH_STATE_NOT_FOUND)
+ {
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_ERROR);
+ }
+ else
+ {
+ gtk_style_context_remove_class (context, GTK_STYLE_CLASS_ERROR);
+ }
+}
+
+static void
+finish_search (GeditViewFrame *frame,
+ gboolean found)
+{
+ const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ if (found || (entry_text[0] == '\0'))
+ {
+ tepl_view_scroll_to_cursor (TEPL_VIEW (frame->view));
+
+ set_search_state (frame, SEARCH_STATE_NORMAL);
+ }
+ else
+ {
+ set_search_state (frame, SEARCH_STATE_NOT_FOUND);
+ }
+}
+
+static void
+start_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+ GtkSourceBuffer *buffer;
+
+ found = gtk_source_search_context_forward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+ }
+ else if (frame->start_mark != NULL)
+ {
+ GtkTextIter start_at;
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+ &start_at,
+ frame->start_mark);
+
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &start_at,
+ &start_at);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+start_search (GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context;
+ GtkTextIter start_at;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ get_iter_at_start_mark (frame, &start_at);
+
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)start_search_finished,
+ frame);
+}
+
+static void
+forward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+
+ found = gtk_source_search_context_forward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ if (found)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_select_range (buffer,
+ &match_start,
+ &match_end);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+forward_search (GeditViewFrame *frame)
+{
+ GtkTextIter start_at;
+ GtkTextBuffer *buffer;
+ GtkSourceSearchContext *search_context;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ renew_flush_timeout (frame);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_get_selection_bounds (buffer, NULL, &start_at);
+
+ gtk_source_search_context_forward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)forward_search_finished,
+ frame);
+}
+
+static void
+backward_search_finished (GtkSourceSearchContext *search_context,
+ GAsyncResult *result,
+ GeditViewFrame *frame)
+{
+ GtkTextIter match_start;
+ GtkTextIter match_end;
+ gboolean found;
+ GtkSourceBuffer *buffer;
+
+ found = gtk_source_search_context_backward_finish (search_context,
+ result,
+ &match_start,
+ &match_end,
+ NULL,
+ NULL);
+
+ buffer = gtk_source_search_context_get_buffer (search_context);
+
+ if (found)
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer),
+ &match_start,
+ &match_end);
+ }
+
+ finish_search (frame, found);
+}
+
+static void
+backward_search (GeditViewFrame *frame)
+{
+ GtkTextIter start_at;
+ GtkTextBuffer *buffer;
+ GtkSourceSearchContext *search_context;
+
+ g_return_if_fail (frame->search_mode == SEARCH);
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ renew_flush_timeout (frame);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start_at, NULL);
+
+ gtk_source_search_context_backward_async (search_context,
+ &start_at,
+ NULL,
+ (GAsyncReadyCallback)backward_search_finished,
+ frame);
+}
+
+static gboolean
+search_widget_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ GeditViewFrame *frame)
+{
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ if (event->direction == GDK_SCROLL_UP)
+ {
+ backward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+ else if (event->direction == GDK_SCROLL_DOWN)
+ {
+ forward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static GtkSourceSearchSettings *
+copy_search_settings (GtkSourceSearchSettings *settings)
+{
+ GtkSourceSearchSettings *new_settings = gtk_source_search_settings_new ();
+ gboolean val;
+ const gchar *text;
+
+ if (settings == NULL)
+ {
+ return new_settings;
+ }
+
+ val = gtk_source_search_settings_get_case_sensitive (settings);
+ gtk_source_search_settings_set_case_sensitive (new_settings, val);
+
+ val = gtk_source_search_settings_get_wrap_around (settings);
+ gtk_source_search_settings_set_wrap_around (new_settings, val);
+
+ val = gtk_source_search_settings_get_at_word_boundaries (settings);
+ gtk_source_search_settings_set_at_word_boundaries (new_settings, val);
+
+ val = gtk_source_search_settings_get_regex_enabled (settings);
+ gtk_source_search_settings_set_regex_enabled (new_settings, val);
+
+ text = gtk_source_search_settings_get_search_text (settings);
+ gtk_source_search_settings_set_search_text (new_settings, text);
+
+ return new_settings;
+}
+
+static gboolean
+search_widget_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ GeditViewFrame *frame)
+{
+ /* Close window */
+ if (event->keyval == GDK_KEY_Tab)
+ {
+ hide_search_widget (frame, FALSE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+
+ return GDK_EVENT_STOP;
+ }
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ /* SEARCH mode */
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
+ {
+ backward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
+ {
+ forward_search (frame);
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+remove_entry_tag_timeout_cb (GeditViewFrame *frame)
+{
+ frame->remove_entry_tag_timeout_id = 0;
+
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+update_entry_tag (GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context;
+ GtkTextBuffer *buffer;
+ GtkTextIter select_start;
+ GtkTextIter select_end;
+ gint count;
+ gint pos;
+ gchar *label;
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+ return;
+ }
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ return;
+ }
+
+ count = gtk_source_search_context_get_occurrences_count (search_context);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ gtk_text_buffer_get_selection_bounds (buffer, &select_start, &select_end);
+
+ pos = gtk_source_search_context_get_occurrence_position (search_context,
+ &select_start,
+ &select_end);
+
+ if (count == -1 || pos == -1)
+ {
+ /* The buffer is not fully scanned. Remove the tag after a short
+ * delay. If we don't remove the tag at all, the information can
+ * be outdated during a too long time (for big buffers). And if
+ * the tag is removed directly, there is some flashing for small
+ * buffers: the tag disappears and reappear after a really short
+ * time.
+ */
+
+ if (frame->remove_entry_tag_timeout_id == 0)
+ {
+ frame->remove_entry_tag_timeout_id =
+ g_timeout_add (500,
+ (GSourceFunc)remove_entry_tag_timeout_cb,
+ frame);
+ }
+
+ return;
+ }
+
+ if (count == 0 || pos == 0)
+ {
+ gd_tagged_entry_remove_tag (frame->search_entry,
+ frame->entry_tag);
+ return;
+ }
+
+ if (frame->remove_entry_tag_timeout_id != 0)
+ {
+ g_source_remove (frame->remove_entry_tag_timeout_id);
+ frame->remove_entry_tag_timeout_id = 0;
+ }
+
+ /* Translators: the first %d is the position of the current search
+ * occurrence, and the second %d is the total number of search
+ * occurrences.
+ */
+ label = g_strdup_printf (_("%d of %d"), pos, count);
+
+ gd_tagged_entry_tag_set_label (frame->entry_tag, label);
+
+ gd_tagged_entry_add_tag (frame->search_entry,
+ frame->entry_tag);
+
+ g_free (label);
+}
+
+static gboolean
+update_entry_tag_idle_cb (GeditViewFrame *frame)
+{
+ frame->idle_update_entry_tag_id = 0;
+
+ update_entry_tag (frame);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+install_update_entry_tag_idle (GeditViewFrame *frame)
+{
+ if (frame->idle_update_entry_tag_id == 0)
+ {
+ frame->idle_update_entry_tag_id = g_idle_add ((GSourceFunc)update_entry_tag_idle_cb,
+ frame);
+ }
+}
+
+static void
+update_search_text (GeditViewFrame *frame)
+{
+ const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ g_free (frame->search_text);
+ frame->search_text = g_strdup (entry_text);
+
+ if (gtk_source_search_settings_get_regex_enabled (frame->search_settings))
+ {
+ gtk_source_search_settings_set_search_text (frame->search_settings,
+ entry_text);
+ }
+ else
+ {
+ gchar *unescaped_entry_text = gtk_source_utils_unescape_search_text (entry_text);
+
+ gtk_source_search_settings_set_search_text (frame->search_settings,
+ unescaped_entry_text);
+
+ g_free (unescaped_entry_text);
+ }
+}
+
+static void
+regex_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_regex_enabled (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+at_word_boundaries_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_at_word_boundaries (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+case_sensitive_toggled_cb (GtkCheckMenuItem *menu_item,
+ GeditViewFrame *frame)
+{
+ gtk_source_search_settings_set_case_sensitive (frame->search_settings,
+ gtk_check_menu_item_get_active (menu_item));
+
+ start_search (frame);
+}
+
+static void
+add_popup_menu_items (GeditViewFrame *frame,
+ GtkWidget *menu)
+{
+ GtkWidget *menu_item;
+ gboolean val;
+
+ /* create "Wrap Around" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Wrap Around"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ g_object_bind_property (frame->search_settings, "wrap-around",
+ menu_item, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ /* create "Match as Regular Expression" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("Match as _Regular Expression"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_regex_enabled (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (regex_toggled_cb),
+ frame);
+
+ /* create "Match Entire Word Only" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("Match _Entire Word Only"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_at_word_boundaries (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (at_word_boundaries_toggled_cb),
+ frame);
+
+ /* create "Match Case" menu item. */
+ menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Match Case"));
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ val = gtk_source_search_settings_get_case_sensitive (frame->search_settings);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), val);
+
+ g_signal_connect (menu_item,
+ "toggled",
+ G_CALLBACK (case_sensitive_toggled_cb),
+ frame);
+}
+
+static void
+popup_menu_hide_cb (GeditViewFrame *frame)
+{
+ renew_flush_timeout (frame);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_focus_out_id);
+}
+
+static void
+setup_popup_menu (GeditViewFrame *frame,
+ GtkWidget *menu)
+{
+ if (frame->flush_timeout_id != 0)
+ {
+ g_source_remove (frame->flush_timeout_id);
+ frame->flush_timeout_id = 0;
+ }
+
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_focus_out_id);
+
+ g_signal_connect_swapped (menu,
+ "hide",
+ G_CALLBACK (popup_menu_hide_cb),
+ frame);
+}
+
+static void
+search_entry_escaped (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ GtkSourceSearchContext *search_context = get_search_context (frame);
+
+ if (frame->search_mode == SEARCH &&
+ search_context != NULL)
+ {
+ GtkSourceSearchContext *search_context;
+ GtkTextBuffer *buffer;
+
+ g_clear_object (&frame->search_settings);
+ frame->search_settings = copy_search_settings (frame->old_search_settings);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+ search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (buffer),
+ frame->search_settings);
+ gedit_document_set_search_context (GEDIT_DOCUMENT (buffer), search_context);
+ g_object_unref (search_context);
+
+ g_free (frame->search_text);
+ frame->search_text = NULL;
+
+ if (frame->old_search_text != NULL)
+ {
+ frame->search_text = g_strdup (frame->old_search_text);
+ }
+ }
+
+ hide_search_widget (frame, TRUE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}
+
+static void
+search_entry_previous_match (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ backward_search (frame);
+}
+
+static void
+search_entry_next_match (GtkSearchEntry *entry,
+ GeditViewFrame *frame)
+{
+ forward_search (frame);
+}
+
+static void
+search_entry_populate_popup (GtkEntry *entry,
+ GtkMenu *menu,
+ GeditViewFrame *frame)
+{
+ GtkWidget *menu_item;
+
+ if (frame->search_mode == GOTO_LINE)
+ {
+ return;
+ }
+
+ setup_popup_menu (frame, GTK_WIDGET (menu));
+
+ /* separator */
+ menu_item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ add_popup_menu_items (frame, GTK_WIDGET (menu));
+}
+
+static void
+search_entry_icon_release (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEventButton *event,
+ GeditViewFrame *frame)
+{
+ GtkWidget *menu;
+
+ if (frame->search_mode == GOTO_LINE ||
+ icon_pos != GTK_ENTRY_ICON_PRIMARY)
+ {
+ return;
+ }
+
+ menu = gtk_menu_new ();
+ gtk_widget_show (menu);
+
+ setup_popup_menu (frame, menu);
+ add_popup_menu_items (frame, menu);
+
+ g_signal_connect (menu,
+ "selection-done",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_menu_popup_at_widget (GTK_MENU (menu), GTK_WIDGET (entry), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+}
+
+static void
+search_entry_activate (GtkEntry *entry,
+ GeditViewFrame *frame)
+{
+ hide_search_widget (frame, FALSE);
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}
+
+static void
+search_entry_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ GeditViewFrame *frame)
+{
+ gunichar c;
+ const gchar *p;
+ const gchar *end;
+ const gchar *next;
+
+ if (frame->search_mode == SEARCH)
+ {
+ return;
+ }
+
+ p = text;
+ end = text + length;
+
+ if (p == end)
+ {
+ return;
+ }
+
+ c = g_utf8_get_char (p);
+
+ if (((c == '-' || c == '+') && *position == 0) ||
+ (c == ':' && *position != 0))
+ {
+ gchar *s = NULL;
+
+ if (c == ':')
+ {
+ s = gtk_editable_get_chars (editable, 0, -1);
+ s = g_utf8_strchr (s, -1, ':');
+ }
+
+ if (s == NULL || s == p)
+ {
+ next = g_utf8_next_char (p);
+ p = next;
+ }
+
+ g_free (s);
+ }
+
+ while (p != end)
+ {
+ next = g_utf8_next_char (p);
+
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isdigit (c))
+ {
+ g_signal_stop_emission_by_name (editable, "insert_text");
+ gtk_widget_error_bell (GTK_WIDGET (frame->search_entry));
+ break;
+ }
+
+ p = next;
+ }
+}
+
+static void
+customize_for_search_mode (GeditViewFrame *frame)
+{
+ GIcon *icon;
+ gint width_request;
+
+ if (frame->search_mode == SEARCH)
+ {
+ icon = g_themed_icon_new_with_default_fallbacks ("edit-find-symbolic");
+
+ width_request = 260;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (frame->search_entry),
+ _("String you want to search for"));
+
+ gtk_widget_show (frame->go_up_button);
+ gtk_widget_show (frame->go_down_button);
+ }
+ else
+ {
+ icon = g_themed_icon_new_with_default_fallbacks ("go-jump-symbolic");
+
+ width_request = 160;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (frame->search_entry),
+ _("Line you want to move the cursor to"));
+
+ gtk_widget_hide (frame->go_up_button);
+ gtk_widget_hide (frame->go_down_button);
+ }
+
+ gtk_entry_set_icon_from_gicon (GTK_ENTRY (frame->search_entry),
+ GTK_ENTRY_ICON_PRIMARY,
+ icon);
+
+ gtk_widget_set_size_request (GTK_WIDGET (frame->search_entry),
+ width_request,
+ -1);
+
+ g_object_unref (icon);
+}
+
+static void
+update_goto_line (GeditViewFrame *frame)
+{
+ const gchar *entry_text;
+ gboolean moved;
+ gboolean moved_offset;
+ gint line;
+ gint offset_line = 0;
+ gint line_offset = 0;
+ gchar **split_text = NULL;
+ const gchar *text;
+ GtkTextIter iter;
+
+ entry_text = gtk_entry_get_text (GTK_ENTRY (frame->search_entry));
+
+ if (entry_text[0] == '\0')
+ {
+ return;
+ }
+
+ get_iter_at_start_mark (frame, &iter);
+
+ split_text = g_strsplit (entry_text, ":", -1);
+
+ if (g_strv_length (split_text) > 1)
+ {
+ text = split_text[0];
+ }
+ else
+ {
+ text = entry_text;
+ }
+
+ if (text[0] == '-')
+ {
+ gint cur_line = gtk_text_iter_get_line (&iter);
+
+ if (text[1] != '\0')
+ {
+ offset_line = MAX (atoi (text + 1), 0);
+ }
+
+ line = MAX (cur_line - offset_line, 0);
+ }
+ else if (entry_text[0] == '+')
+ {
+ gint cur_line = gtk_text_iter_get_line (&iter);
+
+ if (text[1] != '\0')
+ {
+ offset_line = MAX (atoi (text + 1), 0);
+ }
+
+ line = cur_line + offset_line;
+ }
+ else
+ {
+ line = MAX (atoi (text) - 1, 0);
+ }
+
+ if (split_text[1] != NULL)
+ {
+ line_offset = atoi (split_text[1]);
+ }
+
+ g_strfreev (split_text);
+
+ moved = tepl_view_goto_line (TEPL_VIEW (frame->view), line);
+ moved_offset = tepl_view_goto_line_offset (TEPL_VIEW (frame->view), line, line_offset);
+
+ if (!moved || !moved_offset)
+ {
+ set_search_state (frame, SEARCH_STATE_NOT_FOUND);
+ }
+ else
+ {
+ set_search_state (frame, SEARCH_STATE_NORMAL);
+ }
+}
+
+static void
+search_entry_changed_cb (GtkEntry *entry,
+ GeditViewFrame *frame)
+{
+ renew_flush_timeout (frame);
+
+ if (frame->search_mode == SEARCH)
+ {
+ update_search_text (frame);
+ start_search (frame);
+ }
+ else
+ {
+ update_goto_line (frame);
+ }
+}
+
+static gboolean
+search_entry_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event,
+ GeditViewFrame *frame)
+{
+ hide_search_widget (frame, FALSE);
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ GeditViewFrame *frame)
+{
+ GtkTextMark *insert;
+ GtkTextMark *selection_bound;
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ selection_bound = gtk_text_buffer_get_selection_bound (buffer);
+
+ if (mark == insert || mark == selection_bound)
+ {
+ install_update_entry_tag_idle (frame);
+ }
+}
+
+static gboolean
+get_selected_text (GtkTextBuffer *doc,
+ gchar **selected_text,
+ gint *len)
+{
+ GtkTextIter start;
+ GtkTextIter end;
+
+ g_return_val_if_fail (selected_text != NULL, FALSE);
+ g_return_val_if_fail (*selected_text == NULL, FALSE);
+
+ if (!gtk_text_buffer_get_selection_bounds (doc, &start, &end))
+ {
+ if (len != NULL)
+ {
+ *len = 0;
+ }
+
+ return FALSE;
+ }
+
+ *selected_text = gtk_text_buffer_get_slice (doc, &start, &end, TRUE);
+
+ if (len != NULL)
+ {
+ *len = g_utf8_strlen (*selected_text, -1);
+ }
+
+ return TRUE;
+}
+
+static void
+init_search_entry (GeditViewFrame *frame)
+{
+ if (frame->search_mode == GOTO_LINE)
+ {
+ gint line;
+ gchar *line_str;
+ GtkTextIter iter;
+
+ get_iter_at_start_mark (frame, &iter);
+
+ line = gtk_text_iter_get_line (&iter);
+
+ line_str = g_strdup_printf ("%d", line + 1);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), line_str);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_free (line_str);
+ }
+ else
+ {
+ /* SEARCH mode */
+ GtkTextBuffer *buffer;
+ gboolean selection_exists;
+ gchar *search_text = NULL;
+ gint selection_len = 0;
+ GtkSourceSearchContext *search_context;
+
+ if (frame->search_settings == NULL)
+ {
+ frame->search_settings = gtk_source_search_settings_new ();
+ gtk_source_search_settings_set_wrap_around (frame->search_settings, TRUE);
+ }
+
+ g_clear_object (&frame->old_search_settings);
+ frame->old_search_settings = copy_search_settings (frame->search_settings);
+
+ g_free (frame->old_search_text);
+ frame->old_search_text = NULL;
+
+ if (frame->search_text != NULL)
+ {
+ frame->old_search_text = g_strdup (frame->search_text);
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ search_context = get_search_context (frame);
+
+ if (search_context == NULL)
+ {
+ search_context = gtk_source_search_context_new (GTK_SOURCE_BUFFER (buffer),
+ frame->search_settings);
+
+ gedit_document_set_search_context (GEDIT_DOCUMENT (buffer),
+ search_context);
+
+ g_signal_connect_swapped (search_context,
+ "notify::occurrences-count",
+ G_CALLBACK (install_update_entry_tag_idle),
+ frame);
+
+ g_object_unref (search_context);
+ }
+
+ selection_exists = get_selected_text (buffer,
+ &search_text,
+ &selection_len);
+
+ if (selection_exists && (search_text != NULL) && (selection_len <= 160))
+ {
+ gchar *search_text_escaped;
+
+ if (gtk_source_search_settings_get_regex_enabled (frame->search_settings))
+ {
+ search_text_escaped = g_regex_escape_string (search_text, -1);
+ }
+ else
+ {
+ search_text_escaped = gtk_source_utils_escape_search_text (search_text);
+ }
+
+ if (g_strcmp0 (search_text_escaped, frame->search_text) == 0)
+ {
+ /* The search text is the same, no need to
+ * trigger the search again. We prefer to select
+ * the text in the search entry, so the user can
+ * easily search something else.
+ */
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ search_text_escaped);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+ }
+ else
+ {
+ /* search_text_escaped is new, so we trigger the
+ * search (by not blocking the signal), and we
+ * don't select the text in the search entry
+ * because the user wants to search for
+ * search_text_escaped, not for something else.
+ */
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ search_text_escaped);
+
+ gtk_editable_set_position (GTK_EDITABLE (frame->search_entry), -1);
+ }
+
+ g_free (search_text_escaped);
+ }
+ else if (frame->search_text != NULL)
+ {
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry),
+ frame->search_text);
+
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+ }
+
+ g_free (search_text);
+ }
+}
+
+static void
+start_interactive_search_real (GeditViewFrame *frame,
+ SearchMode request_search_mode)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ if (gtk_revealer_get_reveal_child (frame->revealer))
+ {
+ if (frame->search_mode != request_search_mode)
+ {
+ hide_search_widget (frame, TRUE);
+ }
+ else
+ {
+ gtk_editable_select_region (GTK_EDITABLE (frame->search_entry),
+ 0, -1);
+ return;
+ }
+ }
+
+ frame->search_mode = request_search_mode;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->view));
+
+ if (frame->search_mode == SEARCH)
+ {
+ gtk_text_buffer_get_selection_bounds (buffer, &iter, NULL);
+ }
+ else
+ {
+ GtkTextMark *mark = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+ }
+
+ if (frame->start_mark != NULL)
+ {
+ gtk_text_buffer_delete_mark (buffer, frame->start_mark);
+ }
+
+ frame->start_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, FALSE);
+
+ gtk_revealer_set_reveal_child (frame->revealer, TRUE);
+
+ /* NOTE: we must be very careful here to not have any text before
+ focusing the entry because when the entry is focused the text is
+ selected, and gtk+ doesn't allow us to have more than one selection
+ active */
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), "");
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_widget_grab_focus (GTK_WIDGET (frame->search_entry));
+
+ customize_for_search_mode (frame);
+ init_search_entry (frame);
+
+ /* Manage the scroll also for the view */
+ frame->view_scroll_event_id =
+ g_signal_connect (frame->view, "scroll-event",
+ G_CALLBACK (search_widget_scroll_event),
+ frame);
+
+ renew_flush_timeout (frame);
+
+ install_update_entry_tag_idle (frame);
+}
+
+static void
+gedit_view_frame_class_init (GeditViewFrameClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gedit_view_frame_dispose;
+ object_class->finalize = gedit_view_frame_finalize;
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/ui/gedit-view-frame.ui");
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, view);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, revealer);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, go_up_button);
+ gtk_widget_class_bind_template_child (widget_class, GeditViewFrame, go_down_button);
+}
+
+static GMountOperation *
+view_frame_mount_operation_factory (GtkSourceFile *file,
+ gpointer user_data)
+{
+ GtkWidget *view_frame = user_data;
+ GtkWidget *window = gtk_widget_get_toplevel (view_frame);
+
+ return gtk_mount_operation_new (GTK_WINDOW (window));
+}
+
+static void
+gedit_view_frame_init (GeditViewFrame *frame)
+{
+ GeditDocument *doc;
+ GtkSourceFile *file;
+
+ gedit_debug (DEBUG_WINDOW);
+
+ gtk_widget_init_template (GTK_WIDGET (frame));
+
+ doc = get_document (frame);
+ file = gedit_document_get_file (doc);
+
+ gtk_source_file_set_mount_operation_factory (file,
+ view_frame_mount_operation_factory,
+ frame,
+ NULL);
+
+ frame->entry_tag = gd_tagged_entry_tag_new ("");
+
+ gd_tagged_entry_tag_set_style (frame->entry_tag,
+ "gedit-search-entry-occurrences-tag");
+
+ gd_tagged_entry_tag_set_has_close_button (frame->entry_tag, FALSE);
+
+ gtk_widget_set_margin_end (GTK_WIDGET (frame->revealer),
+ SEARCH_POPUP_MARGIN);
+
+ g_signal_connect (doc,
+ "mark-set",
+ G_CALLBACK (mark_set_cb),
+ frame);
+
+ g_signal_connect (frame->revealer,
+ "key-press-event",
+ G_CALLBACK (search_widget_key_press_event),
+ frame);
+
+ g_signal_connect (frame->revealer,
+ "scroll-event",
+ G_CALLBACK (search_widget_scroll_event),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "populate-popup",
+ G_CALLBACK (search_entry_populate_popup),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "icon-release",
+ G_CALLBACK (search_entry_icon_release),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "activate",
+ G_CALLBACK (search_entry_activate),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "insert-text",
+ G_CALLBACK (search_entry_insert_text),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "stop-search",
+ G_CALLBACK (search_entry_escaped),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "next-match",
+ G_CALLBACK (search_entry_next_match),
+ frame);
+
+ g_signal_connect (frame->search_entry,
+ "previous-match",
+ G_CALLBACK (search_entry_previous_match),
+ frame);
+
+ frame->search_entry_changed_id =
+ g_signal_connect (frame->search_entry,
+ "changed",
+ G_CALLBACK (search_entry_changed_cb),
+ frame);
+
+ frame->search_entry_focus_out_id =
+ g_signal_connect (frame->search_entry,
+ "focus-out-event",
+ G_CALLBACK (search_entry_focus_out_event),
+ frame);
+
+ g_signal_connect_swapped (frame->go_up_button,
+ "clicked",
+ G_CALLBACK (backward_search),
+ frame);
+
+ g_signal_connect_swapped (frame->go_down_button,
+ "clicked",
+ G_CALLBACK (forward_search),
+ frame);
+}
+
+GeditViewFrame *
+gedit_view_frame_new (void)
+{
+ return g_object_new (GEDIT_TYPE_VIEW_FRAME, NULL);
+}
+
+GeditView *
+gedit_view_frame_get_view (GeditViewFrame *frame)
+{
+ g_return_val_if_fail (GEDIT_IS_VIEW_FRAME (frame), NULL);
+
+ return frame->view;
+}
+
+void
+gedit_view_frame_popup_search (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ start_interactive_search_real (frame, SEARCH);
+}
+
+void
+gedit_view_frame_popup_goto_line (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ start_interactive_search_real (frame, GOTO_LINE);
+}
+
+void
+gedit_view_frame_clear_search (GeditViewFrame *frame)
+{
+ g_return_if_fail (GEDIT_IS_VIEW_FRAME (frame));
+
+ g_signal_handler_block (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_entry_set_text (GTK_ENTRY (frame->search_entry), "");
+
+ g_signal_handler_unblock (frame->search_entry,
+ frame->search_entry_changed_id);
+
+ gtk_widget_grab_focus (GTK_WIDGET (frame->view));
+}