diff options
Diffstat (limited to 'gedit/gedit-view-frame.c')
-rw-r--r-- | gedit/gedit-view-frame.c | 1592 |
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)); +} |