/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */ /* gtksourceview.c * This file is part of GtkSourceView * * Copyright (C) 2001 - Mikael Hermansson and * Chris Phelps * Copyright (C) 2002 - Jeroen Zwartepoorte * Copyright (C) 2003 - Gustavo Giráldez and Paolo Maggi * * GtkSourceView is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GtkSourceView 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "gtksourceview.h" #include /* For strlen */ #include #include #include #include "gtksourcebuffer.h" #include "gtksourcebuffer-private.h" #include "gtksourcebufferinternal.h" #include "gtksourceview-i18n.h" #include "gtksourceview-enumtypes.h" #include "gtksourcemark.h" #include "gtksourcemarkattributes.h" #include "gtksourcestylescheme.h" #include "gtksourcecompletionprovider.h" #include "gtksourcecompletion-private.h" #include "gtksourcegutter.h" #include "gtksourcegutter-private.h" #include "gtksourcegutterrendererlines.h" #include "gtksourcegutterrenderermarks.h" #include "gtksourceiter.h" #include "gtksourcesearchcontext.h" #include "gtksourcespacedrawer.h" #include "gtksourcespacedrawer-private.h" /** * SECTION:view * @Short_description: Widget that displays a GtkSourceBuffer * @Title: GtkSourceView * @See_also: #GtkTextView, #GtkSourceBuffer * * #GtkSourceView is the main class of the GtkSourceView library. * Use a #GtkSourceBuffer to display text with a #GtkSourceView. * * This class provides: * - Show the line numbers; * - Show a right margin; * - Highlight the current line; * - Indentation settings; * - Configuration for the Home and End keyboard keys; * - Configure and show line marks; * - And a few other things. * * An easy way to test all these features is to use the test-widget mini-program * provided in the GtkSourceView repository, in the tests/ directory. * * # GtkSourceView as GtkBuildable * * The GtkSourceView implementation of the #GtkBuildable interface exposes the * #GtkSourceView:completion object with the internal-child "completion". * * An example of a UI definition fragment with GtkSourceView: * |[ * * 4 * True * * * False * * * * ]| */ /* #define ENABLE_DEBUG */ #undef ENABLE_DEBUG /* #define ENABLE_PROFILE */ #undef ENABLE_PROFILE #ifdef ENABLE_DEBUG #define DEBUG(x) (x) #else #define DEBUG(x) #endif #ifdef ENABLE_PROFILE #define PROFILE(x) (x) #else #define PROFILE(x) #endif #define GUTTER_PIXMAP 16 #define DEFAULT_TAB_WIDTH 8 #define MAX_TAB_WIDTH 32 #define MAX_INDENT_WIDTH 32 #define DEFAULT_RIGHT_MARGIN_POSITION 80 #define MAX_RIGHT_MARGIN_POSITION 1000 #define RIGHT_MARGIN_LINE_ALPHA 40 #define RIGHT_MARGIN_OVERLAY_ALPHA 15 enum { UNDO, REDO, SHOW_COMPLETION, LINE_MARK_ACTIVATED, MOVE_LINES, MOVE_WORDS, SMART_HOME_END, MOVE_TO_MATCHING_BRACKET, CHANGE_NUMBER, CHANGE_CASE, JOIN_LINES, N_SIGNALS }; enum { PROP_0, PROP_COMPLETION, PROP_SHOW_LINE_NUMBERS, PROP_SHOW_LINE_MARKS, PROP_TAB_WIDTH, PROP_INDENT_WIDTH, PROP_AUTO_INDENT, PROP_INSERT_SPACES, PROP_SHOW_RIGHT_MARGIN, PROP_RIGHT_MARGIN_POSITION, PROP_SMART_HOME_END, PROP_HIGHLIGHT_CURRENT_LINE, PROP_INDENT_ON_TAB, PROP_DRAW_SPACES, PROP_BACKGROUND_PATTERN, PROP_SMART_BACKSPACE, PROP_SPACE_DRAWER }; struct _GtkSourceViewPrivate { GtkSourceStyleScheme *style_scheme; GdkRGBA *right_margin_line_color; GdkRGBA *right_margin_overlay_color; GtkSourceSpaceDrawer *space_drawer; GHashTable *mark_categories; GtkSourceBuffer *source_buffer; GtkSourceGutter *left_gutter; GtkSourceGutter *right_gutter; GtkSourceGutterRenderer *line_renderer; GtkSourceGutterRenderer *marks_renderer; GdkRGBA current_line_color; GtkSourceCompletion *completion; guint right_margin_pos; gint cached_right_margin_pos; guint tab_width; gint indent_width; GtkSourceSmartHomeEndType smart_home_end; GtkSourceBackgroundPatternType background_pattern; GdkRGBA background_pattern_color; guint tabs_set : 1; guint show_line_numbers : 1; guint show_line_marks : 1; guint auto_indent : 1; guint insert_spaces : 1; guint highlight_current_line : 1; guint indent_on_tab : 1; guint show_right_margin : 1; guint current_line_color_set : 1; guint background_pattern_color_set : 1; guint smart_backspace : 1; }; typedef struct _MarkCategory MarkCategory; struct _MarkCategory { GtkSourceMarkAttributes *attributes; gint priority; }; static guint signals[N_SIGNALS]; static void gtk_source_view_buildable_interface_init (GtkBuildableIface *iface); G_DEFINE_TYPE_WITH_CODE (GtkSourceView, gtk_source_view, GTK_TYPE_TEXT_VIEW, G_ADD_PRIVATE (GtkSourceView) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_source_view_buildable_interface_init)) /* Implement DnD for application/x-color drops */ typedef enum _GtkSourceViewDropTypes { TARGET_COLOR = 200 } GtkSourceViewDropTypes; static const GtkTargetEntry drop_types[] = { {(gchar *)"application/x-color", 0, TARGET_COLOR} }; /* Prototypes. */ static void gtk_source_view_dispose (GObject *object); static void gtk_source_view_finalize (GObject *object); static void gtk_source_view_undo (GtkSourceView *view); static void gtk_source_view_redo (GtkSourceView *view); static void gtk_source_view_show_completion_real (GtkSourceView *view); static GtkTextBuffer * gtk_source_view_create_buffer (GtkTextView *view); static void remove_source_buffer (GtkSourceView *view); static void set_source_buffer (GtkSourceView *view, GtkTextBuffer *buffer); static void gtk_source_view_populate_popup (GtkTextView *view, GtkWidget *popup); static void gtk_source_view_move_cursor (GtkTextView *text_view, GtkMovementStep step, gint count, gboolean extend_selection); static void gtk_source_view_delete_from_cursor (GtkTextView *text_view, GtkDeleteType type, gint count); static gboolean gtk_source_view_extend_selection (GtkTextView *text_view, GtkTextExtendSelection granularity, const GtkTextIter *location, GtkTextIter *start, GtkTextIter *end); static void gtk_source_view_get_lines (GtkTextView *text_view, gint first_y, gint last_y, GArray *buffer_coords, GArray *line_heights, GArray *numbers, gint *countp); static gboolean gtk_source_view_draw (GtkWidget *widget, cairo_t *cr); static void gtk_source_view_move_lines (GtkSourceView *view, gboolean copy, gint step); static void gtk_source_view_move_words (GtkSourceView *view, gint step); static gboolean gtk_source_view_key_press_event (GtkWidget *widget, GdkEventKey *event); static void view_dnd_drop (GtkTextView *view, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint timestamp, gpointer data); static gint calculate_real_tab_width (GtkSourceView *view, guint tab_size, gchar c); static void gtk_source_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_source_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_source_view_style_updated (GtkWidget *widget); static void gtk_source_view_update_style_scheme (GtkSourceView *view); static void gtk_source_view_draw_layer (GtkTextView *view, GtkTextViewLayer layer, cairo_t *cr); static MarkCategory *mark_category_new (GtkSourceMarkAttributes *attributes, gint priority); static void mark_category_free (MarkCategory *category); static void gtk_source_view_constructed (GObject *object) { GtkSourceView *view = GTK_SOURCE_VIEW (object); set_source_buffer (view, gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); G_OBJECT_CLASS (gtk_source_view_parent_class)->constructed (object); } static void gtk_source_view_move_to_matching_bracket (GtkSourceView *view, gboolean extend_selection) { GtkTextView *text_view = GTK_TEXT_VIEW (view); GtkTextBuffer *buffer; GtkTextMark *insert_mark; GtkTextIter insert; GtkTextIter bracket_match; GtkSourceBracketMatchType result; buffer = gtk_text_view_get_buffer (text_view); insert_mark = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &insert, insert_mark); result = _gtk_source_buffer_find_bracket_match (GTK_SOURCE_BUFFER (buffer), &insert, NULL, &bracket_match); if (result == GTK_SOURCE_BRACKET_MATCH_FOUND) { if (extend_selection) { gtk_text_buffer_move_mark (buffer, insert_mark, &bracket_match); } else { gtk_text_buffer_place_cursor (buffer, &bracket_match); } gtk_text_view_scroll_mark_onscreen (text_view, insert_mark); } } static void gtk_source_view_change_number (GtkSourceView *view, gint count) { GtkTextView *text_view = GTK_TEXT_VIEW (view); GtkTextBuffer *buffer; GtkTextIter start, end; gchar *str; buffer = gtk_text_view_get_buffer (text_view); if (!GTK_SOURCE_IS_BUFFER (buffer)) { return; } if (!gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) { if (!gtk_text_iter_starts_word (&start)) { gtk_text_iter_backward_word_start (&start); } if (!gtk_text_iter_ends_word (&end)) { gtk_text_iter_forward_word_end (&end); } } str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); if (str != NULL && *str != '\0') { gchar *p; gint64 n; glong len; len = gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&start); g_assert (len > 0); n = g_ascii_strtoll (str, &p, 10); /* do the action only if strtoll succeeds (p != str) and * the whole string is the number, e.g. not 123abc */ if ((p - str) == len) { gchar *newstr; newstr = g_strdup_printf ("%"G_GINT64_FORMAT, (n + count)); gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_delete (buffer, &start, &end); gtk_text_buffer_insert (buffer, &start, newstr, -1); gtk_text_buffer_end_user_action (buffer); g_free (newstr); } g_free (str); } } static void gtk_source_view_change_case (GtkSourceView *view, GtkSourceChangeCaseType case_type) { GtkSourceBuffer *buffer; GtkTextIter start, end; buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); gtk_text_view_reset_im_context (GTK_TEXT_VIEW (view)); if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &start, &end)) { /* if no selection, change the current char */ gtk_text_iter_forward_char (&end); } gtk_source_buffer_change_case (buffer, case_type, &start, &end); } static void gtk_source_view_join_lines (GtkSourceView *view) { GtkSourceBuffer *buffer; GtkTextIter start; GtkTextIter end; buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); gtk_text_view_reset_im_context (GTK_TEXT_VIEW (view)); gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); gtk_source_buffer_join_lines (buffer, &start, &end); } static void gtk_source_view_class_init (GtkSourceViewClass *klass) { GObjectClass *object_class; GtkTextViewClass *textview_class; GtkBindingSet *binding_set; GtkWidgetClass *widget_class; object_class = G_OBJECT_CLASS (klass); textview_class = GTK_TEXT_VIEW_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = gtk_source_view_constructed; object_class->dispose = gtk_source_view_dispose; object_class->finalize = gtk_source_view_finalize; object_class->get_property = gtk_source_view_get_property; object_class->set_property = gtk_source_view_set_property; widget_class->key_press_event = gtk_source_view_key_press_event; widget_class->draw = gtk_source_view_draw; widget_class->style_updated = gtk_source_view_style_updated; textview_class->populate_popup = gtk_source_view_populate_popup; textview_class->move_cursor = gtk_source_view_move_cursor; textview_class->delete_from_cursor = gtk_source_view_delete_from_cursor; textview_class->extend_selection = gtk_source_view_extend_selection; textview_class->create_buffer = gtk_source_view_create_buffer; textview_class->draw_layer = gtk_source_view_draw_layer; klass->undo = gtk_source_view_undo; klass->redo = gtk_source_view_redo; klass->show_completion = gtk_source_view_show_completion_real; klass->move_lines = gtk_source_view_move_lines; klass->move_words = gtk_source_view_move_words; /** * GtkSourceView:completion: * * The completion object associated with the view */ g_object_class_install_property (object_class, PROP_COMPLETION, g_param_spec_object ("completion", "Completion", "The completion object associated with the view", GTK_SOURCE_TYPE_COMPLETION, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:show-line-numbers: * * Whether to display line numbers */ g_object_class_install_property (object_class, PROP_SHOW_LINE_NUMBERS, g_param_spec_boolean ("show-line-numbers", "Show Line Numbers", "Whether to display line numbers", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:show-line-marks: * * Whether to display line mark pixbufs */ g_object_class_install_property (object_class, PROP_SHOW_LINE_MARKS, g_param_spec_boolean ("show-line-marks", "Show Line Marks", "Whether to display line mark pixbufs", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:tab-width: * * Width of a tab character expressed in number of spaces. */ g_object_class_install_property (object_class, PROP_TAB_WIDTH, g_param_spec_uint ("tab-width", "Tab Width", "Width of a tab character expressed in spaces", 1, MAX_TAB_WIDTH, DEFAULT_TAB_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:indent-width: * * Width of an indentation step expressed in number of spaces. */ g_object_class_install_property (object_class, PROP_INDENT_WIDTH, g_param_spec_int ("indent-width", "Indent Width", "Number of spaces to use for each step of indent", -1, MAX_INDENT_WIDTH, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_AUTO_INDENT, g_param_spec_boolean ("auto-indent", "Auto Indentation", "Whether to enable auto indentation", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_INSERT_SPACES, g_param_spec_boolean ("insert-spaces-instead-of-tabs", "Insert Spaces Instead of Tabs", "Whether to insert spaces instead of tabs", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:show-right-margin: * * Whether to display the right margin. */ g_object_class_install_property (object_class, PROP_SHOW_RIGHT_MARGIN, g_param_spec_boolean ("show-right-margin", "Show Right Margin", "Whether to display the right margin", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:right-margin-position: * * Position of the right margin. */ g_object_class_install_property (object_class, PROP_RIGHT_MARGIN_POSITION, g_param_spec_uint ("right-margin-position", "Right Margin Position", "Position of the right margin", 1, MAX_RIGHT_MARGIN_POSITION, DEFAULT_RIGHT_MARGIN_POSITION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:smart-home-end: * * Set the behavior of the HOME and END keys. * * Since: 2.0 */ g_object_class_install_property (object_class, PROP_SMART_HOME_END, g_param_spec_enum ("smart-home-end", "Smart Home/End", "HOME and END keys move to first/last " "non whitespace characters on line before going " "to the start/end of the line", GTK_SOURCE_TYPE_SMART_HOME_END_TYPE, GTK_SOURCE_SMART_HOME_END_DISABLED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_HIGHLIGHT_CURRENT_LINE, g_param_spec_boolean ("highlight-current-line", "Highlight current line", "Whether to highlight the current line", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_INDENT_ON_TAB, g_param_spec_boolean ("indent-on-tab", "Indent on tab", "Whether to indent the selected text when the tab key is pressed", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:draw-spaces: * * Set if and how the spaces should be visualized. * * For a finer-grained method, there is also the GtkSourceTag's * #GtkSourceTag:draw-spaces property. * * Since: 2.4 * Deprecated: 3.24: Use the #GtkSourceSpaceDrawer:matrix property * instead. */ g_object_class_install_property (object_class, PROP_DRAW_SPACES, g_param_spec_flags ("draw-spaces", "Draw Spaces", "Set if and how the spaces should be visualized", GTK_SOURCE_TYPE_DRAW_SPACES_FLAGS, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); /** * GtkSourceView:background-pattern: * * Draw a specific background pattern on the view. * * Since: 3.16 */ g_object_class_install_property (object_class, PROP_BACKGROUND_PATTERN, g_param_spec_enum ("background-pattern", "Background pattern", "Draw a specific background pattern on the view", GTK_SOURCE_TYPE_BACKGROUND_PATTERN_TYPE, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:smart-backspace: * * Whether smart Backspace should be used. * * Since: 3.18 */ g_object_class_install_property (object_class, PROP_SMART_BACKSPACE, g_param_spec_boolean ("smart-backspace", "Smart Backspace", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceView:space-drawer: * * The #GtkSourceSpaceDrawer object associated with the view. * * Since: 3.24 */ g_object_class_install_property (object_class, PROP_SPACE_DRAWER, g_param_spec_object ("space-drawer", "Space Drawer", "", GTK_SOURCE_TYPE_SPACE_DRAWER, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); signals[UNDO] = g_signal_new ("undo", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkSourceViewClass, undo), NULL, NULL, NULL, G_TYPE_NONE, 0); signals[REDO] = g_signal_new ("redo", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkSourceViewClass, redo), NULL, NULL, NULL, G_TYPE_NONE, 0); /** * GtkSourceView::show-completion: * @view: The #GtkSourceView who emits the signal * * The ::show-completion signal is a key binding signal which gets * emitted when the user requests a completion, by pressing * Controlspace. * * This will create a #GtkSourceCompletionContext with the activation * type as %GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED. * * Applications should not connect to it, but may emit it with * g_signal_emit_by_name() if they need to activate the completion by * another means, for example with another key binding or a menu entry. */ signals[SHOW_COMPLETION] = g_signal_new ("show-completion", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkSourceViewClass, show_completion), NULL, NULL, NULL, G_TYPE_NONE, 0); /** * GtkSourceView::line-mark-activated: * @view: the #GtkSourceView * @iter: a #GtkTextIter * @event: the #GdkEvent that activated the event * * Emitted when a line mark has been activated (for instance when there * was a button press in the line marks gutter). You can use @iter to * determine on which line the activation took place. */ signals[LINE_MARK_ACTIVATED] = g_signal_new ("line-mark-activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkSourceViewClass, line_mark_activated), NULL, NULL, NULL, G_TYPE_NONE, 2, GTK_TYPE_TEXT_ITER, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); /** * GtkSourceView::move-lines: * @view: the #GtkSourceView which received the signal * @copy: %TRUE if the line should be copied, %FALSE if it should be * moved. This parameter is deprecated and will be removed in a later * version, it should be always %FALSE. * @count: the number of lines to move over. Only 1 and -1 are * supported. * * The ::move-lines signal is a keybinding which gets emitted * when the user initiates moving a line. The default binding key * is Alt+Up/Down arrow. And moves the currently selected lines, * or the current line by @count. For the moment, only * @count of -1 or 1 is valid. * * The @copy parameter is deprecated, it has never been used by * GtkSourceView (the value is always %FALSE) and was buggy. * * Since: 2.10 */ signals[MOVE_LINES] = g_signal_new ("move-lines", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkSourceViewClass, move_lines), NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_BOOLEAN, G_TYPE_INT); /** * GtkSourceView::move-words: * @view: the #GtkSourceView which received the signal * @count: the number of words to move over * * The ::move-words signal is a keybinding which gets emitted * when the user initiates moving a word. The default binding key * is Alt+Left/Right Arrow and moves the current selection, or the current * word by one word. * * Since: 3.0 */ signals[MOVE_WORDS] = g_signal_new ("move-words", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkSourceViewClass, move_words), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); /** * GtkSourceView::smart-home-end: * @view: the #GtkSourceView * @iter: a #GtkTextIter * @count: the count * * Emitted when a the cursor was moved according to the smart home * end setting. The signal is emitted after the cursor is moved, but * during the GtkTextView::move-cursor action. This can be used to find * out whether the cursor was moved by a normal home/end or by a smart * home/end. * * Since: 3.0 */ signals[SMART_HOME_END] = g_signal_new ("smart-home-end", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, GTK_TYPE_TEXT_ITER, G_TYPE_INT); /** * GtkSourceView::move-to-matching-bracket: * @view: the #GtkSourceView * @extend_selection: %TRUE if the move should extend the selection * * Keybinding signal to move the cursor to the matching bracket. * * Since: 3.16 */ signals[MOVE_TO_MATCHING_BRACKET] = /* we have to do it this way since we do not have any more vfunc slots */ g_signal_new_class_handler ("move-to-matching-bracket", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (gtk_source_view_move_to_matching_bracket), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); /** * GtkSourceView::change-number: * @view: the #GtkSourceView * @count: the number to add to the number at the current position * * Keybinding signal to edit a number at the current cursor position. * * Since: 3.16 */ signals[CHANGE_NUMBER] = g_signal_new_class_handler ("change-number", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (gtk_source_view_change_number), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); /** * GtkSourceView::change-case: * @view: the #GtkSourceView * @case_type: the case to use * * Keybinding signal to change case of the text at the current cursor position. * * Since: 3.16 */ signals[CHANGE_CASE] = g_signal_new_class_handler ("change-case", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (gtk_source_view_change_case), NULL, NULL, NULL, G_TYPE_NONE, 1, GTK_SOURCE_TYPE_CHANGE_CASE_TYPE); /** * GtkSourceView::join-lines: * @view: the #GtkSourceView * * Keybinding signal to join the lines currently selected. * * Since: 3.16 */ signals[JOIN_LINES] = g_signal_new_class_handler ("join-lines", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (gtk_source_view_join_lines), NULL, NULL, NULL, G_TYPE_NONE, 0); binding_set = gtk_binding_set_by_class (klass); gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK, "undo", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_F14, 0, "undo", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, "show-completion", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_MOD1_MASK, "move-lines", 2, G_TYPE_BOOLEAN, FALSE, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Up, GDK_MOD1_MASK, "move-lines", 2, G_TYPE_BOOLEAN, FALSE, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_MOD1_MASK, "move-lines", 2, G_TYPE_BOOLEAN, FALSE, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Down, GDK_MOD1_MASK, "move-lines", 2, G_TYPE_BOOLEAN, FALSE, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, GDK_MOD1_MASK, "move-words", 1, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Left, GDK_MOD1_MASK, "move-words", 1, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, GDK_MOD1_MASK, "move-words", 1, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Right, GDK_MOD1_MASK, "move-words", 1, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Up, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Down, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Up, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Page_Up, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Down, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Page_Down, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Home, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Home, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS, G_TYPE_INT, -1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_End, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_End, GDK_MOD1_MASK | GDK_SHIFT_MASK, "move-viewport", 2, GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_percent, GDK_CONTROL_MASK, "move-to-matching-bracket", 1, G_TYPE_BOOLEAN, FALSE); gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "change-number", 1, G_TYPE_INT, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "change-number", 1, G_TYPE_INT, -1); } static GObject * gtk_source_view_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname) { GtkSourceView *view = GTK_SOURCE_VIEW (buildable); if (g_strcmp0 (childname, "completion") == 0) { return G_OBJECT (gtk_source_view_get_completion (view)); } return NULL; } static void gtk_source_view_buildable_interface_init (GtkBuildableIface *iface) { iface->get_internal_child = gtk_source_view_buildable_get_internal_child; } static void gtk_source_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceView *view; g_return_if_fail (GTK_SOURCE_IS_VIEW (object)); view = GTK_SOURCE_VIEW (object); switch (prop_id) { case PROP_SHOW_LINE_NUMBERS: gtk_source_view_set_show_line_numbers (view, g_value_get_boolean (value)); break; case PROP_SHOW_LINE_MARKS: gtk_source_view_set_show_line_marks (view, g_value_get_boolean (value)); break; case PROP_TAB_WIDTH: gtk_source_view_set_tab_width (view, g_value_get_uint (value)); break; case PROP_INDENT_WIDTH: gtk_source_view_set_indent_width (view, g_value_get_int (value)); break; case PROP_AUTO_INDENT: gtk_source_view_set_auto_indent (view, g_value_get_boolean (value)); break; case PROP_INSERT_SPACES: gtk_source_view_set_insert_spaces_instead_of_tabs (view, g_value_get_boolean (value)); break; case PROP_SHOW_RIGHT_MARGIN: gtk_source_view_set_show_right_margin (view, g_value_get_boolean (value)); break; case PROP_RIGHT_MARGIN_POSITION: gtk_source_view_set_right_margin_position (view, g_value_get_uint (value)); break; case PROP_SMART_HOME_END: gtk_source_view_set_smart_home_end (view, g_value_get_enum (value)); break; case PROP_HIGHLIGHT_CURRENT_LINE: gtk_source_view_set_highlight_current_line (view, g_value_get_boolean (value)); break; case PROP_INDENT_ON_TAB: gtk_source_view_set_indent_on_tab (view, g_value_get_boolean (value)); break; case PROP_DRAW_SPACES: G_GNUC_BEGIN_IGNORE_DEPRECATIONS; gtk_source_view_set_draw_spaces (view, g_value_get_flags (value)); G_GNUC_END_IGNORE_DEPRECATIONS; break; case PROP_BACKGROUND_PATTERN: gtk_source_view_set_background_pattern (view, g_value_get_enum (value)); break; case PROP_SMART_BACKSPACE: gtk_source_view_set_smart_backspace (view, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceView *view; g_return_if_fail (GTK_SOURCE_IS_VIEW (object)); view = GTK_SOURCE_VIEW (object); switch (prop_id) { case PROP_COMPLETION: g_value_set_object (value, gtk_source_view_get_completion (view)); break; case PROP_SHOW_LINE_NUMBERS: g_value_set_boolean (value, gtk_source_view_get_show_line_numbers (view)); break; case PROP_SHOW_LINE_MARKS: g_value_set_boolean (value, gtk_source_view_get_show_line_marks (view)); break; case PROP_TAB_WIDTH: g_value_set_uint (value, gtk_source_view_get_tab_width (view)); break; case PROP_INDENT_WIDTH: g_value_set_int (value, gtk_source_view_get_indent_width (view)); break; case PROP_AUTO_INDENT: g_value_set_boolean (value, gtk_source_view_get_auto_indent (view)); break; case PROP_INSERT_SPACES: g_value_set_boolean (value, gtk_source_view_get_insert_spaces_instead_of_tabs (view)); break; case PROP_SHOW_RIGHT_MARGIN: g_value_set_boolean (value, gtk_source_view_get_show_right_margin (view)); break; case PROP_RIGHT_MARGIN_POSITION: g_value_set_uint (value, gtk_source_view_get_right_margin_position (view)); break; case PROP_SMART_HOME_END: g_value_set_enum (value, gtk_source_view_get_smart_home_end (view)); break; case PROP_HIGHLIGHT_CURRENT_LINE: g_value_set_boolean (value, gtk_source_view_get_highlight_current_line (view)); break; case PROP_INDENT_ON_TAB: g_value_set_boolean (value, gtk_source_view_get_indent_on_tab (view)); break; case PROP_DRAW_SPACES: G_GNUC_BEGIN_IGNORE_DEPRECATIONS; g_value_set_flags (value, gtk_source_view_get_draw_spaces (view)); G_GNUC_END_IGNORE_DEPRECATIONS; break; case PROP_BACKGROUND_PATTERN: g_value_set_enum (value, gtk_source_view_get_background_pattern (view)); break; case PROP_SMART_BACKSPACE: g_value_set_boolean (value, gtk_source_view_get_smart_backspace (view)); break; case PROP_SPACE_DRAWER: g_value_set_object (value, gtk_source_view_get_space_drawer (view)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void space_drawer_notify_cb (GtkSourceSpaceDrawer *space_drawer, GParamSpec *pspec, GtkSourceView *view) { gtk_widget_queue_draw (GTK_WIDGET (view)); g_object_notify (G_OBJECT (view), "draw-spaces"); } static void notify_buffer_cb (GtkSourceView *view) { set_source_buffer (view, gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); } static void gtk_source_view_init (GtkSourceView *view) { GtkStyleContext *context; GtkTargetList *target_list; view->priv = gtk_source_view_get_instance_private (view); view->priv->tab_width = DEFAULT_TAB_WIDTH; view->priv->tabs_set = FALSE; view->priv->indent_width = -1; view->priv->indent_on_tab = TRUE; view->priv->smart_home_end = GTK_SOURCE_SMART_HOME_END_DISABLED; view->priv->right_margin_pos = DEFAULT_RIGHT_MARGIN_POSITION; view->priv->cached_right_margin_pos = -1; gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 2); gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 2); view->priv->right_margin_line_color = NULL; view->priv->right_margin_overlay_color = NULL; view->priv->space_drawer = gtk_source_space_drawer_new (); g_signal_connect_object (view->priv->space_drawer, "notify", G_CALLBACK (space_drawer_notify_cb), view, 0); view->priv->mark_categories = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) mark_category_free); target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (view)); g_return_if_fail (target_list != NULL); gtk_target_list_add_table (target_list, drop_types, G_N_ELEMENTS (drop_types)); gtk_widget_set_has_tooltip (GTK_WIDGET (view), TRUE); g_signal_connect (view, "drag_data_received", G_CALLBACK (view_dnd_drop), NULL); g_signal_connect (view, "notify::buffer", G_CALLBACK (notify_buffer_cb), NULL); context = gtk_widget_get_style_context (GTK_WIDGET (view)); gtk_style_context_add_class (context, "sourceview"); } static void gtk_source_view_dispose (GObject *object) { GtkSourceView *view = GTK_SOURCE_VIEW (object); g_clear_object (&view->priv->completion); g_clear_object (&view->priv->left_gutter); g_clear_object (&view->priv->right_gutter); g_clear_object (&view->priv->style_scheme); g_clear_object (&view->priv->space_drawer); remove_source_buffer (view); /* 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, notify_buffer_cb, NULL); G_OBJECT_CLASS (gtk_source_view_parent_class)->dispose (object); } static void gtk_source_view_finalize (GObject *object) { GtkSourceView *view = GTK_SOURCE_VIEW (object); if (view->priv->right_margin_line_color != NULL) { gdk_rgba_free (view->priv->right_margin_line_color); } if (view->priv->right_margin_overlay_color != NULL) { gdk_rgba_free (view->priv->right_margin_overlay_color); } if (view->priv->mark_categories) { g_hash_table_destroy (view->priv->mark_categories); } G_OBJECT_CLASS (gtk_source_view_parent_class)->finalize (object); } static void get_visible_region (GtkTextView *text_view, GtkTextIter *start, GtkTextIter *end) { GdkRectangle visible_rect; gtk_text_view_get_visible_rect (text_view, &visible_rect); gtk_text_view_get_line_at_y (text_view, start, visible_rect.y, NULL); gtk_text_view_get_line_at_y (text_view, end, visible_rect.y + visible_rect.height, NULL); gtk_text_iter_backward_line (start); gtk_text_iter_forward_line (end); } static void highlight_updated_cb (GtkSourceBuffer *buffer, GtkTextIter *_start, GtkTextIter *_end, GtkTextView *text_view) { GtkTextIter start; GtkTextIter end; GtkTextIter visible_start; GtkTextIter visible_end; GtkTextIter intersect_start; GtkTextIter intersect_end; #if 0 { static gint nth_call = 0; g_message ("%s(view=%p) %d [%d-%d]", G_STRFUNC, text_view, ++nth_call, gtk_text_iter_get_offset (_start), gtk_text_iter_get_offset (_end)); } #endif start = *_start; end = *_end; gtk_text_iter_order (&start, &end); get_visible_region (text_view, &visible_start, &visible_end); if (gtk_text_iter_compare (&end, &visible_start) < 0 || gtk_text_iter_compare (&visible_end, &start) < 0) { return; } if (gtk_text_iter_compare (&start, &visible_start) < 0) { intersect_start = visible_start; } else { intersect_start = start; } if (gtk_text_iter_compare (&visible_end, &end) < 0) { intersect_end = visible_end; } else { intersect_end = end; } /* GtkSourceContextEngine sends the highlight-updated signal to notify * the view, and in the view (here) we tell the ContextEngine to update * the highlighting, but only in the visible area. It seems that the * purpose is to reduce the number of tags that the ContextEngine * applies to the buffer. * * A previous implementation of this signal handler queued a redraw on * the view with gtk_widget_queue_draw_area(), instead of calling * directly _gtk_source_buffer_update_syntax_highlight(). The ::draw * handler also calls _gtk_source_buffer_update_syntax_highlight(), so * this had the desired effect, but it was less clear. * See the Git commit 949cd128064201935f90d999544e6a19f8e3baa6. * And: https://bugzilla.gnome.org/show_bug.cgi?id=767565 */ _gtk_source_buffer_update_syntax_highlight (buffer, &intersect_start, &intersect_end, FALSE); } static void search_start_cb (GtkSourceBufferInternal *buffer_internal, GtkSourceSearchContext *search_context, GtkSourceView *view) { GtkTextIter visible_start; GtkTextIter visible_end; GtkSourceBuffer *buffer_search; get_visible_region (GTK_TEXT_VIEW (view), &visible_start, &visible_end); buffer_search = gtk_source_search_context_get_buffer (search_context); g_assert (buffer_search == view->priv->source_buffer); _gtk_source_search_context_update_highlight (search_context, &visible_start, &visible_end, FALSE); } static void source_mark_updated_cb (GtkSourceBuffer *buffer, GtkSourceMark *mark, GtkTextView *text_view) { /* TODO do something more intelligent here, namely * invalidate only the area under the mark if possible */ gtk_widget_queue_draw (GTK_WIDGET (text_view)); } static void buffer_style_scheme_changed_cb (GtkSourceBuffer *buffer, GParamSpec *pspec, GtkSourceView *view) { gtk_source_view_update_style_scheme (view); } static void implicit_trailing_newline_changed_cb (GtkSourceBuffer *buffer, GParamSpec *pspec, GtkSourceView *view) { /* For drawing or not a trailing newline. */ gtk_widget_queue_draw (GTK_WIDGET (view)); } static void remove_source_buffer (GtkSourceView *view) { if (view->priv->source_buffer != NULL) { GtkSourceBufferInternal *buffer_internal; g_signal_handlers_disconnect_by_func (view->priv->source_buffer, highlight_updated_cb, view); g_signal_handlers_disconnect_by_func (view->priv->source_buffer, source_mark_updated_cb, view); g_signal_handlers_disconnect_by_func (view->priv->source_buffer, buffer_style_scheme_changed_cb, view); g_signal_handlers_disconnect_by_func (view->priv->source_buffer, implicit_trailing_newline_changed_cb, view); buffer_internal = _gtk_source_buffer_internal_get_from_buffer (view->priv->source_buffer); g_signal_handlers_disconnect_by_func (buffer_internal, search_start_cb, view); g_object_unref (view->priv->source_buffer); view->priv->source_buffer = NULL; } } static void set_source_buffer (GtkSourceView *view, GtkTextBuffer *buffer) { if (buffer == (GtkTextBuffer*) view->priv->source_buffer) { return; } remove_source_buffer (view); if (GTK_SOURCE_IS_BUFFER (buffer)) { GtkSourceBufferInternal *buffer_internal; view->priv->source_buffer = g_object_ref (buffer); g_signal_connect (buffer, "highlight-updated", G_CALLBACK (highlight_updated_cb), view); g_signal_connect (buffer, "source-mark-updated", G_CALLBACK (source_mark_updated_cb), view); g_signal_connect (buffer, "notify::style-scheme", G_CALLBACK (buffer_style_scheme_changed_cb), view); g_signal_connect (buffer, "notify::implicit-trailing-newline", G_CALLBACK (implicit_trailing_newline_changed_cb), view); buffer_internal = _gtk_source_buffer_internal_get_from_buffer (view->priv->source_buffer); g_signal_connect (buffer_internal, "search-start", G_CALLBACK (search_start_cb), view); } gtk_source_view_update_style_scheme (view); } static void scroll_to_insert (GtkSourceView *view, GtkTextBuffer *buffer) { GtkTextMark *insert; GtkTextIter iter; GdkRectangle visible, location; insert = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert); gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (view), &visible); gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), &iter, &location); if (location.y < visible.y || visible.y + visible.height < location.y) { gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view), insert, 0.0, TRUE, 0.5, 0.5); } else if (location.x < visible.x || visible.x + visible.width < location.x) { gdouble position; GtkAdjustment *adjustment; /* We revert the vertical position of the view because * _scroll_to_iter will cause it to move and the * insert mark is already visible vertically. */ adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view)); position = gtk_adjustment_get_value (adjustment); /* Must use _to_iter as _to_mark scrolls in an * idle handler and would prevent use from * reverting the vertical position of the view. */ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &iter, 0.0, TRUE, 0.5, 0.0); gtk_adjustment_set_value (adjustment, position); } } static void gtk_source_view_undo (GtkSourceView *view) { GtkTextBuffer *buffer; g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (gtk_text_view_get_editable (GTK_TEXT_VIEW (view)) && GTK_SOURCE_IS_BUFFER (buffer) && gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (buffer))) { gtk_source_buffer_undo (GTK_SOURCE_BUFFER (buffer)); scroll_to_insert (view, buffer); } } static void gtk_source_view_redo (GtkSourceView *view) { GtkTextBuffer *buffer; g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (gtk_text_view_get_editable (GTK_TEXT_VIEW (view)) && GTK_SOURCE_IS_BUFFER (buffer) && gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (buffer))) { gtk_source_buffer_redo (GTK_SOURCE_BUFFER (buffer)); scroll_to_insert (view, buffer); } } static void gtk_source_view_show_completion_real (GtkSourceView *view) { GtkSourceCompletion *completion; GtkSourceCompletionContext *context; completion = gtk_source_view_get_completion (view); context = gtk_source_completion_create_context (completion, NULL); gtk_source_completion_show (completion, gtk_source_completion_get_providers (completion), context); } static void menu_item_activate_change_case_cb (GtkWidget *menu_item, GtkTextView *text_view) { GtkTextBuffer *buffer; GtkTextIter start, end; buffer = gtk_text_view_get_buffer (text_view); if (!GTK_SOURCE_IS_BUFFER (buffer)) { return; } if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) { GtkSourceChangeCaseType case_type; case_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "change-case")); gtk_source_buffer_change_case (GTK_SOURCE_BUFFER (buffer), case_type, &start, &end); } } static void menu_item_activate_cb (GtkWidget *menu_item, GtkTextView *text_view) { const gchar *gtksignal; gtksignal = g_object_get_data (G_OBJECT (menu_item), "gtk-signal"); g_signal_emit_by_name (G_OBJECT (text_view), gtksignal); } static void gtk_source_view_populate_popup (GtkTextView *text_view, GtkWidget *popup) { GtkTextBuffer *buffer; GtkMenuShell *menu; GtkWidget *menu_item; GtkMenuShell *case_menu; buffer = gtk_text_view_get_buffer (text_view); if (!GTK_SOURCE_IS_BUFFER (buffer)) { return; } if (!GTK_IS_MENU_SHELL (popup)) { return; } menu = GTK_MENU_SHELL (popup); if (_gtk_source_buffer_is_undo_redo_enabled (GTK_SOURCE_BUFFER (buffer))) { /* separator */ menu_item = gtk_separator_menu_item_new (); gtk_menu_shell_prepend (menu, menu_item); gtk_widget_show (menu_item); /* create redo menu_item. */ menu_item = gtk_menu_item_new_with_mnemonic (_("_Redo")); g_object_set_data (G_OBJECT (menu_item), "gtk-signal", (gpointer)"redo"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_cb), text_view); gtk_menu_shell_prepend (menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_source_buffer_can_redo (GTK_SOURCE_BUFFER (buffer)))); gtk_widget_show (menu_item); /* create undo menu_item. */ menu_item = gtk_menu_item_new_with_mnemonic (_("_Undo")); g_object_set_data (G_OBJECT (menu_item), "gtk-signal", (gpointer)"undo"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_cb), text_view); gtk_menu_shell_prepend (menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_source_buffer_can_undo (GTK_SOURCE_BUFFER (buffer)))); gtk_widget_show (menu_item); } /* separator */ menu_item = gtk_separator_menu_item_new (); gtk_menu_shell_append (menu, menu_item); gtk_widget_show (menu_item); /* create change case menu */ case_menu = GTK_MENU_SHELL (gtk_menu_new ()); menu_item = gtk_menu_item_new_with_mnemonic (_("All _Upper Case")); g_object_set_data (G_OBJECT (menu_item), "change-case", GINT_TO_POINTER(GTK_SOURCE_CHANGE_CASE_UPPER)); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_change_case_cb), text_view); gtk_menu_shell_append (case_menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_text_buffer_get_has_selection (buffer))); gtk_widget_show (menu_item); menu_item = gtk_menu_item_new_with_mnemonic (_("All _Lower Case")); g_object_set_data (G_OBJECT (menu_item), "change-case", GINT_TO_POINTER(GTK_SOURCE_CHANGE_CASE_LOWER)); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_change_case_cb), text_view); gtk_menu_shell_append (case_menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_text_buffer_get_has_selection (buffer))); gtk_widget_show (menu_item); menu_item = gtk_menu_item_new_with_mnemonic (_("_Invert Case")); g_object_set_data (G_OBJECT (menu_item), "change-case", GINT_TO_POINTER(GTK_SOURCE_CHANGE_CASE_TOGGLE)); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_change_case_cb), text_view); gtk_menu_shell_append (case_menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_text_buffer_get_has_selection (buffer))); gtk_widget_show (menu_item); menu_item = gtk_menu_item_new_with_mnemonic (_("_Title Case")); g_object_set_data (G_OBJECT (menu_item), "change-case", GINT_TO_POINTER(GTK_SOURCE_CHANGE_CASE_TITLE)); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (menu_item_activate_change_case_cb), text_view); gtk_menu_shell_append (case_menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_text_buffer_get_has_selection (buffer))); gtk_widget_show (menu_item); menu_item = gtk_menu_item_new_with_mnemonic (_("C_hange Case")); gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), GTK_WIDGET (case_menu)); gtk_menu_shell_append (menu, menu_item); gtk_widget_set_sensitive (menu_item, (gtk_text_view_get_editable (text_view) && gtk_text_buffer_get_has_selection (buffer))); gtk_widget_show (menu_item); } static void move_cursor (GtkTextView *text_view, const GtkTextIter *new_location, gboolean extend_selection) { GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view); GtkTextMark *insert = gtk_text_buffer_get_insert (buffer); if (extend_selection) { gtk_text_buffer_move_mark (buffer, insert, new_location); } else { gtk_text_buffer_place_cursor (buffer, new_location); } gtk_text_view_scroll_mark_onscreen (text_view, insert); } static void move_to_first_char (GtkTextView *text_view, GtkTextIter *iter, gboolean display_line) { GtkTextIter last; last = *iter; if (display_line) { gtk_text_view_backward_display_line_start (text_view, iter); gtk_text_view_forward_display_line_end (text_view, &last); } else { gtk_text_iter_set_line_offset (iter, 0); if (!gtk_text_iter_ends_line (&last)) { gtk_text_iter_forward_to_line_end (&last); } } while (gtk_text_iter_compare (iter, &last) < 0) { gunichar c; c = gtk_text_iter_get_char (iter); if (g_unichar_isspace (c)) { if (!gtk_text_iter_forward_visible_cursor_position (iter)) { break; } } else { break; } } } static void move_to_last_char (GtkTextView *text_view, GtkTextIter *iter, gboolean display_line) { GtkTextIter first; first = *iter; if (display_line) { gtk_text_view_forward_display_line_end (text_view, iter); gtk_text_view_backward_display_line_start (text_view, &first); } else { if (!gtk_text_iter_ends_line (iter)) { gtk_text_iter_forward_to_line_end (iter); } gtk_text_iter_set_line_offset (&first, 0); } while (gtk_text_iter_compare (iter, &first) > 0) { gunichar c; if (!gtk_text_iter_backward_visible_cursor_position (iter)) { break; } c = gtk_text_iter_get_char (iter); if (!g_unichar_isspace (c)) { /* We've gone one cursor position too far. */ gtk_text_iter_forward_visible_cursor_position (iter); break; } } } static void do_cursor_move_home_end (GtkTextView *text_view, GtkTextIter *cur, GtkTextIter *iter, gboolean extend_selection, gint count) { /* if we are clearing selection, we need to move_cursor even * if we are at proper iter because selection_bound may need * to be moved */ if (!gtk_text_iter_equal (cur, iter) || !extend_selection) { move_cursor (text_view, iter, extend_selection); g_signal_emit (text_view, signals[SMART_HOME_END], 0, iter, count); } } /* Returns %TRUE if handled. */ static gboolean move_cursor_smart_home_end (GtkTextView *text_view, GtkMovementStep step, gint count, gboolean extend_selection) { GtkSourceView *source_view = GTK_SOURCE_VIEW (text_view); GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view); gboolean move_display_line; GtkTextMark *mark; GtkTextIter cur; GtkTextIter iter; g_assert (step == GTK_MOVEMENT_DISPLAY_LINE_ENDS || step == GTK_MOVEMENT_PARAGRAPH_ENDS); move_display_line = step == GTK_MOVEMENT_DISPLAY_LINE_ENDS; mark = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &cur, mark); iter = cur; if (count == -1) { gboolean at_home; move_to_first_char (text_view, &iter, move_display_line); if (move_display_line) { at_home = gtk_text_view_starts_display_line (text_view, &cur); } else { at_home = gtk_text_iter_starts_line (&cur); } switch (source_view->priv->smart_home_end) { case GTK_SOURCE_SMART_HOME_END_BEFORE: if (!gtk_text_iter_equal (&cur, &iter) || at_home) { do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; } break; case GTK_SOURCE_SMART_HOME_END_AFTER: if (at_home) { do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; } break; case GTK_SOURCE_SMART_HOME_END_ALWAYS: do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; case GTK_SOURCE_SMART_HOME_END_DISABLED: default: break; } } else if (count == 1) { gboolean at_end; move_to_last_char (text_view, &iter, move_display_line); if (move_display_line) { GtkTextIter display_end; display_end = cur; gtk_text_view_forward_display_line_end (text_view, &display_end); at_end = gtk_text_iter_equal (&cur, &display_end); } else { at_end = gtk_text_iter_ends_line (&cur); } switch (source_view->priv->smart_home_end) { case GTK_SOURCE_SMART_HOME_END_BEFORE: if (!gtk_text_iter_equal (&cur, &iter) || at_end) { do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; } break; case GTK_SOURCE_SMART_HOME_END_AFTER: if (at_end) { do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; } break; case GTK_SOURCE_SMART_HOME_END_ALWAYS: do_cursor_move_home_end (text_view, &cur, &iter, extend_selection, count); return TRUE; case GTK_SOURCE_SMART_HOME_END_DISABLED: default: break; } } return FALSE; } static void move_cursor_words (GtkTextView *text_view, gint count, gboolean extend_selection) { GtkTextBuffer *buffer; GtkTextIter insert; GtkTextIter newplace; GtkTextIter line_start; GtkTextIter line_end; gchar *line_text; buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer)); line_start = line_end = newplace = insert; /* Get the text of the current line for RTL analysis */ gtk_text_iter_set_line_offset (&line_start, 0); gtk_text_iter_forward_line (&line_end); line_text = gtk_text_iter_get_visible_text (&line_start, &line_end); /* Swap direction for RTL to maintain visual cursor movement. * Otherwise, cursor will move in opposite direction which is counter * intuitve and causes confusion for RTL users. */ if (pango_find_base_dir (line_text, -1) == PANGO_DIRECTION_RTL) { count *= -1; } g_free (line_text); if (count < 0) { if (!_gtk_source_iter_backward_visible_word_starts (&newplace, -count)) { gtk_text_iter_set_line_offset (&newplace, 0); } } else if (count > 0) { if (!_gtk_source_iter_forward_visible_word_ends (&newplace, count)) { gtk_text_iter_forward_to_line_end (&newplace); } } move_cursor (text_view, &newplace, extend_selection); } static void gtk_source_view_move_cursor (GtkTextView *text_view, GtkMovementStep step, gint count, gboolean extend_selection) { if (!gtk_text_view_get_cursor_visible (text_view)) { goto chain_up; } gtk_text_view_reset_im_context (text_view); switch (step) { case GTK_MOVEMENT_DISPLAY_LINE_ENDS: case GTK_MOVEMENT_PARAGRAPH_ENDS: if (move_cursor_smart_home_end (text_view, step, count, extend_selection)) { return; } break; case GTK_MOVEMENT_WORDS: move_cursor_words (text_view, count, extend_selection); return; case GTK_MOVEMENT_LOGICAL_POSITIONS: case GTK_MOVEMENT_VISUAL_POSITIONS: case GTK_MOVEMENT_DISPLAY_LINES: case GTK_MOVEMENT_PARAGRAPHS: case GTK_MOVEMENT_PAGES: case GTK_MOVEMENT_BUFFER_ENDS: case GTK_MOVEMENT_HORIZONTAL_PAGES: default: break; } chain_up: GTK_TEXT_VIEW_CLASS (gtk_source_view_parent_class)->move_cursor (text_view, step, count, extend_selection); } static void gtk_source_view_delete_from_cursor (GtkTextView *text_view, GtkDeleteType type, gint count) { GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view); GtkTextIter insert; GtkTextIter start; GtkTextIter end; if (type != GTK_DELETE_WORD_ENDS) { GTK_TEXT_VIEW_CLASS (gtk_source_view_parent_class)->delete_from_cursor (text_view, type, count); return; } gtk_text_view_reset_im_context (text_view); gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer)); start = insert; end = insert; if (count > 0) { if (!_gtk_source_iter_forward_visible_word_ends (&end, count)) { gtk_text_iter_forward_to_line_end (&end); } } else { if (!_gtk_source_iter_backward_visible_word_starts (&start, -count)) { gtk_text_iter_set_line_offset (&start, 0); } } gtk_text_buffer_delete_interactive (buffer, &start, &end, gtk_text_view_get_editable (text_view)); } static gboolean gtk_source_view_extend_selection (GtkTextView *text_view, GtkTextExtendSelection granularity, const GtkTextIter *location, GtkTextIter *start, GtkTextIter *end) { if (granularity == GTK_TEXT_EXTEND_SELECTION_WORD) { _gtk_source_iter_extend_selection_word (location, start, end); return GDK_EVENT_STOP; } return GTK_TEXT_VIEW_CLASS (gtk_source_view_parent_class)->extend_selection (text_view, granularity, location, start, end); } static void gtk_source_view_ensure_redrawn_rect_is_highlighted (GtkSourceView *view, cairo_t *cr) { GdkRectangle clip; GtkTextIter iter1, iter2; if (view->priv->source_buffer == NULL || !gdk_cairo_get_clip_rectangle (cr, &clip)) { return; } gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (view), &iter1, clip.y, NULL); gtk_text_iter_backward_line (&iter1); gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (view), &iter2, clip.y + clip.height, NULL); gtk_text_iter_forward_line (&iter2); DEBUG ({ g_print (" draw area: %d - %d\n", clip.y, clip.y + clip.height); g_print (" lines to update: %d - %d\n", gtk_text_iter_get_line (&iter1), gtk_text_iter_get_line (&iter2)); }); _gtk_source_buffer_update_syntax_highlight (view->priv->source_buffer, &iter1, &iter2, FALSE); _gtk_source_buffer_update_search_highlight (view->priv->source_buffer, &iter1, &iter2, FALSE); } /* This function is taken from gtk+/tests/testtext.c */ static void gtk_source_view_get_lines (GtkTextView *text_view, gint first_y, gint last_y, GArray *buffer_coords, GArray *line_heights, GArray *numbers, gint *countp) { GtkTextIter iter; gint count; gint last_line_num = -1; g_array_set_size (buffer_coords, 0); g_array_set_size (numbers, 0); if (line_heights != NULL) g_array_set_size (line_heights, 0); /* Get iter at first y */ gtk_text_view_get_line_at_y (text_view, &iter, first_y, NULL); /* For each iter, get its location and add it to the arrays. * Stop when we pass last_y */ count = 0; while (!gtk_text_iter_is_end (&iter)) { gint y, height; gtk_text_view_get_line_yrange (text_view, &iter, &y, &height); g_array_append_val (buffer_coords, y); if (line_heights) { g_array_append_val (line_heights, height); } last_line_num = gtk_text_iter_get_line (&iter); g_array_append_val (numbers, last_line_num); ++count; if ((y + height) >= last_y) break; gtk_text_iter_forward_line (&iter); } if (gtk_text_iter_is_end (&iter)) { gint y, height; gint line_num; gtk_text_view_get_line_yrange (text_view, &iter, &y, &height); line_num = gtk_text_iter_get_line (&iter); if (line_num != last_line_num) { g_array_append_val (buffer_coords, y); if (line_heights) g_array_append_val (line_heights, height); g_array_append_val (numbers, line_num); ++count; } } *countp = count; } /* Another solution to paint the line background is to use the * GtkTextTag:paragraph-background property. But there are several issues: * - GtkTextTags are per buffer, not per view. It's better to keep the line * highlighting per view. * - There is a problem for empty lines: a text tag can not be applied to an * empty region. And it can not be worked around easily for the last line. * * See https://bugzilla.gnome.org/show_bug.cgi?id=310847 for more details. */ static void gtk_source_view_paint_line_background (GtkTextView *text_view, cairo_t *cr, int y, /* in buffer coordinates */ int height, const GdkRGBA *color) { gdouble x1, y1, x2, y2; cairo_save (cr); cairo_clip_extents (cr, &x1, &y1, &x2, &y2); gdk_cairo_set_source_rgba (cr, (GdkRGBA *)color); cairo_set_line_width (cr, 1); cairo_rectangle (cr, x1 + .5, y + .5, x2 - x1 - 1, height - 1); cairo_stroke_preserve (cr); cairo_fill (cr); cairo_restore (cr); } static void gtk_source_view_paint_marks_background (GtkSourceView *view, cairo_t *cr) { GtkTextView *text_view; GdkRectangle clip; GArray *numbers; GArray *pixels; GArray *heights; gint y1, y2; gint count; gint i; if (view->priv->source_buffer == NULL || !gdk_cairo_get_clip_rectangle (cr, &clip)) { return; } text_view = GTK_TEXT_VIEW (view); y1 = clip.y; y2 = y1 + clip.height; numbers = g_array_new (FALSE, FALSE, sizeof (gint)); pixels = g_array_new (FALSE, FALSE, sizeof (gint)); heights = g_array_new (FALSE, FALSE, sizeof (gint)); /* get the line numbers and y coordinates. */ gtk_source_view_get_lines (text_view, y1, y2, pixels, heights, numbers, &count); if (count == 0) { gint n = 0; gint y; gint height; GtkTextIter iter; gtk_text_buffer_get_start_iter (gtk_text_view_get_buffer (text_view), &iter); gtk_text_view_get_line_yrange (text_view, &iter, &y, &height); g_array_append_val (pixels, y); g_array_append_val (pixels, height); g_array_append_val (numbers, n); count = 1; } DEBUG ({ g_print (" Painting marks background for line numbers %d - %d\n", g_array_index (numbers, gint, 0), g_array_index (numbers, gint, count - 1)); }); for (i = 0; i < count; ++i) { gint line_to_paint; GSList *marks; GdkRGBA background; int priority; line_to_paint = g_array_index (numbers, gint, i); marks = gtk_source_buffer_get_source_marks_at_line (view->priv->source_buffer, line_to_paint, NULL); priority = -1; while (marks != NULL) { GtkSourceMarkAttributes *attrs; gint prio; GdkRGBA bg; attrs = gtk_source_view_get_mark_attributes (view, gtk_source_mark_get_category (marks->data), &prio); if (attrs != NULL && prio > priority && gtk_source_mark_attributes_get_background (attrs, &bg)) { priority = prio; background = bg; } marks = g_slist_delete_link (marks, marks); } if (priority != -1) { gtk_source_view_paint_line_background (text_view, cr, g_array_index (pixels, gint, i), g_array_index (heights, gint, i), &background); } } g_array_free (heights, TRUE); g_array_free (pixels, TRUE); g_array_free (numbers, TRUE); } static void gtk_source_view_paint_right_margin (GtkSourceView *view, cairo_t *cr) { GdkRectangle clip; gdouble x; GtkTextView *text_view = GTK_TEXT_VIEW (view); #ifdef ENABLE_PROFILE static GTimer *timer = NULL; if (timer == NULL) { timer = g_timer_new (); } g_timer_start (timer); #endif g_return_if_fail (view->priv->right_margin_line_color != NULL); if (!gdk_cairo_get_clip_rectangle (cr, &clip)) { return; } if (view->priv->cached_right_margin_pos < 0) { view->priv->cached_right_margin_pos = calculate_real_tab_width (view, view->priv->right_margin_pos, '_'); } x = view->priv->cached_right_margin_pos + gtk_text_view_get_left_margin (text_view); cairo_save (cr); cairo_set_line_width (cr, 1.0); if (x + 1 >= clip.x && x <= clip.x + clip.width) { cairo_move_to (cr, x + 0.5, clip.y); cairo_line_to (cr, x + 0.5, clip.y + clip.height); gdk_cairo_set_source_rgba (cr, view->priv->right_margin_line_color); cairo_stroke (cr); } /* Only draw the overlay when the style scheme explicitly sets it. */ if (view->priv->right_margin_overlay_color != NULL && clip.x + clip.width > x + 1) { /* Draw the rectangle next to the line (x+1). */ cairo_rectangle (cr, x + 1, clip.y, clip.x + clip.width - (x + 1), clip.height); gdk_cairo_set_source_rgba (cr, view->priv->right_margin_overlay_color); cairo_fill (cr); } cairo_restore (cr); PROFILE ({ g_timer_stop (timer); g_print (" gtk_source_view_paint_right_margin time: " "%g (sec * 1000)\n", g_timer_elapsed (timer, NULL) * 1000); }); } static gint realign (gint offset, guint align) { if (offset > 0 && align > 0) { gint padding; padding = (align - (offset % align)) % align; return offset + padding; } return 0; } static void gtk_source_view_paint_background_pattern_grid (GtkSourceView *view, cairo_t *cr) { GdkRectangle clip; gint x, y, x2, y2; PangoContext *context; PangoLayout *layout; gint grid_width = 16; gint grid_height = 16; context = gtk_widget_get_pango_context (GTK_WIDGET (view)); layout = pango_layout_new (context); pango_layout_set_text (layout, "X", 1); pango_layout_get_pixel_size (layout, &grid_width, &grid_height); g_object_unref (layout); /* each character becomes 2 stacked boxes. */ grid_height = MAX (1, grid_height / 2); grid_width = MAX (1, grid_width); cairo_save (cr); gdk_cairo_get_clip_rectangle (cr, &clip); cairo_set_line_width (cr, 1.0); gdk_cairo_set_source_rgba (cr, &view->priv->background_pattern_color); /* Align our drawing position with a multiple of the grid size. */ x = realign (clip.x - grid_width, grid_width); y = realign (clip.y - grid_height, grid_height); x2 = realign (x + clip.width + grid_width * 2, grid_width); y2 = realign (y + clip.height + grid_height * 2, grid_height); for (; x <= x2; x += grid_width) { cairo_move_to (cr, x + .5, clip.y - .5); cairo_line_to (cr, x + .5, clip.y + clip.height - .5); } for (; y <= y2; y += grid_height) { cairo_move_to (cr, clip.x + .5, y - .5); cairo_line_to (cr, clip.x + clip.width + .5, y - .5); } cairo_stroke (cr); cairo_restore (cr); } static void gtk_source_view_paint_current_line_highlight (GtkSourceView *view, cairo_t *cr) { GtkTextBuffer *buffer; GtkTextIter cur; gint y; gint height; buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); gtk_text_buffer_get_iter_at_mark (buffer, &cur, gtk_text_buffer_get_insert (buffer)); gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (view), &cur, &y, &height); gtk_source_view_paint_line_background (GTK_TEXT_VIEW (view), cr, y, height, &view->priv->current_line_color); } static void gtk_source_view_draw_layer (GtkTextView *text_view, GtkTextViewLayer layer, cairo_t *cr) { GtkSourceView *view; view = GTK_SOURCE_VIEW (text_view); cairo_save (cr); if (layer == GTK_TEXT_VIEW_LAYER_BELOW_TEXT) { gtk_source_view_ensure_redrawn_rect_is_highlighted (view, cr); if (view->priv->background_pattern == GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID && view->priv->background_pattern_color_set) { gtk_source_view_paint_background_pattern_grid (view, cr); } if (gtk_widget_is_sensitive (GTK_WIDGET (view)) && view->priv->highlight_current_line && view->priv->current_line_color_set) { gtk_source_view_paint_current_line_highlight (view, cr); } gtk_source_view_paint_marks_background (view, cr); } else if (layer == GTK_TEXT_VIEW_LAYER_ABOVE_TEXT) { /* Draw the right margin vertical line + overlay. */ if (view->priv->show_right_margin) { gtk_source_view_paint_right_margin (view, cr); } if (view->priv->space_drawer != NULL) { _gtk_source_space_drawer_draw (view->priv->space_drawer, view, cr); } } cairo_restore (cr); } static gboolean gtk_source_view_draw (GtkWidget *widget, cairo_t *cr) { GtkSourceView *view = GTK_SOURCE_VIEW (widget); gboolean event_handled; #ifdef ENABLE_PROFILE static GTimer *timer = NULL; if (timer == NULL) { timer = g_timer_new (); } g_timer_start (timer); #endif DEBUG ({ g_print ("> gtk_source_view_draw start\n"); }); event_handled = GTK_WIDGET_CLASS (gtk_source_view_parent_class)->draw (widget, cr); if (view->priv->left_gutter != NULL) { _gtk_source_gutter_draw (view->priv->left_gutter, view, cr); } if (view->priv->right_gutter != NULL) { _gtk_source_gutter_draw (view->priv->right_gutter, view, cr); } PROFILE ({ g_timer_stop (timer); g_print (" gtk_source_view_draw time: %g (sec * 1000)\n", g_timer_elapsed (timer, NULL) * 1000); }); DEBUG ({ g_print ("> gtk_source_view_draw end\n"); }); return event_handled; } /* This is a pretty important function... We call it when the tab_stop is changed, * and when the font is changed. * NOTE: You must change this with the default font for now... * It may be a good idea to set the tab_width for each GtkTextTag as well * based on the font that we set at creation time * something like style_cache_set_tabs_from_font or the like. * Now, this *may* not be necessary because most tabs wont be inside of another highlight, * except for things like multi-line comments (which will usually have an italic font which * may or may not be a different size than the standard one), or if some random language * definition decides that it would be spiffy to have a bg color for "start of line" whitespace * "^\(\t\| \)+" would probably do the trick for that. */ static gint calculate_real_tab_width (GtkSourceView *view, guint tab_size, gchar c) { PangoLayout *layout; gchar *tab_string; gint tab_width = 0; if (tab_size == 0) { return -1; } tab_string = g_strnfill (tab_size, c); layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), tab_string); g_free (tab_string); if (layout != NULL) { pango_layout_get_pixel_size (layout, &tab_width, NULL); g_object_unref (G_OBJECT (layout)); } else { tab_width = -1; } return tab_width; } static GtkTextBuffer * gtk_source_view_create_buffer (GtkTextView *text_view) { return GTK_TEXT_BUFFER (gtk_source_buffer_new (NULL)); } /* ---------------------------------------------------------------------- * Public interface * ---------------------------------------------------------------------- */ /** * gtk_source_view_new: * * Creates a new #GtkSourceView. * * By default, an empty #GtkSourceBuffer will be lazily created and can be * retrieved with gtk_text_view_get_buffer(). * * If you want to specify your own buffer, either override the * #GtkTextViewClass create_buffer factory method, or use * gtk_source_view_new_with_buffer(). * * Returns: a new #GtkSourceView. */ GtkWidget * gtk_source_view_new (void) { return g_object_new (GTK_SOURCE_TYPE_VIEW, NULL); } /** * gtk_source_view_new_with_buffer: * @buffer: a #GtkSourceBuffer. * * Creates a new #GtkSourceView widget displaying the buffer * @buffer. One buffer can be shared among many widgets. * * Returns: a new #GtkSourceView. */ GtkWidget * gtk_source_view_new_with_buffer (GtkSourceBuffer *buffer) { g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL); return g_object_new (GTK_SOURCE_TYPE_VIEW, "buffer", buffer, NULL); } /** * gtk_source_view_get_show_line_numbers: * @view: a #GtkSourceView. * * Returns whether line numbers are displayed beside the text. * * Return value: %TRUE if the line numbers are displayed. */ gboolean gtk_source_view_get_show_line_numbers (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->show_line_numbers; } /** * gtk_source_view_set_show_line_numbers: * @view: a #GtkSourceView. * @show: whether line numbers should be displayed. * * If %TRUE line numbers will be displayed beside the text. */ void gtk_source_view_set_show_line_numbers (GtkSourceView *view, gboolean show) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); show = show != FALSE; if (show == view->priv->show_line_numbers) { return; } if (view->priv->line_renderer == NULL) { GtkSourceGutter *gutter; gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT); view->priv->line_renderer = gtk_source_gutter_renderer_lines_new (); g_object_set (view->priv->line_renderer, "alignment-mode", GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST, "yalign", 0.5, "xalign", 1.0, "xpad", 3, NULL); gtk_source_gutter_insert (gutter, view->priv->line_renderer, GTK_SOURCE_VIEW_GUTTER_POSITION_LINES); } gtk_source_gutter_renderer_set_visible (view->priv->line_renderer, show); view->priv->show_line_numbers = show; g_object_notify (G_OBJECT (view), "show_line_numbers"); } /** * gtk_source_view_get_show_line_marks: * @view: a #GtkSourceView. * * Returns whether line marks are displayed beside the text. * * Return value: %TRUE if the line marks are displayed. * * Since: 2.2 */ gboolean gtk_source_view_get_show_line_marks (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->show_line_marks; } static void gutter_renderer_marks_activate (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, const GdkRectangle *area, GdkEvent *event, GtkSourceView *view) { g_signal_emit (view, signals[LINE_MARK_ACTIVATED], 0, iter, event); } /** * gtk_source_view_set_show_line_marks: * @view: a #GtkSourceView. * @show: whether line marks should be displayed. * * If %TRUE line marks will be displayed beside the text. * * Since: 2.2 */ void gtk_source_view_set_show_line_marks (GtkSourceView *view, gboolean show) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); show = show != FALSE; if (show == view->priv->show_line_marks) { return; } if (view->priv->marks_renderer == NULL) { GtkSourceGutter *gutter; gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT); view->priv->marks_renderer = gtk_source_gutter_renderer_marks_new (); gtk_source_gutter_insert (gutter, view->priv->marks_renderer, GTK_SOURCE_VIEW_GUTTER_POSITION_MARKS); g_signal_connect (view->priv->marks_renderer, "activate", G_CALLBACK (gutter_renderer_marks_activate), view); } gtk_source_gutter_renderer_set_visible (view->priv->marks_renderer, show); view->priv->show_line_marks = show; g_object_notify (G_OBJECT (view), "show_line_marks"); } static gboolean set_tab_stops_internal (GtkSourceView *view) { PangoTabArray *tab_array; gint real_tab_width; real_tab_width = calculate_real_tab_width (view, view->priv->tab_width, ' '); if (real_tab_width < 0) { return FALSE; } tab_array = pango_tab_array_new (1, TRUE); pango_tab_array_set_tab (tab_array, 0, PANGO_TAB_LEFT, real_tab_width); gtk_text_view_set_tabs (GTK_TEXT_VIEW (view), tab_array); view->priv->tabs_set = TRUE; pango_tab_array_free (tab_array); return TRUE; } /** * gtk_source_view_set_tab_width: * @view: a #GtkSourceView. * @width: width of tab in characters. * * Sets the width of tabulation in characters. The #GtkTextBuffer still contains * \t characters, but they can take a different visual width in a #GtkSourceView * widget. */ void gtk_source_view_set_tab_width (GtkSourceView *view, guint width) { guint save_width; g_return_if_fail (GTK_SOURCE_VIEW (view)); g_return_if_fail (0 < width && width <= MAX_TAB_WIDTH); if (view->priv->tab_width == width) { return; } save_width = view->priv->tab_width; view->priv->tab_width = width; if (set_tab_stops_internal (view)) { g_object_notify (G_OBJECT (view), "tab-width"); } else { g_warning ("Impossible to set tab width."); view->priv->tab_width = save_width; } } /** * gtk_source_view_get_tab_width: * @view: a #GtkSourceView. * * Returns the width of tabulation in characters. * * Return value: width of tab. */ guint gtk_source_view_get_tab_width (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), DEFAULT_TAB_WIDTH); return view->priv->tab_width; } /** * gtk_source_view_set_indent_width: * @view: a #GtkSourceView. * @width: indent width in characters. * * Sets the number of spaces to use for each step of indent when the tab key is * pressed. If @width is -1, the value of the #GtkSourceView:tab-width property * will be used. * * The #GtkSourceView:indent-width interacts with the * #GtkSourceView:insert-spaces-instead-of-tabs property and * #GtkSourceView:tab-width. An example will be clearer: if the * #GtkSourceView:indent-width is 4 and * #GtkSourceView:tab-width is 8 and * #GtkSourceView:insert-spaces-instead-of-tabs is %FALSE, then pressing the tab * key at the beginning of a line will insert 4 spaces. So far so good. Pressing * the tab key a second time will remove the 4 spaces and insert a \t character * instead (since #GtkSourceView:tab-width is 8). On the other hand, if * #GtkSourceView:insert-spaces-instead-of-tabs is %TRUE, the second tab key * pressed will insert 4 more spaces for a total of 8 spaces in the * #GtkTextBuffer. * * The test-widget program (available in the GtkSourceView repository) may be * useful to better understand the indentation settings (enable the space * drawing!). */ void gtk_source_view_set_indent_width (GtkSourceView *view, gint width) { g_return_if_fail (GTK_SOURCE_VIEW (view)); g_return_if_fail (width == -1 || (0 < width && width <= MAX_INDENT_WIDTH)); if (view->priv->indent_width != width) { view->priv->indent_width = width; g_object_notify (G_OBJECT (view), "indent-width"); } } /** * gtk_source_view_get_indent_width: * @view: a #GtkSourceView. * * Returns the number of spaces to use for each step of indent. * See gtk_source_view_set_indent_width() for details. * * Return value: indent width. */ gint gtk_source_view_get_indent_width (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), 0); return view->priv->indent_width; } static gchar * compute_indentation (GtkSourceView *view, GtkTextIter *cur) { GtkTextIter start; GtkTextIter end; gunichar ch; start = *cur; gtk_text_iter_set_line_offset (&start, 0); end = start; ch = gtk_text_iter_get_char (&end); while (g_unichar_isspace (ch) && (ch != '\n') && (ch != '\r') && (gtk_text_iter_compare (&end, cur) < 0)) { if (!gtk_text_iter_forward_char (&end)) { break; } ch = gtk_text_iter_get_char (&end); } if (gtk_text_iter_equal (&start, &end)) { return NULL; } return gtk_text_iter_get_slice (&start, &end); } static guint get_real_indent_width (GtkSourceView *view) { return view->priv->indent_width < 0 ? view->priv->tab_width : (guint) view->priv->indent_width; } static gchar * get_indent_string (guint tabs, guint spaces) { gchar *str; str = g_malloc (tabs + spaces + 1); if (tabs > 0) { memset (str, '\t', tabs); } if (spaces > 0) { memset (str + tabs, ' ', spaces); } str[tabs + spaces] = '\0'; return str; } /** * gtk_source_view_indent_lines: * @view: a #GtkSourceView. * @start: #GtkTextIter of the first line to indent * @end: #GtkTextIter of the last line to indent * * Inserts one indentation level at the beginning of the specified lines. The * empty lines are not indented. * * Since: 3.16 */ void gtk_source_view_indent_lines (GtkSourceView *view, GtkTextIter *start, GtkTextIter *end) { GtkTextBuffer *buf; gboolean bracket_hl; GtkTextMark *start_mark, *end_mark; gint start_line, end_line; gchar *tab_buffer = NULL; guint tabs = 0; guint spaces = 0; gint i; if (view->priv->completion != NULL) { gtk_source_completion_block_interactive (view->priv->completion); } buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); bracket_hl = gtk_source_buffer_get_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf)); gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf), FALSE); start_mark = gtk_text_buffer_create_mark (buf, NULL, start, FALSE); end_mark = gtk_text_buffer_create_mark (buf, NULL, end, FALSE); start_line = gtk_text_iter_get_line (start); end_line = gtk_text_iter_get_line (end); if ((gtk_text_iter_get_visible_line_offset (end) == 0) && (end_line > start_line)) { end_line--; } if (view->priv->insert_spaces) { spaces = get_real_indent_width (view); tab_buffer = g_strnfill (spaces, ' '); } else if (view->priv->indent_width > 0 && view->priv->indent_width != (gint)view->priv->tab_width) { guint indent_width; indent_width = get_real_indent_width (view); spaces = indent_width % view->priv->tab_width; tabs = indent_width / view->priv->tab_width; tab_buffer = get_indent_string (tabs, spaces); } else { tabs = 1; tab_buffer = g_strdup ("\t"); } gtk_text_buffer_begin_user_action (buf); for (i = start_line; i <= end_line; i++) { GtkTextIter iter; GtkTextIter iter2; guint replaced_spaces = 0; gtk_text_buffer_get_iter_at_line (buf, &iter, i); /* Don't add indentation on completely empty lines, to not add * trailing spaces. * Note that non-empty lines containing only whitespaces are * indented like any other non-empty line, because those lines * already contain trailing spaces, some users use those * whitespaces to more easily insert text at the right place * without the need to insert the indentation each time. */ if (gtk_text_iter_ends_line (&iter)) { continue; } /* add spaces always after tabs, to avoid the case * where "\t" becomes " \t" with no visual difference */ while (gtk_text_iter_get_char (&iter) == '\t') { gtk_text_iter_forward_char (&iter); } /* if tabs are allowed try to merge the spaces * with the tab we are going to insert (if any) */ iter2 = iter; while (!view->priv->insert_spaces && (gtk_text_iter_get_char (&iter2) == ' ') && replaced_spaces < view->priv->tab_width) { ++replaced_spaces; gtk_text_iter_forward_char (&iter2); } if (replaced_spaces > 0) { gchar *indent_buf; guint t, s; t = tabs + (spaces + replaced_spaces) / view->priv->tab_width; s = (spaces + replaced_spaces) % view->priv->tab_width; indent_buf = get_indent_string (t, s); gtk_text_buffer_delete (buf, &iter, &iter2); gtk_text_buffer_insert (buf, &iter, indent_buf, -1); g_free (indent_buf); } else { gtk_text_buffer_insert (buf, &iter, tab_buffer, -1); } } gtk_text_buffer_end_user_action (buf); g_free (tab_buffer); gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf), bracket_hl); if (view->priv->completion != NULL) { gtk_source_completion_unblock_interactive (view->priv->completion); } gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), gtk_text_buffer_get_insert (buf)); /* revalidate iters */ gtk_text_buffer_get_iter_at_mark (buf, start, start_mark); gtk_text_buffer_get_iter_at_mark (buf, end, end_mark); gtk_text_buffer_delete_mark (buf, start_mark); gtk_text_buffer_delete_mark (buf, end_mark); } /** * gtk_source_view_unindent_lines: * @view: a #GtkSourceView. * @start: #GtkTextIter of the first line to indent * @end: #GtkTextIter of the last line to indent * * Removes one indentation level at the beginning of the * specified lines. * * Since: 3.16 */ void gtk_source_view_unindent_lines (GtkSourceView *view, GtkTextIter *start, GtkTextIter *end) { GtkTextBuffer *buf; gboolean bracket_hl; GtkTextMark *start_mark, *end_mark; gint start_line, end_line; gint tab_width; gint indent_width; gint i; if (view->priv->completion != NULL) { gtk_source_completion_block_interactive (view->priv->completion); } buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); bracket_hl = gtk_source_buffer_get_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf)); gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf), FALSE); start_mark = gtk_text_buffer_create_mark (buf, NULL, start, FALSE); end_mark = gtk_text_buffer_create_mark (buf, NULL, end, FALSE); start_line = gtk_text_iter_get_line (start); end_line = gtk_text_iter_get_line (end); if ((gtk_text_iter_get_visible_line_offset (end) == 0) && (end_line > start_line)) { end_line--; } tab_width = view->priv->tab_width; indent_width = get_real_indent_width (view); gtk_text_buffer_begin_user_action (buf); for (i = start_line; i <= end_line; i++) { GtkTextIter iter, iter2; gint to_delete = 0; gint to_delete_equiv = 0; gtk_text_buffer_get_iter_at_line (buf, &iter, i); iter2 = iter; while (!gtk_text_iter_ends_line (&iter2) && to_delete_equiv < indent_width) { gunichar c; c = gtk_text_iter_get_char (&iter2); if (c == '\t') { to_delete_equiv += tab_width - to_delete_equiv % tab_width; ++to_delete; } else if (c == ' ') { ++to_delete_equiv; ++to_delete; } else { break; } gtk_text_iter_forward_char (&iter2); } if (to_delete > 0) { gtk_text_iter_set_line_offset (&iter2, to_delete); gtk_text_buffer_delete (buf, &iter, &iter2); } } gtk_text_buffer_end_user_action (buf); gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buf), bracket_hl); if (view->priv->completion != NULL) { gtk_source_completion_unblock_interactive (view->priv->completion); } gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), gtk_text_buffer_get_insert (buf)); /* revalidate iters */ gtk_text_buffer_get_iter_at_mark (buf, start, start_mark); gtk_text_buffer_get_iter_at_mark (buf, end, end_mark); gtk_text_buffer_delete_mark (buf, start_mark); gtk_text_buffer_delete_mark (buf, end_mark); } static gint get_line_offset_in_equivalent_spaces (GtkSourceView *view, GtkTextIter *iter) { GtkTextIter i; gint tab_width; gint n = 0; tab_width = view->priv->tab_width; i = *iter; gtk_text_iter_set_line_offset (&i, 0); while (!gtk_text_iter_equal (&i, iter)) { gunichar c; c = gtk_text_iter_get_char (&i); if (c == '\t') { n += tab_width - n % tab_width; } else { ++n; } gtk_text_iter_forward_char (&i); } return n; } static void insert_tab_or_spaces (GtkSourceView *view, GtkTextIter *start, GtkTextIter *end) { GtkTextBuffer *buf; gchar *tab_buf; gint cursor_offset = 0; if (view->priv->insert_spaces) { gint indent_width; gint pos; gint spaces; indent_width = get_real_indent_width (view); /* CHECK: is this a performance problem? */ pos = get_line_offset_in_equivalent_spaces (view, start); spaces = indent_width - pos % indent_width; tab_buf = g_strnfill (spaces, ' '); } else if (view->priv->indent_width > 0 && view->priv->indent_width != (gint)view->priv->tab_width) { GtkTextIter iter; gint i; gint tab_width; gint indent_width; gint from; gint to; gint preceding_spaces = 0; gint following_tabs = 0; gint equiv_spaces; gint tabs; gint spaces; tab_width = view->priv->tab_width; indent_width = get_real_indent_width (view); /* CHECK: is this a performance problem? */ from = get_line_offset_in_equivalent_spaces (view, start); to = indent_width * (1 + from / indent_width); equiv_spaces = to - from; /* extend the selection to include * preceding spaces so that if indentation * width < tab width, two conseutive indentation * width units get compressed into a tab */ iter = *start; for (i = 0; i < tab_width; ++i) { gtk_text_iter_backward_char (&iter); if (gtk_text_iter_get_char (&iter) == ' ') { ++preceding_spaces; } else { break; } } gtk_text_iter_backward_chars (start, preceding_spaces); /* now also extend the selection to the following tabs * since we do not want to insert spaces before a tab * since it may have no visual effect */ while (gtk_text_iter_get_char (end) == '\t') { ++following_tabs; gtk_text_iter_forward_char (end); } tabs = (preceding_spaces + equiv_spaces) / tab_width; spaces = (preceding_spaces + equiv_spaces) % tab_width; tab_buf = get_indent_string (tabs + following_tabs, spaces); cursor_offset = gtk_text_iter_get_offset (start) + tabs + (following_tabs > 0 ? 1 : spaces); } else { tab_buf = g_strdup ("\t"); } buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); gtk_text_buffer_begin_user_action (buf); gtk_text_buffer_delete (buf, start, end); gtk_text_buffer_insert (buf, start, tab_buf, -1); /* adjust cursor position if needed */ if (cursor_offset > 0) { GtkTextIter iter; gtk_text_buffer_get_iter_at_offset (buf, &iter, cursor_offset); gtk_text_buffer_place_cursor (buf, &iter); } gtk_text_buffer_end_user_action (buf); g_free (tab_buf); } static void gtk_source_view_move_words (GtkSourceView *view, gint step) { GtkTextBuffer *buf; GtkTextIter s, e, ns, ne; GtkTextMark *nsmark, *nemark; gchar *old_text, *new_text; buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (step == 0 || gtk_text_view_get_editable (GTK_TEXT_VIEW (view)) == FALSE) { return; } gtk_text_buffer_get_selection_bounds (buf, &s, &e); if (gtk_text_iter_compare (&s, &e) == 0) { if (!gtk_text_iter_starts_word (&s)) { if (!gtk_text_iter_inside_word (&s) && !gtk_text_iter_ends_word (&s)) { return; } else { gtk_text_iter_backward_word_start (&s); } } if (!gtk_text_iter_starts_word (&s)) { return; } e = s; if (!gtk_text_iter_ends_word (&e)) { if (!gtk_text_iter_forward_word_end (&e)) { gtk_text_iter_forward_to_end (&e); } if (!gtk_text_iter_ends_word (&e)) { return; } } } /* Swap the selection with the next or previous word, based on step */ if (step > 0) { ne = e; if (!gtk_text_iter_forward_word_ends (&ne, step)) { gtk_text_iter_forward_to_end (&ne); } if (!gtk_text_iter_ends_word (&ne) || gtk_text_iter_equal (&ne, &e)) { return; } ns = ne; if (!gtk_text_iter_backward_word_start (&ns)) { return; } } else { ns = s; if (!gtk_text_iter_backward_word_starts (&ns, -step)) { return; } ne = ns; if (!gtk_text_iter_forward_word_end (&ne)) { return; } } if (gtk_text_iter_in_range (&ns, &s, &e) || gtk_text_iter_in_range (&ne, &s, &e)) { return; } old_text = gtk_text_buffer_get_text (buf, &s, &e, TRUE); new_text = gtk_text_buffer_get_text (buf, &ns, &ne, TRUE); gtk_text_buffer_begin_user_action (buf); nsmark = gtk_text_buffer_create_mark (buf, NULL, &ns, TRUE); nemark = gtk_text_buffer_create_mark (buf, NULL, &ne, FALSE); gtk_text_buffer_delete (buf, &s, &e); gtk_text_buffer_insert (buf, &s, new_text, -1); gtk_text_buffer_get_iter_at_mark (buf, &ns, nsmark); gtk_text_buffer_get_iter_at_mark (buf, &ne, nemark); gtk_text_buffer_delete (buf, &ns, &ne); gtk_text_buffer_insert (buf, &ns, old_text, -1); ne = ns; gtk_text_buffer_get_iter_at_mark (buf, &ns, nsmark); gtk_text_buffer_select_range (buf, &ns, &ne); gtk_text_buffer_delete_mark (buf, nsmark); gtk_text_buffer_delete_mark (buf, nemark); gtk_text_buffer_end_user_action (buf); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), gtk_text_buffer_get_insert (buf)); g_free (old_text); g_free (new_text); } static gboolean buffer_contains_trailing_newline (GtkTextBuffer *buffer) { GtkTextIter iter; gunichar ch; gtk_text_buffer_get_end_iter (buffer, &iter); gtk_text_iter_backward_char (&iter); ch = gtk_text_iter_get_char (&iter); return (ch == '\n' || ch == '\r'); } /* FIXME could be a function of GtkSourceBuffer, it's also useful for the * FileLoader. */ static void remove_trailing_newline (GtkTextBuffer *buffer) { GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_end_iter (buffer, &end); start = end; gtk_text_iter_set_line_offset (&start, 0); if (gtk_text_iter_ends_line (&start) && gtk_text_iter_backward_line (&start)) { if (!gtk_text_iter_ends_line (&start)) { gtk_text_iter_forward_to_line_end (&start); } gtk_text_buffer_delete (buffer, &start, &end); } } static void gtk_source_view_move_lines (GtkSourceView *view, gboolean copy, gint step) { GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; GtkTextIter insert_pos; GtkTextMark *start_mark; GtkTextMark *end_mark; gchar *text; gboolean initially_contains_trailing_newline; gboolean down; if (copy) { g_warning ("The 'copy' parameter of GtkSourceView::move-lines is deprecated."); } if (step != 1 && step != -1) { g_warning ("The 'count' parameter of GtkSourceView::move-lines should be either 1 or -1."); } buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (step == 0 || !gtk_text_view_get_editable (GTK_TEXT_VIEW (view))) { return; } /* FIXME: for now we just handle a step of one line */ down = step > 0; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); /* Get the entire lines, including the paragraph terminator. */ gtk_text_iter_set_line_offset (&start, 0); if (!gtk_text_iter_starts_line (&end) || gtk_text_iter_get_line (&start) == gtk_text_iter_get_line (&end)) { gtk_text_iter_forward_line (&end); } if ((!down && gtk_text_iter_is_start (&start)) || (down && gtk_text_iter_is_end (&end))) { /* Nothing to do, and the undo/redo history must remain * unchanged. */ return; } start_mark = gtk_text_buffer_create_mark (buffer, NULL, &start, TRUE); end_mark = gtk_text_buffer_create_mark (buffer, NULL, &end, FALSE); gtk_text_buffer_begin_user_action (buffer); initially_contains_trailing_newline = buffer_contains_trailing_newline (buffer); if (!initially_contains_trailing_newline) { /* Insert a trailing newline. */ gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, "\n", -1); } /* At this point all lines finish with a newline or carriage return, so * there are no special cases for the last line. */ gtk_text_buffer_get_iter_at_mark (buffer, &start, start_mark); gtk_text_buffer_get_iter_at_mark (buffer, &end, end_mark); gtk_text_buffer_delete_mark (buffer, start_mark); gtk_text_buffer_delete_mark (buffer, end_mark); start_mark = NULL; end_mark = NULL; text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE); if (!copy) { gtk_text_buffer_delete (buffer, &start, &end); } if (down) { insert_pos = end; gtk_text_iter_forward_line (&insert_pos); } else { insert_pos = start; gtk_text_iter_backward_line (&insert_pos); } start_mark = gtk_text_buffer_create_mark (buffer, NULL, &insert_pos, TRUE); gtk_text_buffer_insert (buffer, &insert_pos, text, -1); g_free (text); /* Select the moved text. */ gtk_text_buffer_get_iter_at_mark (buffer, &start, start_mark); gtk_text_buffer_delete_mark (buffer, start_mark); gtk_text_buffer_select_range (buffer, &start, &insert_pos); if (!initially_contains_trailing_newline) { remove_trailing_newline (buffer); } gtk_text_buffer_end_user_action (buffer); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), gtk_text_buffer_get_insert (buffer)); } static gboolean do_smart_backspace (GtkSourceView *view) { GtkTextBuffer *buffer; gboolean default_editable; GtkTextIter insert; GtkTextIter end; GtkTextIter leading_end; guint visual_column; gint indent_width; buffer = GTK_TEXT_BUFFER (view->priv->source_buffer); default_editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view)); if (gtk_text_buffer_get_selection_bounds (buffer, &insert, &end)) { return FALSE; } /* If the line isn't empty up to our cursor, ignore. */ _gtk_source_iter_get_leading_spaces_end_boundary (&insert, &leading_end); if (gtk_text_iter_compare (&leading_end, &insert) < 0) { return FALSE; } visual_column = gtk_source_view_get_visual_column (view, &insert); indent_width = view->priv->indent_width; if (indent_width <= 0) { indent_width = view->priv->tab_width; } g_return_val_if_fail (indent_width > 0, FALSE); /* If the cursor is not at an indent_width boundary, it probably means * that we want to adjust the spaces. */ if ((gint)visual_column < indent_width) { return FALSE; } if ((visual_column % indent_width) == 0) { guint target_column; g_assert ((gint)visual_column >= indent_width); target_column = visual_column - indent_width; while (gtk_source_view_get_visual_column (view, &insert) > target_column) { gtk_text_iter_backward_cursor_position (&insert); } gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_delete_interactive (buffer, &insert, &end, default_editable); while (gtk_source_view_get_visual_column (view, &insert) < target_column) { if (!gtk_text_buffer_insert_interactive (buffer, &insert, " ", 1, default_editable)) { break; } } gtk_text_buffer_end_user_action (buffer); return TRUE; } return FALSE; } static gboolean do_ctrl_backspace (GtkSourceView *view) { GtkTextBuffer *buffer; GtkTextIter insert; GtkTextIter end; GtkTextIter leading_end; gboolean default_editable; buffer = GTK_TEXT_BUFFER (view->priv->source_buffer); default_editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (view)); if (gtk_text_buffer_get_selection_bounds (buffer, &insert, &end)) { return FALSE; } /* A BackSpace at the beginning of the line should only move us to the * end of the previous line. Anything more than that is non-obvious because it requires * looking in a position other than where the cursor is. */ if ((gtk_text_iter_get_line_offset (&insert) == 0) && (gtk_text_iter_get_line (&insert) > 0)) { gtk_text_iter_backward_cursor_position (&insert); gtk_text_buffer_delete_interactive (buffer, &insert, &end, default_editable); return TRUE; } /* If only leading whitespaces are on the left of the cursor, delete up * to the zero position. */ _gtk_source_iter_get_leading_spaces_end_boundary (&insert, &leading_end); if (gtk_text_iter_compare (&insert, &leading_end) <= 0) { gtk_text_iter_set_line_offset (&insert, 0); gtk_text_buffer_delete_interactive (buffer, &insert, &end, default_editable); return TRUE; } return FALSE; } static gboolean gtk_source_view_key_press_event (GtkWidget *widget, GdkEventKey *event) { GtkSourceView *view; GtkTextBuffer *buf; GtkTextIter cur; GtkTextMark *mark; guint modifiers; gint key; gboolean editable; view = GTK_SOURCE_VIEW (widget); buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)); /* Be careful when testing for modifier state equality: * caps lock, num lock,etc need to be taken into account */ modifiers = gtk_accelerator_get_default_mod_mask (); key = event->keyval; mark = gtk_text_buffer_get_insert (buf); gtk_text_buffer_get_iter_at_mark (buf, &cur, mark); if ((key == GDK_KEY_Return || key == GDK_KEY_KP_Enter) && !(event->state & GDK_SHIFT_MASK) && view->priv->auto_indent) { /* Auto-indent means that when you press ENTER at the end of a * line, the new line is automatically indented at the same * level as the previous line. * SHIFT+ENTER allows to avoid autoindentation. */ gchar *indent = NULL; /* Calculate line indentation and create indent string. */ indent = compute_indentation (view, &cur); if (indent != NULL) { /* Allow input methods to internally handle a key press event. * If this function returns TRUE, then no further processing should be done * for this keystroke. */ if (gtk_text_view_im_context_filter_keypress (GTK_TEXT_VIEW (view), event)) { g_free (indent); return GDK_EVENT_STOP; } /* If an input method has inserted some text while handling the key press event, * the cur iterm may be invalid, so get the iter again */ gtk_text_buffer_get_iter_at_mark (buf, &cur, mark); /* Insert new line and auto-indent. */ gtk_text_buffer_begin_user_action (buf); gtk_text_buffer_insert (buf, &cur, "\n", 1); gtk_text_buffer_insert (buf, &cur, indent, strlen (indent)); g_free (indent); gtk_text_buffer_end_user_action (buf); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (widget), mark); return GDK_EVENT_STOP; } } /* if tab or shift+tab: * with shift+tab key is GDK_ISO_Left_Tab (yay! on win32 and mac too!) */ if ((key == GDK_KEY_Tab || key == GDK_KEY_KP_Tab || key == GDK_KEY_ISO_Left_Tab) && ((event->state & modifiers) == 0 || (event->state & modifiers) == GDK_SHIFT_MASK) && editable && gtk_text_view_get_accepts_tab (GTK_TEXT_VIEW (view))) { GtkTextIter s, e; gboolean has_selection; has_selection = gtk_text_buffer_get_selection_bounds (buf, &s, &e); if (view->priv->indent_on_tab) { /* shift+tab: always unindent */ if (event->state & GDK_SHIFT_MASK) { _gtk_source_buffer_save_and_clear_selection (GTK_SOURCE_BUFFER (buf)); gtk_source_view_unindent_lines (view, &s, &e); _gtk_source_buffer_restore_selection (GTK_SOURCE_BUFFER (buf)); return GDK_EVENT_STOP; } /* tab: if we have a selection which spans one whole line * or more, we mass indent, if the selection spans less then * the full line just replace the text with \t */ if (has_selection && ((gtk_text_iter_starts_line (&s) && gtk_text_iter_ends_line (&e)) || (gtk_text_iter_get_line (&s) != gtk_text_iter_get_line (&e)))) { _gtk_source_buffer_save_and_clear_selection (GTK_SOURCE_BUFFER (buf)); gtk_source_view_indent_lines (view, &s, &e); _gtk_source_buffer_restore_selection (GTK_SOURCE_BUFFER (buf)); return GDK_EVENT_STOP; } } insert_tab_or_spaces (view, &s, &e); return GDK_EVENT_STOP; } if (key == GDK_KEY_BackSpace) { if ((event->state & modifiers) == 0) { if (view->priv->smart_backspace && do_smart_backspace (view)) { return GDK_EVENT_STOP; } } else if ((event->state & modifiers) == GDK_CONTROL_MASK) { if (do_ctrl_backspace (view)) { return GDK_EVENT_STOP; } } } return GTK_WIDGET_CLASS (gtk_source_view_parent_class)->key_press_event (widget, event); } /** * gtk_source_view_get_auto_indent: * @view: a #GtkSourceView. * * Returns whether auto-indentation of text is enabled. * * Returns: %TRUE if auto indentation is enabled. */ gboolean gtk_source_view_get_auto_indent (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->auto_indent; } /** * gtk_source_view_set_auto_indent: * @view: a #GtkSourceView. * @enable: whether to enable auto indentation. * * If %TRUE auto-indentation of text is enabled. * * When Enter is pressed to create a new line, the auto-indentation inserts the * same indentation as the previous line. This is not a * "smart indentation" where an indentation level is added or removed depending * on the context. */ void gtk_source_view_set_auto_indent (GtkSourceView *view, gboolean enable) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); enable = enable != FALSE; if (view->priv->auto_indent != enable) { view->priv->auto_indent = enable; g_object_notify (G_OBJECT (view), "auto_indent"); } } /** * gtk_source_view_get_insert_spaces_instead_of_tabs: * @view: a #GtkSourceView. * * Returns whether when inserting a tabulator character it should * be replaced by a group of space characters. * * Returns: %TRUE if spaces are inserted instead of tabs. */ gboolean gtk_source_view_get_insert_spaces_instead_of_tabs (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->insert_spaces; } /** * gtk_source_view_set_insert_spaces_instead_of_tabs: * @view: a #GtkSourceView. * @enable: whether to insert spaces instead of tabs. * * If %TRUE a tab key pressed is replaced by a group of space characters. Of * course it is still possible to insert a real \t programmatically with the * #GtkTextBuffer API. */ void gtk_source_view_set_insert_spaces_instead_of_tabs (GtkSourceView *view, gboolean enable) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); enable = enable != FALSE; if (view->priv->insert_spaces != enable) { view->priv->insert_spaces = enable; g_object_notify (G_OBJECT (view), "insert_spaces_instead_of_tabs"); } } /** * gtk_source_view_get_indent_on_tab: * @view: a #GtkSourceView. * * Returns whether when the tab key is pressed the current selection * should get indented instead of replaced with the \t character. * * Return value: %TRUE if the selection is indented when tab is pressed. */ gboolean gtk_source_view_get_indent_on_tab (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->indent_on_tab; } /** * gtk_source_view_set_indent_on_tab: * @view: a #GtkSourceView. * @enable: whether to indent a block when tab is pressed. * * If %TRUE, when the tab key is pressed when several lines are selected, the * selected lines are indented of one level instead of being replaced with a \t * character. Shift+Tab unindents the selection. * * If the first or last line is not selected completely, it is also indented or * unindented. * * When the selection doesn't span several lines, the tab key always replaces * the selection with a normal \t character. */ void gtk_source_view_set_indent_on_tab (GtkSourceView *view, gboolean enable) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); enable = enable != FALSE; if (view->priv->indent_on_tab != enable) { view->priv->indent_on_tab = enable; g_object_notify (G_OBJECT (view), "indent_on_tab"); } } static void view_dnd_drop (GtkTextView *view, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint timestamp, gpointer data) { GtkTextIter iter; if (info == TARGET_COLOR) { guint16 *vals; gchar string[] = "#000000"; gint buffer_x; gint buffer_y; gint length = gtk_selection_data_get_length (selection_data); if (length < 0) { return; } if (gtk_selection_data_get_format (selection_data) != 16 || length != 8) { g_warning ("Received invalid color data\n"); return; } vals = (gpointer) gtk_selection_data_get_data (selection_data); vals[0] /= 256; vals[1] /= 256; vals[2] /= 256; g_snprintf (string, sizeof (string), "#%02X%02X%02X", vals[0], vals[1], vals[2]); gtk_text_view_window_to_buffer_coords (view, GTK_TEXT_WINDOW_TEXT, x, y, &buffer_x, &buffer_y); gtk_text_view_get_iter_at_location (view, &iter, buffer_x, buffer_y); if (gtk_text_view_get_editable (view)) { gtk_text_buffer_insert (gtk_text_view_get_buffer (view), &iter, string, strlen (string)); gtk_text_buffer_place_cursor (gtk_text_view_get_buffer (view), &iter); } /* * FIXME: Check if the iter is inside a selection * If it is, remove the selection and then insert at * the cursor position - Paolo */ return; } } /** * gtk_source_view_get_highlight_current_line: * @view: a #GtkSourceView. * * Returns whether the current line is highlighted. * * Return value: %TRUE if the current line is highlighted. */ gboolean gtk_source_view_get_highlight_current_line (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->highlight_current_line; } /** * gtk_source_view_set_highlight_current_line: * @view: a #GtkSourceView. * @highlight: whether to highlight the current line. * * If @highlight is %TRUE the current line will be highlighted. */ void gtk_source_view_set_highlight_current_line (GtkSourceView *view, gboolean highlight) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); highlight = highlight != FALSE; if (view->priv->highlight_current_line != highlight) { view->priv->highlight_current_line = highlight; gtk_widget_queue_draw (GTK_WIDGET (view)); g_object_notify (G_OBJECT (view), "highlight_current_line"); } } /** * gtk_source_view_get_show_right_margin: * @view: a #GtkSourceView. * * Returns whether a right margin is displayed. * * Return value: %TRUE if the right margin is shown. */ gboolean gtk_source_view_get_show_right_margin (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->show_right_margin; } /** * gtk_source_view_set_show_right_margin: * @view: a #GtkSourceView. * @show: whether to show a right margin. * * If %TRUE a right margin is displayed. */ void gtk_source_view_set_show_right_margin (GtkSourceView *view, gboolean show) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); show = show != FALSE; if (view->priv->show_right_margin != show) { view->priv->show_right_margin = show; gtk_widget_queue_draw (GTK_WIDGET (view)); g_object_notify (G_OBJECT (view), "show-right-margin"); } } /** * gtk_source_view_get_right_margin_position: * @view: a #GtkSourceView. * * Gets the position of the right margin in the given @view. * * Return value: the position of the right margin. */ guint gtk_source_view_get_right_margin_position (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), DEFAULT_RIGHT_MARGIN_POSITION); return view->priv->right_margin_pos; } /** * gtk_source_view_set_right_margin_position: * @view: a #GtkSourceView. * @pos: the width in characters where to position the right margin. * * Sets the position of the right margin in the given @view. */ void gtk_source_view_set_right_margin_position (GtkSourceView *view, guint pos) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); g_return_if_fail (1 <= pos && pos <= MAX_RIGHT_MARGIN_POSITION); if (view->priv->right_margin_pos != pos) { view->priv->right_margin_pos = pos; view->priv->cached_right_margin_pos = -1; gtk_widget_queue_draw (GTK_WIDGET (view)); g_object_notify (G_OBJECT (view), "right-margin-position"); } } /** * gtk_source_view_set_smart_backspace: * @view: a #GtkSourceView. * @smart_backspace: whether to enable smart Backspace handling. * * When set to %TRUE, pressing the Backspace key will try to delete spaces * up to the previous tab stop. * * Since: 3.18 */ void gtk_source_view_set_smart_backspace (GtkSourceView *view, gboolean smart_backspace) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); smart_backspace = smart_backspace != FALSE; if (smart_backspace != view->priv->smart_backspace) { view->priv->smart_backspace = smart_backspace; g_object_notify (G_OBJECT (view), "smart-backspace"); } } /** * gtk_source_view_get_smart_backspace: * @view: a #GtkSourceView. * * Returns %TRUE if pressing the Backspace key will try to delete spaces * up to the previous tab stop. * * Returns: %TRUE if smart Backspace handling is enabled. * * Since: 3.18 */ gboolean gtk_source_view_get_smart_backspace (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->smart_backspace; } /** * gtk_source_view_set_smart_home_end: * @view: a #GtkSourceView. * @smart_home_end: the desired behavior among #GtkSourceSmartHomeEndType. * * Set the desired movement of the cursor when HOME and END keys * are pressed. */ void gtk_source_view_set_smart_home_end (GtkSourceView *view, GtkSourceSmartHomeEndType smart_home_end) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); if (view->priv->smart_home_end != smart_home_end) { view->priv->smart_home_end = smart_home_end; g_object_notify (G_OBJECT (view), "smart_home_end"); } } /** * gtk_source_view_get_smart_home_end: * @view: a #GtkSourceView. * * Returns a #GtkSourceSmartHomeEndType end value specifying * how the cursor will move when HOME and END keys are pressed. * * Returns: a #GtkSourceSmartHomeEndType value. */ GtkSourceSmartHomeEndType gtk_source_view_get_smart_home_end (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); return view->priv->smart_home_end; } /** * gtk_source_view_set_draw_spaces: * @view: a #GtkSourceView. * @flags: #GtkSourceDrawSpacesFlags specifing how white spaces should * be displayed * * Set if and how the spaces should be visualized. Specifying @flags as 0 will * disable display of spaces. * * For a finer-grained method, there is also the GtkSourceTag's * #GtkSourceTag:draw-spaces property. * * Deprecated: 3.24: Use gtk_source_space_drawer_set_types_for_locations() * instead. */ void gtk_source_view_set_draw_spaces (GtkSourceView *view, GtkSourceDrawSpacesFlags flags) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); if (view->priv->space_drawer == NULL) { return; } _gtk_source_space_drawer_set_flags (view->priv->space_drawer, flags); } /** * gtk_source_view_get_draw_spaces: * @view: a #GtkSourceView * * Returns the #GtkSourceDrawSpacesFlags specifying if and how spaces * should be displayed for this @view. * * Returns: the #GtkSourceDrawSpacesFlags, 0 if no spaces should be drawn. * Deprecated: 3.24: Use gtk_source_space_drawer_get_types_for_locations() * instead. */ GtkSourceDrawSpacesFlags gtk_source_view_get_draw_spaces (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), 0); if (view->priv->space_drawer == NULL) { return 0; } return _gtk_source_space_drawer_get_flags (view->priv->space_drawer); } /** * gtk_source_view_get_visual_column: * @view: a #GtkSourceView. * @iter: a position in @view. * * Determines the visual column at @iter taking into consideration the * #GtkSourceView:tab-width of @view. * * Returns: the visual column at @iter. */ guint gtk_source_view_get_visual_column (GtkSourceView *view, const GtkTextIter *iter) { gunichar tab_char; GtkTextIter position; guint column, indent_width; g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), 0); g_return_val_if_fail (iter != NULL, 0); tab_char = g_utf8_get_char ("\t"); column = 0; indent_width = get_real_indent_width (view); position = *iter; gtk_text_iter_set_line_offset (&position, 0); while (!gtk_text_iter_equal (&position, iter)) { if (gtk_text_iter_get_char (&position) == tab_char) { column += (indent_width - (column % indent_width)); } else { ++column; } /* FIXME: this does not handle invisible text correctly, but * gtk_text_iter_forward_visible_cursor_position is too slow */ if (!gtk_text_iter_forward_char (&position)) { break; } } return column; } static void update_background_pattern_color (GtkSourceView *view) { if (view->priv->style_scheme == NULL) { view->priv->background_pattern_color_set = FALSE; return; } view->priv->background_pattern_color_set = _gtk_source_style_scheme_get_background_pattern_color (view->priv->style_scheme, &view->priv->background_pattern_color); } static void update_current_line_color (GtkSourceView *view) { if (view->priv->style_scheme == NULL) { view->priv->current_line_color_set = FALSE; return; } view->priv->current_line_color_set = _gtk_source_style_scheme_get_current_line_color (view->priv->style_scheme, &view->priv->current_line_color); } static void update_right_margin_colors (GtkSourceView *view) { GtkWidget *widget = GTK_WIDGET (view); if (view->priv->right_margin_line_color != NULL) { gdk_rgba_free (view->priv->right_margin_line_color); view->priv->right_margin_line_color = NULL; } if (view->priv->right_margin_overlay_color != NULL) { gdk_rgba_free (view->priv->right_margin_overlay_color); view->priv->right_margin_overlay_color = NULL; } if (view->priv->style_scheme != NULL) { GtkSourceStyle *style; style = _gtk_source_style_scheme_get_right_margin_style (view->priv->style_scheme); if (style != NULL) { gchar *color_str = NULL; gboolean color_set; GdkRGBA color; g_object_get (style, "foreground", &color_str, "foreground-set", &color_set, NULL); if (color_set && color_str != NULL && gdk_rgba_parse (&color, color_str)) { view->priv->right_margin_line_color = gdk_rgba_copy (&color); view->priv->right_margin_line_color->alpha = RIGHT_MARGIN_LINE_ALPHA / 255.; } g_free (color_str); color_str = NULL; g_object_get (style, "background", &color_str, "background-set", &color_set, NULL); if (color_set && color_str != NULL && gdk_rgba_parse (&color, color_str)) { view->priv->right_margin_overlay_color = gdk_rgba_copy (&color); view->priv->right_margin_overlay_color->alpha = RIGHT_MARGIN_OVERLAY_ALPHA / 255.; } g_free (color_str); } } if (view->priv->right_margin_line_color == NULL) { GtkStyleContext *context; GdkRGBA color; context = gtk_widget_get_style_context (widget); gtk_style_context_save (context); gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); gtk_style_context_restore (context); view->priv->right_margin_line_color = gdk_rgba_copy (&color); view->priv->right_margin_line_color->alpha = RIGHT_MARGIN_LINE_ALPHA / 255.; } } static void update_style (GtkSourceView *view) { update_background_pattern_color (view); update_current_line_color (view); update_right_margin_colors (view); if (view->priv->space_drawer != NULL) { _gtk_source_space_drawer_update_color (view->priv->space_drawer, view); } gtk_widget_queue_draw (GTK_WIDGET (view)); } static void gtk_source_view_update_style_scheme (GtkSourceView *view) { GtkTextBuffer *buffer; GtkSourceStyleScheme *new_scheme = NULL; buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (GTK_SOURCE_IS_BUFFER (buffer)) { new_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer)); } if (view->priv->style_scheme == new_scheme) { return; } if (view->priv->style_scheme != NULL) { _gtk_source_style_scheme_unapply (view->priv->style_scheme, view); } g_set_object (&view->priv->style_scheme, new_scheme); if (view->priv->style_scheme != NULL) { _gtk_source_style_scheme_apply (view->priv->style_scheme, view); } update_style (view); } static void gtk_source_view_style_updated (GtkWidget *widget) { GtkSourceView *view = GTK_SOURCE_VIEW (widget); /* Call default handler first. */ if (GTK_WIDGET_CLASS (gtk_source_view_parent_class)->style_updated != NULL) { GTK_WIDGET_CLASS (gtk_source_view_parent_class)->style_updated (widget); } /* Re-set tab stops, but only if we already modified them, i.e. * do nothing with good old 8-space tabs. */ if (view->priv->tabs_set) { set_tab_stops_internal (view); } /* Make sure the margin position is recalculated on next redraw. */ view->priv->cached_right_margin_pos = -1; update_style (view); } static MarkCategory * mark_category_new (GtkSourceMarkAttributes *attributes, gint priority) { MarkCategory* category = g_slice_new (MarkCategory); category->attributes = g_object_ref (attributes); category->priority = priority; return category; } static void mark_category_free (MarkCategory *category) { if (category != NULL) { g_object_unref (category->attributes); g_slice_free (MarkCategory, category); } } /** * gtk_source_view_get_completion: * @view: a #GtkSourceView. * * Gets the #GtkSourceCompletion associated with @view. The returned object is * guaranteed to be the same for the lifetime of @view. Each #GtkSourceView * object has a different #GtkSourceCompletion. * * Returns: (transfer none): the #GtkSourceCompletion associated with @view. */ GtkSourceCompletion * gtk_source_view_get_completion (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); if (view->priv->completion == NULL) { view->priv->completion = gtk_source_completion_new (view); } return view->priv->completion; } /** * gtk_source_view_get_gutter: * @view: a #GtkSourceView. * @window_type: the gutter window type. * * Returns the #GtkSourceGutter object associated with @window_type for @view. * Only GTK_TEXT_WINDOW_LEFT and GTK_TEXT_WINDOW_RIGHT are supported, * respectively corresponding to the left and right gutter. The line numbers * and mark category icons are rendered in the left gutter. * * Since: 2.8 * * Returns: (transfer none): the #GtkSourceGutter. */ GtkSourceGutter * gtk_source_view_get_gutter (GtkSourceView *view, GtkTextWindowType window_type) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); g_return_val_if_fail (window_type == GTK_TEXT_WINDOW_LEFT || window_type == GTK_TEXT_WINDOW_RIGHT, NULL); if (window_type == GTK_TEXT_WINDOW_LEFT) { if (view->priv->left_gutter == NULL) { view->priv->left_gutter = _gtk_source_gutter_new (view, window_type); } return view->priv->left_gutter; } else { if (view->priv->right_gutter == NULL) { view->priv->right_gutter = _gtk_source_gutter_new (view, window_type); } return view->priv->right_gutter; } } /** * gtk_source_view_set_mark_attributes: * @view: a #GtkSourceView. * @category: the category. * @attributes: mark attributes. * @priority: priority of the category. * * Sets attributes and priority for the @category. */ void gtk_source_view_set_mark_attributes (GtkSourceView *view, const gchar *category, GtkSourceMarkAttributes *attributes, gint priority) { MarkCategory *mark_category; g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); g_return_if_fail (category != NULL); g_return_if_fail (GTK_SOURCE_IS_MARK_ATTRIBUTES (attributes)); g_return_if_fail (priority >= 0); mark_category = mark_category_new (attributes, priority); g_hash_table_replace (view->priv->mark_categories, g_strdup (category), mark_category); } /** * gtk_source_view_get_mark_attributes: * @view: a #GtkSourceView. * @category: the category. * @priority: place where priority of the category will be stored. * * Gets attributes and priority for the @category. * * Returns: (transfer none): #GtkSourceMarkAttributes for the @category. * The object belongs to @view, so it must not be unreffed. */ GtkSourceMarkAttributes * gtk_source_view_get_mark_attributes (GtkSourceView *view, const gchar *category, gint *priority) { MarkCategory *mark_category; g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); g_return_val_if_fail (category != NULL, NULL); mark_category = g_hash_table_lookup (view->priv->mark_categories, category); if (mark_category != NULL) { if (priority != NULL) { *priority = mark_category->priority; } return mark_category->attributes; } return NULL; } /** * gtk_source_view_set_background_pattern: * @view: a #GtkSourceView. * @background_pattern: the #GtkSourceBackgroundPatternType. * * Set if and how the background pattern should be displayed. * * Since: 3.16 */ void gtk_source_view_set_background_pattern (GtkSourceView *view, GtkSourceBackgroundPatternType background_pattern) { g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); if (view->priv->background_pattern != background_pattern) { view->priv->background_pattern = background_pattern; gtk_widget_queue_draw (GTK_WIDGET (view)); g_object_notify (G_OBJECT (view), "background-pattern"); } } /** * gtk_source_view_get_background_pattern: * @view: a #GtkSourceView * * Returns the #GtkSourceBackgroundPatternType specifying if and how * the background pattern should be displayed for this @view. * * Returns: the #GtkSourceBackgroundPatternType. * Since: 3.16 */ GtkSourceBackgroundPatternType gtk_source_view_get_background_pattern (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); return view->priv->background_pattern; } /** * gtk_source_view_get_space_drawer: * @view: a #GtkSourceView. * * Gets the #GtkSourceSpaceDrawer associated with @view. The returned object is * guaranteed to be the same for the lifetime of @view. Each #GtkSourceView * object has a different #GtkSourceSpaceDrawer. * * Returns: (transfer none): the #GtkSourceSpaceDrawer associated with @view. * Since: 3.24 */ GtkSourceSpaceDrawer * gtk_source_view_get_space_drawer (GtkSourceView *view) { g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); return view->priv->space_drawer; }