[sushi] text: add a text/source code viewer plugin
- From: Cosimo Cecchi <cosimoc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [sushi] text: add a text/source code viewer plugin
- Date: Thu, 12 May 2011 23:40:23 +0000 (UTC)
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]