[sushi] text: add a text/source code viewer plugin



commit a2e16c2f3405384c5bafef99bf2b379f92d2f8bd
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Thu May 12 18:34:33 2011 -0400

    text: add a text/source code viewer plugin
    
    It uses GtkSourceView, and automatically enables syntax highlighting and
    line number display for source code files.
    
    Some code for the loader object is taken from
    gtksourceview:tests/test-widget.c, thanks to the GtkSourceView authors!

 configure.ac                     |    3 +-
 data/style/gtk-style.css         |    5 +
 src/Makefile-js.am               |    3 +-
 src/Makefile-sushi.am            |    5 +-
 src/js/viewers/text.js           |  115 +++++++++++++++
 src/libsushi/sushi-text-loader.c |  288 ++++++++++++++++++++++++++++++++++++++
 src/libsushi/sushi-text-loader.h |   37 +++++
 7 files changed, 453 insertions(+), 3 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 628228c..9d58efe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,7 +70,8 @@ PKG_CHECK_MODULES(SUSHI,
                   gstreamer-tag-0.10
                   libmusicbrainz3
                   evince-document-3.0
-                  evince-view-3.0)
+                  evince-view-3.0
+                  gtksourceview-3.0)
 
 GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
 AC_SUBST(GLIB_MKENUMS)
diff --git a/data/style/gtk-style.css b/data/style/gtk-style.css
index 91ba2e7..33681b5 100644
--- a/data/style/gtk-style.css
+++ b/data/style/gtk-style.css
@@ -21,6 +21,11 @@ SushiFontWidget {
     padding: 2;
 }
 
+GtkSourceView {
+    background-color: shade (@np_fg_color, 1.10);
+    font: Monospace 10;
+}
+
 .spinner {
     color: @np_fg_color;
 }
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 9d8500e..18f580d 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -16,7 +16,8 @@ dist_jsviewers_DATA = \
     js/viewers/evince.js \
     js/viewers/folder.js \
     js/viewers/font.js \
-    js/viewers/html.js
+    js/viewers/html.js \
+    js/viewers/text.js
 
 jsutildir = $(pkgdatadir)/js/util
 dist_jsutil_DATA = \
diff --git a/src/Makefile-sushi.am b/src/Makefile-sushi.am
index 883f107..feca2db 100644
--- a/src/Makefile-sushi.am
+++ b/src/Makefile-sushi.am
@@ -20,6 +20,7 @@ sushi_source_h = \
     libsushi/sushi-file-loader.h \
     libsushi/sushi-font-loader.h \
     libsushi/sushi-font-widget.h \
+    libsushi/sushi-text-loader.h \
     libsushi/sushi-utils.h
 
 sushi_source_c = \
@@ -29,6 +30,7 @@ sushi_source_c = \
     libsushi/sushi-file-loader.c \
     libsushi/sushi-font-loader.c \
     libsushi/sushi-font-widget.c \
+    libsushi/sushi-text-loader.c \
     libsushi/sushi-utils.c
 
 sushi-enum-types.h: stamp-sushi-enum-types.h Makefile
@@ -84,7 +86,8 @@ Sushi_1_0_gir_INCLUDES = \
     GstTag-0.10 \
     GdkPixbuf-2.0 \
     Gtk-3.0 \
-    EvinceDocument-3.0
+    EvinceDocument-3.0 \
+    GtkSource-3.0
 
 Sushi_1_0_gir_FILES = \
     $(addprefix $(srcdir)/,$(sushi_source_h)) \
diff --git a/src/js/viewers/text.js b/src/js/viewers/text.js
new file mode 100644
index 0000000..e79afea
--- /dev/null
+++ b/src/js/viewers/text.js
@@ -0,0 +1,115 @@
+let MimeHandler = imports.ui.mimeHandler;
+let GtkClutter = imports.gi.GtkClutter;
+let Gtk = imports.gi.Gtk;
+let GLib = imports.gi.GLib;
+let GtkSource = imports.gi.GtkSource;
+
+let Sushi = imports.gi.Sushi;
+
+let Utils = imports.ui.utils;
+
+function TextRenderer(args) {
+    this._init(args);
+}
+
+TextRenderer.prototype = {
+    _init : function(args) {
+        this.moveOnClick = false;
+        this.canFullScreen = true;
+    },
+
+    render : function(file, mainWindow) {
+        this._mainWindow = mainWindow;
+        this._file = file;
+
+        this._textLoader = new Sushi.TextLoader();
+        this._textLoader.connect("loaded",
+                                 Lang.bind(this, this._onBufferLoaded));
+        this._textLoader.uri = file.get_uri();
+
+        this._spinnerBox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 12);
+        this._spinnerBox.show();
+
+        let spinner = Gtk.Spinner.new();
+        spinner.show();
+        spinner.start();
+        spinner.set_size_request(SPINNER_SIZE, SPINNER_SIZE);
+        
+        this._spinnerBox.pack_start(spinner, true, true, 0);
+
+        let label = new Gtk.Label();
+        label.set_text(_("Loading..."));
+        label.show();
+        this._spinnerBox.pack_start(label, true, true, 0);
+
+        this._actor = new GtkClutter.Actor({ contents: this._spinnerBox });
+        this._actor.set_reactive(true);
+
+        return this._actor;
+    },
+
+    _onBufferLoaded : function(loader, buffer) {
+        this._spinnerBox.destroy();
+
+        this._buffer = buffer;
+        this._buffer["highlight-syntax"] = true;
+
+        let styleManager = GtkSource.StyleSchemeManager.get_default();
+        let scheme = styleManager.get_scheme("tango");
+        this._buffer.set_style_scheme(scheme);
+
+        this._view = new GtkSource.View({ buffer: this._buffer,
+                                          editable: false,
+                                          "cursor-visible": false });
+
+        if (this._buffer.get_language())
+            this._view.set_show_line_numbers(true);
+
+        /* block any button press event */
+        this._view.connect("button-press-event",
+                           Lang.bind(this, function() {
+                               return true;
+                           }));
+
+        /* we don't want the ibeam cursor, since we don't allow
+         * editing/selecting
+         */
+        this._view.connect("realize",
+                           Lang.bind(this, function() {
+                               let window = this._view.get_window(Gtk.TextWindowType.TEXT);
+                               window.set_cursor(null);
+                           }));
+
+        this._scrolledWin = Gtk.ScrolledWindow.new(null, null);
+        this._scrolledWin.add(this._view);
+        this._scrolledWin.show_all();    
+
+        this._actor.get_widget().add(this._scrolledWin);
+        this._mainWindow.refreshSize();    
+    },
+
+    getSizeForAllocation : function(allocation) {
+        let baseSize = [];
+
+        if (!this._view) {
+            baseSize = [ this._spinnerBox.get_preferred_size()[0].width,
+                         this._spinnerBox.get_preferred_size()[0].height ];
+        } else {
+            baseSize = allocation;
+        }
+
+        return baseSize;
+    }
+}
+
+let handler = new MimeHandler.MimeHandler();
+let renderer = new TextRenderer();
+
+/* register for text/plain and let the mime handler call us
+ * for child types.
+ */
+let mimeTypes = [
+    "text/plain",
+];
+
+handler.registerMimeTypes(mimeTypes, renderer);
\ No newline at end of file
diff --git a/src/libsushi/sushi-text-loader.c b/src/libsushi/sushi-text-loader.c
new file mode 100644
index 0000000..94e1694
--- /dev/null
+++ b/src/libsushi/sushi-text-loader.c
@@ -0,0 +1,288 @@
+#include "sushi-text-loader.h"
+
+#include <gtksourceview/gtksourceview.h>
+#include <gtksourceview/gtksourcelanguagemanager.h>
+
+#include <string.h>
+
+#include "sushi-utils.h"
+
+G_DEFINE_TYPE (SushiTextLoader, sushi_text_loader, G_TYPE_OBJECT);
+
+enum {
+  PROP_URI = 1,
+  NUM_PROPERTIES
+};
+
+enum {
+  LOADED,
+  NUM_SIGNALS
+};
+
+static GParamSpec* properties[NUM_PROPERTIES] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+struct _SushiTextLoaderPrivate {
+  gchar *uri;
+
+  GtkSourceBuffer *buffer;
+};
+
+/* code adapted from gtksourceview:tests/test-widget.c
+ * License: LGPL v2.1+
+ * Copyright (C) 2001 - Mikael Hermansson <tyan linux se>
+ * Copyright (C) 2003 - Gustavo Giráldez <gustavo giraldez gmx net>
+ */
+static GtkSourceLanguage *
+get_language_for_file (GtkTextBuffer *buffer,
+                       const gchar *filename)
+{
+  GtkSourceLanguageManager *manager;
+  GtkSourceLanguage *language;
+  GtkTextIter start, end;
+  gchar *text;
+  gchar *content_type;
+  gboolean result_uncertain;
+
+  gtk_text_buffer_get_start_iter (buffer, &start);
+
+  if (gtk_text_buffer_get_char_count (buffer) < 1024)
+    gtk_text_buffer_get_end_iter (buffer, &end);
+  else
+    gtk_text_buffer_get_iter_at_offset (buffer, &end, 1024);
+
+  text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
+
+  content_type = g_content_type_guess (filename,
+                                       (guchar*) text,
+                                       strlen (text),
+                                       &result_uncertain);
+  if (result_uncertain) {
+    g_free (content_type);
+    content_type = NULL;
+  }
+
+  manager = gtk_source_language_manager_get_default ();
+  language = gtk_source_language_manager_guess_language (manager,
+                                                         filename,
+                                                         content_type);
+
+  g_free (content_type);
+  g_free (text);
+
+  return language;
+}
+
+static GtkSourceLanguage *
+get_language_by_id (const gchar *id)
+{
+  GtkSourceLanguageManager *manager;
+  manager = gtk_source_language_manager_get_default ();
+
+  return gtk_source_language_manager_get_language (manager, id);
+}
+
+static GtkSourceLanguage *
+text_loader_get_buffer_language (SushiTextLoader *self,
+                                 GFile *file)
+{
+  GtkSourceBuffer *buffer = self->priv->buffer;
+  GtkSourceLanguage *language = NULL;
+  GtkTextIter start, end;
+  gchar *text;
+  gchar *lang_string;
+
+  gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start);
+  end = start;
+  gtk_text_iter_forward_line (&end);
+
+#define LANG_STRING "gtk-source-lang:"
+  text = gtk_text_iter_get_slice (&start, &end);
+  lang_string = strstr (text, LANG_STRING);
+
+  if (lang_string != NULL) {
+    gchar **tokens;
+
+    lang_string += strlen (LANG_STRING);
+    g_strchug (lang_string);
+
+    tokens = g_strsplit_set (lang_string, " \t\n", 2);
+
+    if (tokens != NULL && tokens[0] != NULL)
+      language = get_language_by_id (tokens[0]);
+
+    g_strfreev (tokens);
+  }
+
+  if (language == NULL) {
+    gchar *basename;
+
+    basename = g_file_get_basename (file);
+    language = get_language_for_file (GTK_TEXT_BUFFER (buffer), basename);
+
+    g_free (basename);
+  }
+
+  g_free (text);
+
+  return language;
+}
+
+static void
+load_contents_async_ready_cb (GObject *source,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+  SushiTextLoader *self = user_data;
+  GError *error = NULL;
+  gchar *contents;
+  GtkSourceLanguage *language = NULL;
+
+  g_file_load_contents_finish (G_FILE (source), res,
+                               &contents, NULL, NULL,
+                               &error);
+
+  if (error != NULL) {
+    /* FIXME: we need to report the error */
+    g_print ("Can't load the text file: %s\n", error->message);
+    g_error_free (error);
+
+    return;
+  }
+
+  gtk_source_buffer_begin_not_undoable_action (self->priv->buffer);
+  gtk_text_buffer_set_text (GTK_TEXT_BUFFER (self->priv->buffer), contents, -1);
+  gtk_source_buffer_end_not_undoable_action (self->priv->buffer);
+
+  language = text_loader_get_buffer_language (self, G_FILE (source));
+  gtk_source_buffer_set_language (self->priv->buffer, language);
+
+  g_signal_emit (self, signals[LOADED], 0, self->priv->buffer);
+
+  g_free (contents);
+}
+
+static void
+start_loading_buffer (SushiTextLoader *self)
+{
+  GFile *file;
+
+  self->priv->buffer = gtk_source_buffer_new (NULL);
+
+  file = g_file_new_for_uri (self->priv->uri);
+  g_file_load_contents_async (file, NULL,
+                              load_contents_async_ready_cb,
+                              self);
+
+  g_object_unref (file);
+}
+
+static void
+sushi_text_loader_set_uri (SushiTextLoader *self,
+                          const gchar *uri)
+{
+  if (g_strcmp0 (uri, self->priv->uri) != 0) {
+    g_free (self->priv->uri);
+
+    self->priv->uri = g_strdup (uri);
+    g_clear_object (&self->priv->buffer);
+
+    start_loading_buffer (self);
+
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_URI]);
+  }
+}
+
+static void
+sushi_text_loader_dispose (GObject *object)
+{
+  SushiTextLoader *self = SUSHI_TEXT_LOADER (object);
+
+  g_free (self->priv->uri);
+
+  G_OBJECT_CLASS (sushi_text_loader_parent_class)->dispose (object);
+}
+
+static void
+sushi_text_loader_get_property (GObject *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  SushiTextLoader *self = SUSHI_TEXT_LOADER (object);
+
+  switch (prop_id) {
+  case PROP_URI:
+    g_value_set_string (value, self->priv->uri);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+sushi_text_loader_set_property (GObject *object,
+                               guint       prop_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+  SushiTextLoader *self = SUSHI_TEXT_LOADER (object);
+
+  switch (prop_id) {
+  case PROP_URI:
+    sushi_text_loader_set_uri (self, g_value_get_string (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+sushi_text_loader_class_init (SushiTextLoaderClass *klass)
+{
+  GObjectClass *oclass;
+
+  oclass = G_OBJECT_CLASS (klass);
+  oclass->dispose = sushi_text_loader_dispose;
+  oclass->get_property = sushi_text_loader_get_property;
+  oclass->set_property = sushi_text_loader_set_property;
+
+  properties[PROP_URI] =
+    g_param_spec_string ("uri",
+                         "URI",
+                         "The URI to load",
+                         NULL,
+                         G_PARAM_READWRITE);
+
+  signals[LOADED] =
+    g_signal_new ("loaded",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE,
+                  1, GTK_SOURCE_TYPE_BUFFER);
+
+  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+
+  g_type_class_add_private (klass, sizeof (SushiTextLoaderPrivate));
+}
+
+static void
+sushi_text_loader_init (SushiTextLoader *self)
+{
+  self->priv =
+    G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                 SUSHI_TYPE_TEXT_LOADER,
+                                 SushiTextLoaderPrivate);
+}
+
+SushiTextLoader *
+sushi_text_loader_new (const gchar *uri)
+{
+  return g_object_new (SUSHI_TYPE_TEXT_LOADER,
+                       "uri", uri,
+                       NULL);
+}
diff --git a/src/libsushi/sushi-text-loader.h b/src/libsushi/sushi-text-loader.h
new file mode 100644
index 0000000..02a6afb
--- /dev/null
+++ b/src/libsushi/sushi-text-loader.h
@@ -0,0 +1,37 @@
+#ifndef __SUSHI_TEXT_LOADER_H__
+#define __SUSHI_TEXT_LOADER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define SUSHI_TYPE_TEXT_LOADER            (sushi_text_loader_get_type ())
+#define SUSHI_TEXT_LOADER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SUSHI_TYPE_TEXT_LOADER, SushiTextLoader))
+#define SUSHI_IS_TEXT_LOADER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SUSHI_TYPE_TEXT_LOADER))
+#define SUSHI_TEXT_LOADER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  SUSHI_TYPE_TEXT_LOADER, SushiTextLoaderClass))
+#define SUSHI_IS_TEXT_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  SUSHI_TYPE_TEXT_LOADER))
+#define SUSHI_TEXT_LOADER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  SUSHI_TYPE_TEXT_LOADER, SushiTextLoaderClass))
+
+typedef struct _SushiTextLoader          SushiTextLoader;
+typedef struct _SushiTextLoaderPrivate   SushiTextLoaderPrivate;
+typedef struct _SushiTextLoaderClass     SushiTextLoaderClass;
+
+struct _SushiTextLoader
+{
+  GObject parent_instance;
+
+  SushiTextLoaderPrivate *priv;
+};
+
+struct _SushiTextLoaderClass
+{
+  GObjectClass parent_class;
+};
+
+GType    sushi_text_loader_get_type     (void) G_GNUC_CONST;
+
+SushiTextLoader *sushi_text_loader_new (const gchar *uri);
+
+G_END_DECLS
+
+#endif /* __SUSHI_TEXT_LOADER_H__ */



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