[geany/geany-plugins] bc9b59: markdown: Massive refactoring

Matthew Brush git-noreply at xxxxx
Sun Aug 26 09:16:27 UTC 2012


Branch:      refs/heads/master
Author:      Matthew Brush <matt at geany.org>
Committer:   Matthew Brush <matt at geany.org>
Date:        Tue, 17 Jul 2012 03:58:18
Commit:      bc9b59029b5263600b3ce56e9e03fa9059680b47
             https://github.com/geany/geany-plugins/commit/bc9b59029b5263600b3ce56e9e03fa9059680b47

Log Message:
-----------
markdown: Massive refactoring

* Make MarkdownViewer a GObject, and a subclass of WebKitWebView
* A MarkdownViewer "has a" MarkdownConfig property
* Optimize (somewhat) string handling/duplication also using changes
  to Discount code.
* Main plugin code manages the scrolled window holding the
  MarkdownViewer and monitors the MarkdownViewer's MarkdownConfig
  for property changes to know when to move the scrolled window
  between the sidebar and message window notebooks.
* Various other cleanups and changes


Modified Paths:
--------------
    markdown/src/conf.c
    markdown/src/conf.h
    markdown/src/plugin.c
    markdown/src/viewer.c
    markdown/src/viewer.h

Modified: markdown/src/conf.c
29 files changed, 26 insertions(+), 3 deletions(-)
===================================================================
@@ -1,5 +1,5 @@
 /*
- * markdownconfig.c
+ * config.c - Part of the Geany Markdown plugin
  *
  * Copyright 2012 Matthew Brush <mbrush at codebrainz.ca>
  *
@@ -83,6 +83,7 @@ struct _MarkdownConfigPrivate
   gchar filename[PATH_MAX];
   GKeyFile *kf;
   guint handle;
+  guint dlg_handle;
   gboolean initialized;
   gchar *tmpl_text;
   gsize tmpl_text_len;
@@ -432,7 +433,7 @@ static gboolean on_idle_timeout(MarkdownConfig *conf)
 
   contents = g_key_file_to_data(conf->priv->kf, &len, &error);
 
-  g_debug("Saving: %s\n%s", conf->priv->filename, contents);
+  //g_debug("Saving: %s\n%s", conf->priv->filename, contents);
 
   if (error) {
     g_warning("Error getting config data as string: %s", error->message);
@@ -666,7 +667,8 @@ GtkWidget *markdown_config_gui(MarkdownConfig *conf, GtkDialog *dialog)
     g_free(tmpl_file);
   }
 
-  g_signal_connect_swapped(dialog, "response", G_CALLBACK(on_dialog_response), conf);
+  conf->priv->dlg_handle = g_signal_connect_swapped(dialog, "response",
+    G_CALLBACK(on_dialog_response), conf);
 
   gtk_widget_show_all(table);
 
@@ -682,3 +684,24 @@ GtkWidget *markdown_config_gui(MarkdownConfig *conf, GtkDialog *dialog)
   }
   return (const gchar *) conf->priv->tmpl_text;
 }
+
+gchar *
+markdown_config_get_dirname(MarkdownConfig *conf)
+{
+  g_return_val_if_fail(conf, NULL);
+  return g_path_get_dirname(conf->priv->filename);
+}
+
+MarkdownConfigViewPos markdown_config_get_view_pos(MarkdownConfig *conf)
+{
+  guint view_pos;
+  g_return_val_if_fail(MARKDOWN_IS_CONFIG(conf), MARKDOWN_CONFIG_VIEW_POS_SIDEBAR);
+  g_object_get(conf, "view-pos", &view_pos, NULL);
+  return (MarkdownConfigViewPos) view_pos;
+}
+
+void markdown_config_set_view_pos(MarkdownConfig *conf, MarkdownConfigViewPos view_pos)
+{
+  g_return_if_fail(MARKDOWN_IS_CONFIG(conf));
+  g_object_set(conf, "view-pos", view_pos, NULL);
+}


Modified: markdown/src/conf.h
16 files changed, 9 insertions(+), 7 deletions(-)
===================================================================
@@ -1,5 +1,5 @@
 /*
- * markdownconfig.h
+ * config.h - Part of the Geany Markdown plugin
  *
  * Copyright 2012 Matthew Brush <mbrush at codebrainz.ca>
  *
@@ -21,16 +21,14 @@
  *
  */
 
-
-#ifndef __MARKDOWNCONFIG_H__
-#define __MARKDOWNCONFIG_H__
+#ifndef MARKDOWN_CONF_H
+#define MARKDOWN_CONF_H 1
 
 #include <gtk/gtk.h>
 #include <glib-object.h>
 
 G_BEGIN_DECLS
 
-
 #define MARKDOWN_TYPE_CONFIG             (markdown_config_get_type ())
 #define MARKDOWN_CONFIG(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), MARKDOWN_TYPE_CONFIG, MarkdownConfig))
 #define MARKDOWN_CONFIG_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), MARKDOWN_TYPE_CONFIG, MarkdownConfigClass))
@@ -59,14 +57,18 @@ struct _MarkdownConfigClass
   GObjectClass parent_class;
 };
 
-
 GType markdown_config_get_type(void);
 MarkdownConfig *markdown_config_new(const gchar *filename);
 gboolean markdown_config_save(MarkdownConfig *conf);
 GtkWidget *markdown_config_gui(MarkdownConfig *conf, GtkDialog *dialog);
 
 const gchar *markdown_config_get_template_text(MarkdownConfig *conf);
+gchar *markdown_config_get_dirname(MarkdownConfig *conf);
+
+/* Property accessors */
+MarkdownConfigViewPos markdown_config_get_view_pos(MarkdownConfig *conf);
+void markdown_config_set_view_pos(MarkdownConfig *conf, MarkdownConfigViewPos view_pos);
 
 G_END_DECLS
 
-#endif /* __MARKDOWNCONFIG_H__ */
+#endif /* MARKDOWN_CONF_H */


Modified: markdown/src/plugin.c
245 files changed, 128 insertions(+), 117 deletions(-)
===================================================================
@@ -44,174 +44,185 @@
 #ifndef MARKDOWN_HELP_FILE
 #  define MARKDOWN_HELP_FILE MARKDOWN_DOC_DIR "/html/help.html"
 #endif
+#define MARKDOWN_HELP_FILE_URI "file://" MARKDOWN_HELP_FILE
+
+#define MARKDOWN_PREVIEW_LABEL _("Markdown Preview")
 
 /* Global data */
-typedef struct MarkdownPlugin {
-  struct {
-    guint update_view;
-    guint save_conf;
-  } handler_ids;
-  GtkWidget *menu_item;
-  MarkdownViewer *viewer;
-  MarkdownConfig *config;
-} MarkdownPlugin;
-MarkdownPlugin markdown_plugin = { { 0, 0 }, NULL, NULL, NULL };
+static MarkdownViewer *g_viewer = NULL;
+static GtkWidget *g_scrolled_win = NULL;
 
 /* Forward declarations */
-static gboolean on_idle_handler(MarkdownPlugin *plugin);
-static void handle_update_later(MarkdownPlugin *plugin);
-static gboolean on_editor_notify(GObject *obj, GeanyEditor *editor, SCNotification *notif, MarkdownPlugin *plugin);
-static void on_document_signal(GObject *obj, GeanyDocument *doc, MarkdownPlugin *plugin);
-static void on_document_filetype_set(GObject *obj, GeanyDocument *doc, GeanyFiletype *ft_old, MarkdownPlugin *plugin);
-
-static void
-on_conf_prop_notify(GObject *obj, GParamSpec *pspec, MarkdownPlugin *plugin)
-{
-  handle_update_later(plugin);
-}
+static void update_markdown_viewer(MarkdownViewer *viewer);
+static gboolean on_editor_notify(GObject *obj, GeanyEditor *editor, SCNotification *notif, MarkdownViewer *viewer);
+static void on_document_signal(GObject *obj, GeanyDocument *doc, MarkdownViewer *viewer);
+static void on_document_filetype_set(GObject *obj, GeanyDocument *doc, GeanyFiletype *ft_old, MarkdownViewer *viewer);
+static void on_view_pos_notify(GObject *obj, GParamSpec *pspec, MarkdownViewer *viewer);
 
 /* Main plugin entry point on plugin load. */
 void plugin_init(GeanyData *data)
 {
   gchar *conf_fn;
+  MarkdownConfig *conf;
   MarkdownConfigViewPos view_pos;
+  GtkWidget *viewer;
 
-  conf_fn = g_build_filename(geany->app->configdir, "plugins", "markdown", "markdown.conf", NULL);
-  markdown_plugin.config = markdown_config_new(conf_fn);
+  /* Setup the config object which is needed by the view. */
+  conf_fn = g_build_filename(geany->app->configdir, "plugins", "markdown",
+    "markdown.conf", NULL);
+  conf = markdown_config_new(conf_fn);
   g_free(conf_fn);
 
-  g_signal_connect(markdown_plugin.config, "notify",
-    G_CALLBACK(on_conf_prop_notify), &markdown_plugin);
-
-  g_object_get(markdown_plugin.config, "view-pos", &view_pos, NULL);
-
-  switch (view_pos) {
-    case MARKDOWN_CONFIG_VIEW_POS_MSGWIN:
-      markdown_plugin.viewer = markdown_viewer_new(
-        GTK_NOTEBOOK(geany->main_widgets->message_window_notebook));
-      break;
-    case MARKDOWN_CONFIG_VIEW_POS_SIDEBAR:
-    default:
-      markdown_plugin.viewer = markdown_viewer_new(
-        GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook));
-      break;
+  viewer = markdown_viewer_new(conf);
+  /* store as global for plugin_cleanup() */
+  g_viewer = MARKDOWN_VIEWER(viewer);
+  view_pos = markdown_config_get_view_pos(conf);
+
+  g_scrolled_win = gtk_scrolled_window_new(NULL, NULL);
+  gtk_container_add(GTK_CONTAINER(g_scrolled_win), viewer);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(g_scrolled_win),
+    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+  if (view_pos == MARKDOWN_CONFIG_VIEW_POS_MSGWIN) {
+    gtk_notebook_append_page(
+      GTK_NOTEBOOK(geany->main_widgets->message_window_notebook),
+      g_scrolled_win, gtk_label_new(MARKDOWN_PREVIEW_LABEL));
+  } else {
+    gtk_notebook_append_page(
+      GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook),
+      g_scrolled_win, gtk_label_new(MARKDOWN_PREVIEW_LABEL));
   }
 
-  plugin_signal_connect(geany_plugin, NULL, "editor-notify", TRUE,
-    G_CALLBACK(on_editor_notify), &markdown_plugin);
-  plugin_signal_connect(geany_plugin, NULL, "document-activate", TRUE,
-    G_CALLBACK(on_document_signal), &markdown_plugin);
-  plugin_signal_connect(geany_plugin, NULL, "document-filetype-set", TRUE,
-    G_CALLBACK(on_document_filetype_set), &markdown_plugin);
-  plugin_signal_connect(geany_plugin, NULL, "document-new", TRUE,
-    G_CALLBACK(on_document_signal), &markdown_plugin);
-  plugin_signal_connect(geany_plugin, NULL, "document-open", TRUE,
-    G_CALLBACK(on_document_signal), &markdown_plugin);
-  plugin_signal_connect(geany_plugin, NULL, "document-reload", TRUE,
-    G_CALLBACK(on_document_signal), &markdown_plugin);
-
-  handle_update_later(&markdown_plugin);
-
-  /* Prevent segmentation fault when plugin is reloaded. */
+  gtk_widget_show_all(g_scrolled_win);
+
+  g_signal_connect(conf, "notify::view-pos", G_CALLBACK(on_view_pos_notify), viewer);
+
+#define MD_PSC(sig, cb) \
+  plugin_signal_connect(geany_plugin, NULL, (sig), TRUE, G_CALLBACK(cb), viewer)
+  /* Geany takes care of disconnecting these for us when the plugin is unloaded,
+   * the macro is just to make the code smaller/clearer. */
+  MD_PSC("editor-notify", on_editor_notify);
+  MD_PSC("document-activate", on_document_signal);
+  MD_PSC("document-filetype-set", on_document_filetype_set);
+  MD_PSC("document-new", on_document_signal);
+  MD_PSC("document-open", on_document_signal);
+  MD_PSC("document-reload", on_document_signal);
+#undef MD_PSC
+
+  /* Prevent segfault in plugin when it registers GTypes and gets unloaded
+   * and when reloaded tries to re-register the GTypes. */
   plugin_module_make_resident(geany_plugin);
 }
 
 /* Cleanup resources on plugin unload. */
 void plugin_cleanup(void)
 {
-  g_object_unref(markdown_plugin.config);
-  markdown_viewer_free(markdown_plugin.viewer);
-  return;
+  gtk_widget_destroy(g_scrolled_win);
 }
 
+/* Called to show the preferences GUI. */
 GtkWidget *plugin_configure(GtkDialog *dialog)
 {
-  return markdown_config_gui(markdown_plugin.config, dialog);
+  MarkdownConfig *conf = NULL;
+  g_object_get(g_viewer, "config", &conf, NULL);
+  return markdown_config_gui(conf, dialog);
 }
 
+/* Called to show the plugin's help */
 void plugin_help(void)
 {
-  gchar *uri = g_strdup_printf("file://%s", MARKDOWN_HELP_FILE);
-  utils_open_browser(uri);
-  g_free(uri);
+  utils_open_browser(MARKDOWN_HELP_FILE_URI);
 }
 
-/* Update markdown preview when idle. */
-static gboolean on_idle_handler(MarkdownPlugin *plugin)
+/* All of the various signal handlers call this function to update the
+ * MarkdownViewer on specific events. This causes a bunch of memory
+ * allocations, re-compiles the Markdown to HTML, reformats the HTML
+ * template, copies the HTML into the webview and causes it to (eventually)
+ * be redrawn. Only call it when really needed, like when the scintilla
+ * editor's text contents change and not on other editor events.
+ */
+static void
+update_markdown_viewer(MarkdownViewer *viewer)
 {
-  gchar *md_text;
   GeanyDocument *doc = document_get_current();
-  MarkdownConfigViewPos view_pos;
-
-  g_object_get(plugin->config, "view-pos", &view_pos, NULL);
-
-  switch (view_pos) {
-    case MARKDOWN_CONFIG_VIEW_POS_MSGWIN:
-      markdown_viewer_set_notebook(plugin->viewer,
-        GTK_NOTEBOOK(geany->main_widgets->message_window_notebook));
-      break;
-    case MARKDOWN_CONFIG_VIEW_POS_SIDEBAR:
-    default:
-      markdown_viewer_set_notebook(plugin->viewer,
-        GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook));
-      break;
-  }
 
-  /* Only handle valid Markdown documents */
-  if (!DOC_VALID(doc) || g_strcmp0(doc->file_type->name, "Markdown") != 0) {
-    markdown_viewer_load_markdown_string(plugin->viewer,
-      _("The current document does not have a Markdown filetype."), "UTF-8",
-      plugin->config);
-    plugin->handler_ids.update_view = 0;
-    return FALSE;
+  if (DOC_VALID(doc) && g_strcmp0(doc->file_type->name, "Markdown") == 0) {
+    gchar *text;
+    text = (gchar*) scintilla_send_message(doc->editor->sci, SCI_GETCHARACTERPOINTER, 0, 0);
+    markdown_viewer_set_markdown(viewer, text, doc->encoding);
+  } else {
+    markdown_viewer_set_markdown(viewer,
+      _("The current document does not have a Markdown filetype."), "UTF-8");
   }
 
-  md_text = (gchar*) scintilla_send_message(doc->editor->sci,
-    SCI_GETCHARACTERPOINTER, 0, 0);
-  markdown_viewer_load_markdown_string(plugin->viewer, md_text,
-    doc->encoding, plugin->config);
-
-  plugin->handler_ids.update_view = 0;
-
-  return FALSE;
+  markdown_viewer_queue_update(viewer);
 }
 
-/* Queue update of the markdown view later if an update isn't queued. */
-static void handle_update_later(MarkdownPlugin *plugin)
-{
-  if (plugin->handler_ids.update_view == 0) {
-    plugin->handler_ids.update_view = plugin_idle_add(geany_plugin,
-      (GSourceFunc) on_idle_handler, plugin);
-  }
-}
+/* Return TRUE if event is a buffer modification that inserts or deletes
+ * text and which caused a text changed length greater than 0. */
+#define IS_MOD_NOTIF(nt) (nt->nmhdr.code == SCN_MODIFIED && \
+                          nt->length > 0 && ( \
+                          (nt->modificationType & SC_MOD_INSERTTEXT) || \
+                          (nt->modificationType & SC_MOD_DELETETEXT)))
 
 /* Queue update of the markdown preview on editor text change. */
 static gboolean on_editor_notify(GObject *obj, GeanyEditor *editor,
-  SCNotification *notif, MarkdownPlugin *plugin)
+  SCNotification *notif, MarkdownViewer *viewer)
 {
-  GeanyDocument *doc = document_get_current();
-
-  if (DOC_VALID(doc) && g_strcmp0(doc->file_type->name, "Markdown") == 0 &&
-      notif->nmhdr.code == SCN_MODIFIED &&
-      ((notif->modificationType & SC_MOD_INSERTTEXT) || (notif->modificationType & SC_MOD_DELETETEXT)) &&
-      notif->length > 0)
-  {
-    handle_update_later(plugin);
+  if (IS_MOD_NOTIF(notif)) {
+    update_markdown_viewer(viewer);
   }
-
-  return FALSE;
+  return FALSE; /* Allow others to handle this event too */
 }
 
 /* Queue update of the markdown preview on document signals (new, open,
  * activate, etc.) */
-static void on_document_signal(GObject *obj, GeanyDocument *doc, MarkdownPlugin *plugin)
+static void on_document_signal(GObject *obj, GeanyDocument *doc, MarkdownViewer *viewer)
 {
-  handle_update_later(plugin);
+  update_markdown_viewer(viewer);
 }
 
 /* Queue update of the markdown preview when a document's filetype is set */
 static void on_document_filetype_set(GObject *obj, GeanyDocument *doc, GeanyFiletype *ft_old,
-  MarkdownPlugin *plugin)
+  MarkdownViewer *viewer)
+{
+  update_markdown_viewer(viewer);
+}
+
+/* Move the MarkdownViewer to the correct notebook when the view position
+ * is changed. */
+static void
+on_view_pos_notify(GObject *obj, GParamSpec *pspec, MarkdownViewer *viewer)
 {
-  handle_update_later(plugin);
+  gint page_num;
+  GtkNotebook *newnb;
+  GtkNotebook *snb = GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook);
+  GtkNotebook *mnb = GTK_NOTEBOOK(geany->main_widgets->message_window_notebook);
+  MarkdownConfigViewPos view_pos;
+
+  g_object_ref(g_scrolled_win); /* Prevent it from being destroyed */
+
+  /* Remove the tab from whichever notebook its in (sidebar or msgwin) */
+  page_num = gtk_notebook_page_num(snb, g_scrolled_win);
+  if (page_num >= 0) {
+    gtk_notebook_remove_page(snb, page_num);
+  } else {
+    page_num = gtk_notebook_page_num(mnb, g_scrolled_win);
+    if (page_num >= 0) {
+      gtk_notebook_remove_page(mnb, page_num);
+    } else {
+      g_warning("Unable to relocate the Markdown preview tab: not found");
+    }
+  }
+
+  /* Check the user preference to get the new notebook */
+  view_pos = markdown_config_get_view_pos(MARKDOWN_CONFIG(obj));
+  newnb = (view_pos == MARKDOWN_CONFIG_VIEW_POS_MSGWIN) ? mnb : snb;
+
+  page_num = gtk_notebook_append_page(newnb, g_scrolled_win,
+    gtk_label_new(MARKDOWN_PREVIEW_LABEL));
+
+  gtk_notebook_set_current_page(newnb, page_num);
+
+  g_object_unref(g_scrolled_win); /* The new notebook owns it now */
 }


Modified: markdown/src/viewer.c
412 files changed, 276 insertions(+), 136 deletions(-)
===================================================================
@@ -19,200 +19,340 @@
  * MA 02110-1301, USA.
  */
 
-#include <string.h>
-#include <stdio.h>
 #include <gtk/gtk.h>
 #include <webkit/webkitwebview.h>
-#include <geanyplugin.h>
 #include "markdown.h"
 #include "viewer.h"
 #include "conf.h"
 
-#define MARKDOWN_VIEWER_TAB_LABEL _("Markdown Preview")
+#define MD_ENC_MAX 256
 
-struct MarkdownViewer {
-  GtkScrolledWindow *scrolled_win; /* The GtkScrolledWindow containing the WebKitView */
-  WebKitWebView     *webview;      /* The Webkit preview widget */
-  GtkNotebook       *notebook;     /* Either the sidebar notebook or the msgwin notebook */
-  gdouble old_pos; /* Position before reload, used to reset scroll pos. */
+enum
+{
+  PROP_0,
+  PROP_CONFIG,
+  PROP_TEXT,
+  PROP_ENCODING,
+  N_PROPERTIES
+};
+
+struct _MarkdownViewerPrivate
+{
+  MarkdownConfig *conf;
+  guint load_handle;
+  guint update_handle;
+  guint prop_handle;
+  GString *text;
+  gchar enc[MD_ENC_MAX];
+  gdouble vscroll_pos;
+  gdouble hscroll_pos;
 };
 
-MarkdownViewer *markdown_viewer_new(GtkNotebook *notebook)
-{
-  MarkdownViewer *tmpl = g_slice_new0(MarkdownViewer);
-  if (tmpl) {
-    tmpl->scrolled_win = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
-    tmpl->webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
-    gtk_container_add(GTK_CONTAINER(tmpl->scrolled_win), GTK_WIDGET(tmpl->webview));
-    gtk_scrolled_window_set_policy(tmpl->scrolled_win, GTK_POLICY_AUTOMATIC,
-      GTK_POLICY_AUTOMATIC);
-    tmpl->notebook = notebook;
-    gtk_notebook_append_page(tmpl->notebook, GTK_WIDGET(tmpl->scrolled_win),
-      gtk_label_new(MARKDOWN_VIEWER_TAB_LABEL));
-    gtk_widget_show_all(GTK_WIDGET(notebook));
+static void markdown_viewer_finalize (GObject *object);
+
+static GParamSpec *viewer_props[N_PROPERTIES] = { NULL };
+
+G_DEFINE_TYPE (MarkdownViewer, markdown_viewer, WEBKIT_TYPE_WEB_VIEW)
+
+static GString *
+update_internal_text(MarkdownViewer *self, const gchar *val)
+{
+  if (!self->priv->text) {
+    self->priv->text = g_string_new(val);
+  } else {
+    gsize len = strlen(val);
+    g_string_overwrite_len(self->priv->text, 0, val, len);
+    g_string_truncate(self->priv->text, len);
   }
-  return tmpl;
+  /* TODO: queue re-draw */
+  return self->priv->text;
 }
 
-void markdown_viewer_free(MarkdownViewer *viewer)
+static void
+markdown_viewer_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-  if (viewer) {
-    gtk_widget_destroy(GTK_WIDGET(viewer->scrolled_win));
-    g_slice_free(MarkdownViewer, viewer);
+  MarkdownViewer *self = MARKDOWN_VIEWER(obj);
+
+  switch (prop_id) {
+    case PROP_CONFIG:
+      if (self->priv->conf) {
+        g_object_unref(self->priv->conf);
+      }
+      self->priv->conf = MARKDOWN_CONFIG(g_value_get_object(value));
+      break;
+    case PROP_TEXT:
+      update_internal_text(self, g_value_get_string(value));
+      break;
+    case PROP_ENCODING:
+      strncpy(self->priv->enc, g_value_get_string(value), MD_ENC_MAX);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+      break;
   }
 }
 
-GtkNotebook *markdown_viewer_get_notebook(MarkdownViewer *viewer)
+static void
+markdown_viewer_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-  g_return_val_if_fail(viewer, NULL);
-  return viewer->notebook;
+  MarkdownViewer *self = MARKDOWN_VIEWER(obj);
+
+  switch (prop_id) {
+    case PROP_CONFIG:
+      g_value_set_object(value, self->priv->conf);
+      break;
+    case PROP_TEXT:
+      g_value_set_string(value, self->priv->text->str);
+      break;
+    case PROP_ENCODING:
+      g_value_set_string(value, self->priv->enc);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+      break;
+  }
 }
 
-void markdown_viewer_set_notebook(MarkdownViewer *viewer, GtkNotebook *nb)
+static void
+markdown_viewer_class_init(MarkdownViewerClass *klass)
 {
-  gint page_num;
+  GObjectClass *g_object_class;
+
+  g_object_class = G_OBJECT_CLASS(klass);
+  g_object_class->set_property = markdown_viewer_set_property;
+  g_object_class->get_property = markdown_viewer_get_property;
+  g_object_class->finalize = markdown_viewer_finalize;
+  g_type_class_add_private((gpointer)klass, sizeof(MarkdownViewerPrivate));
+
+  viewer_props[PROP_CONFIG] = g_param_spec_object("config", "Config",
+    "MarkdownConfig object", MARKDOWN_TYPE_CONFIG,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+  viewer_props[PROP_TEXT] = g_param_spec_string("text", "MarkdownText",
+    "The Markdown text to render", "", G_PARAM_READWRITE);
+  viewer_props[PROP_ENCODING] = g_param_spec_string("encoding", "TextEncoding",
+    "The encoding of the Markdown text", "UTF-8", G_PARAM_READWRITE);
+
+  g_object_class_install_properties(g_object_class, N_PROPERTIES, viewer_props);
+}
 
-  g_return_if_fail(viewer);
-  g_return_if_fail(GTK_IS_NOTEBOOK(nb));
+static void
+markdown_viewer_finalize(GObject *object)
+{
+  MarkdownViewer *self;
+  g_return_if_fail(MARKDOWN_IS_VIEWER(object));
+  self = MARKDOWN_VIEWER(object);
+  if (self->priv->conf) {
+    g_signal_handler_disconnect(self->priv->conf, self->priv->prop_handle);
+    g_object_unref(self->priv->conf);
+  }
+  if (self->priv->text) {
+    g_string_free(self->priv->text, TRUE);
+  }
+  G_OBJECT_CLASS(markdown_viewer_parent_class)->finalize(object);
+}
 
-  g_object_ref(G_OBJECT(viewer->scrolled_win));
+static void
+markdown_viewer_init(MarkdownViewer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, MARKDOWN_TYPE_VIEWER, MarkdownViewerPrivate);
+}
 
-  page_num = gtk_notebook_page_num(viewer->notebook, GTK_WIDGET(viewer->scrolled_win));
-  gtk_notebook_remove_page(viewer->notebook, page_num);
 
-  viewer->notebook = nb;
+GtkWidget *
+markdown_viewer_new(MarkdownConfig *conf)
+{
+  MarkdownViewer *self;
 
-  page_num = gtk_notebook_append_page(viewer->notebook, GTK_WIDGET(viewer->scrolled_win),
-    gtk_label_new(MARKDOWN_VIEWER_TAB_LABEL));
+  self = g_object_new(MARKDOWN_TYPE_VIEWER, "config", conf, NULL);
 
-  gtk_notebook_set_current_page(viewer->notebook, page_num);
+  /* Cause the view to be updated whenever the config changes. */
+  self->priv->prop_handle = g_signal_connect_swapped(self->priv->conf, "notify",
+      G_CALLBACK(markdown_viewer_queue_update), self);
 
-  g_object_unref(G_OBJECT(viewer->scrolled_win));
+  return GTK_WIDGET(self);
 }
 
-void on_viewer_load_status_notify(GObject *obj, GParamSpec *pspec, MarkdownViewer *viewer)
+static void
+replace_all(MarkdownViewer *self,
+            GString *haystack,
+            const gchar *needle,
+            const gchar *replacement)
 {
-  GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(viewer->scrolled_win);
-  gtk_adjustment_set_value(vadj, viewer->old_pos);
-  while (gtk_events_pending()) {
-    gtk_main_iteration();
+  gchar *ptr;
+  gsize needle_len = strlen(needle);
+
+  /* For each occurrence of needle in haystack */
+  while ((ptr = strstr(haystack->str, needle)) != NULL) {
+    goffset offset = ptr - haystack->str;
+    g_string_erase(haystack, offset, needle_len);
+    g_string_insert(haystack, offset, replacement);
   }
 }
 
 static gchar *
-markdown_to_html(const gchar *md_text)
+template_replace(MarkdownViewer *self, const gchar *html_text)
 {
-  Document *md;
-  gchar *result = NULL;
+  MarkdownConfigViewPos view_pos;
+  guint font_point_size = 0, code_font_point_size = 0;
+  gchar *font_name = NULL, *code_font_name = NULL;
+  gchar *bg_color = NULL, *fg_color = NULL;
+  gchar font_pt_size[10] = { 0 };
+  gchar code_font_pt_size[10] = { 0 };
+  GString *tmpl;
+
+  { /* Read all the configuration settings into strings */
+    g_object_get(self->priv->conf,
+                 "view-pos", &view_pos,
+                 "font-name", &font_name,
+                 "code-font-name", &code_font_name,
+                 "font-point-size", &font_point_size,
+                 "code-font-point-size", &code_font_point_size,
+                 "bg-color", &bg_color,
+                 "fg-color", &fg_color,
+                 NULL);
+    g_snprintf(font_pt_size, 10, "%d", font_point_size);
+    g_snprintf(code_font_pt_size, 10, "%d", code_font_point_size);
+  }
 
-  if (!md_text)
-    return g_strdup("");
+  /* Load the template into a GString to be modified in place */
+  tmpl = g_string_new(markdown_config_get_template_text(self->priv->conf));
 
-  md = mkd_string(md_text, strlen(md_text), 0);
-  if (md) {
-    if (mkd_compile(md, 0)) {
-      gchar *res = NULL;
-      mkd_document(md, &res);
-      result = g_strdup(res);
-    }
-    mkd_cleanup(md);
+  replace_all(self, tmpl, "@@font_name@@", font_name);
+  replace_all(self, tmpl, "@@code_font_name@@", code_font_name);
+  replace_all(self, tmpl, "@@font_point_size@@", font_pt_size);
+  replace_all(self, tmpl, "@@code_font_point_size@@", code_font_pt_size);
+  replace_all(self, tmpl, "@@bg_color@@", bg_color);
+  replace_all(self, tmpl, "@@fg_color@@", fg_color);
+  replace_all(self, tmpl, "@@markdown@@", html_text);
+
+  g_free(font_name);
+  g_free(code_font_name);
+  g_free(bg_color);
+  g_free(fg_color);
+
+  return g_string_free(tmpl, FALSE);
+}
+
+static gboolean
+push_scroll_pos(MarkdownViewer *self)
+{
+  GtkWidget *parent;
+  gboolean pushed = FALSE;
+
+  parent = gtk_widget_get_parent(GTK_WIDGET(self));
+  if (GTK_IS_SCROLLED_WINDOW(parent)) {
+    GtkAdjustment *adj;
+    adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(parent));
+    self->priv->vscroll_pos = gtk_adjustment_get_value(adj);
+    adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(parent));
+    self->priv->hscroll_pos = gtk_adjustment_get_value(adj);
+    pushed = TRUE;
   }
 
-  return result;
+  return pushed;
 }
 
-static gchar *
-str_replace(const gchar *haystack, const gchar *needle, const gchar *repl)
+static gboolean
+pop_scroll_pos(MarkdownViewer *self)
 {
-  gchar *out_str, **parts;
+  GtkWidget *parent;
+  gboolean popped = FALSE;
+
+  /* first process any pending events, like drawing of the webview */
+  while (gtk_events_pending()) {
+    gtk_main_iteration();
+  }
 
-  parts = g_strsplit(haystack, needle, 0);
-  out_str = g_strjoinv(repl, parts);
-  g_strfreev(parts);
+  parent = gtk_widget_get_parent(GTK_WIDGET(self));
+  if (GTK_IS_SCROLLED_WINDOW(parent)) {
+    GtkAdjustment *adj;
+    adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(parent));
+    gtk_adjustment_set_value(adj, self->priv->vscroll_pos);
+    adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(parent));
+    gtk_adjustment_set_value(adj, self->priv->hscroll_pos);
+    /* process any new events, like making sure the new scroll position
+     * takes effect. */
+    while (gtk_events_pending()) {
+      gtk_main_iteration();
+    }
+    popped = TRUE;
+  }
 
-  return out_str;
+  return popped;
 }
 
-static gchar *
-markdown_viewer_replace(const gchar *html_text, MarkdownConfig *config)
+static void
+on_webview_load_status_notify(WebKitWebView *view, GParamSpec *pspec,
+  MarkdownViewer *self)
 {
-  MarkdownConfigViewPos view_pos;
-  guint font_point_size = 0;
-  gchar *out_str, *tmp;
-  gchar *font_pt_size, *code_font_pt_size;
-  gchar *font_name = NULL, *code_font_name = NULL;
-  gchar *bg_color = NULL, *fg_color = NULL;
-  guint code_font_point_size = 0;
-
-  g_object_get(config,
-               "view-pos", &view_pos,
-               "font-name", &font_name,
-               "code-font-name", &code_font_name,
-               "font-point-size", &font_point_size,
-               "code-font-point-size", &code_font_point_size,
-               "bg-color", &bg_color,
-               "fg-color", &fg_color,
-               NULL);
-
-  font_pt_size = g_strdup_printf("%d", font_point_size);
-  code_font_pt_size = g_strdup_printf("%d", code_font_point_size);
-
-  tmp = str_replace(markdown_config_get_template_text(config),
-    "@@font_name@@", font_name);
-  out_str = tmp;
-  tmp = str_replace(out_str, "@@code_font_name@@", code_font_name);
-  g_free(out_str); out_str = tmp;
-  tmp = str_replace(out_str, "@@font_point_size@@", font_pt_size);
-  g_free(out_str); out_str = tmp;
-  tmp = str_replace(out_str, "@@code_font_point_size@@", code_font_pt_size);
-  g_free(out_str); out_str = tmp;
-  tmp = str_replace(out_str, "@@bg_color@@", bg_color);
-  g_free(out_str); out_str = tmp;
-  tmp = str_replace(out_str, "@@fg_color@@", fg_color);
-  g_free(out_str); out_str = tmp;
-  tmp = str_replace(out_str, "@@markdown@@", html_text);
-  g_free(out_str); out_str = tmp;
+  WebKitLoadStatus load_status;
 
-  g_free(font_name);
-  g_free(code_font_name);
-  g_free(font_pt_size);
-  g_free(code_font_pt_size);
-  g_free(bg_color);
-  g_free(fg_color);
+  g_object_get(view, "load-status", &load_status, NULL);
 
-  /*g_debug("Replaced:\n%s", out_str);*/
+  /* When the webkit is done loading, reset the scroll position. */
+  if (load_status == WEBKIT_LOAD_FINISHED) {
+    pop_scroll_pos(self);
+  }
+}
 
-  return out_str;
+static gchar *
+markdown_viewer_get_html(MarkdownViewer *self)
+{
+  gchar *md_as_html, *html = NULL;
+  md_as_html = mkd_compile_document(self->priv->text->str, 0);
+  if (md_as_html) {
+    html = template_replace(self, md_as_html);
+    g_free(md_as_html);
+  }
+  return html;
 }
 
-void markdown_viewer_load_markdown_string(MarkdownViewer *viewer,
-  const gchar *md_str, const gchar *encoding, MarkdownConfig *config)
+static gboolean
+markdown_viewer_update_view(MarkdownViewer *self)
 {
-  g_return_if_fail(viewer);
-  gchar *html = markdown_to_html(md_str);
-  gchar *new_text = markdown_viewer_replace(html, config);
-  GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(viewer->scrolled_win);
-  viewer->old_pos = gtk_adjustment_get_value(vadj);
-  g_free(html);
-  if (new_text) {
-    gchar *base_uri = g_strdup_printf("file://.");
-    g_signal_connect(viewer->webview, "notify::load-status",
-      G_CALLBACK(on_viewer_load_status_notify), viewer);
-    webkit_web_view_load_string(viewer->webview, new_text, "text/html",
-      encoding, base_uri);
-    g_free(new_text);
-    g_free(base_uri);
+  gchar *html = markdown_viewer_get_html(self);
+
+  push_scroll_pos(self);
+
+  if (html) {
+    static const gchar *base_uri = "file://.";
+
+    /* Connect a signal handler (only needed once) to restore the scroll
+     * position once the webview is reloaded. */
+    if (self->priv->load_handle == 0) {
+      self->priv->load_handle =
+        g_signal_connect_swapped(WEBKIT_WEB_VIEW(self), "notify::load-status",
+          G_CALLBACK(on_webview_load_status_notify), self);
+    }
+
+    webkit_web_view_load_string(WEBKIT_WEB_VIEW(self), html, "text/html",
+      self->priv->enc, base_uri);
+
+    g_free(html);
   }
+
+  if (self->priv->update_handle != 0) {
+    g_source_remove(self->priv->update_handle);
+  }
+  self->priv->update_handle = 0;
+
+  return FALSE; /* When used as an idle handler, says to remove the source */
 }
 
-void markdown_viewer_show(MarkdownViewer *viewer)
+void
+markdown_viewer_queue_update(MarkdownViewer *self)
 {
-  g_return_if_fail(viewer);
-  gtk_widget_show(GTK_WIDGET(viewer->scrolled_win));
+  g_return_if_fail(MARKDOWN_IS_VIEWER(self));
+  if (self->priv->update_handle == 0) {
+    self->priv->update_handle = g_idle_add(
+      (GSourceFunc) markdown_viewer_update_view, self);
+  }
 }
 
-void markdown_viewer_hide(MarkdownViewer *viewer)
+void
+markdown_viewer_set_markdown(MarkdownViewer *self, const gchar *text, const gchar *encoding)
 {
-  g_return_if_fail(viewer);
-  gtk_widget_hide(GTK_WIDGET(viewer->scrolled_win));
+  g_return_if_fail(MARKDOWN_IS_VIEWER(self));
+  g_object_set(self, "text", text, "encoding", encoding, NULL);
+  markdown_viewer_queue_update(self);
 }


Modified: markdown/src/viewer.h
46 files changed, 31 insertions(+), 15 deletions(-)
===================================================================
@@ -22,25 +22,41 @@
 #ifndef MARKDOWN_VIEWER_H
 #define MARKDOWN_VIEWER_H 1
 
-G_BEGIN_DECLS
-
 #include <gtk/gtk.h>
-#include "conf.h"
-
-typedef struct MarkdownViewer MarkdownViewer;
+#include <webkit/webkitwebview.h>
 
-MarkdownViewer *markdown_viewer_new(GtkNotebook *notebook);
-void markdown_viewer_free(MarkdownViewer *viewer);
-
-GtkNotebook *markdown_viewer_get_notebook(MarkdownViewer *viewer);
-void markdown_viewer_set_notebook(MarkdownViewer *viewer, GtkNotebook *nb);
+G_BEGIN_DECLS
 
-void markdown_viewer_show(MarkdownViewer *viewer);
-void markdown_viewer_hide(MarkdownViewer *viewer);
+#include "conf.h"
 
-void markdown_viewer_load_markdown_string(MarkdownViewer *viewer,
-  const gchar *md_str, const gchar *encoding, MarkdownConfig *config);
+#define MARKDOWN_TYPE_VIEWER             (markdown_viewer_get_type ())
+#define MARKDOWN_VIEWER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), MARKDOWN_TYPE_VIEWER, MarkdownViewer))
+#define MARKDOWN_VIEWER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), MARKDOWN_TYPE_VIEWER, MarkdownViewerClass))
+#define MARKDOWN_IS_VIEWER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MARKDOWN_TYPE_VIEWER))
+#define MARKDOWN_IS_VIEWER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), MARKDOWN_TYPE_VIEWER))
+#define MARKDOWN_VIEWER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), MARKDOWN_TYPE_VIEWER, MarkdownViewerClass))
+
+typedef struct _MarkdownViewer         MarkdownViewer;
+typedef struct _MarkdownViewerClass    MarkdownViewerClass;
+typedef struct _MarkdownViewerPrivate  MarkdownViewerPrivate;
+
+struct _MarkdownViewer
+{
+  WebKitWebView parent;
+  MarkdownViewerPrivate *priv;
+};
+
+struct _MarkdownViewerClass
+{
+  WebKitWebViewClass parent_class;
+};
+
+GType markdown_viewer_get_type(void);
+GtkWidget *markdown_viewer_new(MarkdownConfig *conf);
+void markdown_viewer_set_markdown(MarkdownViewer *self, const gchar *text,
+  const gchar *encoding);
+void markdown_viewer_queue_update(MarkdownViewer *self);
 
 G_END_DECLS
 
-#endif /* MARKDOWN_VIEWER_H */
+#endif /* __MARKDOWNVIEWER_H__ */


@@ Diff output truncated at 100000 characters. @@


--------------
This E-Mail was brought to you by github_commit_mail.py (Source: TBD).



More information about the Plugins-Commits mailing list