449 lines
12 KiB
C
449 lines
12 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
|
|
* gtksourcegutterrendererlines.c
|
|
* This file is part of GtkSourceView
|
|
*
|
|
* Copyright (C) 2010 - Jesse van den Kieboom
|
|
*
|
|
* 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 "gtksourcegutterrendererlines.h"
|
|
#include "gtksourceview.h"
|
|
|
|
struct _GtkSourceGutterRendererLinesPrivate
|
|
{
|
|
gint num_line_digits;
|
|
gint prev_line_num;
|
|
guint cursor_visible : 1;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceGutterRendererLines, gtk_source_gutter_renderer_lines, GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT)
|
|
|
|
static GtkTextBuffer *
|
|
get_buffer (GtkSourceGutterRendererLines *renderer)
|
|
{
|
|
GtkTextView *view;
|
|
|
|
view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (renderer));
|
|
|
|
return view != NULL ? gtk_text_view_get_buffer (view) : NULL;
|
|
}
|
|
|
|
static inline gint
|
|
count_num_digits (gint num_lines)
|
|
{
|
|
if (num_lines < 100)
|
|
{
|
|
return 2;
|
|
}
|
|
else if (num_lines < 1000)
|
|
{
|
|
return 3;
|
|
}
|
|
else if (num_lines < 10000)
|
|
{
|
|
return 4;
|
|
}
|
|
else if (num_lines < 100000)
|
|
{
|
|
return 5;
|
|
}
|
|
else if (num_lines < 1000000)
|
|
{
|
|
return 6;
|
|
}
|
|
else
|
|
{
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
static void
|
|
recalculate_size (GtkSourceGutterRendererLines *renderer)
|
|
{
|
|
gint num_lines;
|
|
gint num_digits = 0;
|
|
GtkTextBuffer *buffer;
|
|
|
|
buffer = get_buffer (renderer);
|
|
|
|
num_lines = gtk_text_buffer_get_line_count (buffer);
|
|
|
|
num_digits = count_num_digits (num_lines);
|
|
|
|
if (num_digits != renderer->priv->num_line_digits)
|
|
{
|
|
gchar markup[24];
|
|
gint size;
|
|
|
|
renderer->priv->num_line_digits = num_digits;
|
|
|
|
num_lines = MAX (num_lines, 99);
|
|
|
|
g_snprintf (markup, sizeof markup, "<b>%d</b>", num_lines);
|
|
gtk_source_gutter_renderer_text_measure_markup (GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer),
|
|
markup,
|
|
&size,
|
|
NULL);
|
|
|
|
gtk_source_gutter_renderer_set_size (GTK_SOURCE_GUTTER_RENDERER (renderer),
|
|
size);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_buffer_changed (GtkSourceBuffer *buffer,
|
|
GtkSourceGutterRendererLines *renderer)
|
|
{
|
|
recalculate_size (renderer);
|
|
}
|
|
|
|
static void
|
|
gutter_renderer_change_buffer (GtkSourceGutterRenderer *renderer,
|
|
GtkTextBuffer *old_buffer)
|
|
{
|
|
GtkSourceGutterRendererLines *lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer);
|
|
GtkTextBuffer *buffer;
|
|
|
|
if (old_buffer != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (old_buffer,
|
|
on_buffer_changed,
|
|
lines);
|
|
}
|
|
|
|
buffer = get_buffer (lines);
|
|
|
|
if (buffer != NULL)
|
|
{
|
|
g_signal_connect_object (buffer,
|
|
"changed",
|
|
G_CALLBACK (on_buffer_changed),
|
|
lines,
|
|
0);
|
|
|
|
recalculate_size (lines);
|
|
}
|
|
|
|
lines->priv->prev_line_num = 0;
|
|
|
|
if (GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->change_buffer != NULL)
|
|
{
|
|
GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->change_buffer (renderer,
|
|
old_buffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_view_style_updated (GtkTextView *view,
|
|
GtkSourceGutterRendererLines *renderer)
|
|
{
|
|
/* Force to recalculate the size. */
|
|
renderer->priv->num_line_digits = -1;
|
|
recalculate_size (renderer);
|
|
}
|
|
|
|
static void
|
|
on_view_notify_cursor_visible (GtkTextView *view,
|
|
GParamSpec *pspec,
|
|
GtkSourceGutterRendererLines *renderer)
|
|
{
|
|
renderer->priv->cursor_visible = gtk_text_view_get_cursor_visible (view);
|
|
}
|
|
|
|
static void
|
|
gutter_renderer_change_view (GtkSourceGutterRenderer *renderer,
|
|
GtkTextView *old_view)
|
|
{
|
|
GtkTextView *new_view;
|
|
|
|
if (old_view != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (old_view,
|
|
on_view_style_updated,
|
|
renderer);
|
|
g_signal_handlers_disconnect_by_func (old_view,
|
|
on_view_notify_cursor_visible,
|
|
renderer);
|
|
}
|
|
|
|
new_view = gtk_source_gutter_renderer_get_view (renderer);
|
|
|
|
if (new_view != NULL)
|
|
{
|
|
g_signal_connect_object (new_view,
|
|
"style-updated",
|
|
G_CALLBACK (on_view_style_updated),
|
|
renderer,
|
|
0);
|
|
|
|
g_signal_connect_object (new_view,
|
|
"notify::cursor-visible",
|
|
G_CALLBACK (on_view_notify_cursor_visible),
|
|
renderer,
|
|
0);
|
|
|
|
GTK_SOURCE_GUTTER_RENDERER_LINES (renderer)->priv->cursor_visible = gtk_text_view_get_cursor_visible (new_view);
|
|
}
|
|
|
|
if (GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->change_view != NULL)
|
|
{
|
|
GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->change_view (renderer,
|
|
old_view);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gutter_renderer_query_data (GtkSourceGutterRenderer *renderer,
|
|
GtkTextIter *start,
|
|
GtkTextIter *end,
|
|
GtkSourceGutterRendererState state)
|
|
{
|
|
GtkSourceGutterRendererLines *lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer);
|
|
gchar text[24];
|
|
gint line;
|
|
gint len;
|
|
gboolean current_line;
|
|
|
|
line = gtk_text_iter_get_line (start) + 1;
|
|
|
|
current_line = (state & GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR) &&
|
|
lines->priv->cursor_visible;
|
|
|
|
if (current_line)
|
|
{
|
|
len = g_snprintf (text, sizeof text, "<b>%d</b>", line);
|
|
}
|
|
else
|
|
{
|
|
len = g_snprintf (text, sizeof text, "%d", line);
|
|
}
|
|
|
|
gtk_source_gutter_renderer_text_set_markup (GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer),
|
|
text,
|
|
len);
|
|
}
|
|
|
|
static gint
|
|
get_last_visible_line_number (GtkSourceGutterRendererLines *lines)
|
|
{
|
|
GtkTextView *view;
|
|
GdkRectangle visible_rect;
|
|
GtkTextIter iter;
|
|
|
|
view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (lines));
|
|
|
|
gtk_text_view_get_visible_rect (view, &visible_rect);
|
|
|
|
gtk_text_view_get_line_at_y (view,
|
|
&iter,
|
|
visible_rect.y + visible_rect.height,
|
|
NULL);
|
|
|
|
gtk_text_iter_forward_line (&iter);
|
|
|
|
return gtk_text_iter_get_line (&iter);
|
|
}
|
|
|
|
static void
|
|
gutter_renderer_end (GtkSourceGutterRenderer *renderer)
|
|
{
|
|
GtkSourceGutterRendererLines *lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer);
|
|
GtkTextBuffer *buffer = get_buffer (lines);
|
|
|
|
if (buffer != NULL)
|
|
{
|
|
gint line_num = get_last_visible_line_number (lines);
|
|
|
|
/* When the text is modified in a GtkTextBuffer, GtkTextView tries to
|
|
* redraw the smallest required region. But the information displayed in
|
|
* the gutter may become invalid in a bigger region.
|
|
* See https://bugzilla.gnome.org/show_bug.cgi?id=732418 for an example
|
|
* where line numbers are not updated correctly when splitting a wrapped
|
|
* line.
|
|
* The performances should not be a big problem here. Correctness is
|
|
* more important than performances. It just triggers a second
|
|
* draw.
|
|
* The queue_draw() is called in gutter_renderer_end(), because
|
|
* the first draw is anyway needed to avoid flickering (if the
|
|
* first draw is not done, there will be a white region in the
|
|
* gutter during one frame).
|
|
* Another solution that has better performances is to compare
|
|
* the total number of lines in the buffer, instead of the last
|
|
* visible line. But it has the drawback that the gutter is
|
|
* continuously redrawn during file loading.
|
|
*
|
|
* FIXME A better solution would be to add a vfunc in the
|
|
* GutterRenderer so that the Gutter can ask each renderer for
|
|
* the invalidation region, before drawing. So that only one
|
|
* draw is needed, and the solution would be more generic (if
|
|
* other renderers also need a different invalidation region
|
|
* than the GtkTextView). But the GutterRendererClass doesn't
|
|
* have padding for future expansion, so it must wait for
|
|
* GtkSourceView 4.
|
|
*/
|
|
if (lines->priv->prev_line_num != line_num)
|
|
{
|
|
lines->priv->prev_line_num = line_num;
|
|
gtk_source_gutter_renderer_queue_draw (renderer);
|
|
}
|
|
}
|
|
|
|
if (GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->end != NULL)
|
|
{
|
|
GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_lines_parent_class)->end (renderer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
extend_selection_to_line (GtkSourceGutterRendererLines *renderer,
|
|
GtkTextIter *line_start)
|
|
{
|
|
GtkTextIter start;
|
|
GtkTextIter end;
|
|
GtkTextIter line_end;
|
|
GtkTextBuffer *buffer;
|
|
|
|
buffer = get_buffer (renderer);
|
|
|
|
gtk_text_buffer_get_selection_bounds (buffer,
|
|
&start,
|
|
&end);
|
|
|
|
line_end = *line_start;
|
|
|
|
if (!gtk_text_iter_ends_line (&line_end))
|
|
{
|
|
gtk_text_iter_forward_to_line_end (&line_end);
|
|
}
|
|
|
|
if (gtk_text_iter_compare (&start, line_start) < 0)
|
|
{
|
|
gtk_text_buffer_select_range (buffer,
|
|
&start,
|
|
&line_end);
|
|
}
|
|
else if (gtk_text_iter_compare (&end, &line_end) < 0)
|
|
{
|
|
/* if the selection is in this line, extend
|
|
* the selection to the whole line */
|
|
gtk_text_buffer_select_range (buffer,
|
|
&line_end,
|
|
line_start);
|
|
}
|
|
else
|
|
{
|
|
gtk_text_buffer_select_range (buffer,
|
|
&end,
|
|
line_start);
|
|
}
|
|
}
|
|
|
|
static void
|
|
select_line (GtkSourceGutterRendererLines *renderer,
|
|
GtkTextIter *line_start)
|
|
{
|
|
GtkTextIter iter;
|
|
GtkTextBuffer *buffer;
|
|
|
|
buffer = get_buffer (renderer);
|
|
|
|
iter = *line_start;
|
|
|
|
if (!gtk_text_iter_ends_line (&iter))
|
|
{
|
|
gtk_text_iter_forward_to_line_end (&iter);
|
|
}
|
|
|
|
/* Select the line, put the cursor at the end of the line */
|
|
gtk_text_buffer_select_range (buffer, &iter, line_start);
|
|
}
|
|
|
|
static void
|
|
gutter_renderer_activate (GtkSourceGutterRenderer *renderer,
|
|
GtkTextIter *iter,
|
|
GdkRectangle *rect,
|
|
GdkEvent *event)
|
|
{
|
|
GtkSourceGutterRendererLines *lines;
|
|
|
|
lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer);
|
|
|
|
if (event->type == GDK_BUTTON_PRESS && (event->button.button == 1))
|
|
{
|
|
GtkTextBuffer *buffer;
|
|
|
|
buffer = get_buffer (lines);
|
|
|
|
if ((event->button.state & GDK_CONTROL_MASK) != 0)
|
|
{
|
|
/* Single click + Ctrl -> select the line */
|
|
select_line (lines, iter);
|
|
}
|
|
else if ((event->button.state & GDK_SHIFT_MASK) != 0)
|
|
{
|
|
/* Single click + Shift -> extended current
|
|
selection to include the clicked line */
|
|
extend_selection_to_line (lines, iter);
|
|
}
|
|
else
|
|
{
|
|
gtk_text_buffer_place_cursor (buffer, iter);
|
|
}
|
|
}
|
|
else if (event->type == GDK_2BUTTON_PRESS && (event->button.button == 1))
|
|
{
|
|
select_line (lines, iter);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer,
|
|
GtkTextIter *iter,
|
|
GdkRectangle *area,
|
|
GdkEvent *event)
|
|
{
|
|
return get_buffer (GTK_SOURCE_GUTTER_RENDERER_LINES (renderer)) != NULL;
|
|
}
|
|
|
|
static void
|
|
gtk_source_gutter_renderer_lines_class_init (GtkSourceGutterRendererLinesClass *klass)
|
|
{
|
|
GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass);
|
|
|
|
renderer_class->query_data = gutter_renderer_query_data;
|
|
renderer_class->end = gutter_renderer_end;
|
|
renderer_class->query_activatable = gutter_renderer_query_activatable;
|
|
renderer_class->activate = gutter_renderer_activate;
|
|
renderer_class->change_buffer = gutter_renderer_change_buffer;
|
|
renderer_class->change_view = gutter_renderer_change_view;
|
|
}
|
|
|
|
static void
|
|
gtk_source_gutter_renderer_lines_init (GtkSourceGutterRendererLines *self)
|
|
{
|
|
self->priv = gtk_source_gutter_renderer_lines_get_instance_private (self);
|
|
}
|
|
|
|
GtkSourceGutterRenderer *
|
|
gtk_source_gutter_renderer_lines_new (void)
|
|
{
|
|
return g_object_new (GTK_SOURCE_TYPE_GUTTER_RENDERER_LINES, NULL);
|
|
}
|