5065 lines
130 KiB
C
5065 lines
130 KiB
C
/* -*- 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 <tyan@linux.se> and
|
|
* Chris Phelps <chicane@reninet.com>
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include "gtksourceview.h"
|
|
|
|
#include <string.h> /* For strlen */
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <pango/pango-tabs.h>
|
|
|
|
#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:
|
|
* |[
|
|
* <object class="GtkSourceView" id="source_view">
|
|
* <property name="tab_width">4</property>
|
|
* <property name="auto_indent">True</property>
|
|
* <child internal-child="completion">
|
|
* <object class="GtkSourceCompletion">
|
|
* <property name="select_on_show">False</property>
|
|
* </object>
|
|
* </child>
|
|
* </object>
|
|
* ]|
|
|
*/
|
|
|
|
/*
|
|
#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
|
|
* <keycombo><keycap>Control</keycap><keycap>space</keycap></keycombo>.
|
|
*
|
|
* 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 <Control>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 <emphasis>not</emphasis> 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;
|
|
}
|