From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- gedit/gedit-view.c | 605 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 605 insertions(+) create mode 100644 gedit/gedit-view.c (limited to 'gedit/gedit-view.c') diff --git a/gedit/gedit-view.c b/gedit/gedit-view.c new file mode 100644 index 0000000..f74f16d --- /dev/null +++ b/gedit/gedit-view.c @@ -0,0 +1,605 @@ +/* + * This file is part of gedit + * + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi + * Copyright (C) 2003-2005 Paolo Maggi + * + * 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 2 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 . + */ + +#include "gedit-view.h" +#include +#include "gedit-view-activatable.h" +#include "gedit-plugins-engine.h" +#include "gedit-debug.h" +#include "gedit-utils.h" +#include "gedit-settings.h" + +struct _GeditViewPrivate +{ + PeasExtensionSet *extensions; + + gchar *direct_save_uri; + + TeplSignalGroup *file_signal_group; +}; + +enum +{ + TARGET_URI_LIST = 100, + TARGET_XDNDDIRECTSAVE +}; + +enum +{ + SIGNAL_DROP_URIS, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +G_DEFINE_TYPE_WITH_PRIVATE (GeditView, gedit_view, TEPL_TYPE_VIEW) + +static void +update_editable (GeditView *view) +{ + GeditDocument *doc; + GtkSourceFile *file; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + file = gedit_document_get_file (doc); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), + !gtk_source_file_is_readonly (file)); +} + +static void +file_read_only_notify_cb (GtkSourceFile *file, + GParamSpec *pspec, + GeditView *view) +{ + update_editable (view); +} + +static void +buffer_changed (GeditView *view) +{ + GeditDocument *doc; + GtkSourceFile *file; + + doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + file = gedit_document_get_file (doc); + + tepl_signal_group_clear (&view->priv->file_signal_group); + view->priv->file_signal_group = tepl_signal_group_new (G_OBJECT (file)); + + tepl_signal_group_add (view->priv->file_signal_group, + g_signal_connect (file, + "notify::read-only", + G_CALLBACK (file_read_only_notify_cb), + view)); + + update_editable (view); +} + +static void +buffer_notify_cb (GeditView *view, + GParamSpec *pspec, + gpointer user_data) +{ + buffer_changed (view); +} + +static void +gedit_view_init (GeditView *view) +{ + GtkTargetList *target_list; + GtkStyleContext *style_context; + + gedit_debug (DEBUG_VIEW); + + view->priv = gedit_view_get_instance_private (view); + + /* Drag and drop support */ + view->priv->direct_save_uri = NULL; + target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (view)); + + if (target_list != NULL) + { + gtk_target_list_add (target_list, + gdk_atom_intern ("XdndDirectSave0", FALSE), + 0, + TARGET_XDNDDIRECTSAVE); + gtk_target_list_add_uri_targets (target_list, TARGET_URI_LIST); + } + + /* GeditViewActivatable */ + view->priv->extensions = + peas_extension_set_new (PEAS_ENGINE (gedit_plugins_engine_get_default ()), + GEDIT_TYPE_VIEW_ACTIVATABLE, + "view", view, + NULL); + + /* Act on buffer changes */ + buffer_changed (view); + g_signal_connect (view, + "notify::buffer", + G_CALLBACK (buffer_notify_cb), + NULL); + + /* CSS stuff */ + style_context = gtk_widget_get_style_context (GTK_WIDGET (view)); + gtk_style_context_add_class (style_context, "gedit-view"); +} + +static void +gedit_view_dispose (GObject *object) +{ + GeditView *view = GEDIT_VIEW (object); + + g_clear_object (&view->priv->extensions); + tepl_signal_group_clear (&view->priv->file_signal_group); + + /* Disconnect notify buffer because the destroy of the textview will set + * the buffer to NULL, and we call get_buffer in the notify which would + * reinstate a buffer which we don't want. + * There is no problem calling g_signal_handlers_disconnect_by_func() + * several times (if dispose() is called several times). + */ + g_signal_handlers_disconnect_by_func (view, buffer_notify_cb, NULL); + + G_OBJECT_CLASS (gedit_view_parent_class)->dispose (object); +} + +static void +update_font (GeditView *view) +{ + TeplSettings *settings; + gchar *selected_font; + + settings = tepl_settings_get_singleton (); + + selected_font = tepl_settings_get_selected_font (settings); + tepl_utils_override_font_string (GTK_WIDGET (view), selected_font); + g_free (selected_font); +} + +static void +font_changed_cb (TeplSettings *settings, + GeditView *view) +{ + update_font (view); +} + +static void +gedit_view_constructed (GObject *object) +{ + GeditView *view = GEDIT_VIEW (object); + GeditSettings *gedit_settings; + TeplSettings *tepl_settings; + GSettings *editor_settings; + + G_OBJECT_CLASS (gedit_view_parent_class)->constructed (object); + + gedit_settings = _gedit_settings_get_singleton (); + tepl_settings = tepl_settings_get_singleton (); + + editor_settings = _gedit_settings_peek_editor_settings (gedit_settings); + + update_font (view); + g_signal_connect_object (tepl_settings, + "font-changed", + G_CALLBACK (font_changed_cb), + view, + 0); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS, + view, "show-line-numbers", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_AUTO_INDENT, + view, "auto-indent", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_TABS_SIZE, + view, "tab-width", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_INSERT_SPACES, + view, "insert-spaces-instead-of-tabs", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN, + view, "show-right-margin", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_BACKGROUND_PATTERN, + view, "background-pattern", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION, + view, "right-margin-position", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE, + view, "highlight-current-line", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_WRAP_MODE, + view, "wrap-mode", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); + + g_settings_bind (editor_settings, GEDIT_SETTINGS_SMART_HOME_END, + view, "smart-home-end", + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_NO_SENSITIVITY); +} + +static GdkAtom +drag_get_uri_target (GtkWidget *widget, + GdkDragContext *context) +{ + GdkAtom target; + GtkTargetList *target_list; + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add_uri_targets (target_list, 0); + + target = gtk_drag_dest_find_target (widget, context, target_list); + gtk_target_list_unref (target_list); + + return target; +} + +static gboolean +gedit_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint timestamp) +{ + gboolean drop_zone; + + /* Chain up to allow textview to scroll and position dnd mark, note + * that this needs to be checked if gtksourceview or gtktextview + * changes drag_motion behaviour. + */ + drop_zone = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_motion (widget, + context, + x, y, + timestamp); + + /* If this is a URL, deal with it here */ + if (drag_get_uri_target (widget, context) != GDK_NONE) + { + gdk_drag_status (context, + gdk_drag_context_get_suggested_action (context), + timestamp); + drop_zone = TRUE; + } + + return drop_zone; +} + +static void +gedit_view_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp) +{ + /* If this is an URL emit SIGNAL_DROP_URIS, otherwise chain up the + * signal. + */ + switch (info) + { + case TARGET_URI_LIST: + { + gchar **uri_list; + + uri_list = gedit_utils_drop_get_uris (selection_data); + + if (uri_list != NULL) + { + g_signal_emit (widget, signals[SIGNAL_DROP_URIS], 0, uri_list); + g_strfreev (uri_list); + + gtk_drag_finish (context, TRUE, FALSE, timestamp); + } + + break; + + } + case TARGET_XDNDDIRECTSAVE: + { + GeditView *view; + + view = GEDIT_VIEW (widget); + + /* Indicate that we don't provide "F" fallback */ + if (gtk_selection_data_get_format (selection_data) == 8 && + gtk_selection_data_get_length (selection_data) == 1 && + gtk_selection_data_get_data (selection_data)[0] == 'F') + { + gdk_property_change (gdk_drag_context_get_source_window (context), + gdk_atom_intern ("XdndDirectSave0", FALSE), + gdk_atom_intern ("text/plain", FALSE), 8, + GDK_PROP_MODE_REPLACE, (const guchar *) "", 0); + } + else if (gtk_selection_data_get_format (selection_data) == 8 && + gtk_selection_data_get_length (selection_data) == 1 && + gtk_selection_data_get_data (selection_data)[0] == 'S' && + view->priv->direct_save_uri != NULL) + { + gchar **uris; + + uris = g_new (gchar *, 2); + uris[0] = view->priv->direct_save_uri; + uris[1] = NULL; + g_signal_emit (widget, signals[SIGNAL_DROP_URIS], 0, uris); + g_free (uris); + } + + g_free (view->priv->direct_save_uri); + view->priv->direct_save_uri = NULL; + + gtk_drag_finish (context, TRUE, FALSE, timestamp); + + break; + } + default: + { + GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_data_received (widget, + context, + x, y, + selection_data, + info, + timestamp); + break; + } + } +} + +static gboolean +gedit_view_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint timestamp) +{ + gboolean drop_zone; + GdkAtom target; + guint info; + gboolean found; + GtkTargetList *target_list; + + target_list = gtk_drag_dest_get_target_list (widget); + target = gtk_drag_dest_find_target (widget, context, target_list); + found = gtk_target_list_find (target_list, target, &info); + + if (found && (info == TARGET_URI_LIST || info == TARGET_XDNDDIRECTSAVE)) + { + if (info == TARGET_XDNDDIRECTSAVE) + { + gchar *uri; + uri = gedit_utils_set_direct_save_filename (context); + + if (uri != NULL) + { + GeditView *view = GEDIT_VIEW (widget); + g_free (view->priv->direct_save_uri); + view->priv->direct_save_uri = uri; + } + } + + gtk_drag_get_data (widget, context, target, timestamp); + drop_zone = TRUE; + } + else + { + /* Chain up */ + drop_zone = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_drop (widget, + context, + x, y, + timestamp); + } + + return drop_zone; +} + +static void +extension_added (PeasExtensionSet *extensions, + PeasPluginInfo *info, + PeasExtension *exten, + GeditView *view) +{ + gedit_view_activatable_activate (GEDIT_VIEW_ACTIVATABLE (exten)); +} + +static void +extension_removed (PeasExtensionSet *extensions, + PeasPluginInfo *info, + PeasExtension *exten, + GeditView *view) +{ + gedit_view_activatable_deactivate (GEDIT_VIEW_ACTIVATABLE (exten)); +} + +static void +gedit_view_realize (GtkWidget *widget) +{ + GeditView *view = GEDIT_VIEW (widget); + + GTK_WIDGET_CLASS (gedit_view_parent_class)->realize (widget); + + g_signal_connect (view->priv->extensions, + "extension-added", + G_CALLBACK (extension_added), + view); + + g_signal_connect (view->priv->extensions, + "extension-removed", + G_CALLBACK (extension_removed), + view); + + /* We only activate the extensions when the view is realized, + * because most plugins will expect this behaviour, and we won't + * change the buffer later anyway. + */ + peas_extension_set_foreach (view->priv->extensions, + (PeasExtensionSetForeachFunc) extension_added, + view); +} + +static void +gedit_view_unrealize (GtkWidget *widget) +{ + GeditView *view = GEDIT_VIEW (widget); + + g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_added, view); + g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_removed, view); + + /* We need to deactivate the extension on unrealize because it is not + * mandatory that a view has been realized when we dispose it, leading + * to deactivating the plugin without being activated. + */ + peas_extension_set_foreach (view->priv->extensions, + (PeasExtensionSetForeachFunc) extension_removed, + view); + + GTK_WIDGET_CLASS (gedit_view_parent_class)->unrealize (widget); +} + +static GtkTextBuffer * +gedit_view_create_buffer (GtkTextView *text_view) +{ + return GTK_TEXT_BUFFER (gedit_document_new ()); +} + +static void +gedit_view_class_init (GeditViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass); + GtkBindingSet *binding_set; + + object_class->dispose = gedit_view_dispose; + object_class->constructed = gedit_view_constructed; + + /* Override the gtk_text_view_drag_motion and drag_drop + * functions to get URIs + * + * If the mime type is text/uri-list, then we will accept + * the potential drop, or request the data (depending on the + * function). + * + * If the drag context has any other mime type, then pass the + * information onto the GtkTextView's standard handlers. + * + * See bug #89881 for details + */ + widget_class->drag_motion = gedit_view_drag_motion; + widget_class->drag_data_received = gedit_view_drag_data_received; + widget_class->drag_drop = gedit_view_drag_drop; + + widget_class->realize = gedit_view_realize; + widget_class->unrealize = gedit_view_unrealize; + + text_view_class->create_buffer = gedit_view_create_buffer; + + /** + * GeditView::drop-uris: + * @view: a #GeditView. + * @uri_list: a %NULL-terminated list of URIs. + * + * The #GeditView::drop-uris signal allows plugins to intercept the + * default drag-and-drop behaviour of 'text/uri-list'. #GeditView + * handles drag-and-drop in the default handlers of + * #GtkWidget::drag-drop, #GtkWidget::drag-motion and + * #GtkWidget::drag-data-received. The view emits the + * #GeditView::drop-uris signal from #GtkWidget::drag-data-received if + * valid URIs have been dropped. Plugins should connect to + * #GtkWidget::drag-motion, #GtkWidget::drag-drop and + * #GtkWidget::drag-data-received to change this default behaviour. They + * should NOT use this signal because this will not prevent gedit from + * loading the URI. + */ + signals[SIGNAL_DROP_URIS] = + g_signal_new ("drop-uris", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GeditViewClass, drop_uris), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_STRV); + + /* FIXME: some of these bindings - especially for the "change-case" - + * could be moved to a plugin that enables more advanced keyboard + * shortcuts. Enabling too many keyboard shortcuts by default makes the + * user experience worse, because when mistyping a key it could trigger + * an unknown/unexpected action, so it's a bit a WTF moment for the + * user. gedit must not become like Emacs or Vim. More advanced stuff + * are put in plugins. Also, the "change-case" is available in the + * right-click menu, so it's already easily accessible. + */ + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_d, + GDK_CONTROL_MASK, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_PARAGRAPHS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_u, + GDK_CONTROL_MASK, + "change-case", 1, + G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_UPPER); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_l, + GDK_CONTROL_MASK, + "change-case", 1, + G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_LOWER); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_asciitilde, + GDK_CONTROL_MASK, + "change-case", 1, + G_TYPE_ENUM, GTK_SOURCE_CHANGE_CASE_TOGGLE); +} + +/** + * gedit_view_new: + * @doc: a #GeditDocument + * + * Creates a new #GeditView object displaying the @doc document. + * @doc cannot be %NULL. + * + * Returns: a new #GeditView. + */ +GtkWidget * +gedit_view_new (GeditDocument *doc) +{ + g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL); + + return g_object_new (GEDIT_TYPE_VIEW, + "buffer", doc, + NULL); +} + +/* ex:set ts=8 noet: */ -- cgit v1.2.3