[gtksourceview/wip/minimap] Add source map widget
- From: Ignacio Casal Quinteiro <icq src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/minimap] Add source map widget
- Date: Fri, 1 May 2015 14:45:39 +0000 (UTC)
commit ad891b6fb9213f4516f5492700fda5540f030c7a
Author: Ignacio Casal Quinteiro <icq gnome org>
Date: Fri May 1 15:25:34 2015 +0200
Add source map widget
gtksourceview/Makefile.am | 3 +
gtksourceview/gtksource.h | 1 +
gtksourceview/gtksourcemap.c | 1040 +++++++++++++++++++++++++++++++++++
gtksourceview/gtksourcemap.h | 45 ++
gtksourceview/gtksourceview-utils.c | 158 ++++++
gtksourceview/gtksourceview-utils.h | 4 +
tests/Makefile.am | 1 +
tests/test-widget.ui | 19 +
8 files changed, 1271 insertions(+), 0 deletions(-)
---
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..518fcfc
--- /dev/null
+++ b/gtksourceview/gtksourcemap.c
@@ -0,0 +1,1040 @@
+/* gtksourcemap.c
+ *
+ * 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/>.
+ */
+
+#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")))
+ {
+ return;
+ }
+
+ g_object_get (style,
+ "background", &background,
+ NULL);
+
+ if ((background != NULL) && *background == '#')
+ {
+ 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_clear_object (&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;
+ GtkTextIter iter;
+
+ priv = gtk_source_map_get_instance_private (map);
+
+ if (priv->view != NULL)
+ {
+ 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;
+
+ priv = gtk_source_map_get_instance_private (map);
+
+#define SCROLL_ACCELERATION 4
+
+ /*
+ * 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);
+ }
+ }
+
+#undef SCROLL_ACCELERATION
+
+ 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));
+ g_object_class_install_property (object_class, PROP_VIEW, pspecs[PROP_VIEW]);
+
+ 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_property (object_class, PROP_FONT_DESC, pspecs[PROP_FONT_DESC]);
+
+ 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);
+
+ /* adjust box to not cover line changes */
+ alloc->x = 3;
+ alloc->width = our_alloc.width - alloc->x;
+
+ 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);
+}
+
+GtkWidget *
+gtk_source_map_new (void)
+{
+ return g_object_new (GTK_SOURCE_TYPE_MAP, NULL);
+}
+
+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:
+ *
+ * Gets the #GtkSourceMap:view property, which is the view this widget is mapping.
+ *
+ * Returns: (transfer none) (nullable): A #GtkSourceView or %NULL.
+ */
+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;
+}
diff --git a/gtksourceview/gtksourcemap.h b/gtksourceview/gtksourcemap.h
new file mode 100644
index 0000000..7e24fd6
--- /dev/null
+++ b/gtksourceview/gtksourcemap.h
@@ -0,0 +1,45 @@
+/* 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);
+
+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.ui b/tests/test-widget.ui
index 0578526..c5ada05 100644
--- a/tests/test-widget.ui
+++ b/tests/test-widget.ui
@@ -528,6 +528,25 @@
</packing>
</child>
<child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkSourceMap" id="map">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="view">view</property>
+ </object>
+ </child>
+ </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]