[gtksourceview] Implemented GtkSourceGutter



commit 0d803836e458f3b816b22bb8f79b289a4139da70
Author: Jesse van den Kieboom <jesse icecrew nl>
Date:   Sat May 30 12:34:06 2009 +0200

    Implemented GtkSourceGutter
    
    GtkSourceGutter replaces the custom drawing of lines and marks
    and at the same time, provides a public API to allow users to extend
    the gutter. The gutter works very much the same as a GtkCellLayout, in
    the sense that you can pack GtkCellRenderer objects in the gutter.
---
 docs/reference/Makefile.am                    |    3 +-
 docs/reference/gtksourceview-2.0-sections.txt |   25 +
 docs/reference/gtksourceview-2.0.types        |    2 +
 docs/reference/gtksourceview-docs.sgml        |    1 +
 docs/reference/tmpl/gutter.sgml               |  149 +++
 docs/reference/tmpl/view.sgml                 |   21 +-
 gtksourceview/Makefile.am                     |    5 +-
 gtksourceview/gtksourcegutter-private.h       |   17 +
 gtksourceview/gtksourcegutter.c               | 1194 ++++++++++++++++++++
 gtksourceview/gtksourcegutter.h               |  108 ++
 gtksourceview/gtksourceview-marshal.list      |    3 +
 gtksourceview/gtksourceview.c                 | 1434 ++++++++++++-------------
 gtksourceview/gtksourceview.h                 |   13 +-
 tests/test-widget.c                           |   87 +-
 14 files changed, 2253 insertions(+), 809 deletions(-)

diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 4506c5a..9675b51 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -33,7 +33,8 @@ IGNORE_HFILES =					\
 	gtktextregion.h				\
 	gtksourceengine.h			\
 	gtksourcecontextengine.h		\
-	gtksourceprintjob.h
+	gtksourceprintjob.h			\
+	gtksourcegutter-private.h
 
 # Extra options to supply to gtkdoc-mkdb
 MKDB_OPTIONS = --sgml-mode --output-format=xml --ignore-files=trio
diff --git a/docs/reference/gtksourceview-2.0-sections.txt b/docs/reference/gtksourceview-2.0-sections.txt
index 341efc1..c47424b 100644
--- a/docs/reference/gtksourceview-2.0-sections.txt
+++ b/docs/reference/gtksourceview-2.0-sections.txt
@@ -83,6 +83,7 @@ gtk_source_view_set_tab_width
 gtk_source_view_get_tab_width
 gtk_source_view_set_draw_spaces
 gtk_source_view_get_draw_spaces
+gtk_source_view_get_gutter
 <SUBSECTION Standard>
 GTK_IS_SOURCE_VIEW
 GTK_IS_SOURCE_VIEW_CLASS
@@ -95,6 +96,30 @@ gtk_source_view_get_type
 </SECTION>
 
 <SECTION>
+<FILE>gutter</FILE>
+<TITLE>GtkSourceGutter</TITLE>
+<INCLUDE>gtksourceview/gtksourcegutter.h</INCLUDE>
+GtkSourceGutter
+gtk_source_gutter_get_window
+gtk_source_gutter_insert
+gtk_source_gutter_reorder
+gtk_source_gutter_remove
+gtk_source_gutter_set_cell_data_func
+gtk_source_gutter_set_cell_size_func
+gtk_source_gutter_queue_draw
+<SUBSECTION Standard>
+GtkSourceGutterClass
+GTK_IS_SOURCE_GUTTER
+GTK_IS_SOURCE_GUTTER_CLASS
+GTK_SOURCE_GUTTER
+GTK_SOURCE_GUTTER_CLASS
+GTK_SOURCE_GUTTER_GET_CLASS
+GTK_TYPE_SOURCE_GUTTER
+GtkSourceGutterPrivate
+gtk_source_gutter_get_type
+</SECTION>
+
+<SECTION>
 <FILE>language</FILE>
 <TITLE>GtkSourceLanguage</TITLE>
 <INCLUDE>gtksourceview/gtksourcelanguage.h</INCLUDE>
diff --git a/docs/reference/gtksourceview-2.0.types b/docs/reference/gtksourceview-2.0.types
index 74b795d..fe4d27d 100644
--- a/docs/reference/gtksourceview-2.0.types
+++ b/docs/reference/gtksourceview-2.0.types
@@ -5,6 +5,7 @@
 #include <gtksourceview/gtksourcelanguagemanager.h>
 #include <gtksourceview/gtksourcestyleschememanager.h>
 #include <gtksourceview/gtksourcemark.h>
+#include <gtksourceview/gtksourcegutter.h>
 
 gtk_source_view_get_type
 gtk_source_buffer_get_type
@@ -15,3 +16,4 @@ gtk_source_style_get_type
 gtk_source_style_scheme_get_type
 gtk_source_style_scheme_manager_get_type
 gtk_source_mark_get_type
+gtk_source_gutter_get_type
diff --git a/docs/reference/gtksourceview-docs.sgml b/docs/reference/gtksourceview-docs.sgml
index 0f74543..47cef36 100644
--- a/docs/reference/gtksourceview-docs.sgml
+++ b/docs/reference/gtksourceview-docs.sgml
@@ -15,6 +15,7 @@
     <title>API reference</title>
     <xi:include href="xml/buffer.xml"/>
     <xi:include href="xml/iter.xml"/>
+    <xi:include href="xml/gutter.xml"/>
     <xi:include href="xml/mark.xml"/>
     <xi:include href="xml/view.xml"/>
     <xi:include href="xml/language.xml"/>
diff --git a/docs/reference/tmpl/gutter.sgml b/docs/reference/tmpl/gutter.sgml
new file mode 100644
index 0000000..9ffb548
--- /dev/null
+++ b/docs/reference/tmpl/gutter.sgml
@@ -0,0 +1,149 @@
+<!-- ##### SECTION Title ##### -->
+GtkSourceGutter
+
+<!-- ##### SECTION Short_Description ##### -->
+Gutter object for #GtkSourceView
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+The #GtkSourceGutter object represents the left and right gutters of the 
+text view. It is used by #GtkSourceView to draw the line numbers and category
+marks that might be present on a line. By packing additional #GtkCellRenderer
+objects in the gutter, you can extend the gutter by your own custom drawings.
+</para>
+<para>
+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. Instead, you should use 
+#gtk_source_gutter_set_cell_data_func to set a callback to fill in any
+of the cell renderers properties, given the line for which the cell is to be
+rendered. Renderers are inserted into the gutter at a certain position.
+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). You can use these values to
+position custom renderers accordingly.
+</para>
+<para>
+The width of a cell renderer can be specified as either fixed (using
+#gtk_cell_renderer_set_fixed_size) or dynamic, in which case you 
+<emphasis>MUST</emphasis> set
+#gtk_source_gutter_set_cell_size_func. This callback is used to set the
+properties of the renderer such that #gtk_cell_renderer_get_size yields the
+maximum width of the cell.
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### STRUCT GtkSourceGutter ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SIGNAL GtkSourceGutter::cell-activated ##### -->
+<para>
+
+</para>
+
+ sourcegutter: the object which received the signal.
+ arg1: 
+ arg2: 
+ arg3: 
+
+<!-- ##### SIGNAL GtkSourceGutter::query-tooltip ##### -->
+<para>
+
+</para>
+
+ sourcegutter: the object which received the signal.
+ arg1: 
+ arg2: 
+ arg3: 
+ Returns: 
+
+<!-- ##### ARG GtkSourceGutter:view ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GtkSourceGutter:window-type ##### -->
+<para>
+
+</para>
+
+<!-- ##### FUNCTION gtk_source_gutter_get_window ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_insert ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ renderer: 
+ position: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_reorder ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ renderer: 
+ position: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_remove ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ renderer: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_set_cell_data_func ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ renderer: 
+ func: 
+ func_data: 
+ destroy: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_set_cell_size_func ##### -->
+<para>
+
+</para>
+
+ gutter: 
+ renderer: 
+ func: 
+ func_data: 
+ destroy: 
+
+
+<!-- ##### FUNCTION gtk_source_gutter_queue_draw ##### -->
+<para>
+
+</para>
+
+ gutter: 
+
+
diff --git a/docs/reference/tmpl/view.sgml b/docs/reference/tmpl/view.sgml
index a527c75..39511cf 100644
--- a/docs/reference/tmpl/view.sgml
+++ b/docs/reference/tmpl/view.sgml
@@ -25,6 +25,15 @@ a text view which syntax highlighting, undo/redo and text marks. Use a
 </para>
 
 
+<!-- ##### SIGNAL GtkSourceView::line-mark-activated ##### -->
+<para>
+
+</para>
+
+ sourceview: the object which received the signal.
+ arg1: 
+ arg2: 
+
 <!-- ##### SIGNAL GtkSourceView::redo ##### -->
 <para>
 
@@ -107,10 +116,10 @@ a text view which syntax highlighting, undo/redo and text marks. Use a
 @parent_class: 
 @undo: 
 @redo: 
+ line_mark_activated: 
 @_gtk_source_reserved1: 
 @_gtk_source_reserved2: 
 @_gtk_source_reserved3: 
- _gtk_source_reserved4: 
 
 <!-- ##### USER_FUNCTION GtkSourceViewMarkTooltipFunc ##### -->
 <para>
@@ -461,3 +470,13 @@ Function type for setting up a tooltip for #GtkSourceMark.
 @Returns: 
 
 
+<!-- ##### FUNCTION gtk_source_view_get_gutter ##### -->
+<para>
+
+</para>
+
+ view: 
+ window_type: 
+ Returns: 
+
+
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index e646d1d..75063ab 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -27,7 +27,8 @@ libgtksourceview_headers =			\
 	gtksourcestyleschememanager.h		\
 	gtksourcestylescheme.h			\
 	gtksourcemark.h				\
-	gtksourceprintcompositor.h
+	gtksourceprintcompositor.h		\
+	gtksourcegutter.h
 
 libgtksourceview_2_0_la_SOURCES = 	\
 	gtksourcebuffer.c 		\
@@ -56,6 +57,8 @@ libgtksourceview_2_0_la_SOURCES = 	\
 	gtksourcecontextengine.c	\
 	gtksourcemark.c			\
 	gtksourceprintcompositor.c      \
+	gtksourcegutter-private.h	\
+	gtksourcegutter.c		\
 	$(libgtksourceview_headers)
 
 # do not distribute generated files
diff --git a/gtksourceview/gtksourcegutter-private.h b/gtksourceview/gtksourcegutter-private.h
new file mode 100644
index 0000000..bd96a78
--- /dev/null
+++ b/gtksourceview/gtksourcegutter-private.h
@@ -0,0 +1,17 @@
+#ifndef __GTK_SOURCE_GUTTER_PRIVATE_H__
+#define __GTK_SOURCE_GUTTER_PRIVATE_H__
+
+#include "gtksourcegutter.h"
+
+G_BEGIN_DECLS
+
+struct _GtkSourceView;
+
+GtkSourceGutter *gtk_source_gutter_new (struct _GtkSourceView *view,
+                                        GtkTextWindowType      type);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_GUTTER_PRIVATE_H__ */
+
+/* vi:ts=8 */
diff --git a/gtksourceview/gtksourcegutter.c b/gtksourceview/gtksourcegutter.c
new file mode 100644
index 0000000..8599828
--- /dev/null
+++ b/gtksourceview/gtksourcegutter.c
@@ -0,0 +1,1194 @@
+/*
+ * gtksourcegutter.c
+ * This file is part of gtksourceview
+ *
+ * Copyright (C) 2009 - Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+#include "gtksourcegutter-private.h"
+#include "gtksourceview.h"
+#include "gtksourceview-i18n.h"
+#include "gtksourceview-marshal.h"
+
+#define GTK_SOURCE_GUTTER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GTK_TYPE_SOURCE_GUTTER, GtkSourceGutterPrivate))
+
+/* Properties */
+enum
+{
+	PROP_0,
+	PROP_VIEW,
+	PROP_WINDOW_TYPE
+};
+
+/* Signals */
+enum
+{
+	CELL_ACTIVATED,
+	QUERY_TOOLTIP,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0,};
+
+typedef struct
+{
+	GtkCellRenderer *renderer;
+	gint position;
+
+	GtkSourceGutterDataFunc data_func;
+	gpointer data_func_data;
+	GDestroyNotify data_func_destroy;
+
+	GtkSourceGutterSizeFunc size_func;
+	gpointer size_func_data;
+	GDestroyNotify size_func_destroy;
+} Renderer;
+
+enum
+{
+	EXPOSE_EVENT,
+	MOTION_NOTIFY_EVENT,
+	BUTTON_PRESS_EVENT,
+	ENTER_NOTIFY_EVENT,
+	LEAVE_NOTIFY_EVENT,
+	QUERY_TOOLTIP_EVENT,
+	LAST_EXTERNAL_SIGNAL
+};
+
+struct _GtkSourceGutterPrivate
+{
+	GtkSourceView *view;
+	GtkTextWindowType window_type;
+
+	GList *renderers;
+	guint signals[LAST_EXTERNAL_SIGNAL];
+};
+
+G_DEFINE_TYPE (GtkSourceGutter, gtk_source_gutter, G_TYPE_OBJECT)
+
+static gboolean on_view_expose_event (GtkSourceView   *view,
+                                      GdkEventExpose  *event,
+                                      GtkSourceGutter *gutter);
+
+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
+view_notify (GtkSourceGutter *gutter,
+             gpointer         where_the_object_was)
+{
+	gutter->priv->view = NULL;
+}
+
+static Renderer *
+renderer_new (GtkCellRenderer  *renderer,
+              gint              position)
+{
+	Renderer *ret = g_slice_new0 (Renderer);
+
+	ret->renderer = g_object_ref (renderer);
+	ret->position = position;
+
+	return ret;
+}
+
+static void
+renderer_free (Renderer *renderer)
+{
+	if (renderer->data_func_destroy && renderer->data_func_data)
+	{
+		renderer->data_func_destroy (renderer->data_func_data);
+	}
+
+	if (renderer->size_func_destroy && renderer->size_func_data)
+	{
+		renderer->size_func_destroy (renderer->size_func_data);
+	}
+
+	g_object_unref (renderer->renderer);
+	g_slice_free (Renderer, renderer);
+}
+
+static void
+gtk_source_gutter_finalize (GObject *object)
+{
+	G_OBJECT_CLASS (gtk_source_gutter_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_gutter_dispose (GObject *object)
+{
+	GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (object);
+	gint i;
+
+	g_list_foreach (gutter->priv->renderers, (GFunc)renderer_free, NULL);
+	g_list_free (gutter->priv->renderers);
+
+	if (gutter->priv->view)
+	{
+		for (i = 0; i < LAST_EXTERNAL_SIGNAL; ++i)
+		{
+			g_signal_handler_disconnect (gutter->priv->view,
+				                     gutter->priv->signals[i]);
+		}
+
+		g_object_weak_unref (G_OBJECT (gutter->priv->view),
+		                     (GWeakNotify)view_notify,
+		                     gutter);
+
+		gutter->priv->view = NULL;
+	}
+
+	gutter->priv->renderers = NULL;
+}
+
+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;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+set_view (GtkSourceGutter *gutter,
+          GtkSourceView   *view)
+{
+	gutter->priv->view = view;
+
+	g_object_weak_ref (G_OBJECT (view),
+	                   (GWeakNotify)view_notify,
+	                   gutter);
+
+	gutter->priv->signals[EXPOSE_EVENT] =
+		g_signal_connect (view,
+	                  "expose-event",
+	                  G_CALLBACK (on_view_expose_event),
+	                  gutter);
+
+	gutter->priv->signals[MOTION_NOTIFY_EVENT] =
+		g_signal_connect (view,
+	                  "motion-notify-event",
+	                  G_CALLBACK (on_view_motion_notify_event),
+	                  gutter);
+
+	gutter->priv->signals[ENTER_NOTIFY_EVENT] =
+		g_signal_connect (view,
+	                  "enter-notify-event",
+	                  G_CALLBACK (on_view_enter_notify_event),
+	                  gutter);
+
+	gutter->priv->signals[LEAVE_NOTIFY_EVENT] =
+		g_signal_connect (view,
+	                  "leave-notify-event",
+	                  G_CALLBACK (on_view_leave_notify_event),
+	                  gutter);
+
+	gutter->priv->signals[BUTTON_PRESS_EVENT] =
+		g_signal_connect (view,
+	                  "button-press-event",
+	                  G_CALLBACK (on_view_button_press_event),
+	                  gutter);
+
+	gutter->priv->signals[QUERY_TOOLTIP_EVENT] =
+		g_signal_connect (view,
+	                  "query-tooltip",
+	                  G_CALLBACK (on_view_query_tooltip),
+	                  gutter);
+}
+
+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)
+	{
+		gdk_window_invalidate_rect (window, NULL, FALSE);
+	}
+}
+
+static void
+revalidate_size (GtkSourceGutter *gutter)
+{
+	GdkWindow *window;
+
+	window = gtk_source_gutter_get_window (gutter);
+
+	if (!window && gutter->priv->renderers)
+	{
+		/* Make window visible by setting its size to minimum size,
+		   actual size will be calculated in expose */
+		gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (gutter->priv->view),
+		                                      gutter->priv->window_type,
+		                                      1);
+		do_redraw (gutter);
+	}
+	else if (window && !gutter->priv->renderers)
+	{
+		/* Make window invisible by setting size to 0 */
+		gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (gutter->priv->view),
+		                                      gutter->priv->window_type,
+		                                      0);
+	}
+	else if (window)
+	{
+		/* Redraw the window. Actual size will be calculated in expose */
+		do_redraw (gutter);
+	}
+}
+
+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;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+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->finalize = gtk_source_gutter_finalize;
+	object_class->dispose = gtk_source_gutter_dispose;
+
+	/**
+	 * GtkSourceGutter:view:
+	 *
+	 * The #GtkSourceView of the gutter
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_VIEW,
+					 g_param_spec_object ("view",
+							      _("View"),
+							      _("The gutters' GtkSourceView"),
+							      GTK_TYPE_SOURCE_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::cell-activated:
+	 * @gutter: the #GtkSourceGutter
+	 * @renderer: the #GtkCellRenderer which was activated
+	 * @iter: the #GtkTextIter at which the cell was activated
+	 * @event: the #GdkEvent with which the cell was activated
+	 *
+	 * Emitted when a cell has been activated (for instance when there was
+	 * a button press on the cell). The signal is only emitted for cells
+	 * that have the #activatable property set to %TRUE.
+	 */
+	signals [CELL_ACTIVATED] =
+		g_signal_new ("cell-activated",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceGutterClass, cell_activated),
+			      NULL,
+			      NULL,
+			      _gtksourceview_marshal_VOID__OBJECT_BOXED_POINTER,
+			      G_TYPE_NONE,
+			      3,
+			      GTK_TYPE_CELL_RENDERER,
+			      GTK_TYPE_TEXT_ITER,
+			      G_TYPE_POINTER);
+
+	/**
+	 * GtkSourceGutter::query-tooltip:
+	 * @gutter: the #GtkSourceGutter
+	 * @renderer: the #GtkCellRenderer which was activated
+	 * @iter: the #GtkTextIter at which the cell was activated
+	 * @tooltip: the #GtkTooltip
+	 *
+	 * Emitted when a tooltip is requested for a specific cell. Signal
+	 * handlers can return %TRUE to notify the tooltip has been handled.
+	 */
+	signals [QUERY_TOOLTIP] =
+		g_signal_new ("query-tooltip",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceGutterClass, query_tooltip),
+			      g_signal_accumulator_true_handled,
+			      NULL,
+			      _gtksourceview_marshal_BOOL__OBJECT_BOXED_OBJECT,
+			      G_TYPE_BOOLEAN,
+			      3,
+			      GTK_TYPE_CELL_RENDERER,
+			      GTK_TYPE_TEXT_ITER,
+			      GTK_TYPE_TOOLTIP);
+
+	g_type_class_add_private (object_class, sizeof(GtkSourceGutterPrivate));
+}
+
+static void
+gtk_source_gutter_init (GtkSourceGutter *self)
+{
+	self->priv = GTK_SOURCE_GUTTER_GET_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)
+{
+	GList *item;
+
+	gutter->priv->renderers = 
+		g_list_insert_sorted_with_data (gutter->priv->renderers,
+			                        renderer,
+			                        (GCompareDataFunc)sort_by_position,
+			                        NULL);
+}
+
+GtkSourceGutter *
+gtk_source_gutter_new (GtkSourceView     *view,
+                       GtkTextWindowType  type)
+{
+	return g_object_new (GTK_TYPE_SOURCE_GUTTER,
+	                     "view", view,
+	                     "window_type", type,
+	                     NULL);
+}
+
+/* Public API */
+
+/**
+ * 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: the #GdkWindow of the gutter, or %NULL if the gutter has no window.
+ *
+ * Since: 2.8
+ */
+GdkWindow *
+gtk_source_gutter_get_window (GtkSourceGutter *gutter)
+{
+	g_return_val_if_fail (GTK_IS_SOURCE_GUTTER (gutter), NULL);
+	g_return_val_if_fail (gutter->priv->view != NULL, NULL);
+
+	return gtk_text_view_get_window (GTK_TEXT_VIEW (gutter->priv->view),
+	                                 gutter->priv->window_type);
+}
+
+/**
+ * gtk_source_gutter_insert:
+ * @gutter: a #GtkSourceGutter
+ * @renderer: a #GtkCellRenderer
+ * @position: the renderers position
+ *
+ * Inserts @renderer into @gutter at @position.
+ *
+ * Since: 2.8
+ */
+void
+gtk_source_gutter_insert (GtkSourceGutter *gutter,
+                          GtkCellRenderer *renderer,
+                          gint             position)
+{
+	g_return_if_fail (GTK_IS_SOURCE_GUTTER (gutter));
+	g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+	append_renderer (gutter, renderer_new (renderer, position));
+	revalidate_size (gutter);
+}
+
+static gboolean
+renderer_find (GtkSourceGutter  *gutter,
+               GtkCellRenderer  *renderer,
+               Renderer        **ret,
+               GList           **retlist)
+{
+	GList *list;
+
+	for (list = gutter->priv->renderers; list; list = g_list_next (list))
+	{
+		*ret = (Renderer *)list->data;
+
+		if ((*ret)->renderer == renderer)
+		{
+			if (retlist)
+			{
+				*retlist = list;
+			}
+
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+
+/**
+ * gtk_source_gutter_reorder:
+ * @gutter: a #GtkSourceGutter
+ * @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,
+                           GtkCellRenderer *renderer,
+                           gint             position)
+{
+	Renderer *ret;
+	GList *retlist;
+
+	g_return_if_fail (GTK_IS_SOURCE_GUTTER (gutter));
+	g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+	if (renderer_find (gutter, renderer, &ret, &retlist))
+	{
+		gutter->priv->renderers = g_list_remove_link (gutter->priv->renderers, retlist);
+		append_renderer (gutter, renderer);
+	}
+}
+
+/**
+ * gtk_source_gutter_remove:
+ * @gutter: a #GtkSourceGutter
+ * @renderer: a #GtkCellRenderer
+ *
+ * Removes @renderer from @gutter.
+ *
+ * Since: 2.8
+ */
+void
+gtk_source_gutter_remove (GtkSourceGutter *gutter,
+                          GtkCellRenderer *renderer)
+{
+	Renderer *ret;
+	GList *retlist;
+
+	g_return_if_fail (GTK_IS_SOURCE_GUTTER (gutter));
+	g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+	if (renderer_find (gutter, renderer, &ret, &retlist))
+	{
+		gutter->priv->renderers = g_list_remove_link (gutter->priv->renderers, retlist);
+
+		revalidate_size (gutter);
+		renderer_free (ret);
+	}
+}
+
+/**
+ * gtk_source_gutter_set_cell_data_func:
+ * @gutter: a #GtkSourceGutter
+ * @renderer: a #GtkCellRenderer
+ * @func: the #GtkSourceGutterDataFunc to use
+ * @func_data: the user data for @func
+ * @destroy: the destroy notification for @func_data
+ *
+ * Sets the #GtkSourceGutterDataFunc to use for @renderer. This function is
+ * used to setup the cell renderer properties for rendering the current cell.
+ *
+ * Since: 2.8
+ */
+void
+gtk_source_gutter_set_cell_data_func (GtkSourceGutter         *gutter,
+                                      GtkCellRenderer         *renderer,
+                                      GtkSourceGutterDataFunc  func,
+                                      gpointer                 func_data,
+                                      GDestroyNotify           destroy)
+{
+	Renderer *ret;
+
+	g_return_if_fail (GTK_IS_SOURCE_GUTTER (gutter));
+	g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+	if (!renderer_find (gutter, renderer, &ret, NULL))
+	{
+		return;
+	}
+
+	if (ret->data_func_data && ret->data_func_destroy)
+	{
+		ret->data_func_destroy (ret->data_func_data);
+	}
+
+	ret->data_func = func;
+	ret->data_func_data = func_data;
+	ret->data_func_destroy = destroy;
+
+	revalidate_size (gutter);
+}
+
+/**
+ * gtk_source_gutter_set_cell_size_func:
+ * @gutter: a #GtkSourceGutter
+ * @renderer: a #GtkCellRenderer
+ * @func: the #GtkSourceGutterSizeFunc to use
+ * @func_data: the user data for @func
+ * @destroy: the destroy notification for @func_data
+ *
+ * Sets the #GtkSourceGutterSizeFunc to use for @renderer. This function is
+ * used to setup the cell renderer properties for measuring the maximum size
+ * of the cell.
+ *
+ * Since: 2.8
+ */
+void
+gtk_source_gutter_set_cell_size_func (GtkSourceGutter         *gutter,
+                                      GtkCellRenderer         *renderer,
+                                      GtkSourceGutterSizeFunc  func,
+                                      gpointer                 func_data,
+                                      GDestroyNotify           destroy)
+{
+	Renderer *ret;
+
+	g_return_if_fail (GTK_IS_SOURCE_GUTTER (gutter));
+	g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+	if (!renderer_find (gutter, renderer, &ret, NULL))
+	{
+		return;
+	}
+
+	if (ret->size_func_data && ret->size_func_destroy)
+	{
+		ret->size_func_destroy (ret->size_func_data);
+	}
+
+	ret->size_func = func;
+	ret->size_func_data = func_data;
+	ret->size_func_destroy = destroy;
+
+	revalidate_size (gutter);
+}
+
+/**
+ * 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_IS_SOURCE_GUTTER (gutter));
+
+	revalidate_size (gutter);
+}
+
+/* Callbacks */
+static gint
+calculate_size (GtkSourceGutter  *gutter,
+                Renderer         *renderer)
+{
+	gint width = -1;
+
+	gtk_cell_renderer_get_fixed_size (renderer->renderer, &width, NULL);
+
+	if (width == -1 && renderer->size_func)
+	{
+		gint height;
+		renderer->size_func (gutter, renderer->renderer, renderer->size_func_data);
+
+		gtk_cell_renderer_get_size (renderer->renderer,
+		                            GTK_WIDGET (gutter->priv->view),
+		                            NULL, NULL, NULL,
+		                            &width, &height);
+	}
+
+	return width == -1 ? 1 : width;
+}
+
+static gint
+calculate_sizes (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 = (Renderer *)item->data;
+		gint width;
+
+		width = calculate_size (gutter, renderer);
+		g_array_append_val (sizes, width);
+
+		total_width += width;
+	}
+
+	return total_width;
+}
+
+/* This function is taken from gtk+/tests/testtext.c */
+static void
+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 size;
+      	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;
+  	size = 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;
+
+	if (count == 0)
+	{
+		gint y = 0;
+		gint n = 0;
+		gint height;
+
+		*countp = 1;
+
+		g_array_append_val (buffer_coords, y);
+		g_array_append_val (numbers, n);
+
+		if (line_heights)
+		{
+			gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
+			g_array_append_val (line_heights, height);
+		}
+	}
+}
+
+static gboolean
+on_view_expose_event (GtkSourceView   *view,
+                      GdkEventExpose  *event,
+                      GtkSourceGutter *gutter)
+{
+	GdkWindow *window;
+	GtkTextView *text_view;
+	gint size;
+	gint y1, y2;
+	GArray *numbers;
+	GArray *pixels;
+	GArray *heights;
+	GArray *sizes;
+	GtkTextIter cur;
+	gint cur_line;
+	gint count;
+	gint i;
+	GList *item;
+	gint x;
+	gint y;
+
+	window = gtk_source_gutter_get_window (gutter);
+
+	if (window == NULL || event->window != window)
+	{
+		return FALSE;
+	}
+
+	gdk_window_get_pointer (window, &x, &y, NULL);
+
+	text_view = GTK_TEXT_VIEW (view);
+	sizes = g_array_new (FALSE, FALSE, sizeof (gint));
+
+	size = calculate_sizes (gutter, sizes);
+
+	if (gtk_text_view_get_border_window_size (text_view, gutter->priv->window_type) != size)
+	{
+		/* Will trigger a new expose */
+		gtk_text_view_set_border_window_size (text_view, gutter->priv->window_type, size);
+		return FALSE;
+	}
+
+	y1 = event->area.y;
+	y2 = y1 + event->area.height;
+
+	/* get the extents of the line printing */
+	gtk_text_view_window_to_buffer_coords (text_view,
+					       gutter->priv->window_type,
+					       0,
+					       y1,
+					       NULL,
+					       &y1);
+
+	gtk_text_view_window_to_buffer_coords (text_view,
+					       gutter->priv->window_type,
+					       0,
+					       y2,
+					       NULL,
+					       &y2);
+
+	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. */
+	get_lines (text_view, y1, y2, pixels, heights, numbers, &count);
+
+	gtk_text_buffer_get_iter_at_mark (text_view->buffer,
+					  &cur,
+					  gtk_text_buffer_get_insert (text_view->buffer));
+
+	cur_line = gtk_text_iter_get_line (&cur);
+
+	for (i = 0; i < count; ++i)
+	{
+		gint pos;
+		gint line_to_paint;
+		GdkRectangle cell_area;
+		gint idx = 0;
+
+		gtk_text_view_buffer_to_window_coords (text_view,
+						       gutter->priv->window_type,
+						       0,
+						       g_array_index (pixels, gint, i),
+						       NULL,
+						       &pos);
+
+		line_to_paint = g_array_index (numbers, gint, i);
+
+		cell_area.x = 0;
+		cell_area.y = pos;
+		cell_area.height = g_array_index (heights, gint, i);
+
+		for (item = gutter->priv->renderers; item; item = g_list_next (item))
+		{
+			Renderer *renderer = (Renderer *)item->data;
+			gint width = g_array_index (sizes, gint, idx++);
+			GtkCellRendererState state = 0;
+
+			cell_area.width = width;
+
+			/* Call data func if needed */
+			if (renderer->data_func)
+			{
+				renderer->data_func (gutter,
+				                     renderer->renderer,
+				                     line_to_paint,
+				                     line_to_paint == cur_line,
+				                     renderer->data_func_data);
+			}
+
+			if (x >= cell_area.x && x < cell_area.x + cell_area.width &&
+			    y >= cell_area.y && y < cell_area.y + cell_area.height)
+			{
+				GtkCellRendererMode mode;
+
+				g_object_get (G_OBJECT (renderer->renderer),
+				              "mode", &mode,
+				              NULL);
+
+				if (mode & GTK_CELL_RENDERER_MODE_ACTIVATABLE)
+					state = GTK_CELL_RENDERER_PRELIT;
+			}
+
+			/* Call render with correct area */
+			gtk_cell_renderer_render (renderer->renderer,
+			                          window,
+			                          GTK_WIDGET (view),
+			                          &cell_area,
+			                          &cell_area,
+			                          &cell_area,
+			                          state);
+
+			cell_area.x += cell_area.width;
+		}
+	}
+
+	return FALSE;
+}
+
+static gboolean
+redraw_for_window (GtkSourceGutter *gutter,
+                   GdkEventAny     *event,
+                   gboolean         act_on_window)
+{
+	if (event->window == gtk_source_gutter_get_window (gutter) || !act_on_window)
+	{
+		gtk_source_gutter_queue_draw (gutter);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+on_view_motion_notify_event (GtkSourceView    *view,
+                             GdkEventMotion   *event,
+                             GtkSourceGutter  *gutter)
+{
+	return redraw_for_window (gutter, (GdkEventAny *)event, TRUE);
+}
+
+
+static gboolean
+on_view_enter_notify_event (GtkSourceView     *view,
+                            GdkEventCrossing  *event,
+                            GtkSourceGutter   *gutter)
+{
+	return redraw_for_window (gutter, (GdkEventAny *)event, TRUE);
+}
+
+
+static gboolean
+on_view_leave_notify_event (GtkSourceView     *view,
+                            GdkEventCrossing  *event,
+                            GtkSourceGutter   *gutter)
+{
+	return redraw_for_window (gutter, (GdkEventAny *)event, FALSE);
+}
+
+static Renderer *
+renderer_at_x (GtkSourceGutter *gutter,
+               gint             x,
+               gint            *start,
+               gint            *width)
+{
+	GList *item;
+
+	for (item = gutter->priv->renderers; item; item = g_list_next (item))
+	{
+		Renderer *renderer = (Renderer *)item->data;
+
+		*width = calculate_size (gutter, renderer);
+
+		if (x >= *start && x < *start + *width)
+		{
+			return renderer;
+		}
+
+		*start += *width;
+	}
+
+	return NULL;
+}
+
+static gboolean
+on_view_button_press_event (GtkSourceView    *view,
+                            GdkEventButton   *event,
+                            GtkSourceGutter  *gutter)
+{
+	Renderer *renderer;
+	gint yline;
+	GtkTextIter line_iter;
+	GtkTextIter cur;
+	gint cur_line;
+	gint line;
+	gint y_buf;
+	gint start = 0;
+	gint width = 0;
+	GtkTextBuffer *buffer;
+	GtkCellRendererMode mode;
+
+	if (event->window != gtk_source_gutter_get_window (gutter))
+	{
+		return FALSE;
+	}
+
+	/* Check cell renderer */
+	renderer = renderer_at_x (gutter, event->x, &start, &width);
+
+	if (!renderer)
+	{
+		return FALSE;
+	}
+
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+	                                       gutter->priv->window_type,
+	                                       event->x, event->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;
+	}
+
+	line = gtk_text_iter_get_line (&line_iter);
+	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));
+
+	cur_line = gtk_text_iter_get_line (&cur);
+
+	if (renderer->data_func)
+	{
+		renderer->data_func (gutter,
+		                     renderer->renderer,
+		                     line,
+		                     line == cur_line,
+		                     renderer->data_func_data);
+	}
+
+	g_object_get (G_OBJECT (renderer->renderer),
+	              "mode", &mode,
+	              NULL);
+
+	if (mode & GTK_CELL_RENDERER_MODE_ACTIVATABLE)
+	{
+		GdkRectangle area;
+		gchar *path;
+		gboolean ret;
+
+		gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (view),
+		                               &line_iter,
+		                               &area.y,
+		                               &area.height);
+		area.x = start;
+		area.width = width;
+
+		path = g_strdup_printf ("%d", line);
+
+		ret = gtk_cell_renderer_activate (renderer->renderer,
+		                                  (GdkEvent *)event,
+		                                  GTK_WIDGET (gutter->priv->view),
+		                                  path,
+		                                  &area,
+		                                  &area,
+		                                  0);
+
+		g_signal_emit (gutter,
+		               signals[CELL_ACTIVATED],
+		               0,
+		               renderer->renderer,
+		               &line_iter,
+		               (GdkEvent *)event);
+
+		g_free (path);
+		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;
+	gboolean ret;
+
+	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;
+	}
+
+	g_signal_emit (gutter,
+	               signals[QUERY_TOOLTIP],
+	               0,
+	               renderer->renderer,
+	               &line_iter,
+	               tooltip,
+	               &ret);
+
+	return ret;
+}
+
+/* vi:ts=8 */
diff --git a/gtksourceview/gtksourcegutter.h b/gtksourceview/gtksourcegutter.h
new file mode 100644
index 0000000..32c57a9
--- /dev/null
+++ b/gtksourceview/gtksourcegutter.h
@@ -0,0 +1,108 @@
+/*
+ * gtksourcegutter.h
+ * This file is part of gtksourceview
+ *
+ * Copyright (C) 2009 - Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef __GTK_SOURCE_GUTTER_H__
+#define __GTK_SOURCE_GUTTER_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SOURCE_GUTTER			(gtk_source_gutter_get_type ())
+#define GTK_SOURCE_GUTTER(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SOURCE_GUTTER, GtkSourceGutter))
+#define GTK_SOURCE_GUTTER_CONST(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SOURCE_GUTTER, GtkSourceGutter const))
+#define GTK_SOURCE_GUTTER_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SOURCE_GUTTER, GtkSourceGutterClass))
+#define GTK_IS_SOURCE_GUTTER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_GUTTER))
+#define GTK_IS_SOURCE_GUTTER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_GUTTER))
+#define GTK_SOURCE_GUTTER_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_GUTTER, GtkSourceGutterClass))
+
+typedef struct _GtkSourceGutter			GtkSourceGutter;
+typedef struct _GtkSourceGutterClass	GtkSourceGutterClass;
+typedef struct _GtkSourceGutterPrivate	GtkSourceGutterPrivate;
+
+struct _GtkSourceGutter {
+	GObject parent;
+
+	GtkSourceGutterPrivate *priv;
+};
+
+struct _GtkSourceGutterClass {
+	GObjectClass parent_class;
+
+	void (*cell_activated) 			(GtkSourceGutter *gutter,
+						 GtkCellRenderer *renderer,
+						 GtkTextIter     *iter,
+						 GdkEvent        *event);
+
+	gboolean (*query_tooltip) 		(GtkSourceGutter *gutter,
+						 GtkCellRenderer *renderer,
+						 GtkTextIter     *iter,
+						 GtkTooltip      *tooltip);
+};
+
+typedef void (*GtkSourceGutterDataFunc)		(GtkSourceGutter         *gutter,
+						 GtkCellRenderer         *cell,
+						 gint                     line_number,
+						 gboolean                 current_line,
+						 gpointer                 data);
+
+typedef void (*GtkSourceGutterSizeFunc)		(GtkSourceGutter         *gutter,
+						 GtkCellRenderer         *cell,
+						 gpointer                 data);
+
+GType gtk_source_gutter_get_type 		(void) G_GNUC_CONST;
+
+GdkWindow *gtk_source_gutter_get_window 	(GtkSourceGutter         *gutter);
+
+void gtk_source_gutter_insert			(GtkSourceGutter         *gutter,
+						 GtkCellRenderer         *renderer,
+						 gint                     position);
+
+void gtk_source_gutter_reorder			(GtkSourceGutter	 *gutter,
+                                                 GtkCellRenderer         *renderer,
+                                                 gint                     position);
+
+void gtk_source_gutter_remove			(GtkSourceGutter         *gutter,
+						 GtkCellRenderer         *renderer);
+
+void gtk_source_gutter_set_cell_data_func	(GtkSourceGutter         *gutter,
+						 GtkCellRenderer         *renderer,
+						 GtkSourceGutterDataFunc  func,
+						 gpointer                 func_data,
+						 GDestroyNotify           destroy);
+
+void gtk_source_gutter_set_cell_size_func	(GtkSourceGutter         *gutter,
+                                                 GtkCellRenderer         *renderer,
+                                                 GtkSourceGutterSizeFunc  func,
+                                                 gpointer                 func_data,
+                                                 GDestroyNotify           destroy);
+
+void gtk_source_gutter_queue_draw		(GtkSourceGutter         *gutter);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_GUTTER_H__ */
+
+/* vi:ts=8 */
diff --git a/gtksourceview/gtksourceview-marshal.list b/gtksourceview/gtksourceview-marshal.list
index 93e1e2a..5518236 100644
--- a/gtksourceview/gtksourceview-marshal.list
+++ b/gtksourceview/gtksourceview-marshal.list
@@ -4,3 +4,6 @@ VOID:BOXED
 VOID:BOXED,BOXED
 VOID:INT,INT
 VOID:STRING
+VOID:OBJECT,BOXED,POINTER
+BOOL:OBJECT,BOXED,OBJECT
+VOID:BOXED,POINTER
diff --git a/gtksourceview/gtksourceview.c b/gtksourceview/gtksourceview.c
index 67878bf..40da9c8 100644
--- a/gtksourceview/gtksourceview.c
+++ b/gtksourceview/gtksourceview.c
@@ -39,6 +39,7 @@
 #include "gtksourceview-typebuiltins.h"
 #include "gtksourcemark.h"
 #include "gtksourceview.h"
+#include "gtksourcegutter-private.h"
 
 /*
 #define ENABLE_DEBUG
@@ -65,7 +66,6 @@
 #define COMPOSITE_ALPHA                 225
 #define GUTTER_PIXMAP 			16
 #define DEFAULT_TAB_WIDTH 		8
-#define MIN_NUMBER_WINDOW_WIDTH		20
 #define MAX_TAB_WIDTH			32
 #define MAX_INDENT_WIDTH		32
 
@@ -79,6 +79,7 @@
 enum {
 	UNDO,
 	REDO,
+	LINE_MARK_ACTIVATED,
 	LAST_SIGNAL
 };
 
@@ -115,7 +116,6 @@ struct _GtkSourceViewPrivate
 	gboolean	 show_right_margin;
 	guint		 right_margin_pos;
 	gint             cached_right_margin_pos;
-	gint		 cached_line_number_width;
 
 	gboolean	 style_scheme_applied;
 	GtkSourceStyleScheme *style_scheme;
@@ -128,10 +128,15 @@ struct _GtkSourceViewPrivate
 	GHashTable 	*mark_categories;
 
 	GtkSourceBuffer *source_buffer;
-	gint		 old_lines;
 
 	GdkColor         current_line_color;
 	guint            current_line_color_set : 1;
+
+	GtkSourceGutter *left_gutter;
+	GtkSourceGutter *right_gutter;
+
+	GtkCellRenderer *line_renderer;
+	GtkCellRenderer *marks_renderer;
 };
 
 
@@ -153,11 +158,11 @@ typedef struct
 {
 	gint priority;
 	GdkPixbuf *pixbuf;
-	
+
 	GtkSourceViewMarkTooltipFunc tooltip_func;
 	gpointer tooltip_data;
 	GDestroyNotify tooltip_data_notify;
-	
+
 	GdkColor background;
 	guint background_set : 1;
 	guint tooltip_markup : 1;
@@ -197,8 +202,6 @@ static gint     gtk_source_view_expose 			(GtkWidget         *widget,
 							 GdkEventExpose    *event);
 static gboolean	gtk_source_view_key_press_event		(GtkWidget         *widget,
 							 GdkEventKey       *event);
-static gboolean	gtk_source_view_button_press_event	(GtkWidget         *widget,
-							 GdkEventButton    *event);
 static void 	view_dnd_drop 				(GtkTextView       *view,
 							 GdkDragContext    *context,
 							 gint               x,
@@ -224,11 +227,6 @@ static void	gtk_source_view_get_property		(GObject           *object,
 static void     gtk_source_view_style_set               (GtkWidget         *widget,
 							 GtkStyle          *previous_style);
 static void	gtk_source_view_realize			(GtkWidget         *widget);
-static gboolean gtk_source_view_query_tooltip		(GtkWidget	   *widget,
-							 gint		    x,
-							 gint               y,
-							 gboolean           keyboard_mode,
-							 GtkTooltip        *tooltip);
 static void	gtk_source_view_update_style_scheme	(GtkSourceView     *view);
 
 static MarkCategory *
@@ -262,11 +260,9 @@ gtk_source_view_class_init (GtkSourceViewClass *klass)
 	object_class->set_property = gtk_source_view_set_property;
 
 	widget_class->key_press_event = gtk_source_view_key_press_event;
-	widget_class->button_press_event = gtk_source_view_button_press_event;
 	widget_class->expose_event = gtk_source_view_expose;
 	widget_class->style_set = gtk_source_view_style_set;
 	widget_class->realize = gtk_source_view_realize;
-	widget_class->query_tooltip = gtk_source_view_query_tooltip;
 
 	textview_class->populate_popup = gtk_source_view_populate_popup;
 	textview_class->move_cursor = gtk_source_view_move_cursor;
@@ -444,6 +440,29 @@ gtk_source_view_class_init (GtkSourceViewClass *klass)
 			      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,
+			      _gtksourceview_marshal_VOID__BOXED_POINTER,
+			      G_TYPE_NONE,
+			      2,
+			      GTK_TYPE_TEXT_ITER,
+			      G_TYPE_POINTER);
+
 	binding_set = gtk_binding_set_by_class (klass);
 
 	gtk_binding_entry_add_signal (binding_set,
@@ -716,6 +735,443 @@ notify_buffer (GtkSourceView *view)
 	set_source_buffer (view, GTK_TEXT_VIEW (view)->buffer);
 }
 
+static gint
+sort_marks_by_priority (gconstpointer m1,
+			gconstpointer m2,
+			gpointer data)
+{
+	GtkSourceMark *mark1 = GTK_SOURCE_MARK (m1);
+	GtkSourceMark *mark2 = GTK_SOURCE_MARK (m2);
+	GtkSourceView *view = GTK_SOURCE_VIEW (data);
+	GtkTextIter iter1, iter2;
+	gint line1;
+	gint line2;
+
+	gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark1)),
+					  &iter1,
+					  GTK_TEXT_MARK (mark1));
+	gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark2)),
+					  &iter2,
+					  GTK_TEXT_MARK (mark2));
+
+	line1 = gtk_text_iter_get_line (&iter1);
+	line2 = gtk_text_iter_get_line (&iter2);
+
+	if (line1 == line2)
+	{
+		guint priority1 = gtk_source_view_get_mark_category_priority (view,
+						gtk_source_mark_get_category (mark1));
+		guint priority2 = gtk_source_view_get_mark_category_priority (view,
+						gtk_source_mark_get_category (mark2));
+
+		return priority1 - priority2;
+	}
+	else
+	{
+		return line2 - line1;
+	}
+}
+
+static GdkPixbuf *
+composite_marks (GtkSourceView *view,
+		 GSList        *marks)
+{
+	GdkPixbuf *composite;
+	gint mark_width, mark_height;
+
+	/* Draw the mark with higher priority */
+	marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view);
+
+	composite = NULL;
+	mark_width = mark_height = 0;
+
+	/* composite all the pixbufs for the marks present at the line */
+	do
+	{
+		GtkSourceMark *mark;
+		GdkPixbuf *pixbuf;
+
+		mark = marks->data;
+
+		pixbuf = gtk_source_view_get_mark_category_pixbuf (view,
+					gtk_source_mark_get_category (mark));
+
+		if (pixbuf != NULL)
+		{
+			if (composite == NULL)
+			{
+				composite = gdk_pixbuf_copy (pixbuf);
+				mark_width = gdk_pixbuf_get_width (composite);
+				mark_height = gdk_pixbuf_get_height (composite);
+			}
+			else
+			{
+				gint pixbuf_w;
+				gint pixbuf_h;
+
+				pixbuf_w = gdk_pixbuf_get_width (pixbuf);
+				pixbuf_h = gdk_pixbuf_get_height (pixbuf);
+
+				gdk_pixbuf_composite (pixbuf,
+						      composite,
+						      0, 0,
+						      mark_width, mark_height,
+						      0, 0,
+						      (double) pixbuf_w / mark_width,
+						      (double) pixbuf_h / mark_height,
+						      GDK_INTERP_BILINEAR,
+						      COMPOSITE_ALPHA);
+			}
+
+			g_object_unref (pixbuf);
+		}
+
+		marks = g_slist_next (marks);
+	}
+	while (marks);
+
+	return composite;
+}
+
+static void
+marks_renderer_data_func (GtkSourceGutter *gutter,
+                          GtkCellRenderer *renderer,
+                          gint             line_number,
+                          gboolean         current_line,
+                          GtkSourceView   *view)
+{
+	GSList *marks;
+	GdkPixbuf *pixbuf = NULL;
+
+	if (view->priv->source_buffer)
+	{
+		marks = gtk_source_buffer_get_source_marks_at_line (view->priv->source_buffer,
+								    line_number,
+								    NULL);
+
+		if (marks != NULL)
+		{
+			GtkTextIter iter;
+
+			gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (view->priv->source_buffer),
+							  &iter,
+							  line_number);
+
+			/* draw marks for the line */
+			pixbuf = composite_marks (view, marks);
+			g_slist_free (marks);
+		}
+	}
+
+	g_object_set (G_OBJECT (renderer),
+	              "pixbuf", pixbuf,
+	              "xpad", 2,
+	              "yalign", 0.0,
+	              "xalign", 0.5,
+	              "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+	              NULL);
+}
+
+static void
+line_renderer_data_func (GtkSourceGutter *gutter,
+                         GtkCellRenderer *renderer,
+                         gint             line_number,
+                         gboolean         current_line,
+                         GtkSourceView   *view)
+{
+	gchar *text;
+
+	text = g_strdup_printf ("%d", line_number + 1);
+	g_object_set (G_OBJECT (renderer),
+	              "text", text,
+	              "xalign", 1.0,
+	              "yalign", 0.0,
+	              "xpad", 2,
+	              "ypad", 0,
+	              "weight", current_line ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+	              "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+	              NULL);
+
+	GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view));
+
+	if (style)
+	{
+		g_object_set (G_OBJECT (renderer),
+			      "foreground-gdk", &style->fg[GTK_STATE_NORMAL],
+			      NULL);
+	}
+
+	g_object_set (G_OBJECT (renderer),
+	              "background-set", FALSE,
+	              NULL);
+
+	g_free (text);
+}
+
+static void
+line_renderer_size_func (GtkSourceGutter *gutter,
+                         GtkCellRenderer *renderer,
+                         GtkSourceView   *view)
+{
+	gchar *markup;
+	gint count;
+
+	count = gtk_text_buffer_get_line_count (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+	markup = g_strdup_printf ("<b>%d</b>", MAX(99, count));
+
+	g_object_set (G_OBJECT (renderer),
+                      "markup", markup,
+                      "xpad", 2,
+                      "ypad", 0,
+                      NULL);
+	g_free (markup);
+}
+
+static void
+extend_selection_to_line (GtkTextBuffer *buf, GtkTextIter *line_start)
+{
+	GtkTextIter start;
+	GtkTextIter end;
+	GtkTextIter line_end;
+
+	gtk_text_buffer_get_selection_bounds (buf, &start, &end);
+
+	line_end = *line_start;
+	gtk_text_iter_forward_to_line_end (&line_end);
+
+	if (gtk_text_iter_compare (&start, line_start) < 0)
+	{
+		gtk_text_buffer_select_range (buf, &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 (buf, &line_end, line_start);
+	}
+	else
+	{
+		gtk_text_buffer_select_range (buf, &end, line_start);
+	}
+}
+
+static void
+select_line (GtkTextBuffer *buf, GtkTextIter *line_start)
+{
+	GtkTextIter iter;
+
+	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 (buf, &iter, line_start);
+}
+
+static void
+renderer_activated (GtkSourceGutter *gutter,
+                    GtkCellRenderer *renderer,
+                    GtkTextIter     *iter,
+                    GdkEvent        *event,
+                    GtkSourceView   *view)
+{
+	if (renderer == view->priv->marks_renderer)
+	{
+		g_signal_emit (view,
+		               signals[LINE_MARK_ACTIVATED],
+		               0,
+		               iter,
+		               event);
+	}
+	else if (renderer == view->priv->line_renderer)
+	{
+		GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+		if (event->type == GDK_BUTTON_PRESS && (event->button.button == 1))
+		{
+			if ((event->button.state & GDK_CONTROL_MASK) != 0)
+			{
+				/* Single click + Ctrl -> select the line */
+				select_line (buf, 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 (buf, iter);
+			}
+			else
+			{
+				gtk_text_buffer_place_cursor (buf, iter);
+			}
+		}
+		else if (event->type == GDK_2BUTTON_PRESS && (event->button.button == 1))
+		{
+			select_line (buf, iter);
+		}
+	}
+}
+
+static void
+set_tooltip_widget_from_marks (GtkSourceView *view,
+			       GtkTooltip *tooltip,
+			       GSList *marks)
+{
+	GtkWidget *vbox;
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_widget_show (vbox);
+
+	while (marks != NULL)
+	{
+		MarkCategory *cat;
+		GtkSourceMark *mark;
+
+		mark = marks->data;
+		cat = gtk_source_view_get_mark_category (view, mark);
+
+		if (cat != NULL)
+		{
+			GtkWidget *image;
+			GtkWidget *label;
+			GtkWidget *hbox;
+			GtkWidget *separator;
+			gchar *text;
+
+			hbox = gtk_hbox_new (FALSE, 4);
+			gtk_widget_show (hbox);
+			gtk_box_pack_start (GTK_BOX (vbox), hbox,
+					    FALSE, FALSE, 0);
+
+			text = cat->tooltip_func (mark, cat->tooltip_data);
+
+			if (text != NULL)
+			{
+				label = gtk_label_new (NULL);
+				if (cat->tooltip_markup)
+					gtk_label_set_markup (GTK_LABEL (label), text);
+				else
+					gtk_label_set_text (GTK_LABEL (label), text);
+
+				gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+				gtk_widget_show (label);
+
+				gtk_box_pack_end (GTK_BOX (hbox), label,
+						  TRUE, TRUE, 0);
+
+				if (cat->pixbuf != NULL)
+				{
+					image = gtk_image_new_from_pixbuf (cat->pixbuf);
+					gtk_widget_show (image);
+
+					gtk_box_pack_start (GTK_BOX (hbox), image,
+							    FALSE, FALSE, 0);
+				}
+
+				if (g_slist_length (marks) != 1)
+				{
+					separator = gtk_hseparator_new ();
+					gtk_widget_show (separator);
+					gtk_box_pack_start (GTK_BOX (vbox), separator,
+							    FALSE, FALSE, 0);
+				}
+
+				g_free (text);
+			}
+		}
+
+		marks = g_slist_delete_link (marks, marks);
+	}
+
+	gtk_tooltip_set_custom (tooltip, vbox);
+}
+
+static gboolean
+renderer_query_tooltip (GtkSourceGutter *gutter,
+                        GtkCellRenderer *renderer,
+                        GtkTextIter     *iter,
+                        GtkTooltip      *tooltip,
+                        GtkSourceView   *view)
+{
+	GSList *marks;
+	GtkSourceBuffer *buffer;
+	gint line;
+
+	if (renderer != view->priv->marks_renderer)
+	{
+		return FALSE;
+	}
+
+	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+	line = gtk_text_iter_get_line (iter);
+
+	marks = gtk_source_buffer_get_source_marks_at_line (buffer,
+							    line,
+							    NULL);
+
+	if (marks != NULL)
+	{
+		marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view);
+		marks = g_slist_reverse (marks);
+
+		set_tooltip_widget_from_marks (view, tooltip, marks);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+init_left_gutter (GtkSourceView *view)
+{
+	GtkSourceGutter *gutter;
+
+	view->priv->line_renderer = gtk_cell_renderer_text_new ();
+	view->priv->marks_renderer = gtk_cell_renderer_pixbuf_new ();
+
+	gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT);
+
+	gtk_source_gutter_insert (gutter, 
+	                          view->priv->line_renderer, 
+	                          GTK_SOURCE_VIEW_GUTTER_POSITION_LINES);
+
+	gtk_source_gutter_insert (gutter, 
+	                          view->priv->marks_renderer,
+	                          GTK_SOURCE_VIEW_GUTTER_POSITION_MARKS);
+
+	gtk_cell_renderer_set_fixed_size (view->priv->line_renderer, 0, 0);
+	gtk_cell_renderer_set_fixed_size (view->priv->marks_renderer, 0, 0);
+
+	gtk_source_gutter_set_cell_data_func (gutter,
+	                                      view->priv->line_renderer,
+	                                      (GtkSourceGutterDataFunc)line_renderer_data_func,
+	                                      view,
+	                                      NULL);
+
+	gtk_source_gutter_set_cell_size_func (gutter,
+	                                      view->priv->line_renderer,
+	                                      (GtkSourceGutterSizeFunc)line_renderer_size_func,
+	                                      view,
+	                                      NULL);
+
+	gtk_source_gutter_set_cell_data_func (gutter,
+	                                      view->priv->marks_renderer,
+	                                      (GtkSourceGutterDataFunc)marks_renderer_data_func,
+	                                      view,
+	                                      NULL);
+
+	g_signal_connect (gutter,
+	                  "cell-activated",
+	                  G_CALLBACK (renderer_activated),
+	                  view);
+
+	g_signal_connect (gutter,
+	                  "query-tooltip",
+	                  G_CALLBACK (renderer_query_tooltip),
+	                  view);
+}
+
 static void
 gtk_source_view_init (GtkSourceView *view)
 {
@@ -731,7 +1187,6 @@ gtk_source_view_init (GtkSourceView *view)
 	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;
-	view->priv->cached_line_number_width = 0;
 
 	gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 2);
 	gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 2);
@@ -744,6 +1199,8 @@ gtk_source_view_init (GtkSourceView *view)
 							     (GDestroyNotify) g_free,
 							     (GDestroyNotify) mark_category_free);
 
+	init_left_gutter (view);
+
 	tl = gtk_drag_dest_get_target_list (GTK_WIDGET (view));
 	g_return_if_fail (tl != NULL);
 
@@ -796,13 +1253,19 @@ gtk_source_view_finalize (GObject *object)
 
 	if (view->priv->right_margin_overlay_color != NULL)
 		gdk_color_free (view->priv->right_margin_overlay_color);
-		
+
 	if (view->priv->spaces_color != NULL)
 		gdk_color_free (view->priv->spaces_color);
 
 	if (view->priv->mark_categories)
 		g_hash_table_destroy (view->priv->mark_categories);
 
+	if (view->priv->left_gutter)
+		g_object_unref (view->priv->left_gutter);
+
+	if (view->priv->right_gutter)
+		g_object_unref (view->priv->right_gutter);
+
 	set_source_buffer (view, NULL);
 
 	G_OBJECT_CLASS (gtk_source_view_parent_class)->finalize (object);
@@ -1254,314 +1717,6 @@ gtk_source_view_get_lines (GtkTextView  *text_view,
 	*countp = count;
 }
 
-static gint
-sort_marks_by_priority (gconstpointer m1,
-			gconstpointer m2,
-			gpointer data)
-{
-	GtkSourceMark *mark1 = GTK_SOURCE_MARK (m1);
-	GtkSourceMark *mark2 = GTK_SOURCE_MARK (m2);
-	GtkSourceView *view = GTK_SOURCE_VIEW (data);
-	GtkTextIter iter1, iter2;
-	gint line1;
-	gint line2;
-
-	gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark1)),
-					  &iter1,
-					  GTK_TEXT_MARK (mark1));
-	gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark2)),
-					  &iter2,
-					  GTK_TEXT_MARK (mark2));
-
-	line1 = gtk_text_iter_get_line (&iter1);
-	line2 = gtk_text_iter_get_line (&iter2);
-
-	if (line1 == line2)
-	{
-		guint priority1 = gtk_source_view_get_mark_category_priority (view,
-						gtk_source_mark_get_category (mark1));
-		guint priority2 = gtk_source_view_get_mark_category_priority (view,
-						gtk_source_mark_get_category (mark2));
-
-		return priority1 - priority2;
-	}
-	else
-	{
-		return line2 - line1;
-	}
-}
-
-static void
-draw_line_marks (GtkSourceView *view,
-		 GSList        *marks,
-		 gint           x,
-		 gint           y,
-		 gint		height)
-{
-	GdkPixbuf *composite;
-	gint mark_width, mark_height;
-
-	/* Draw the mark with higher priority */
-	marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view);
-	
-	composite = NULL;
-	mark_width = mark_height = 0;
-
-	/* composite all the pixbufs for the marks present at the line */
-	do
-	{
-		GtkSourceMark *mark;
-		GdkPixbuf *pixbuf;
-		MarkCategory *cat;
-
-		mark = marks->data;
-
-		cat = gtk_source_view_get_mark_category (view, mark);
-		if (cat != NULL && cat->pixbuf != NULL)
-			pixbuf = g_object_ref (cat->pixbuf);
-
-		if (pixbuf != NULL)
-		{
-			if (composite == NULL)
-			{
-				composite = gdk_pixbuf_copy (pixbuf);
-				mark_width = gdk_pixbuf_get_width (composite);
-				mark_height = gdk_pixbuf_get_height (composite);
-			}
-			else
-			{
-				gint pixbuf_w;
-				gint pixbuf_h;
-
-				pixbuf_w = gdk_pixbuf_get_width (pixbuf);
-				pixbuf_h = gdk_pixbuf_get_height (pixbuf);
-				gdk_pixbuf_composite (pixbuf,
-						      composite,
-						      0, 0,
-						      mark_width, mark_height,
-						      0, 0,
-						      (double) pixbuf_w / mark_width,
-						      (double) pixbuf_h / mark_height,
-						      GDK_INTERP_BILINEAR,
-						      COMPOSITE_ALPHA);
-			}
-			g_object_unref (pixbuf);
-		}
-
-		marks = g_slist_next (marks);
-	}
-	while (marks);
-
-	if (composite != NULL)
-	{
-		GdkWindow *window;
-
-		window = gtk_text_view_get_window (GTK_TEXT_VIEW (view),
-						   GTK_TEXT_WINDOW_LEFT);
-						   
-		gdk_draw_pixbuf (GDK_DRAWABLE (window), NULL, composite,
-				 0, 0, x, y + (height - mark_height) / 2.0,
-				 mark_width, mark_height,
-				 GDK_RGB_DITHER_NORMAL, 0, 0);
-		g_object_unref (composite);
-	}
-}
-
-static void
-gtk_source_view_paint_margin (GtkSourceView *view,
-			      GdkEventExpose *event)
-{
-	GtkTextView *text_view;
-	GdkWindow *win;
-	PangoLayout *layout;
-	GArray *numbers;
-	GArray *pixels;
-	gchar str [8];  /* we don't expect more than ten million lines ;-) */
-	gint y1, y2;
-	gint count;
-	gint margin_width;
-	gint text_width, x_pixmap;
-	gint i;
-	GtkTextIter cur;
-	gint cur_line;
-
-	text_view = GTK_TEXT_VIEW (view);
-
-	if (!view->priv->show_line_numbers && !view->priv->show_line_marks)
-	{
-		gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (text_view),
-						      GTK_TEXT_WINDOW_LEFT,
-						      0);
-
-		return;
-	}
-
-	win = gtk_text_view_get_window (text_view,
-					GTK_TEXT_WINDOW_LEFT);
-
-	y1 = event->area.y;
-	y2 = y1 + event->area.height;
-
-	/* get the extents of the line printing */
-	gtk_text_view_window_to_buffer_coords (text_view,
-					       GTK_TEXT_WINDOW_LEFT,
-					       0,
-					       y1,
-					       NULL,
-					       &y1);
-
-	gtk_text_view_window_to_buffer_coords (text_view,
-					       GTK_TEXT_WINDOW_LEFT,
-					       0,
-					       y2,
-					       NULL,
-					       &y2);
-
-	numbers = g_array_new (FALSE, FALSE, sizeof (gint));
-	pixels = g_array_new (FALSE, FALSE, sizeof (gint));
-
-	/* get the line numbers and y coordinates. */
-	gtk_source_view_get_lines (text_view,
-				   y1,
-				   y2,
-				   pixels,
-				   NULL,
-				   numbers,
-				   &count);
-
-	/* A zero-lined document should display a "1"; we don't need to worry about
-	scrolling effects of the text widget in this special case */
-
-	if (count == 0)
-	{
-		gint y = 0;
-		gint n = 0;
-		count = 1;
-		g_array_append_val (pixels, y);
-		g_array_append_val (numbers, n);
-	}
-
-	DEBUG ({
-		g_message ("Painting line numbers %d - %d",
-			   g_array_index (numbers, gint, 0),
-			   g_array_index (numbers, gint, count - 1));
-	});
-
-	/* set size. */
-	g_snprintf (str, sizeof (str),
-		    "%d", MAX (99, gtk_text_buffer_get_line_count (text_view->buffer)));
-	layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), str);
-
-	pango_layout_get_pixel_size (layout, &text_width, NULL);
-
-	pango_layout_set_width (layout, text_width);
-	pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
-
-	/* determine the width of the left margin. */
-	if (view->priv->show_line_numbers)
-		margin_width = text_width + 4;
-	else
-		margin_width = 0;
-	view->priv->cached_line_number_width = margin_width;
-
-	x_pixmap = margin_width;
-
-	if (view->priv->show_line_marks)
-		margin_width += GUTTER_PIXMAP;
-
-	/* no line & no marks case is short circuited before */
-	g_assert (margin_width != 0);
-
-	gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (text_view),
-					      GTK_TEXT_WINDOW_LEFT,
-					      margin_width);
-
-	gtk_text_buffer_get_iter_at_mark (text_view->buffer,
-					  &cur,
-					  gtk_text_buffer_get_insert (text_view->buffer));
-
-	cur_line = gtk_text_iter_get_line (&cur);
-
-	for (i = 0; i < count; ++i)
-	{
-		gint pos;
-		gint line_to_paint;
-
-		gtk_text_view_buffer_to_window_coords (text_view,
-						       GTK_TEXT_WINDOW_LEFT,
-						       0,
-						       g_array_index (pixels, gint, i),
-						       NULL,
-						       &pos);
-
-		line_to_paint = g_array_index (numbers, gint, i);
-
-		if (view->priv->show_line_numbers)
-		{
-			if (line_to_paint == cur_line)
-			{
-				gchar *markup;
-
-				/* +1 because displayed line numbers start from 1 */
-				markup = g_strdup_printf ("<b>%d</b>", 1 + line_to_paint);
-
-				pango_layout_set_markup (layout, markup, -1);
-
-				g_free (markup);
-			}
-			else
-			{
-				g_snprintf (str, sizeof (str), "%d", 1 + line_to_paint);
-
-				pango_layout_set_markup (layout, str, -1);
-			}
-
-			gtk_paint_layout (GTK_WIDGET (view)->style,
-					  win,
-					  GTK_WIDGET_STATE (view),
-					  FALSE,
-					  NULL,
-					  GTK_WIDGET (view),
-					  NULL,
-					  text_width + 2,
-					  pos,
-					  layout);
-		}
-
-		if (view->priv->show_line_marks && view->priv->source_buffer != NULL)
-		{
-			GSList *marks;
-
-			marks = gtk_source_buffer_get_source_marks_at_line (view->priv->source_buffer,
-									    line_to_paint,
-									    NULL);
-
-			if (marks != NULL)
-			{
-				GtkTextIter iter;
-				gint height;
-			
-				gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (view->priv->source_buffer),
-								  &iter,
-								  line_to_paint);
-				gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (view),
-							       &iter,
-							       NULL,
-							       &height);
-
-				/* draw marks for the line */
-				draw_line_marks (view, marks, x_pixmap, pos, height);
-				g_slist_free (marks);
-			}
-		}
-	}
-
-	g_array_free (pixels, TRUE);
-	g_array_free (numbers, TRUE);
-
-	g_object_unref (G_OBJECT (layout));
-}
-
 static void
 gtk_source_view_paint_line_background (GtkTextView    *text_view,
 				       GdkEventExpose *event,
@@ -1957,6 +2112,104 @@ draw_tabs_and_spaces (GtkSourceView  *view,
 	cairo_destroy (cr);
 }
 
+static void
+gtk_source_view_paint_right_margin (GtkSourceView  *view,
+                                    GdkEventExpose *event)
+{
+	GdkRectangle visible_rect;
+	GdkRectangle redraw_rect;
+	cairo_t *cr;
+	double x;
+
+	GtkTextView *text_view = GTK_TEXT_VIEW (view);
+
+#ifdef ENABLE_PROFILE
+	static GTimer *timer = NULL;
+#endif
+
+	g_return_if_fail (view->priv->right_margin_line_color != NULL);
+
+	if (view->priv->cached_right_margin_pos < 0)
+	{
+		view->priv->cached_right_margin_pos =
+			calculate_real_tab_width (view,
+						  view->priv->right_margin_pos,
+						  '_');
+	}
+
+#ifdef ENABLE_PROFILE
+	if (timer == NULL)
+		timer = g_timer_new ();
+
+	g_timer_start (timer);
+#endif
+
+	gtk_text_view_get_visible_rect (text_view, &visible_rect);
+
+	gtk_text_view_buffer_to_window_coords (text_view,
+				       GTK_TEXT_WINDOW_TEXT,
+				       visible_rect.x,
+				       visible_rect.y,
+				       &redraw_rect.x,
+				       &redraw_rect.y);
+
+	redraw_rect.width = visible_rect.width;
+	redraw_rect.height = visible_rect.height;
+
+	cr = gdk_cairo_create (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT));
+
+	/* Set a clip region for the expose event. */
+	cairo_rectangle (cr, event->area.x, event->area.y,
+			 event->area.width, event->area.height);
+	cairo_clip (cr);
+
+	/* Offset with 0.5 is needed for a sharp line. */
+	x = view->priv->cached_right_margin_pos -
+		visible_rect.x + redraw_rect.x + 0.5 +
+		gtk_text_view_get_left_margin (text_view);
+
+	/* Default line width is 2.0 which is too wide. */
+	cairo_set_line_width (cr, 1.0);
+
+	cairo_move_to (cr, x, redraw_rect.y);
+	cairo_line_to (cr, x, redraw_rect.y + redraw_rect.height);
+
+	cairo_set_source_rgba (cr,
+			       view->priv->right_margin_line_color->red / 65535.,
+			       view->priv->right_margin_line_color->green / 65535.,
+			       view->priv->right_margin_line_color->blue / 65535.,
+			       RIGHT_MARING_LINE_ALPHA / 255.);
+
+	cairo_stroke (cr);
+
+	/* Only draw the overlay when the style scheme explicitly sets it. */
+	if (view->priv->right_margin_overlay_color != NULL)
+	{
+		/* Draw the rectangle next to the line (x+.5). */
+		cairo_rectangle (cr,
+				 x + .5,
+				 redraw_rect.y,
+				 redraw_rect.width - x - .5,
+				 redraw_rect.y + redraw_rect.height);
+
+		cairo_set_source_rgba (cr,
+				       view->priv->right_margin_overlay_color->red / 65535.,
+				       view->priv->right_margin_overlay_color->green / 65535.,
+				       view->priv->right_margin_overlay_color->blue / 65535.,
+				       RIGHT_MARING_OVERLAY_ALPHA / 255.);
+
+		cairo_fill (cr);
+	}
+
+	cairo_destroy (cr);
+
+	PROFILE ({
+		g_timer_stop (timer);
+		g_message ("Time to draw the margin: %g (sec * 1000)",
+		           g_timer_elapsed (timer, NULL) * 1000);
+	});
+}
+
 static gint
 gtk_source_view_expose (GtkWidget      *widget,
 			GdkEventExpose *event)
@@ -2003,161 +2256,45 @@ gtk_source_view_expose (GtkWidget      *widget,
 						     &iter1, &iter2, FALSE);
 	}
 
-	/* now check for the left window, which contains the margin */
-	if (event->window == gtk_text_view_get_window (text_view,
-						       GTK_TEXT_WINDOW_LEFT))
-	{
-		gtk_source_view_paint_margin (view, event);
-		event_handled = TRUE;
-	}
-	else
-	{
-		gint lines;
-
-		/* FIXME: could it be a performances problem? - Paolo */
-		lines = gtk_text_buffer_get_line_count (text_view->buffer);
-
-		if (view->priv->old_lines != lines)
-		{
-			GdkWindow *w;
-			view->priv->old_lines = lines;
-
-			w = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_LEFT);
-
-			if (w != NULL)
-				gdk_window_invalidate_rect (w, NULL, FALSE);
-		}
-
-		if (GTK_WIDGET_IS_SENSITIVE(view) && view->priv->highlight_current_line &&
+	if (GTK_WIDGET_IS_SENSITIVE(view) && view->priv->highlight_current_line &&
 		    (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)))
-		{
-			GtkTextIter cur;
-			gint y, height;
-			GdkColor *color;
-
-			gtk_text_buffer_get_iter_at_mark (text_view->buffer,
-							  &cur,
-							  gtk_text_buffer_get_insert (text_view->buffer));
-			gtk_text_view_get_line_yrange (text_view, &cur, &y, &height);
-
-			if (view->priv->current_line_color_set)
-				color = &view->priv->current_line_color;
-			else
-				color = &widget->style->bg[GTK_WIDGET_STATE (widget)];
-
-			gtk_source_view_paint_line_background (text_view, event, y, height, color);
-		}
-
-		if (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT))
-			gtk_source_view_paint_marks_background (view, event);
-
-		/* Have GtkTextView draw the text first. */
-		if (GTK_WIDGET_CLASS (gtk_source_view_parent_class)->expose_event)
-			event_handled =
-				GTK_WIDGET_CLASS (gtk_source_view_parent_class)->expose_event (widget, event);
-
-		/* Draw the right margin vertical line + overlay. */
-		if (view->priv->show_right_margin &&
-		    (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)))
-		{
-			GdkRectangle visible_rect;
-			GdkRectangle redraw_rect;
-			cairo_t *cr;
-			double x;
-
-#ifdef ENABLE_PROFILE
-			static GTimer *timer = NULL;
-#endif
-
-			g_return_val_if_fail (view->priv->right_margin_line_color != NULL,
-					      event_handled);
-
-			if (view->priv->cached_right_margin_pos < 0)
-			{
-				view->priv->cached_right_margin_pos =
-					calculate_real_tab_width (view,
-								  view->priv->right_margin_pos,
-								  '_');
-			}
-
-#ifdef ENABLE_PROFILE
-			if (timer == NULL)
-				timer = g_timer_new ();
-
-			g_timer_start (timer);
-#endif
-
-			gtk_text_view_get_visible_rect (text_view, &visible_rect);
-
-			gtk_text_view_buffer_to_window_coords (text_view,
-						       GTK_TEXT_WINDOW_TEXT,
-						       visible_rect.x,
-						       visible_rect.y,
-						       &redraw_rect.x,
-						       &redraw_rect.y);
-
-			redraw_rect.width = visible_rect.width;
-			redraw_rect.height = visible_rect.height;
-
-			cr = gdk_cairo_create (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT));
-
-			/* Set a clip region for the expose event. */
-			cairo_rectangle (cr, event->area.x, event->area.y,
-					 event->area.width, event->area.height);
-			cairo_clip (cr);
-
-			/* Offset with 0.5 is needed for a sharp line. */
-			x = view->priv->cached_right_margin_pos -
-				visible_rect.x + redraw_rect.x + 0.5 +
-				gtk_text_view_get_left_margin (text_view);
-
-			/* Default line width is 2.0 which is too wide. */
-			cairo_set_line_width (cr, 1.0);
+	{
+		GtkTextIter cur;
+		gint y, height;
+		GdkColor *color;
 
-			cairo_move_to (cr, x, redraw_rect.y);
-			cairo_line_to (cr, x, redraw_rect.y + redraw_rect.height);
+		gtk_text_buffer_get_iter_at_mark (text_view->buffer,
+						  &cur,
+						  gtk_text_buffer_get_insert (text_view->buffer));
+		gtk_text_view_get_line_yrange (text_view, &cur, &y, &height);
 
-			cairo_set_source_rgba (cr,
-					       view->priv->right_margin_line_color->red / 65535.,
-					       view->priv->right_margin_line_color->green / 65535.,
-					       view->priv->right_margin_line_color->blue / 65535.,
-					       RIGHT_MARING_LINE_ALPHA / 255.);
+		if (view->priv->current_line_color_set)
+			color = &view->priv->current_line_color;
+		else
+			color = &widget->style->bg[GTK_WIDGET_STATE (widget)];
 
-			cairo_stroke (cr);
+		gtk_source_view_paint_line_background (text_view, event, y, height, color);
+	}
 
-			/* Only draw the overlay when the style scheme explicitly sets it. */
-			if (view->priv->right_margin_overlay_color != NULL)
-			{
-				/* Draw the rectangle next to the line (x+.5). */
-				cairo_rectangle (cr,
-						 x + .5,
-						 redraw_rect.y,
-						 redraw_rect.width - x - .5,
-						 redraw_rect.y + redraw_rect.height);
-
-				cairo_set_source_rgba (cr,
-						       view->priv->right_margin_overlay_color->red / 65535.,
-						       view->priv->right_margin_overlay_color->green / 65535.,
-						       view->priv->right_margin_overlay_color->blue / 65535.,
-						       RIGHT_MARING_OVERLAY_ALPHA / 255.);
-
-				cairo_fill (cr);
-			}
+	if (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT))
+		gtk_source_view_paint_marks_background (view, event);
 
-			cairo_destroy (cr);
+	/* Have GtkTextView draw the text first. */
+	if (GTK_WIDGET_CLASS (gtk_source_view_parent_class)->expose_event)
+		event_handled =
+			GTK_WIDGET_CLASS (gtk_source_view_parent_class)->expose_event (widget, event);
 
-			PROFILE ({
-				g_timer_stop (timer);
-				g_message ("Time to draw the margin: %g (sec * 1000)",
-				           g_timer_elapsed (timer, NULL) * 1000);
-			});
-		}
+	/* Draw the right margin vertical line + overlay. */
+	if (view->priv->show_right_margin &&
+	    (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)))
+	{
+		gtk_source_view_paint_right_margin (view, event);
+	}
 
-		if (view->priv->draw_spaces != 0 &&
-		    (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)))
-		{
-			draw_tabs_and_spaces (view, event);
-		}
+	if (view->priv->draw_spaces != 0 &&
+	    (event->window == gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT)))
+	{
+		draw_tabs_and_spaces (view, event);
 	}
 
 	DEBUG ({
@@ -2280,42 +2417,33 @@ void
 gtk_source_view_set_show_line_numbers (GtkSourceView *view,
 				       gboolean       show)
 {
+	GtkSourceGutter *gutter;
+
 	g_return_if_fail (view != NULL);
 	g_return_if_fail (GTK_IS_SOURCE_VIEW (view));
 
 	show = (show != FALSE);
 
-	if (show)
+	if (show == view->priv->show_line_numbers)
 	{
-		if (!view->priv->show_line_numbers)
-		{
-			/* Set left margin to minimum width if no margin is
-			   visible yet. Otherwise, just queue a redraw, so the
-			   expose handler will automatically adjust the margin. */
-			if (!view->priv->show_line_marks)
-				gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view),
-								      GTK_TEXT_WINDOW_LEFT,
-								      MIN_NUMBER_WINDOW_WIDTH);
-			else
-				gtk_widget_queue_draw (GTK_WIDGET (view));
+		return;
+	}
 
-			view->priv->show_line_numbers = show;
+	gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT);
 
-			g_object_notify (G_OBJECT (view), "show_line_numbers");
-		}
+	if (show)
+	{
+		gtk_cell_renderer_set_fixed_size (view->priv->line_renderer, -1, -1);
 	}
 	else
 	{
-		if (view->priv->show_line_numbers)
-		{
-			view->priv->show_line_numbers = show;
+		gtk_cell_renderer_set_fixed_size (view->priv->line_renderer, 0, 0);
+	}
 
-			/* force expose event, which will adjust margin. */
-			gtk_widget_queue_draw (GTK_WIDGET (view));
+	view->priv->show_line_numbers = show;
+	gtk_source_gutter_queue_draw (gutter);
 
-			g_object_notify (G_OBJECT (view), "show_line_numbers");
-		}
-	}
+	g_object_notify (G_OBJECT (view), "show_line_numbers");
 }
 
 /**
@@ -2349,41 +2477,33 @@ void
 gtk_source_view_set_show_line_marks (GtkSourceView *view,
 				     gboolean       show)
 {
+	GtkSourceGutter *gutter;
+
 	g_return_if_fail (GTK_IS_SOURCE_VIEW (view));
 
 	show = (show != FALSE);
+	gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT);
 
-	if (show)
+	if (show == view->priv->show_line_marks)
 	{
-		if (!view->priv->show_line_marks)
-		{
-			/* Set left margin to minimum width if no margin is
-			   visible yet. Otherwise, just queue a redraw, so the
-			   expose handler will automatically adjust the margin. */
-			if (!view->priv->show_line_numbers)
-				gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (view),
-								      GTK_TEXT_WINDOW_LEFT,
-								      MIN_NUMBER_WINDOW_WIDTH);
-			else
-				gtk_widget_queue_draw (GTK_WIDGET (view));
+		return;
+	}
 
-			view->priv->show_line_marks = show;
+	gutter = gtk_source_view_get_gutter (view, GTK_TEXT_WINDOW_LEFT);
 
-			g_object_notify (G_OBJECT (view), "show-line-marks");
-		}
+	if (show)
+	{
+		gtk_cell_renderer_set_fixed_size (view->priv->marks_renderer, GUTTER_PIXMAP, GUTTER_PIXMAP);
 	}
 	else
 	{
-		if (view->priv->show_line_marks)
-		{
-			view->priv->show_line_marks = show;
+		gtk_cell_renderer_set_fixed_size (view->priv->marks_renderer, 0, 0);
+	}
 
-			/* force expose event, which will adjust margin. */
-			gtk_widget_queue_draw (GTK_WIDGET (view));
+	view->priv->show_line_marks = show;
+	gtk_source_gutter_queue_draw (gutter);
 
-			g_object_notify (G_OBJECT (view), "show-line-marks");
-		}
-	}
+	g_object_notify (G_OBJECT (view), "show_line_marks");
 }
 
 static gboolean
@@ -2722,7 +2842,7 @@ set_mark_category_tooltip_func (GtkSourceView   *view,
  *
  * Set a #GtkSourceViewMarkTooltipFunc used to set tooltip on marks from the
  * given mark @category. If @func is %NULL and @markup_func is %NULL then tooltips
- * will not be shown for marks from @category. If @markup_func is not %NULL 
+ * will not be shown for marks from @category. If @markup_func is not %NULL
  * @markup_func is going to be used instead of @func.
  *
  * <informalexample><programlisting>
@@ -3468,236 +3588,6 @@ gtk_source_view_key_press_event (GtkWidget   *widget,
 	return GTK_WIDGET_CLASS (gtk_source_view_parent_class)->key_press_event (widget, event);
 }
 
-static void
-extend_selection_to_line (GtkTextBuffer *buf, GtkTextIter *line_start)
-{
-	GtkTextIter start;
-	GtkTextIter end;
-	GtkTextIter line_end;
-
-	gtk_text_buffer_get_selection_bounds (buf, &start, &end);
-
-	line_end = *line_start;
-	gtk_text_iter_forward_to_line_end (&line_end);
-
-	if (gtk_text_iter_compare (&start, line_start) < 0)
-	{
-		gtk_text_buffer_select_range (buf, &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 (buf, &line_end, line_start);
-	}
-	else
-	{
-		gtk_text_buffer_select_range (buf, &end, line_start);
-	}
-}
-
-static void
-select_line (GtkTextBuffer *buf, GtkTextIter *line_start)
-{
-	GtkTextIter iter;
-
-	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 (buf, &iter, line_start);
-}
-
-static gboolean
-gtk_source_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
-{
-	GtkSourceView *view;
-	GtkTextBuffer *buf;
-	int y_buf;
-	GtkTextIter line_start;
-
-	view = GTK_SOURCE_VIEW (widget);
-	buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
-
-	if (view->priv->show_line_numbers &&
-	    (event->window == gtk_text_view_get_window (GTK_TEXT_VIEW (view),
-						       GTK_TEXT_WINDOW_LEFT)))
-	{
-		gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
-						       GTK_TEXT_WINDOW_LEFT,
-						       event->x, event->y,
-						       NULL, &y_buf);
-
-		gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (view),
-					     &line_start,
-					     y_buf,
-					     NULL);
-
-		if (event->type == GDK_BUTTON_PRESS && (event->button == 1))
-		{
-			if ((event->state & GDK_CONTROL_MASK) != 0)
-			{
-				/* Single click + Ctrl -> select the line */
-				select_line (buf, &line_start);
-			}
-			else if ((event->state & GDK_SHIFT_MASK) != 0)
-			{
-				/* Single click + Shift -> extended current
-				   selection to include the clicked line */
-				extend_selection_to_line (buf, &line_start);
-			}
-			else
-			{
-				gtk_text_buffer_place_cursor (buf, &line_start);
-			}
-		}
-		else if (event->type == GDK_2BUTTON_PRESS && (event->button == 1))
-		{
-			select_line (buf, &line_start);
-		}
-
-		/* consume the event also on right click etc */
-		return TRUE;
-	}
-
-	return GTK_WIDGET_CLASS (gtk_source_view_parent_class)->button_press_event (widget, event);
-}
-
-static void
-set_tooltip_widget_from_marks (GtkSourceView *view,
-			       GtkTooltip *tooltip,
-			       GSList *marks)
-{
-	GtkWidget *vbox;
-	
-	vbox = gtk_vbox_new (FALSE, 0);
-	gtk_widget_show (vbox);
-
-	while (marks != NULL)
-	{
-		MarkCategory *cat;
-		GtkSourceMark *mark;
-
-		mark = marks->data;
-		cat = gtk_source_view_get_mark_category (view, mark);
-
-		if (cat != NULL)
-		{
-			GtkWidget *image;
-			GtkWidget *label;
-			GtkWidget *hbox;
-			GtkWidget *separator;
-			gchar *text;
-	
-			hbox = gtk_hbox_new (FALSE, 4);
-			gtk_widget_show (hbox);
-			gtk_box_pack_start (GTK_BOX (vbox), hbox,
-					    FALSE, FALSE, 0);
-			
-			text = cat->tooltip_func (mark, cat->tooltip_data);
-		
-			if (text != NULL)
-			{
-				label = gtk_label_new (NULL);
-				if (cat->tooltip_markup)
-					gtk_label_set_markup (GTK_LABEL (label), text);
-				else
-					gtk_label_set_text (GTK_LABEL (label), text);
-				
-				gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
-				gtk_widget_show (label);
-			
-				gtk_box_pack_end (GTK_BOX (hbox), label,
-						  TRUE, TRUE, 0);
-			
-				if (cat->pixbuf != NULL)
-				{
-					image = gtk_image_new_from_pixbuf (cat->pixbuf);
-					gtk_widget_show (image);
-			
-					gtk_box_pack_start (GTK_BOX (hbox), image,
-							    FALSE, FALSE, 0);
-				}
-				
-				if (g_slist_length (marks) != 1)
-				{
-					separator = gtk_hseparator_new ();
-					gtk_widget_show (separator);
-					gtk_box_pack_start (GTK_BOX (vbox), separator,
-							    FALSE, FALSE, 0);
-				}
-				
-				g_free (text);
-			}
-		}
-
-		marks = g_slist_delete_link (marks, marks);
-	}
-	
-	gtk_tooltip_set_custom (tooltip, vbox);
-}
-
-static gboolean
-gtk_source_view_query_tooltip (GtkWidget  *widget,
-			       gint        x,
-			       gint        y,
-			       gboolean    keyboard_mode,
-			       GtkTooltip *tooltip)
-{
-	GtkTextView *text_view = GTK_TEXT_VIEW (widget);
-	GtkSourceView *view = GTK_SOURCE_VIEW (widget);
-
-	/* Check if we are inside the marker area */
-	/* TODO: when folding is in, correct this */
-	if (x < gtk_text_view_get_border_window_size (text_view, GTK_TEXT_WINDOW_LEFT) &&
-	    x > view->priv->cached_line_number_width)
-	{
-		GtkTextIter line_iter;
-		GSList *marks;
-		GtkSourceBuffer *buffer;
-		gint line;
-		gint buffer_x, buffer_y;
-
-		buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (text_view));
-		gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (widget),
-						       GTK_TEXT_WINDOW_LEFT,
-						       x,
-						       y,
-						       &buffer_x,
-						       &buffer_y);
-		
-		gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (widget),
-					     &line_iter,
-					     buffer_y,
-					     NULL);
-		line = gtk_text_iter_get_line (&line_iter);
-		marks = gtk_source_buffer_get_source_marks_at_line (buffer,
-								    line,
-								    NULL);
-
-		if (marks != NULL)
-		{
-			marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view);
-			marks = g_slist_reverse (marks);
-			
-			set_tooltip_widget_from_marks (view, tooltip, marks);
-			
-			return TRUE;
-		}
-	}
-
-	if (GTK_WIDGET_CLASS (gtk_source_view_parent_class)->query_tooltip != NULL)
-		return GTK_WIDGET_CLASS (gtk_source_view_parent_class)->query_tooltip (widget,
-										       x,
-										       y,
-										       keyboard_mode,
-										       tooltip);
-	else
-		return FALSE;
-}
-
 /**
  * gtk_source_view_get_auto_indent:
  * @view: a #GtkSourceView.
@@ -4080,7 +3970,7 @@ gtk_source_view_set_draw_spaces (GtkSourceView            *view,
  *
  * Returns the #GtkSourceDrawSpacesFlags specifying if and how spaces
  * should be displayed for this @view.
- * 
+ *
  * Returns: the #GtkSourceDrawSpacesFlags, 0 if no spaces should be drawn.
  **/
 GtkSourceDrawSpacesFlags
@@ -4286,3 +4176,49 @@ gtk_source_view_update_style_scheme (GtkSourceView *view)
 			view->priv->style_scheme_applied = FALSE;
 	}
 }
+
+/**
+ * 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 gutter corresponding to
+ * GTK_TEXT_WINDOW_LEFT.
+ *
+ * Since: 2.8
+ *
+ * Returns: the #GtkSourceGutter.
+ **/
+GtkSourceGutter *
+gtk_source_view_get_gutter (GtkSourceView     *view,
+                            GtkTextWindowType  window_type)
+{
+	g_return_val_if_fail (GTK_IS_SOURCE_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;
+	}
+}
+
diff --git a/gtksourceview/gtksourceview.h b/gtksourceview/gtksourceview.h
index 3eae9af..774edb8 100644
--- a/gtksourceview/gtksourceview.h
+++ b/gtksourceview/gtksourceview.h
@@ -28,6 +28,7 @@
 #include <gtk/gtktextview.h>
 
 #include <gtksourceview/gtksourcebuffer.h>
+#include <gtksourceview/gtksourcegutter.h>
 
 G_BEGIN_DECLS
 
@@ -38,6 +39,11 @@ G_BEGIN_DECLS
 #define GTK_IS_SOURCE_VIEW_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_VIEW))
 #define GTK_SOURCE_VIEW_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_VIEW, GtkSourceViewClass))
 
+typedef enum
+{
+	GTK_SOURCE_VIEW_GUTTER_POSITION_LINES = -30,
+	GTK_SOURCE_VIEW_GUTTER_POSITION_MARKS = -20
+} GtkSourceViewGutterPosition;
 
 typedef struct _GtkSourceView GtkSourceView;
 typedef struct _GtkSourceViewClass GtkSourceViewClass;
@@ -57,12 +63,14 @@ struct _GtkSourceViewClass
 
 	void (*undo) (GtkSourceView *view);
 	void (*redo) (GtkSourceView *view);
+	void (*line_mark_activated) (GtkSourceView *view, 
+	                             GtkTextIter   *iter,
+	                             GdkEvent      *event);
 
 	/* Padding for future expansion */
 	void (*_gtk_source_reserved1) (void);
 	void (*_gtk_source_reserved2) (void);
 	void (*_gtk_source_reserved3) (void);
-	void (*_gtk_source_reserved4) (void);
 };
 
 /**
@@ -213,5 +221,8 @@ void		 gtk_source_view_set_draw_spaces	(GtkSourceView   *view,
 GtkSourceDrawSpacesFlags
 		gtk_source_view_get_draw_spaces		(GtkSourceView   *view);
 
+GtkSourceGutter *gtk_source_view_get_gutter		(GtkSourceView     *view,
+                                                         GtkTextWindowType  window_type);
+
 G_END_DECLS
 #endif				/* end of SOURCE_VIEW_H__ */
diff --git a/tests/test-widget.c b/tests/test-widget.c
index 68adf87..e9bdc96 100644
--- a/tests/test-widget.c
+++ b/tests/test-widget.c
@@ -1112,69 +1112,44 @@ window_deleted_cb (GtkWidget *widget, GdkEvent *ev, gpointer user_data)
 	return TRUE;
 }
 
-static gboolean
-button_press_cb (GtkWidget *widget, GdkEventButton *ev, gpointer user_data)
+static void
+line_mark_activated (GtkSourceGutter *gutter, 
+                     GtkTextIter     *iter,
+                     GdkEventButton  *ev,
+                     GtkSourceView   *view)
 {
-	GtkSourceView *view;
 	GtkSourceBuffer *buffer;
+	GSList *mark_list;
+	const gchar *mark_type;
 
-	g_return_val_if_fail (GTK_IS_SOURCE_VIEW (widget), FALSE);
+	if (ev->button == 1)
+		mark_type = MARK_TYPE_1;
+	else
+		mark_type = MARK_TYPE_2;
 
-	view = GTK_SOURCE_VIEW (widget);
 	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
 
-	if (!gtk_source_view_get_show_line_marks (view))
-		return FALSE;
+	/* get the marks already in the line */
+	mark_list = gtk_source_buffer_get_source_marks_at_line (buffer,
+								gtk_text_iter_get_line (iter),
+								mark_type);
 
-	/* check that the click was on the left gutter */
-	if (ev->window == gtk_text_view_get_window (GTK_TEXT_VIEW (view),
-						    GTK_TEXT_WINDOW_LEFT))
+	if (mark_list != NULL)
 	{
-		gint y_buf;
-		GtkTextIter line_start;
-		GSList *mark_list;
-		const gchar *mark_type;
-
-		if (ev->button == 1)
-			mark_type = MARK_TYPE_1;
-		else
-			mark_type = MARK_TYPE_2;
-
-		gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
-						       GTK_TEXT_WINDOW_LEFT,
-						       ev->x, ev->y,
-						       NULL, &y_buf);
-
-		/* get line bounds */
-		gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (view),
-					     &line_start,
-					     y_buf,
-					     NULL);
-
-		/* get the marks already in the line */
-		mark_list = gtk_source_buffer_get_source_marks_at_line (buffer,
-									gtk_text_iter_get_line (&line_start),
-									mark_type);
-
-		if (mark_list != NULL)
-		{
-			/* just take the first and delete it */
-			gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer),
-						     GTK_TEXT_MARK (mark_list->data));
-		}
-		else
-		{
-			/* no mark found: create one */
-			gtk_source_buffer_create_source_mark (buffer,
-							      NULL,
-							      mark_type,
-							      &line_start);
-		}
-
-		g_slist_free (mark_list);
+		/* just take the first and delete it */
+		gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer),
+					     GTK_TEXT_MARK (mark_list->data));
+	}
+	else
+	{
+		/* no mark found: create one */
+		gtk_source_buffer_create_source_mark (buffer,
+						      NULL,
+						      mark_type,
+						      iter);
 	}
 
-	return FALSE;
+	g_slist_free (mark_list);
 }
 
 
@@ -1222,13 +1197,13 @@ create_view_window (GtkSourceBuffer *buffer, GtkSourceView *from)
 
 	/* view */
 	view = gtk_source_view_new_with_buffer (buffer);
-
+	
 	if (style_scheme)
 		gtk_source_buffer_set_style_scheme (buffer, style_scheme);
 
 	g_signal_connect (buffer, "mark-set", G_CALLBACK (move_cursor_cb), view);
 	g_signal_connect (buffer, "changed", G_CALLBACK (update_cursor_position), view);
-	g_signal_connect (view, "button-press-event", G_CALLBACK (button_press_cb), NULL);
+	g_signal_connect (view, "line-mark-activated", G_CALLBACK (line_mark_activated), view);
 	g_signal_connect (window, "delete-event", (GCallback) window_deleted_cb, view);
 
 	/* action group and UI manager */
@@ -1289,7 +1264,7 @@ create_view_window (GtkSourceBuffer *buffer, GtkSourceView *from)
 		gtk_widget_modify_font (view, font_desc);
 		pango_font_description_free (font_desc);
 	}
-
+	
 	/* change view attributes to match those of from */
 	if (from)
 	{



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]