[gtksourceview/wip/minimap] Add source map widget



commit 337695a0f39873aa49749491a64e0084c91a8691
Author: Ignacio Casal Quinteiro <icq gnome org>
Date:   Fri May 1 15:25:34 2015 +0200

    Add source map widget

 docs/reference/gtksourceview-3.0-sections.txt |   19 +
 docs/reference/gtksourceview-docs.xml         |    1 +
 gtksourceview/Makefile.am                     |    3 +
 gtksourceview/gtksource.h                     |    1 +
 gtksourceview/gtksourcemap.c                  | 1107 +++++++++++++++++++++++++
 gtksourceview/gtksourcemap.h                  |   47 +
 gtksourceview/gtksourceview-utils.c           |  158 ++++
 gtksourceview/gtksourceview-utils.h           |    4 +
 tests/Makefile.am                             |    1 +
 tests/test-widget.c                           |   10 +
 tests/test-widget.ui                          |   33 +-
 11 files changed, 1380 insertions(+), 4 deletions(-)
---
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index cd9d6a0..87c335b 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -491,6 +491,25 @@ gtk_source_language_manager_get_type
 </SECTION>
 
 <SECTION>
+<FILE>map</FILE>
+<TITLE>GtkSourceMap</TITLE>
+GtkSourceMap
+gtk_source_map_new
+gtk_source_map_set_view
+gtk_source_map_get_view
+gtk_source_map_get_child_view
+<SUBSECTION Standard>
+GtkSourceMapClass
+GTK_SOURCE_IS_MAP
+GTK_SOURCE_IS_MAP_CLASS
+GTK_SOURCE_MAP
+GTK_SOURCE_MAP_CLASS
+GTK_SOURCE_MAP_GET_CLASS
+GTK_SOURCE_TYPE_MAP
+gtk_source_map_get_type
+</SECTION>
+
+<SECTION>
 <FILE>mark</FILE>
 <TITLE>GtkSourceMark</TITLE>
 GtkSourceMark
diff --git a/docs/reference/gtksourceview-docs.xml b/docs/reference/gtksourceview-docs.xml
index c371e0c..f753f4a 100644
--- a/docs/reference/gtksourceview-docs.xml
+++ b/docs/reference/gtksourceview-docs.xml
@@ -17,6 +17,7 @@
     <chapter>
       <title>Main Classes</title>
       <xi:include href="xml/buffer.xml"/>
+      <xi:include href="xml/map.xml"/>
       <xi:include href="xml/view.xml"/>
     </chapter>
 
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index d680469..a07fa12 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -39,6 +39,7 @@ libgtksourceview_headers =                    \
        gtksourcegutterrenderertext.h           \
        gtksourcelanguage.h                     \
        gtksourcelanguagemanager.h              \
+       gtksourcemap.h                          \
        gtksourcemark.h                         \
        gtksourcemarkattributes.h               \
        gtksourceprintcompositor.h              \
@@ -119,6 +120,7 @@ libgtksourceview_c_files =                  \
        gtksourcegutterrenderertext.c           \
        gtksourcelanguage.c                     \
        gtksourcelanguagemanager.c              \
+       gtksourcemap.c                          \
        gtksourcemark.c                         \
        gtksourcemarkattributes.c               \
        gtksourceprintcompositor.c              \
@@ -165,6 +167,7 @@ lib_LTLIBRARIES = libgtksourceview-3.0.la
 libgtksourceview_3_0_la_SOURCES =
 
 libgtksourceview_3_0_la_LIBADD =                                       \
+       -lm                                                             \
        libgtksourceview-core.la                                        \
        completion-providers/words/libgtksourcecompletionwords.la       \
        $(DEP_LIBS) $(GTK_MAC_LIBS)
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 0f1f074..51975ad 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -38,6 +38,7 @@
 #include <gtksourceview/gtksourcegutterrendererpixbuf.h>
 #include <gtksourceview/gtksourcelanguage.h>
 #include <gtksourceview/gtksourcelanguagemanager.h>
+#include <gtksourceview/gtksourcemap.h>
 #include <gtksourceview/gtksourcemark.h>
 #include <gtksourceview/gtksourcemarkattributes.h>
 #include <gtksourceview/gtksourceprintcompositor.h>
diff --git a/gtksourceview/gtksourcemap.c b/gtksourceview/gtksourcemap.c
new file mode 100644
index 0000000..838f8e0
--- /dev/null
+++ b/gtksourceview/gtksourcemap.c
@@ -0,0 +1,1107 @@
+/* gtksourcemap.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2015 Ignacio Casal Quinteiro <icq gnome org>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gtksourcemap.h"
+#include "gtksourceview-utils.h"
+
+#define DEFAULT_WIDTH        100
+#define CONCEAL_TIMEOUT      2000
+
+#define gtk_source_clear_weak_pointer(ptr) \
+  (*(ptr) ? (g_object_remove_weak_pointer((GObject*)*(ptr), (gpointer*)ptr),*(ptr)=NULL,1) : 0)
+
+#define gtk_source_set_weak_pointer(ptr,obj) \
+  
((obj!=*(ptr))?(gtk_source_clear_weak_pointer(ptr),*(ptr)=obj,((obj)?g_object_add_weak_pointer((GObject*)obj,(gpointer*)ptr),NULL:NULL),1):0)
+
+typedef struct
+{
+       PangoFontDescription *font_desc;
+
+       GtkCssProvider *view_css_provider;
+       GtkCssProvider *box_css_provider;
+
+       GtkOverlay *overlay;
+       GtkSourceView *child_view;
+       GtkEventBox *overlay_box;
+       GtkSourceView *view;
+
+       guint delayed_conceal_timeout;
+
+       gboolean in_press : 1;
+       gboolean show_map : 1;
+} GtkSourceMapPrivate;
+
+enum
+{
+       PROP_0,
+       PROP_FONT_DESC,
+       PROP_VIEW,
+       LAST_PROP
+};
+
+enum
+{
+       SHOW_MAP,
+       HIDE_MAP,
+       LAST_SIGNAL
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceMap, gtk_source_map, GTK_TYPE_BIN)
+
+static GParamSpec *pspecs[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+
+static gboolean
+gtk_source_map_do_conceal (gpointer user_data)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (user_data);
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       priv->delayed_conceal_timeout = 0;
+
+       if (priv->show_map == TRUE)
+       {
+               priv->show_map = FALSE;
+               g_signal_emit (map, signals[HIDE_MAP], 0);
+       }
+
+       return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gtk_source_map__enter_notify_event (GtkSourceMap     *map,
+                                    GdkEventCrossing *event,
+                                    GtkWidget        *widget)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->show_map == FALSE)
+       {
+               priv->show_map = TRUE;
+               g_signal_emit (map, signals[SHOW_MAP], 0);
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_map_show_map_and_queue_fade (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->delayed_conceal_timeout != 0)
+       {
+               g_source_remove (priv->delayed_conceal_timeout);
+       }
+
+       priv->delayed_conceal_timeout = g_timeout_add (CONCEAL_TIMEOUT,
+                                                      gtk_source_map_do_conceal,
+                                                      map);
+
+       if (priv->show_map == FALSE)
+       {
+               priv->show_map = TRUE;
+               g_signal_emit (map, signals[SHOW_MAP], 0);
+       }
+}
+
+static gboolean
+gtk_source_map__leave_notify_event (GtkSourceMap     *map,
+                                    GdkEventCrossing *event,
+                                    GtkWidget        *widget)
+{
+       gtk_source_map_show_map_and_queue_fade (map);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_map__motion_notify_event (GtkSourceMap   *map,
+                                     GdkEventMotion *motion,
+                                     GtkWidget      *widget)
+{
+       gtk_source_map_show_map_and_queue_fade (map);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_map__scroll_event (GtkSourceMap   *map,
+                              GdkEventScroll *scroll,
+                              GtkWidget      *widget)
+{
+       gtk_source_map_show_map_and_queue_fade (map);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_map_rebuild_css (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->font_desc != NULL)
+       {
+               gchar *css;
+               gchar *tmp;
+
+               tmp = _gtk_source_pango_font_description_to_css (priv->font_desc);
+               css = g_strdup_printf ("GtkSourceView { %s }\n", tmp ?: "");
+               gtk_css_provider_load_from_data (priv->view_css_provider, css, -1, NULL);
+               g_free (css);
+               g_free (tmp);
+       }
+
+       if (priv->view != NULL)
+       {
+               GtkSourceStyleScheme *style_scheme;
+               GtkTextBuffer *buffer;
+
+               buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->view));
+               style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
+
+               if (style_scheme != NULL)
+               {
+                       gchar *background = NULL;
+                       GtkSourceStyle *style;
+
+                       if (!(style = gtk_source_style_scheme_get_style (style_scheme, "map-overlay")) &&
+                           !(style = gtk_source_style_scheme_get_style (style_scheme, "selection")))
+                       {
+                               GtkStyleContext *context;
+                               GdkRGBA color;
+
+                               /* FIXME: how was again to get the selection color? */
+                               context = gtk_widget_get_style_context (GTK_WIDGET (priv->view));
+                               gtk_style_context_save (context);
+                               gtk_style_context_add_class (context, "view");
+                               gtk_style_context_get_color (context,
+                                                            GTK_STATE_FLAG_NORMAL,
+                                                            &color);
+                               gtk_style_context_restore (context);
+                               background = gdk_rgba_to_string (&color);
+                       }
+                       else
+                       {
+                               g_object_get (style,
+                                             "background", &background,
+                                             NULL);
+                       }
+
+                       if (background != NULL)
+                       {
+                               gchar *css;
+
+                               css = g_strdup_printf ("GtkSourceMap GtkEventBox {"
+                                                        "background-color: %s;"
+                                                        "opacity: 0.75;"
+                                                        "border-top: 1px solid shade(%s,0.9);"
+                                                        "border-bottom: 1px solid shade(%s,0.9);"
+                                                      "}\n",
+                                                      background, background, background);
+                               gtk_css_provider_load_from_data (priv->box_css_provider, css, -1, NULL);
+                               g_free (css);
+                       }
+                       g_free (background);
+               }
+       }
+}
+
+static void
+update_scrubber_height (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+       GtkAllocation alloc;
+       gdouble ratio;
+       gint child_height;
+       gint view_height;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       gtk_widget_get_allocation (GTK_WIDGET (priv->view), &alloc);
+       gtk_widget_get_preferred_height (GTK_WIDGET (priv->view),
+                                        NULL, &view_height);
+       gtk_widget_get_preferred_height (GTK_WIDGET (priv->child_view),
+                                        NULL, &child_height);
+
+       ratio = alloc.height / (gdouble)view_height;
+       child_height *= ratio;
+
+       if (child_height > 0)
+       {
+               g_object_set (priv->overlay_box,
+                             "height-request", child_height,
+                             NULL);
+       }
+}
+
+static void
+update_child_vadjustment (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+       GtkAdjustment *vadj;
+       GtkAdjustment *child_vadj;
+       gdouble value;
+       gdouble upper;
+       gdouble page_size;
+       gdouble child_value;
+       gdouble child_upper;
+       gdouble child_page_size;
+       gdouble new_value = 0.0;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->view));
+       child_vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->child_view));
+
+       g_object_get (vadj,
+                     "upper", &upper,
+                     "value", &value,
+                     "page-size", &page_size,
+                     NULL);
+
+       g_object_get (child_vadj,
+                     "upper", &child_upper,
+                     "value", &child_value,
+                     "page-size", &child_page_size,
+                     NULL);
+
+       /*
+        * TODO: Technically we should take into account lower here, but in practice
+        *       it is always 0.0.
+        */
+       if (child_page_size < child_upper)
+       {
+               new_value = (value / (upper - page_size)) * (child_upper - child_page_size);
+       }
+
+       gtk_adjustment_set_value (child_vadj, new_value);
+}
+
+static void
+gtk_source_map__view_vadj_value_changed (GtkSourceMap  *map,
+                                         GtkAdjustment *vadj)
+{
+       GtkSourceMapPrivate *priv;
+       gdouble page_size;
+       gdouble upper;
+       gdouble lower;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       gtk_widget_queue_resize (GTK_WIDGET (priv->overlay_box));
+
+       g_object_get (vadj,
+                     "lower", &lower,
+                     "page-size", &page_size,
+                     "upper", &upper,
+                     NULL);
+
+       update_child_vadjustment (map);
+}
+
+static void
+gtk_source_map__view_vadj_notify_upper (GtkSourceMap  *map,
+                                        GParamSpec    *pspec,
+                                        GtkAdjustment *vadj)
+{
+       update_scrubber_height (map);
+}
+
+static void
+gtk_source_map__buffer_notify_style_scheme (GtkSourceMap  *map,
+                                            GParamSpec    *pspec,
+                                            GtkTextBuffer *buffer)
+{
+       gtk_source_map_rebuild_css (map);
+}
+
+static void
+gtk_source_map__view_notify_buffer (GtkSourceMap  *map,
+                                    GParamSpec    *pspec,
+                                    GtkSourceView *view)
+{
+       GtkTextBuffer *buffer;
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+       g_signal_connect_object (buffer,
+                                "notify::style-scheme",
+                                G_CALLBACK (gtk_source_map__buffer_notify_style_scheme),
+                                map,
+                                G_CONNECT_SWAPPED);
+
+       gtk_source_map_rebuild_css (map);
+}
+
+static void
+gtk_source_map_set_font_desc (GtkSourceMap               *map,
+                              const PangoFontDescription *font_desc)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (font_desc != priv->font_desc)
+       {
+               g_clear_pointer (&priv->font_desc, pango_font_description_free);
+
+               if (font_desc)
+               {
+                       priv->font_desc = pango_font_description_copy (font_desc);
+               }
+       }
+
+       gtk_source_map_rebuild_css (map);
+}
+
+static void
+gtk_source_map_set_font_name (GtkSourceMap *map,
+                              const gchar  *font_name)
+{
+       PangoFontDescription *font_desc;
+
+       if (font_name == NULL)
+       {
+               font_name = "Monospace 1";
+       }
+
+       font_desc = pango_font_description_from_string (font_name);
+       gtk_source_map_set_font_desc (map, font_desc);
+       pango_font_description_free (font_desc);
+}
+
+static void
+gtk_source_map_get_preferred_width (GtkWidget *widget,
+                                    gint      *mininum_width,
+                                    gint      *natural_width)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (widget);
+       GtkSourceMapPrivate *priv;
+       PangoLayout *layout;
+       guint right_margin_position;
+       gint height;
+       gint width;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->font_desc == NULL)
+       {
+               *mininum_width = *natural_width = DEFAULT_WIDTH;
+               return;
+       }
+
+       layout = gtk_widget_create_pango_layout (GTK_WIDGET (priv->child_view), "X");
+       pango_layout_get_pixel_size (layout, &width, &height);
+       g_object_unref (layout);
+
+       right_margin_position = gtk_source_view_get_right_margin_position (priv->view);
+       width *= right_margin_position;
+
+       *mininum_width = *natural_width = width;
+}
+
+static void
+gtk_source_map_get_preferred_height (GtkWidget *widget,
+                                     gint      *minimum_height,
+                                     gint      *natural_height)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (widget);
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->view == NULL)
+       {
+               *minimum_height = *natural_height = 0;
+               return;
+       }
+
+       gtk_widget_get_preferred_height (GTK_WIDGET (priv->child_view),
+                                        minimum_height, natural_height);
+
+       *minimum_height = 0;
+}
+
+static gboolean
+gtk_source_map__child_view_button_press_event (GtkSourceMap   *map,
+                                               GdkEventButton *event,
+                                               GtkSourceView  *child_view)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->view != NULL)
+       {
+               GtkTextIter iter;
+               gint x;
+               gint y;
+
+               gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (child_view),
+                                                      GTK_TEXT_WINDOW_WIDGET,
+                                                      event->x, event->y, &x, &y);
+               gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (child_view),
+                                                   &iter, x, y);
+               gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->view), &iter,
+                                             0.0, TRUE, 1.0, 0.5);
+       }
+
+       return GDK_EVENT_STOP;
+}
+
+static void
+gtk_source_map__child_view_state_flags_changed (GtkWidget     *widget,
+                                                GtkStateFlags  flags,
+                                                GtkWidget     *child_view)
+{
+       GdkWindow *window;
+
+       window = gtk_text_view_get_window (GTK_TEXT_VIEW (child_view),
+                                          GTK_TEXT_WINDOW_TEXT);
+       if (window != NULL)
+       {
+               gdk_window_set_cursor (window, NULL);
+       }
+}
+
+static void
+gtk_source_map__child_view_realize_after (GtkWidget *widget,
+                                          GtkWidget *child_view)
+{
+       gtk_source_map__child_view_state_flags_changed (widget, 0, child_view);
+}
+
+static gboolean
+gtk_source_map__overlay_box_button_press_event (GtkSourceMap   *map,
+                                                GdkEventButton *event,
+                                                GtkEventBox    *overlay_box)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       gtk_grab_add (GTK_WIDGET (overlay_box));
+
+       priv->in_press = TRUE;
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_map__overlay_box_button_release_event (GtkSourceMap   *map,
+                                                  GdkEventButton *event,
+                                                  GtkEventBox    *overlay_box)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       priv->in_press = FALSE;
+
+       gtk_grab_remove (GTK_WIDGET (overlay_box));
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_source_map__overlay_box_motion_notify_event (GtkSourceMap   *map,
+                                                 GdkEventMotion *event,
+                                                 GtkEventBox    *overlay_box)
+{
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->in_press && (priv->view != NULL))
+       {
+               GtkAllocation alloc;
+               GtkAllocation child_alloc;
+               GtkTextBuffer *buffer;
+               GdkRectangle rect;
+               GtkTextIter iter;
+               gdouble ratio;
+               gint child_height;
+               gint x;
+               gint y;
+
+               gtk_widget_get_allocation (GTK_WIDGET (overlay_box), &alloc);
+               gtk_widget_get_allocation (GTK_WIDGET (priv->child_view), &child_alloc);
+
+               gtk_widget_translate_coordinates (GTK_WIDGET (overlay_box),
+                                                 GTK_WIDGET (priv->child_view),
+                                                 event->x, event->y, &x, &y);
+
+               buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->child_view));
+               gtk_text_buffer_get_end_iter (buffer, &iter);
+               gtk_text_view_get_iter_location (GTK_TEXT_VIEW (priv->child_view),
+                                                &iter, &rect);
+
+               child_height = MIN (child_alloc.height, (rect.y + rect.height));
+
+               y = CLAMP (y, child_alloc.y, child_alloc.y + child_height) - child_alloc.y;
+               ratio = (gdouble)y / (gdouble)child_height;
+               y = (rect.y + rect.height) * ratio;
+
+               gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (priv->child_view),
+                                                   &iter, x, y);
+
+               gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->view), &iter,
+                                             0.0, TRUE, 1.0, 0.5);
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_map_size_allocate (GtkWidget     *widget,
+                              GtkAllocation *alloc)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (widget);
+
+       GTK_WIDGET_CLASS (gtk_source_map_parent_class)->size_allocate (widget, alloc);
+
+       update_scrubber_height (map);
+}
+
+static gboolean
+gtk_source_map_do_scroll_event (GtkSourceMap   *map,
+                                GdkEventScroll *event,
+                                GtkWidget      *widget)
+{
+       GtkSourceMapPrivate *priv;
+       static const gint scroll_acceleration = 4;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       /*
+        * TODO: This doesn't propagate kinetic scrolling or anything.
+        *       We should probably make something that does that.
+        */
+       if (priv->view != NULL)
+       {
+               gdouble x;
+               gdouble y;
+               gint count = 0;
+
+               if (event->direction == GDK_SCROLL_UP)
+               {
+                       count = -scroll_acceleration;
+               }
+               else if (event->direction == GDK_SCROLL_DOWN)
+               {
+                       count = scroll_acceleration;
+               }
+               else
+               {
+                       gdk_event_get_scroll_deltas ((GdkEvent *)event, &x, &y);
+
+                       if (y > 0)
+                       {
+                               count = scroll_acceleration;
+                       }
+                       else if (y < 0)
+                       {
+                               count = -scroll_acceleration;
+                       }
+               }
+
+               if (count != 0)
+               {
+                       g_signal_emit_by_name (priv->view, "move-viewport",
+                                              GTK_SCROLL_STEPS, count);
+               }
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_map_destroy (GtkWidget *widget)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (widget);
+       GtkSourceMapPrivate *priv;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->delayed_conceal_timeout)
+       {
+               g_source_remove (priv->delayed_conceal_timeout);
+               priv->delayed_conceal_timeout = 0;
+       }
+
+       g_clear_object (&priv->box_css_provider);
+       g_clear_object (&priv->view_css_provider);
+       g_clear_pointer (&priv->font_desc, pango_font_description_free);
+       gtk_source_clear_weak_pointer (&priv->view);
+
+       GTK_WIDGET_CLASS (gtk_source_map_parent_class)->destroy (widget);
+}
+
+static void
+gtk_source_map_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (object);
+
+       switch (prop_id)
+       {
+               case PROP_VIEW:
+                       g_value_set_object (value, gtk_source_map_get_view (map));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_map_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       GtkSourceMap *map = GTK_SOURCE_MAP (object);
+
+       switch (prop_id)
+       {
+               case PROP_FONT_DESC:
+                       gtk_source_map_set_font_desc (map, g_value_get_boxed (value));
+                       break;
+
+               case PROP_VIEW:
+                       gtk_source_map_set_view (map, g_value_get_object (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_map_class_init (GtkSourceMapClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->get_property = gtk_source_map_get_property;
+       object_class->set_property = gtk_source_map_set_property;
+
+       widget_class->destroy = gtk_source_map_destroy;
+       widget_class->get_preferred_height = gtk_source_map_get_preferred_height;
+       widget_class->get_preferred_width = gtk_source_map_get_preferred_width;
+       widget_class->size_allocate = gtk_source_map_size_allocate;
+
+       pspecs[PROP_VIEW] =
+               g_param_spec_object ("view",
+                                    _("View"),
+                                    _("The view this widget is mapping."),
+                                    GTK_SOURCE_TYPE_VIEW,
+                                    (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+       pspecs[PROP_FONT_DESC] =
+               g_param_spec_boxed ("font-desc",
+                                   _("Font Description"),
+                                   _("The Pango font description to use."),
+                                   PANGO_TYPE_FONT_DESCRIPTION,
+                                   (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class, LAST_PROP, pspecs);
+
+       signals[HIDE_MAP] =
+               g_signal_new ("hide-map",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             0);
+
+       signals[SHOW_MAP] =
+               g_signal_new ("show-map",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             0);
+}
+
+static gboolean
+gtk_source_map_get_child_position (GtkOverlay   *overlay,
+                                   GtkWidget    *child,
+                                   GdkRectangle *alloc,
+                                   GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+       GtkTextIter iter;
+       GdkRectangle visible_area;
+       GdkRectangle loc;
+       GtkAllocation our_alloc;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (priv->view == NULL)
+       {
+               return FALSE;
+       }
+
+       gtk_widget_get_allocation (GTK_WIDGET (overlay), &our_alloc);
+
+       alloc->x = 0;
+       alloc->width = our_alloc.width;
+
+       gtk_widget_get_preferred_height (child, NULL, &alloc->height);
+
+       gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (priv->view),
+                                       &visible_area);
+       gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (priv->view),
+                                           &iter, visible_area.x,
+                                           visible_area.y);
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (priv->child_view),
+                                        &iter, &loc);
+       gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (priv->child_view),
+                                              GTK_TEXT_WINDOW_WIDGET,
+                                              loc.x, loc.y,
+                                              NULL, &alloc->y);
+
+       return TRUE;
+}
+
+static void
+gtk_source_map_init (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+       GtkSourceCompletion *completion;
+       GtkStyleContext *context;
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       priv->overlay = GTK_OVERLAY (gtk_overlay_new ());
+       gtk_widget_show (GTK_WIDGET (priv->overlay));
+       g_signal_connect_object (priv->overlay,
+                                "get-child-position",
+                                G_CALLBACK (gtk_source_map_get_child_position),
+                                map,
+                                0);
+       gtk_container_add (GTK_CONTAINER (map), GTK_WIDGET (priv->overlay));
+
+       priv->child_view = g_object_new (GTK_SOURCE_TYPE_VIEW,
+                                        "auto-indent", FALSE,
+                                        "can-focus", FALSE,
+                                        "draw-spaces", 0,
+                                        "editable", FALSE,
+                                        "expand", FALSE,
+                                        "monospace", TRUE,
+                                        "show-line-numbers", FALSE,
+                                        "show-line-marks", FALSE,
+                                        "show-right-margin", FALSE,
+                                        "visible", TRUE,
+                                        NULL);
+
+       g_signal_connect_object (priv->child_view,
+                                "button-press-event",
+                                G_CALLBACK (gtk_source_map__child_view_button_press_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_events (GTK_WIDGET (priv->child_view), GDK_SCROLL_MASK);
+       g_signal_connect_object (priv->child_view,
+                                "scroll-event",
+                                G_CALLBACK (gtk_source_map_do_scroll_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->child_view,
+                                "state-flags-changed",
+                                G_CALLBACK (gtk_source_map__child_view_state_flags_changed),
+                                map,
+                                G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+       g_signal_connect_object (priv->child_view,
+                                "realize",
+                                G_CALLBACK (gtk_source_map__child_view_realize_after),
+                                map,
+                                G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+       priv->view_css_provider = gtk_css_provider_new ();
+       context = gtk_widget_get_style_context (GTK_WIDGET (priv->child_view));
+       gtk_style_context_add_provider (context,
+                                       GTK_STYLE_PROVIDER (priv->view_css_provider),
+                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_container_add (GTK_CONTAINER (priv->overlay),
+                          GTK_WIDGET (priv->child_view));
+
+       priv->overlay_box = g_object_new (GTK_TYPE_EVENT_BOX,
+                                         "opacity", 0.5,
+                                         "visible", TRUE,
+                                         "height-request", 10,
+                                         "width-request", 100,
+                                         NULL);
+       g_signal_connect_object (priv->overlay_box,
+                                "button-press-event",
+                                G_CALLBACK (gtk_source_map__overlay_box_button_press_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "scroll-event",
+                                G_CALLBACK (gtk_source_map_do_scroll_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "button-release-event",
+                                G_CALLBACK (gtk_source_map__overlay_box_button_release_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "motion-notify-event",
+                                G_CALLBACK (gtk_source_map__overlay_box_motion_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       context = gtk_widget_get_style_context (GTK_WIDGET (priv->overlay_box));
+       priv->box_css_provider = gtk_css_provider_new ();
+       gtk_style_context_add_provider (context,
+                                       GTK_STYLE_PROVIDER (priv->box_css_provider),
+                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+       gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay),
+                                GTK_WIDGET (priv->overlay_box));
+
+       completion = gtk_source_view_get_completion (priv->child_view);
+       gtk_source_completion_block_interactive (completion);
+
+       gtk_source_map_set_font_name (map, "Monospace 1");
+
+       gtk_widget_add_events (GTK_WIDGET (priv->overlay_box),
+                              (GDK_SCROLL_MASK |
+                               GDK_ENTER_NOTIFY_MASK |
+                               GDK_LEAVE_NOTIFY_MASK));
+       gtk_widget_add_events (GTK_WIDGET (priv->child_view),
+                              (GDK_ENTER_NOTIFY_MASK |
+                               GDK_LEAVE_NOTIFY_MASK));
+
+       g_signal_connect_object (priv->overlay_box,
+                                "enter-notify-event",
+                                G_CALLBACK (gtk_source_map__enter_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "leave-notify-event",
+                                G_CALLBACK (gtk_source_map__leave_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "motion-notify-event",
+                                G_CALLBACK (gtk_source_map__motion_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->overlay_box,
+                                "scroll-event",
+                                G_CALLBACK (gtk_source_map__scroll_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (priv->child_view,
+                                "enter-notify-event",
+                                G_CALLBACK (gtk_source_map__enter_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->child_view,
+                                "leave-notify-event",
+                                G_CALLBACK (gtk_source_map__leave_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->child_view,
+                                "motion-notify-event",
+                                G_CALLBACK (gtk_source_map__motion_notify_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (priv->child_view,
+                                "scroll-event",
+                                G_CALLBACK (gtk_source_map__scroll_event),
+                                map,
+                                G_CONNECT_SWAPPED);
+}
+
+/**
+ * gtk_source_map_new:
+ *
+ * Creates a new #GtkSourceMap.
+ *
+ * Returns: a new #GtkSourceMap.
+ *
+ * Since: 3.18
+ */
+GtkWidget *
+gtk_source_map_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_MAP, NULL);
+}
+
+/**
+ * gtk_source_map_set_view:
+ * @map: a #GtkSourceMap
+ * @view: a #GtkSourceView
+ *
+ * Sets the view that @map will be doing the mapping to.
+ *
+ * Since: 3.18
+ */
+void
+gtk_source_map_set_view (GtkSourceMap  *map,
+                         GtkSourceView *view)
+{
+       GtkSourceMapPrivate *priv;
+
+       g_return_if_fail (GTK_SOURCE_IS_MAP (map));
+       g_return_if_fail (view == NULL || GTK_SOURCE_IS_VIEW (view));
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       if (gtk_source_set_weak_pointer (&priv->view, view))
+       {
+               if (view != NULL)
+               {
+                       GtkAdjustment *vadj;
+                       GtkTextBuffer *buffer;
+
+                       g_object_bind_property (priv->view, "buffer",
+                                               priv->child_view, "buffer",
+                                               G_BINDING_SYNC_CREATE);
+                       g_object_bind_property (priv->view, "indent-width",
+                                               priv->child_view, "indent-width",
+                                               G_BINDING_SYNC_CREATE);
+                       g_object_bind_property (priv->view, "tab-width",
+                                               priv->child_view, "tab-width",
+                                               G_BINDING_SYNC_CREATE);
+
+                       g_signal_connect_object (view,
+                                                "notify::buffer",
+                                                G_CALLBACK (gtk_source_map__view_notify_buffer),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+                       g_signal_connect_object (view,
+                                                "enter-notify-event",
+                                                G_CALLBACK (gtk_source_map__enter_notify_event),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+                       g_signal_connect_object (view,
+                                                "leave-notify-event",
+                                                G_CALLBACK (gtk_source_map__leave_notify_event),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+                       g_signal_connect_object (view,
+                                                "motion-notify-event",
+                                                G_CALLBACK (gtk_source_map__motion_notify_event),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+                       g_signal_connect_object (view,
+                                                "scroll-event",
+                                                G_CALLBACK (gtk_source_map__scroll_event),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+
+                       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+                       gtk_source_map__buffer_notify_style_scheme (map, NULL, buffer);
+
+                       vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->view));
+
+                       g_signal_connect_object (vadj,
+                                                "value-changed",
+                                                G_CALLBACK (gtk_source_map__view_vadj_value_changed),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+                       g_signal_connect_object (vadj,
+                                                "notify::upper",
+                                                G_CALLBACK (gtk_source_map__view_vadj_notify_upper),
+                                                map,
+                                                G_CONNECT_SWAPPED);
+
+                       if ((gtk_widget_get_events (GTK_WIDGET (priv->view)) & GDK_ENTER_NOTIFY_MASK) == 0)
+                       {
+                               gtk_widget_add_events (GTK_WIDGET (priv->view),
+                                                      GDK_ENTER_NOTIFY_MASK);
+                       }
+
+                       if ((gtk_widget_get_events (GTK_WIDGET (priv->view)) & GDK_LEAVE_NOTIFY_MASK) == 0)
+                       {
+                               gtk_widget_add_events (GTK_WIDGET (priv->view),
+                                                      GDK_LEAVE_NOTIFY_MASK);
+                       }
+
+                       gtk_source_map_rebuild_css (map);
+               }
+
+               g_object_notify_by_pspec (G_OBJECT (map), pspecs[PROP_VIEW]);
+       }
+}
+
+/**
+ * gtk_source_map_get_view:
+ * @map: a #GtkSourceMap.
+ *
+ * Gets the #GtkSourceMap:view property, which is the view this widget is mapping.
+ *
+ * Returns: (transfer none) (nullable): a #GtkSourceView or %NULL.
+ *
+ * Since: 3.18
+ */
+GtkSourceView *
+gtk_source_map_get_view (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_MAP (map), NULL);
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       return priv->view;
+}
+
+/**
+ * gtk_source_map_get_child_view:
+ * @map: a #GtkSourceMap.
+ *
+ * Gets the internal #GtkSourceView in case further tweaking is required.
+ *
+ * Returns: (transfer none): the internal #GtkSourceView.
+ *
+ * Since: 3.18
+ */
+GtkSourceView *
+gtk_source_map_get_child_view (GtkSourceMap *map)
+{
+       GtkSourceMapPrivate *priv;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_MAP (map), NULL);
+
+       priv = gtk_source_map_get_instance_private (map);
+
+       return priv->child_view;
+}
diff --git a/gtksourceview/gtksourcemap.h b/gtksourceview/gtksourcemap.h
new file mode 100644
index 0000000..de3c170
--- /dev/null
+++ b/gtksourceview/gtksourcemap.h
@@ -0,0 +1,47 @@
+/* gtksourcemap.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SOURCE_MAP_H__
+#define __GTK_SOURCE_MAP_H__
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_MAP (gtk_source_map_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GtkSourceMap, gtk_source_map, GTK_SOURCE, MAP, GtkBin)
+
+struct _GtkSourceMapClass
+{
+       GtkBinClass parent_class;
+
+       gpointer padding[10];
+};
+
+GtkWidget              *gtk_source_map_new             (void);
+
+void                    gtk_source_map_set_view        (GtkSourceMap  *map,
+                                                         GtkSourceView *view);
+
+GtkSourceView          *gtk_source_map_get_view        (GtkSourceMap  *map);
+
+GtkSourceView          *gtk_source_map_get_child_view  (GtkSourceMap  *map);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_MAP_H__ */
diff --git a/gtksourceview/gtksourceview-utils.c b/gtksourceview/gtksourceview-utils.c
index b7072ed..f662329 100644
--- a/gtksourceview/gtksourceview-utils.c
+++ b/gtksourceview/gtksourceview-utils.c
@@ -24,6 +24,7 @@
 #endif
 
 #include <errno.h>
+#include <math.h>
 #include "gtksourceview-utils.h"
 
 #define SOURCEVIEW_DIR "gtksourceview-3.0"
@@ -149,3 +150,160 @@ _gtk_source_string_to_int (const gchar *str)
        return number;
 }
 
+#define FONT_FAMILY  "font-family"
+#define FONT_VARIANT "font-variant"
+#define FONT_STRETCH "font-stretch"
+#define FONT_WEIGHT  "font-weight"
+#define FONT_SIZE    "font-size"
+
+gchar *
+_gtk_source_pango_font_description_to_css (const PangoFontDescription *font_desc)
+{
+       PangoFontMask mask;
+       GString *str;
+
+#define ADD_KEYVAL(key,fmt) \
+       g_string_append(str,key":"fmt";")
+#define ADD_KEYVAL_PRINTF(key,fmt,...) \
+       g_string_append_printf(str,key":"fmt";", __VA_ARGS__)
+
+       g_return_val_if_fail (font_desc, NULL);
+
+       str = g_string_new (NULL);
+
+       mask = pango_font_description_get_set_fields (font_desc);
+
+       if ((mask & PANGO_FONT_MASK_FAMILY) != 0)
+       {
+               const gchar *family;
+
+               family = pango_font_description_get_family (font_desc);
+               ADD_KEYVAL_PRINTF (FONT_FAMILY, "\"%s\"", family);
+       }
+
+       if ((mask & PANGO_FONT_MASK_STYLE) != 0)
+       {
+               PangoVariant variant;
+
+               variant = pango_font_description_get_variant (font_desc);
+
+               switch (variant)
+               {
+                       case PANGO_VARIANT_NORMAL:
+                               ADD_KEYVAL (FONT_VARIANT, "normal");
+                               break;
+
+                       case PANGO_VARIANT_SMALL_CAPS:
+                               ADD_KEYVAL (FONT_VARIANT, "small-caps");
+                               break;
+
+                       default:
+                               break;
+               }
+       }
+
+       if ((mask & PANGO_FONT_MASK_WEIGHT))
+       {
+               gint weight;
+
+               weight = pango_font_description_get_weight (font_desc);
+
+               /*
+                * WORKAROUND:
+                *
+                * font-weight with numbers does not appear to be working as expected
+                * right now. So for the common (bold/normal), let's just use the string
+                * and let gtk warn for the other values, which shouldn't really be
+                * used for this.
+                */
+
+               switch (weight)
+               {
+                       case PANGO_WEIGHT_SEMILIGHT:
+                       /*
+                        * 350 is not actually a valid css font-weight, so we will just round
+                        * up to 400.
+                        */
+                       case PANGO_WEIGHT_NORMAL:
+                               ADD_KEYVAL (FONT_WEIGHT, "normal");
+                               break;
+
+                       case PANGO_WEIGHT_BOLD:
+                               ADD_KEYVAL (FONT_WEIGHT, "bold");
+                               break;
+
+                       case PANGO_WEIGHT_THIN:
+                       case PANGO_WEIGHT_ULTRALIGHT:
+                       case PANGO_WEIGHT_LIGHT:
+                       case PANGO_WEIGHT_BOOK:
+                       case PANGO_WEIGHT_MEDIUM:
+                       case PANGO_WEIGHT_SEMIBOLD:
+                       case PANGO_WEIGHT_ULTRABOLD:
+                       case PANGO_WEIGHT_HEAVY:
+                       case PANGO_WEIGHT_ULTRAHEAVY:
+                       default:
+                               /* round to nearest hundred */
+                               weight = round (weight / 100.0) * 100;
+                               ADD_KEYVAL_PRINTF ("font-weight", "%d", weight);
+                               break;
+               }
+       }
+
+       if ((mask & PANGO_FONT_MASK_STRETCH))
+       {
+               switch (pango_font_description_get_stretch (font_desc))
+               {
+                       case PANGO_STRETCH_ULTRA_CONDENSED:
+                               ADD_KEYVAL (FONT_STRETCH, "untra-condensed");
+                               break;
+
+                       case PANGO_STRETCH_EXTRA_CONDENSED:
+                               ADD_KEYVAL (FONT_STRETCH, "extra-condensed");
+                               break;
+
+                       case PANGO_STRETCH_CONDENSED:
+                               ADD_KEYVAL (FONT_STRETCH, "condensed");
+                               break;
+
+                       case PANGO_STRETCH_SEMI_CONDENSED:
+                               ADD_KEYVAL (FONT_STRETCH, "semi-condensed");
+                               break;
+
+                       case PANGO_STRETCH_NORMAL:
+                               ADD_KEYVAL (FONT_STRETCH, "normal");
+                               break;
+
+                       case PANGO_STRETCH_SEMI_EXPANDED:
+                               ADD_KEYVAL (FONT_STRETCH, "semi-expanded");
+                               break;
+
+                       case PANGO_STRETCH_EXPANDED:
+                               ADD_KEYVAL (FONT_STRETCH, "expanded");
+                               break;
+
+                       case PANGO_STRETCH_EXTRA_EXPANDED:
+                               ADD_KEYVAL (FONT_STRETCH, "extra-expanded");
+                               break;
+
+                       case PANGO_STRETCH_ULTRA_EXPANDED:
+                               ADD_KEYVAL (FONT_STRETCH, "untra-expanded");
+                               break;
+
+                       default:
+                               break;
+               }
+       }
+
+       if ((mask & PANGO_FONT_MASK_SIZE))
+       {
+               gint font_size;
+
+               font_size = pango_font_description_get_size (font_desc) / PANGO_SCALE;
+               ADD_KEYVAL_PRINTF ("font-size", "%dpx", font_size);
+       }
+
+       return g_string_free (str, FALSE);
+
+#undef ADD_KEYVAL
+#undef ADD_KEYVAL_PRINTF
+}
diff --git a/gtksourceview/gtksourceview-utils.h b/gtksourceview/gtksourceview-utils.h
index d6ac3f9..087d721 100644
--- a/gtksourceview/gtksourceview-utils.h
+++ b/gtksourceview/gtksourceview-utils.h
@@ -23,6 +23,7 @@
 #define __GTK_SOURCE_VIEW_UTILS_H__
 
 #include <glib.h>
+#include <pango/pango.h>
 
 G_BEGIN_DECLS
 
@@ -38,6 +39,9 @@ GSList         *_gtk_source_view_get_file_list    (gchar       **path,
 G_GNUC_INTERNAL
 gint    _gtk_source_string_to_int          (const gchar *str);
 
+G_GNUC_INTERNAL
+gchar  *_gtk_source_pango_font_description_to_css (const PangoFontDescription *font_desc);
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_VIEW_UTILS_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 454bcb5..03bccaa 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -12,6 +12,7 @@ AM_CPPFLAGS =                                 \
 
 LDADD = $(top_builddir)/gtksourceview/completion-providers/words/libgtksourcecompletionwords.la \
        $(top_builddir)/gtksourceview/libgtksourceview-core.la \
+       -lm \
        $(DEP_LIBS) \
        $(TESTS_LIBS)
 
diff --git a/tests/test-widget.c b/tests/test-widget.c
index 1702d3b..e5a6f66 100644
--- a/tests/test-widget.c
+++ b/tests/test-widget.c
@@ -52,6 +52,8 @@ struct _TestWidgetPrivate
        GtkSourceView *view;
        GtkSourceBuffer *buffer;
        GtkSourceFile *file;
+       GtkSourceMap *map;
+       GtkCheckButton *show_map_checkbutton;
        GtkCheckButton *indent_width_checkbutton;
        GtkSpinButton *indent_width_spinbutton;
        GtkLabel *cursor_position_info;
@@ -980,6 +982,8 @@ test_widget_class_init (TestWidgetClass *klass)
        gtk_widget_class_bind_template_callback (widget_class, smart_home_end_changed_cb);
 
        gtk_widget_class_bind_template_child_private (widget_class, TestWidget, view);
+       gtk_widget_class_bind_template_child_private (widget_class, TestWidget, map);
+       gtk_widget_class_bind_template_child_private (widget_class, TestWidget, show_map_checkbutton);
        gtk_widget_class_bind_template_child_private (widget_class, TestWidget, indent_width_checkbutton);
        gtk_widget_class_bind_template_child_private (widget_class, TestWidget, indent_width_spinbutton);
        gtk_widget_class_bind_template_child_private (widget_class, TestWidget, cursor_position_info);
@@ -1037,6 +1041,12 @@ test_widget_init (TestWidget *self)
                                "style-scheme",
                                G_BINDING_SYNC_CREATE);
 
+       g_object_bind_property (self->priv->show_map_checkbutton,
+                               "active",
+                               self->priv->map,
+                               "visible",
+                               G_BINDING_SYNC_CREATE);
+
        g_signal_connect (self->priv->background_pattern,
                          "changed",
                          G_CALLBACK (on_background_pattern_changed),
diff --git a/tests/test-widget.ui b/tests/test-widget.ui
index 0578526..82c4f40 100644
--- a/tests/test-widget.ui
+++ b/tests/test-widget.ui
@@ -171,6 +171,20 @@
               </packing>
             </child>
             <child>
+              <object class="GtkCheckButton" id="show_map_checkbutton">
+                <property name="label">Show source map</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">6</property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkCheckButton" id="highlight_current_line">
                 <property name="label">Highlight current line</property>
                 <property name="visible">True</property>
@@ -181,7 +195,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">6</property>
+                <property name="top_attach">7</property>
               </packing>
             </child>
             <child>
@@ -195,7 +209,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">7</property>
+                <property name="top_attach">8</property>
               </packing>
             </child>
             <child>
@@ -209,7 +223,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">8</property>
+                <property name="top_attach">9</property>
               </packing>
             </child>
             <child>
@@ -249,7 +263,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">9</property>
+                <property name="top_attach">10</property>
               </packing>
             </child>
             <child>
@@ -528,6 +542,17 @@
           </packing>
         </child>
         <child>
+          <object class="GtkSourceMap" id="map">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="view">view</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
           <object class="GtkLabel" id="cursor_position_info">
             <property name="visible">True</property>
             <property name="can_focus">False</property>


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