gtksourceview3/gtksourceview/gtksourcegutter.c

1774 lines
45 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- /
* gtksourcegutter.c
* This file is part of GtkSourceView
*
* Copyright (C) 2009 - 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 "gtksourcegutter.h"
#include "gtksourcegutter-private.h"
#include "gtksourceview.h"
#include "gtksourceview-i18n.h"
#include "gtksourcegutterrenderer.h"
#include "gtksourcegutterrenderer-private.h"
/**
* SECTION:gutter
* @Short_description: Gutter object for GtkSourceView
* @Title: GtkSourceGutter
* @See_also: #GtkSourceView, #GtkSourceMark
*
* The #GtkSourceGutter object represents the left or right gutter of the text
* view. It is used by #GtkSourceView to draw the line numbers and
* #GtkSourceMark<!-- -->s that might be present on a line. By packing
* additional #GtkSourceGutterRenderer objects in the gutter, you can extend the
* gutter with your own custom drawings.
*
* To get a #GtkSourceGutter, use the gtk_source_view_get_gutter() function.
*
* The gutter works very much the same way as cells rendered in a #GtkTreeView.
* The concept is similar, with the exception that the gutter does not have an
* underlying #GtkTreeModel. The builtin line number renderer is at position
* #GTK_SOURCE_VIEW_GUTTER_POSITION_LINES (-30) and the marks renderer is at
* #GTK_SOURCE_VIEW_GUTTER_POSITION_MARKS (-20). The gutter sorts the renderers
* in ascending order, from left to right. So the marks are displayed on the
* right of the line numbers.
*/
enum
{
PROP_0,
PROP_VIEW,
PROP_WINDOW_TYPE,
PROP_XPAD,
PROP_YPAD
};
typedef struct
{
GtkSourceGutterRenderer *renderer;
gint prelit;
gint position;
gulong queue_draw_handler;
gulong size_changed_handler;
gulong notify_xpad_handler;
gulong notify_ypad_handler;
gulong notify_visible_handler;
} Renderer;
struct _GtkSourceGutterPrivate
{
GtkSourceView *view;
GtkTextWindowType window_type;
GtkOrientation orientation;
GList *renderers;
gint xpad;
gint ypad;
guint is_drawing : 1;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceGutter, gtk_source_gutter, G_TYPE_OBJECT)
static gboolean on_view_motion_notify_event (GtkSourceView *view,
GdkEventMotion *event,
GtkSourceGutter *gutter);
static gboolean on_view_enter_notify_event (GtkSourceView *view,
GdkEventCrossing *event,
GtkSourceGutter *gutter);
static gboolean on_view_leave_notify_event (GtkSourceView *view,
GdkEventCrossing *event,
GtkSourceGutter *gutter);
static gboolean on_view_button_press_event (GtkSourceView *view,
GdkEventButton *event,
GtkSourceGutter *gutter);
static gboolean on_view_query_tooltip (GtkSourceView *view,
gint x,
gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip,
GtkSourceGutter *gutter);
static void on_view_style_updated (GtkSourceView *view,
GtkSourceGutter *gutter);
static void do_redraw (GtkSourceGutter *gutter);
static void update_gutter_size (GtkSourceGutter *gutter);
static GdkWindow *
get_window (GtkSourceGutter *gutter)
{
return gtk_text_view_get_window (GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type);
}
static void
on_renderer_size_changed (GtkSourceGutterRenderer *renderer,
GParamSpec *spec,
GtkSourceGutter *gutter)
{
update_gutter_size (gutter);
}
static void
on_renderer_queue_draw (GtkSourceGutterRenderer *renderer,
GtkSourceGutter *gutter)
{
do_redraw (gutter);
}
static void
on_renderer_notify_padding (GtkSourceGutterRenderer *renderer,
GParamSpec *spec,
GtkSourceGutter *gutter)
{
update_gutter_size (gutter);
}
static void
on_renderer_notify_visible (GtkSourceGutterRenderer *renderer,
GParamSpec *spec,
GtkSourceGutter *gutter)
{
update_gutter_size (gutter);
}
static Renderer *
renderer_new (GtkSourceGutter *gutter,
GtkSourceGutterRenderer *renderer,
gint position)
{
Renderer *ret = g_slice_new0 (Renderer);
ret->renderer = g_object_ref_sink (renderer);
ret->position = position;
ret->prelit = -1;
_gtk_source_gutter_renderer_set_view (renderer,
GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type);
ret->size_changed_handler =
g_signal_connect (ret->renderer,
"notify::size",
G_CALLBACK (on_renderer_size_changed),
gutter);
ret->queue_draw_handler =
g_signal_connect (ret->renderer,
"queue-draw",
G_CALLBACK (on_renderer_queue_draw),
gutter);
ret->notify_xpad_handler =
g_signal_connect (ret->renderer,
"notify::xpad",
G_CALLBACK (on_renderer_notify_padding),
gutter);
ret->notify_ypad_handler =
g_signal_connect (ret->renderer,
"notify::ypad",
G_CALLBACK (on_renderer_notify_padding),
gutter);
ret->notify_visible_handler =
g_signal_connect (ret->renderer,
"notify::visible",
G_CALLBACK (on_renderer_notify_visible),
gutter);
return ret;
}
static void
renderer_free (Renderer *renderer)
{
g_signal_handler_disconnect (renderer->renderer,
renderer->queue_draw_handler);
g_signal_handler_disconnect (renderer->renderer,
renderer->size_changed_handler);
g_signal_handler_disconnect (renderer->renderer,
renderer->notify_xpad_handler);
g_signal_handler_disconnect (renderer->renderer,
renderer->notify_ypad_handler);
g_signal_handler_disconnect (renderer->renderer,
renderer->notify_visible_handler);
_gtk_source_gutter_renderer_set_view (renderer->renderer,
NULL,
GTK_TEXT_WINDOW_PRIVATE);
g_object_unref (renderer->renderer);
g_slice_free (Renderer, renderer);
}
static void
gtk_source_gutter_dispose (GObject *object)
{
GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (object);
g_list_free_full (gutter->priv->renderers, (GDestroyNotify)renderer_free);
gutter->priv->renderers = NULL;
gutter->priv->view = NULL;
G_OBJECT_CLASS (gtk_source_gutter_parent_class)->dispose (object);
}
static void
gtk_source_gutter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSourceGutter *self = GTK_SOURCE_GUTTER (object);
switch (prop_id)
{
case PROP_VIEW:
g_value_set_object (value, self->priv->view);
break;
case PROP_WINDOW_TYPE:
g_value_set_enum (value, self->priv->window_type);
break;
case PROP_XPAD:
g_value_set_int (value, self->priv->xpad);
break;
case PROP_YPAD:
g_value_set_int (value, self->priv->ypad);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
on_view_realize (GtkSourceView *view,
GtkSourceGutter *gutter)
{
update_gutter_size (gutter);
}
static void
set_view (GtkSourceGutter *gutter,
GtkSourceView *view)
{
gutter->priv->view = view;
g_signal_connect_object (view,
"motion-notify-event",
G_CALLBACK (on_view_motion_notify_event),
gutter,
0);
g_signal_connect_object (view,
"enter-notify-event",
G_CALLBACK (on_view_enter_notify_event),
gutter,
0);
g_signal_connect_object (view,
"leave-notify-event",
G_CALLBACK (on_view_leave_notify_event),
gutter,
0);
g_signal_connect_object (view,
"button-press-event",
G_CALLBACK (on_view_button_press_event),
gutter,
0);
g_signal_connect_object (view,
"query-tooltip",
G_CALLBACK (on_view_query_tooltip),
gutter,
0);
g_signal_connect_object (view,
"realize",
G_CALLBACK (on_view_realize),
gutter,
0);
g_signal_connect_object (view,
"style-updated",
G_CALLBACK (on_view_style_updated),
gutter,
0);
}
static void
do_redraw (GtkSourceGutter *gutter)
{
GdkWindow *window;
window = gtk_text_view_get_window (GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type);
if (window && !gutter->priv->is_drawing)
{
gdk_window_invalidate_rect (window, NULL, FALSE);
}
}
static gint
calculate_gutter_size (GtkSourceGutter *gutter,
GArray *sizes)
{
GList *item;
gint total_width = 0;
/* Calculate size */
for (item = gutter->priv->renderers; item; item = g_list_next (item))
{
Renderer *renderer = item->data;
gint width;
if (!gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
width = 0;
}
else
{
gint xpad;
gint size;
size = gtk_source_gutter_renderer_get_size (renderer->renderer);
gtk_source_gutter_renderer_get_padding (renderer->renderer,
&xpad,
NULL);
width = size + 2 * xpad;
}
if (sizes)
{
g_array_append_val (sizes, width);
}
total_width += width;
}
return total_width;
}
static void
update_gutter_size (GtkSourceGutter *gutter)
{
gint width = calculate_gutter_size (gutter, NULL);
gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type,
width);
}
static gboolean
set_padding (GtkSourceGutter *gutter,
gint *field,
gint padding,
const gchar *name,
gboolean resize)
{
if (*field == padding || padding < 0)
{
return FALSE;
}
*field = padding;
g_object_notify (G_OBJECT (gutter), name);
if (resize)
{
update_gutter_size (gutter);
}
return TRUE;
}
static gboolean
set_xpad (GtkSourceGutter *gutter,
gint xpad,
gboolean resize)
{
return set_padding (gutter, &gutter->priv->xpad, xpad, "xpad", resize);
}
static gboolean
set_ypad (GtkSourceGutter *gutter,
gint ypad,
gboolean resize)
{
return set_padding (gutter, &gutter->priv->ypad, ypad, "ypad", resize);
}
static void
gtk_source_gutter_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSourceGutter *self = GTK_SOURCE_GUTTER (object);
switch (prop_id)
{
case PROP_VIEW:
set_view (self, GTK_SOURCE_VIEW (g_value_get_object (value)));
break;
case PROP_WINDOW_TYPE:
self->priv->window_type = g_value_get_enum (value);
break;
case PROP_XPAD:
set_xpad (self, g_value_get_int (value), TRUE);
break;
case PROP_YPAD:
set_ypad (self, g_value_get_int (value), TRUE);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_source_gutter_constructed (GObject *object)
{
GtkSourceGutter *gutter;
gutter = GTK_SOURCE_GUTTER (object);
if (gutter->priv->window_type == GTK_TEXT_WINDOW_LEFT ||
gutter->priv->window_type == GTK_TEXT_WINDOW_RIGHT)
{
gutter->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
}
else
{
gutter->priv->orientation = GTK_ORIENTATION_VERTICAL;
}
G_OBJECT_CLASS (gtk_source_gutter_parent_class)->constructed (object);
}
static void
gtk_source_gutter_class_init (GtkSourceGutterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gtk_source_gutter_set_property;
object_class->get_property = gtk_source_gutter_get_property;
object_class->dispose = gtk_source_gutter_dispose;
object_class->constructed = gtk_source_gutter_constructed;
/**
* GtkSourceGutter:view:
*
* The #GtkSourceView of the gutter.
*/
g_object_class_install_property (object_class,
PROP_VIEW,
g_param_spec_object ("view",
"View",
"",
GTK_SOURCE_TYPE_VIEW,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/**
* GtkSourceGutter:window-type:
*
* The text window type on which the window is placed.
*/
g_object_class_install_property (object_class,
PROP_WINDOW_TYPE,
g_param_spec_enum ("window_type",
"Window Type",
"The gutters' text window type",
GTK_TYPE_TEXT_WINDOW_TYPE,
0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/**
* GtkSourceGutter:xpad:
*
* The x-padding.
*
* Deprecated: 3.12: Use the #GtkSourceGutterRenderer's
* #GtkSourceGutterRenderer:xpad property instead.
*/
g_object_class_install_property (object_class,
PROP_XPAD,
g_param_spec_int ("xpad",
"X Padding",
"The x-padding",
-1,
G_MAXINT,
0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_DEPRECATED));
/**
* GtkSourceGutter:ypad:
*
* The y-padding.
*
* Deprecated: 3.12: Use the #GtkSourceGutterRenderer's
* #GtkSourceGutterRenderer:ypad property instead.
*/
g_object_class_install_property (object_class,
PROP_YPAD,
g_param_spec_int ("ypad",
"Y Padding",
"The y-padding",
-1,
G_MAXINT,
0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_DEPRECATED));
}
static void
gtk_source_gutter_init (GtkSourceGutter *self)
{
self->priv = gtk_source_gutter_get_instance_private (self);
}
static gint
sort_by_position (Renderer *r1,
Renderer *r2,
gpointer data)
{
if (r1->position < r2->position)
{
return -1;
}
else if (r1->position > r2->position)
{
return 1;
}
else
{
return 0;
}
}
static void
append_renderer (GtkSourceGutter *gutter,
Renderer *renderer)
{
gutter->priv->renderers =
g_list_insert_sorted_with_data (gutter->priv->renderers,
renderer,
(GCompareDataFunc)sort_by_position,
NULL);
update_gutter_size (gutter);
}
GtkSourceGutter *
_gtk_source_gutter_new (GtkSourceView *view,
GtkTextWindowType type)
{
return g_object_new (GTK_SOURCE_TYPE_GUTTER,
"view", view,
"window_type", type,
NULL);
}
/* Public API */
/**
* gtk_source_gutter_get_view:
* @gutter: a #GtkSourceGutter.
*
* Returns: (transfer none): the associated #GtkSourceView.
* Since: 3.24
*/
GtkSourceView *
gtk_source_gutter_get_view (GtkSourceGutter *gutter)
{
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), NULL);
return gutter->priv->view;
}
/**
* gtk_source_gutter_get_window_type:
* @gutter: a #GtkSourceGutter.
*
* Returns: the #GtkTextWindowType of @gutter.
* Since: 3.24
*/
GtkTextWindowType
gtk_source_gutter_get_window_type (GtkSourceGutter *gutter)
{
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), GTK_TEXT_WINDOW_PRIVATE);
return gutter->priv->window_type;
}
/**
* gtk_source_gutter_get_window:
* @gutter: a #GtkSourceGutter.
*
* Get the #GdkWindow of the gutter. The window will only be available when the
* gutter has at least one, non-zero width, cell renderer packed.
*
* Returns: (transfer none): the #GdkWindow of the gutter, or %NULL
* if the gutter has no window.
*
* Since: 2.8
* Deprecated: 3.12: Use gtk_text_view_get_window() instead.
*/
GdkWindow *
gtk_source_gutter_get_window (GtkSourceGutter *gutter)
{
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), NULL);
g_return_val_if_fail (gutter->priv->view != NULL, NULL);
return get_window (gutter);
}
/**
* gtk_source_gutter_insert:
* @gutter: a #GtkSourceGutter.
* @renderer: a gutter renderer (must inherit from #GtkSourceGutterRenderer).
* @position: the renderer position.
*
* Insert @renderer into the gutter. If @renderer is yet unowned then gutter
* claims its ownership. Otherwise just increases renderer's reference count.
* @renderer cannot be already inserted to another gutter.
*
* Returns: %TRUE if operation succeeded. Otherwise %FALSE.
*
* Since: 3.0
*
**/
gboolean
gtk_source_gutter_insert (GtkSourceGutter *gutter,
GtkSourceGutterRenderer *renderer,
gint position)
{
Renderer* internal_renderer;
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), FALSE);
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), FALSE);
g_return_val_if_fail (gtk_source_gutter_renderer_get_view (renderer) == NULL, FALSE);
g_return_val_if_fail (gtk_source_gutter_renderer_get_window_type (renderer) == GTK_TEXT_WINDOW_PRIVATE, FALSE);
internal_renderer = renderer_new (gutter, renderer, position);
append_renderer (gutter, internal_renderer);
return TRUE;
}
static gboolean
renderer_find (GtkSourceGutter *gutter,
GtkSourceGutterRenderer *renderer,
Renderer **ret,
GList **retlist)
{
GList *list;
for (list = gutter->priv->renderers; list; list = g_list_next (list))
{
*ret = list->data;
if ((*ret)->renderer == renderer)
{
if (retlist)
{
*retlist = list;
}
return TRUE;
}
}
return FALSE;
}
/**
* gtk_source_gutter_reorder:
* @gutter: a #GtkSourceGutterRenderer.
* @renderer: a #GtkCellRenderer.
* @position: the new renderer position.
*
* Reorders @renderer in @gutter to new @position.
*
* Since: 2.8
*/
void
gtk_source_gutter_reorder (GtkSourceGutter *gutter,
GtkSourceGutterRenderer *renderer,
gint position)
{
Renderer *ret;
GList *retlist;
g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter));
g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer));
if (renderer_find (gutter, renderer, &ret, &retlist))
{
gutter->priv->renderers =
g_list_delete_link (gutter->priv->renderers,
retlist);
ret->position = position;
append_renderer (gutter, ret);
}
}
/**
* gtk_source_gutter_remove:
* @gutter: a #GtkSourceGutter.
* @renderer: a #GtkSourceGutterRenderer.
*
* Removes @renderer from @gutter.
*
* Since: 2.8
*/
void
gtk_source_gutter_remove (GtkSourceGutter *gutter,
GtkSourceGutterRenderer *renderer)
{
Renderer *ret;
GList *retlist;
g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter));
g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer));
if (renderer_find (gutter, renderer, &ret, &retlist))
{
gutter->priv->renderers =
g_list_delete_link (gutter->priv->renderers,
retlist);
update_gutter_size (gutter);
renderer_free (ret);
}
}
/**
* gtk_source_gutter_queue_draw:
* @gutter: a #GtkSourceGutter.
*
* Invalidates the drawable area of the gutter. You can use this to force a
* redraw of the gutter if something has changed and needs to be redrawn.
*
* Since: 2.8
*/
void
gtk_source_gutter_queue_draw (GtkSourceGutter *gutter)
{
g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter));
do_redraw (gutter);
}
typedef struct _LinesInfo LinesInfo;
struct _LinesInfo
{
gint total_height;
gint lines_count;
GArray *buffer_coords;
GArray *line_heights;
GArray *line_numbers;
GtkTextIter start;
GtkTextIter end;
};
static LinesInfo *
lines_info_new (void)
{
LinesInfo *info;
info = g_slice_new0 (LinesInfo);
info->buffer_coords = g_array_new (FALSE, FALSE, sizeof (gint));
info->line_heights = g_array_new (FALSE, FALSE, sizeof (gint));
info->line_numbers = g_array_new (FALSE, FALSE, sizeof (gint));
return info;
}
static void
lines_info_free (LinesInfo *info)
{
if (info != NULL)
{
g_array_free (info->buffer_coords, TRUE);
g_array_free (info->line_heights, TRUE);
g_array_free (info->line_numbers, TRUE);
g_slice_free (LinesInfo, info);
}
}
/* This function is taken and adapted from gtk+/tests/testtext.c */
static LinesInfo *
get_lines_info (GtkTextView *text_view,
gint first_y_buffer_coord,
gint last_y_buffer_coord)
{
LinesInfo *info;
GtkTextIter iter;
gint last_line_num = -1;
info = lines_info_new ();
/* Get iter at first y */
gtk_text_view_get_line_at_y (text_view, &iter, first_y_buffer_coord, NULL);
info->start = iter;
/* For each iter, get its location and add it to the arrays.
* Stop when we pass last_y_buffer_coord.
*/
while (!gtk_text_iter_is_end (&iter))
{
gint y;
gint height;
gint line_num;
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
g_array_append_val (info->buffer_coords, y);
g_array_append_val (info->line_heights, height);
info->total_height += height;
line_num = gtk_text_iter_get_line (&iter);
g_array_append_val (info->line_numbers, line_num);
last_line_num = line_num;
info->lines_count++;
if (last_y_buffer_coord <= (y + height))
{
break;
}
gtk_text_iter_forward_line (&iter);
}
if (gtk_text_iter_is_end (&iter))
{
gint y;
gint 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 (info->buffer_coords, y);
g_array_append_val (info->line_heights, height);
info->total_height += height;
g_array_append_val (info->line_numbers, line_num);
info->lines_count++;
}
}
if (info->lines_count == 0)
{
gint y = 0;
gint n = 0;
gint height;
info->lines_count = 1;
g_array_append_val (info->buffer_coords, y);
g_array_append_val (info->line_numbers, n);
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
g_array_append_val (info->line_heights, height);
info->total_height += height;
}
info->end = iter;
return info;
}
/* Returns %TRUE if @clip is set. @clip contains the area that should be drawn. */
static gboolean
get_clip_rectangle (GtkSourceGutter *gutter,
GtkSourceView *view,
cairo_t *cr,
GdkRectangle *clip)
{
GdkWindow *window = get_window (gutter);
if (window == NULL || !gtk_cairo_should_draw_window (cr, window))
{
return FALSE;
}
gtk_cairo_transform_to_window (cr, GTK_WIDGET (view), window);
return gdk_cairo_get_clip_rectangle (cr, clip);
}
static void
apply_style (GtkSourceGutter *gutter,
GtkSourceView *view,
GtkStyleContext *style_context,
cairo_t *cr)
{
const gchar *class;
GdkRGBA fg_color;
switch (gutter->priv->window_type)
{
case GTK_TEXT_WINDOW_TOP:
class = GTK_STYLE_CLASS_TOP;
break;
case GTK_TEXT_WINDOW_RIGHT:
class = GTK_STYLE_CLASS_RIGHT;
break;
case GTK_TEXT_WINDOW_BOTTOM:
class = GTK_STYLE_CLASS_BOTTOM;
break;
case GTK_TEXT_WINDOW_LEFT:
class = GTK_STYLE_CLASS_LEFT;
break;
case GTK_TEXT_WINDOW_PRIVATE:
case GTK_TEXT_WINDOW_WIDGET:
case GTK_TEXT_WINDOW_TEXT:
default:
g_return_if_reached ();
}
/* Apply classes ourselves, since we are in connect_after and so they
* are not set by gtk.
*/
gtk_style_context_add_class (style_context, class);
gtk_style_context_get_color (style_context,
gtk_style_context_get_state (style_context),
&fg_color);
gdk_cairo_set_source_rgba (cr, &fg_color);
}
/* Call gtk_source_gutter_renderer_begin() on each renderer. */
static void
begin_draw (GtkSourceGutter *gutter,
GtkTextView *view,
GArray *renderer_widths,
LinesInfo *info,
cairo_t *cr)
{
GdkRectangle background_area = { 0 };
GdkRectangle cell_area;
GList *l;
gint renderer_num;
background_area.x = 0;
background_area.height = info->total_height;
gtk_text_view_buffer_to_window_coords (view,
gutter->priv->window_type,
0,
g_array_index (info->buffer_coords, gint, 0),
NULL,
&background_area.y);
cell_area = background_area;
for (l = gutter->priv->renderers, renderer_num = 0;
l != NULL;
l = l->next, renderer_num++)
{
Renderer *renderer = l->data;
gint width;
gint xpad;
width = g_array_index (renderer_widths, gint, renderer_num);
if (!gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
g_assert_cmpint (width, ==, 0);
continue;
}
gtk_source_gutter_renderer_get_padding (renderer->renderer,
&xpad,
NULL);
background_area.width = width;
cell_area.width = background_area.width - 2 * xpad;
cell_area.x = background_area.x + xpad;
cairo_save (cr);
gdk_cairo_rectangle (cr, &background_area);
cairo_clip (cr);
gtk_source_gutter_renderer_begin (renderer->renderer,
cr,
&background_area,
&cell_area,
&info->start,
&info->end);
cairo_restore (cr);
background_area.x += background_area.width;
}
}
static void
draw_cells (GtkSourceGutter *gutter,
GtkTextView *view,
GArray *renderer_widths,
LinesInfo *info,
cairo_t *cr)
{
GtkTextBuffer *buffer;
GtkTextIter insert_iter;
gint cur_line;
GtkTextIter selection_start;
GtkTextIter selection_end;
gint selection_start_line = 0;
gint selection_end_line = 0;
gboolean has_selection;
GtkTextIter start;
gint i;
buffer = gtk_text_view_get_buffer (view);
gtk_text_buffer_get_iter_at_mark (buffer,
&insert_iter,
gtk_text_buffer_get_insert (buffer));
cur_line = gtk_text_iter_get_line (&insert_iter);
has_selection = gtk_text_buffer_get_selection_bounds (buffer,
&selection_start,
&selection_end);
if (has_selection)
{
selection_start_line = gtk_text_iter_get_line (&selection_start);
selection_end_line = gtk_text_iter_get_line (&selection_end);
}
start = info->start;
i = 0;
while (i < info->lines_count)
{
GtkTextIter end;
GdkRectangle background_area;
GtkSourceGutterRendererState state;
gint pos;
gint line_to_paint;
gint renderer_num;
GList *l;
end = start;
if (!gtk_text_iter_ends_line (&end))
{
/*
* It turns out that gtk_text_iter_forward_to_line_end
* is slower than jumping to the next line in the
* btree index and then moving backwards a character.
* We don't really care that we might be after the
* newline breaking characters, since those are part
* of the same line (rather than the next line).
*/
if (gtk_text_iter_forward_line (&end))
{
gtk_text_iter_backward_char (&end);
}
}
/* Possible improvement: if buffer and window coords have the
* same unit, there are probably some possible performance
* improvements by avoiding some buffer <-> window coords
* conversions.
*/
gtk_text_view_buffer_to_window_coords (view,
gutter->priv->window_type,
0,
g_array_index (info->buffer_coords, gint, i),
NULL,
&pos);
line_to_paint = g_array_index (info->line_numbers, gint, i);
background_area.y = pos;
background_area.height = g_array_index (info->line_heights, gint, i);
background_area.x = 0;
state = GTK_SOURCE_GUTTER_RENDERER_STATE_NORMAL;
if (line_to_paint == cur_line)
{
state |= GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR;
}
if (has_selection &&
selection_start_line <= line_to_paint && line_to_paint <= selection_end_line)
{
state |= GTK_SOURCE_GUTTER_RENDERER_STATE_SELECTED;
}
for (l = gutter->priv->renderers, renderer_num = 0;
l != NULL;
l = l->next, renderer_num++)
{
Renderer *renderer;
GdkRectangle cell_area;
gint width;
gint xpad;
gint ypad;
renderer = l->data;
width = g_array_index (renderer_widths, gint, renderer_num);
if (!gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
g_assert_cmpint (width, ==, 0);
continue;
}
gtk_source_gutter_renderer_get_padding (renderer->renderer,
&xpad,
&ypad);
background_area.width = width;
cell_area.y = background_area.y + ypad;
cell_area.height = background_area.height - 2 * ypad;
cell_area.x = background_area.x + xpad;
cell_area.width = background_area.width - 2 * xpad;
if (renderer->prelit >= 0 &&
cell_area.y <= renderer->prelit && renderer->prelit <= cell_area.y + cell_area.height)
{
state |= GTK_SOURCE_GUTTER_RENDERER_STATE_PRELIT;
}
gtk_source_gutter_renderer_query_data (renderer->renderer,
&start,
&end,
state);
cairo_save (cr);
gdk_cairo_rectangle (cr, &background_area);
cairo_clip (cr);
/* Call render with correct area */
gtk_source_gutter_renderer_draw (renderer->renderer,
cr,
&background_area,
&cell_area,
&start,
&end,
state);
cairo_restore (cr);
background_area.x += background_area.width;
state &= ~GTK_SOURCE_GUTTER_RENDERER_STATE_PRELIT;
}
i++;
gtk_text_iter_forward_line (&start);
}
}
static void
end_draw (GtkSourceGutter *gutter)
{
GList *l;
for (l = gutter->priv->renderers; l != NULL; l = l->next)
{
Renderer *renderer = l->data;
if (gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
gtk_source_gutter_renderer_end (renderer->renderer);
}
}
}
void
_gtk_source_gutter_draw (GtkSourceGutter *gutter,
GtkSourceView *view,
cairo_t *cr)
{
GdkRectangle clip;
GtkTextView *text_view;
gint first_y_window_coord;
gint last_y_window_coord;
gint first_y_buffer_coord;
gint last_y_buffer_coord;
GArray *renderer_widths;
LinesInfo *info;
GtkStyleContext *style_context;
if (!get_clip_rectangle (gutter, view, cr, &clip))
{
return;
}
gutter->priv->is_drawing = TRUE;
renderer_widths = g_array_new (FALSE, FALSE, sizeof (gint));
calculate_gutter_size (gutter, renderer_widths);
text_view = GTK_TEXT_VIEW (view);
first_y_window_coord = clip.y;
last_y_window_coord = first_y_window_coord + clip.height;
/* get the extents of the line printing */
gtk_text_view_window_to_buffer_coords (text_view,
gutter->priv->window_type,
0,
first_y_window_coord,
NULL,
&first_y_buffer_coord);
gtk_text_view_window_to_buffer_coords (text_view,
gutter->priv->window_type,
0,
last_y_window_coord,
NULL,
&last_y_buffer_coord);
info = get_lines_info (text_view,
first_y_buffer_coord,
last_y_buffer_coord);
style_context = gtk_widget_get_style_context (GTK_WIDGET (view));
gtk_style_context_save (style_context);
apply_style (gutter, view, style_context, cr);
begin_draw (gutter,
text_view,
renderer_widths,
info,
cr);
draw_cells (gutter,
text_view,
renderer_widths,
info,
cr);
/* Allow to call queue_redraw() in ::end. */
gutter->priv->is_drawing = FALSE;
end_draw (gutter);
gtk_style_context_restore (style_context);
g_array_free (renderer_widths, TRUE);
lines_info_free (info);
}
static Renderer *
renderer_at_x (GtkSourceGutter *gutter,
gint x,
gint *start,
gint *width)
{
GList *item;
gint s;
gint w;
update_gutter_size (gutter);
s = 0;
for (item = gutter->priv->renderers; item; item = g_list_next (item))
{
Renderer *renderer = item->data;
gint xpad;
if (!gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
continue;
}
w = gtk_source_gutter_renderer_get_size (renderer->renderer);
gtk_source_gutter_renderer_get_padding (renderer->renderer,
&xpad,
NULL);
s += xpad;
if (w > 0 && x >= s && x < s + w)
{
if (width)
{
*width = w;
}
if (start)
{
*start = s;
}
return renderer;
}
s += w + xpad;
}
return NULL;
}
static void
get_renderer_rect (GtkSourceGutter *gutter,
Renderer *renderer,
GtkTextIter *iter,
gint line,
GdkRectangle *rectangle,
gint start)
{
gint y;
gint ypad;
rectangle->x = start;
gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (gutter->priv->view),
iter,
&y,
&rectangle->height);
rectangle->width = gtk_source_gutter_renderer_get_size (renderer->renderer);
gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type,
0,
y,
NULL,
&rectangle->y);
gtk_source_gutter_renderer_get_padding (renderer->renderer,
NULL,
&ypad);
rectangle->y += ypad;
rectangle->height -= 2 * ypad;
}
static gboolean
renderer_query_activatable (GtkSourceGutter *gutter,
Renderer *renderer,
GdkEvent *event,
gint x,
gint y,
GtkTextIter *line_iter,
GdkRectangle *rect,
gint start)
{
gint y_buf;
gint yline;
GtkTextIter iter;
GdkRectangle r;
if (!renderer)
{
return FALSE;
}
gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (gutter->priv->view),
gutter->priv->window_type,
x,
y,
NULL,
&y_buf);
gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (gutter->priv->view),
&iter,
y_buf,
&yline);
if (yline > y_buf)
{
return FALSE;
}
get_renderer_rect (gutter, renderer, &iter, yline, &r, start);
if (line_iter)
{
*line_iter = iter;
}
if (rect)
{
*rect = r;
}
if (y < r.y || y > r.y + r.height)
{
return FALSE;
}
return gtk_source_gutter_renderer_query_activatable (renderer->renderer,
&iter,
&r,
event);
}
static gboolean
redraw_for_window (GtkSourceGutter *gutter,
GdkEvent *event,
gboolean act_on_window,
gint x,
gint y)
{
Renderer *at_x = NULL;
gint start = 0;
GList *item;
gboolean redraw;
if (event->any.window != get_window (gutter) && act_on_window)
{
return FALSE;
}
if (act_on_window)
{
at_x = renderer_at_x (gutter, x, &start, NULL);
}
redraw = FALSE;
for (item = gutter->priv->renderers; item; item = g_list_next (item))
{
Renderer *renderer = item->data;
gint prelit = renderer->prelit;
if (!gtk_source_gutter_renderer_get_visible (renderer->renderer))
{
renderer->prelit = -1;
}
else
{
if (renderer != at_x || !act_on_window)
{
renderer->prelit = -1;
}
else if (renderer_query_activatable (gutter,
renderer,
event,
x,
y,
NULL,
NULL,
start))
{
renderer->prelit = y;
}
else
{
renderer->prelit = -1;
}
}
redraw |= (renderer->prelit != prelit);
}
if (redraw)
{
do_redraw (gutter);
}
return FALSE;
}
static gboolean
on_view_motion_notify_event (GtkSourceView *view,
GdkEventMotion *event,
GtkSourceGutter *gutter)
{
return redraw_for_window (gutter,
(GdkEvent *)event,
TRUE,
(gint)event->x,
(gint)event->y);
}
static gboolean
on_view_enter_notify_event (GtkSourceView *view,
GdkEventCrossing *event,
GtkSourceGutter *gutter)
{
return redraw_for_window (gutter,
(GdkEvent *)event,
TRUE,
(gint)event->x,
(gint)event->y);
}
static gboolean
on_view_leave_notify_event (GtkSourceView *view,
GdkEventCrossing *event,
GtkSourceGutter *gutter)
{
return redraw_for_window (gutter,
(GdkEvent *)event,
FALSE,
(gint)event->x,
(gint)event->y);
}
static gboolean
on_view_button_press_event (GtkSourceView *view,
GdkEventButton *event,
GtkSourceGutter *gutter)
{
Renderer *renderer;
GtkTextIter line_iter;
gint start = -1;
GdkRectangle rect;
if (event->window != get_window (gutter))
{
return FALSE;
}
if (event->type != GDK_BUTTON_PRESS)
{
return FALSE;
}
/* Check cell renderer */
renderer = renderer_at_x (gutter, event->x, &start, NULL);
if (renderer_query_activatable (gutter,
renderer,
(GdkEvent *)event,
(gint)event->x,
(gint)event->y,
&line_iter,
&rect,
start))
{
gtk_source_gutter_renderer_activate (renderer->renderer,
&line_iter,
&rect,
(GdkEvent *)event);
do_redraw (gutter);
return TRUE;
}
return FALSE;
}
static gboolean
on_view_query_tooltip (GtkSourceView *view,
gint x,
gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip,
GtkSourceGutter *gutter)
{
GtkTextView *text_view = GTK_TEXT_VIEW (view);
Renderer *renderer;
gint start = 0;
gint width = 0;
gint y_buf;
gint yline;
GtkTextIter line_iter;
GdkRectangle rect;
if (keyboard_mode)
{
return FALSE;
}
/* Check cell renderer */
renderer = renderer_at_x (gutter, x, &start, &width);
if (!renderer)
{
return FALSE;
}
gtk_text_view_window_to_buffer_coords (text_view,
gutter->priv->window_type,
x, y,
NULL, &y_buf);
gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (view),
&line_iter,
y_buf,
&yline);
if (yline > y_buf)
{
return FALSE;
}
get_renderer_rect (gutter,
renderer,
&line_iter,
yline,
&rect,
start);
return gtk_source_gutter_renderer_query_tooltip (renderer->renderer,
&line_iter,
&rect,
x,
y,
tooltip);
}
static void
on_view_style_updated (GtkSourceView *view,
GtkSourceGutter *gutter)
{
gtk_source_gutter_queue_draw (gutter);
}
/**
* gtk_source_gutter_set_padding:
* @gutter:
* @xpad:
* @ypad:
*
* Deprecated: 3.12: Use gtk_source_gutter_renderer_set_padding() instead.
*/
void
gtk_source_gutter_set_padding (GtkSourceGutter *gutter,
gint xpad,
gint ypad)
{
g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter));
if (set_xpad (gutter, xpad, FALSE) || set_ypad (gutter, ypad, FALSE))
{
update_gutter_size (gutter);
}
}
/**
* gtk_source_gutter_get_padding:
* @gutter:
* @xpad:
* @ypad:
*
* Deprecated: 3.12: Use gtk_source_gutter_renderer_get_padding() instead.
*/
void
gtk_source_gutter_get_padding (GtkSourceGutter *gutter,
gint *xpad,
gint *ypad)
{
g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter));
if (xpad)
{
*xpad = gutter->priv->xpad;
}
if (ypad)
{
*ypad = gutter->priv->ypad;
}
}
/**
* gtk_source_gutter_get_renderer_at_pos:
* @gutter: A #GtkSourceGutter.
* @x: The x position to get identified.
* @y: The y position to get identified.
*
* Finds the #GtkSourceGutterRenderer at (x, y).
*
* Returns: (nullable) (transfer none): the renderer at (x, y) or %NULL.
*/
/* TODO: better document this function. The (x,y) position is different from
* the position passed to gtk_source_gutter_insert() and
* gtk_source_gutter_reorder(). The (x,y) coordinate can come from a click
* event, for example? Is the (x,y) a coordinate of the Gutter's GdkWindow?
* Where is the (0,0)? And so on.
* Also, this function doesn't seem to be used.
*/
GtkSourceGutterRenderer *
gtk_source_gutter_get_renderer_at_pos (GtkSourceGutter *gutter,
gint x,
gint y)
{
Renderer *renderer;
g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), NULL);
renderer = renderer_at_x (gutter, x, NULL, NULL);
if (renderer == NULL)
{
return NULL;
}
return renderer->renderer;
}