[gimp] app: add infrastructure for editing pango markup based text styles
- From: Michael Natterer <mitch src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] app: add infrastructure for editing pango markup based text styles
- Date: Fri, 26 Feb 2010 00:39:05 +0000 (UTC)
commit 360f5739da8345d037dfd6d2697ecd50450a0059
Author: Michael Natterer <mitch gimp org>
Date: Fri Feb 26 01:22:27 2010 +0100
app: add infrastructure for editing pango markup based text styles
- keep around tags for styles in GimpTextBuffer. For now only bold,
italic, underline and strikethrough.
- add GimpTextStyleEditor, a widget which allows setting tags on
a GimpTextBuffer's selection.
- add serialize/deserialize code to/from pango markup using
GtkTextBuffer's rich text (de)serialization infrastructure.
Doesn't produce or handle <span> yet.
app/widgets/Makefile.am | 4 +
app/widgets/gimptextbuffer-serialize.c | 609 ++++++++++++++++++++++++++++++++
app/widgets/gimptextbuffer-serialize.h | 46 +++
app/widgets/gimptextbuffer.c | 130 +++++++
app/widgets/gimptextbuffer.h | 16 +
app/widgets/gimptextstyleeditor.c | 423 ++++++++++++++++++++++
app/widgets/gimptextstyleeditor.h | 61 ++++
app/widgets/widgets-types.h | 1 +
8 files changed, 1290 insertions(+), 0 deletions(-)
---
diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am
index 86f8bf9..331c80d 100644
--- a/app/widgets/Makefile.am
+++ b/app/widgets/Makefile.am
@@ -298,10 +298,14 @@ libappwidgets_a_sources = \
gimptemplateview.h \
gimptextbuffer.c \
gimptextbuffer.h \
+ gimptextbuffer-serialize.c \
+ gimptextbuffer-serialize.h \
gimptexteditor.c \
gimptexteditor.h \
gimptextproxy.c \
gimptextproxy.h \
+ gimptextstyleeditor.c \
+ gimptextstyleeditor.h \
gimpthumbbox.c \
gimpthumbbox.h \
gimptoggleaction.c \
diff --git a/app/widgets/gimptextbuffer-serialize.c b/app/widgets/gimptextbuffer-serialize.c
new file mode 100644
index 0000000..7f9eb95
--- /dev/null
+++ b/app/widgets/gimptextbuffer-serialize.c
@@ -0,0 +1,609 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextBuffer-serialize
+ * Copyright (C) 2010 Michael Natterer <mitch gimp org>
+ *
+ * inspired by
+ * gtktextbufferserialize.c
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * 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 "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "widgets-types.h"
+
+#include "gimptextbuffer.h"
+#include "gimptextbuffer-serialize.h"
+
+#include "gimp-intl.h"
+
+
+/* serialize */
+
+static void
+find_list_delta (GSList *old_list,
+ GSList *new_list,
+ GList **added,
+ GList **removed)
+{
+ GSList *tmp;
+ GList *tmp_added = NULL;
+ GList *tmp_removed = NULL;
+
+ /* Find added tags */
+ for (tmp = new_list; tmp; tmp = g_slist_next (tmp))
+ {
+ if (! g_slist_find (old_list, tmp->data))
+ tmp_added = g_list_prepend (tmp_added, tmp->data);
+ }
+
+ *added = tmp_added;
+
+ /* Find removed tags */
+ for (tmp = old_list; tmp; tmp = g_slist_next (tmp))
+ {
+ if (! g_slist_find (new_list, tmp->data))
+ tmp_removed = g_list_prepend (tmp_removed, tmp->data);
+ }
+
+ /* We reverse the list here to match the xml semantics */
+ *removed = g_list_reverse (tmp_removed);
+}
+
+static gboolean
+open_tag (GimpTextBuffer *buffer,
+ GString *string,
+ GtkTextTag *tag)
+{
+ const gchar *name = gimp_text_buffer_tag_to_name (buffer, tag);
+
+ if (tag)
+ {
+ g_string_append_printf (string, "<%s>", name);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+close_tag (GimpTextBuffer *buffer,
+ GString *string,
+ GtkTextTag *tag)
+{
+ const gchar *name = gimp_text_buffer_tag_to_name (buffer, tag);
+
+ if (tag)
+ {
+ g_string_append_printf (string, "</%s>", name);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+guint8 *
+gimp_text_buffer_serialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data)
+{
+ GString *string;
+ GtkTextIter iter, old_iter;
+ GSList *tag_list, *new_tag_list;
+ GSList *active_tags;
+
+ string = g_string_new ("<markup>");
+
+ iter = *start;
+ tag_list = NULL;
+ active_tags = NULL;
+
+ do
+ {
+ GList *added, *removed;
+ GList *tmp;
+ gchar *tmp_text, *escaped_text;
+
+ new_tag_list = gtk_text_iter_get_tags (&iter);
+
+ find_list_delta (tag_list, new_tag_list, &added, &removed);
+
+ /* Handle removed tags */
+ for (tmp = removed; tmp; tmp = tmp->next)
+ {
+ GtkTextTag *tag = tmp->data;
+
+ /* Only close the tag if we didn't close it before (by using
+ * the stack logic in the while() loop below)
+ */
+ if (g_slist_find (active_tags, tag))
+ {
+ /* Drop all tags that were opened after this one (which are
+ * above this on in the stack)
+ */
+ while (active_tags->data != tag)
+ {
+ close_tag (GIMP_TEXT_BUFFER (content_buffer),
+ string, active_tags->data);
+
+ added = g_list_prepend (added, active_tags->data);
+ active_tags = g_slist_remove (active_tags, active_tags->data);
+ }
+
+ /* then, close the tag itself */
+ close_tag (GIMP_TEXT_BUFFER (content_buffer), string, tag);
+
+ active_tags = g_slist_remove (active_tags, active_tags->data);
+ }
+ }
+
+ /* Handle added tags */
+ for (tmp = added; tmp; tmp = tmp->next)
+ {
+ GtkTextTag *tag = tmp->data;
+
+ open_tag (GIMP_TEXT_BUFFER (content_buffer), string, tag);
+
+ active_tags = g_slist_prepend (active_tags, tag);
+ }
+
+ g_slist_free (tag_list);
+ tag_list = new_tag_list;
+
+ g_list_free (added);
+ g_list_free (removed);
+
+ old_iter = iter;
+
+ /* Now try to go to either the next tag toggle, or if a pixbuf appears */
+ while (TRUE)
+ {
+ gunichar ch = gtk_text_iter_get_char (&iter);
+
+ if (ch == 0xFFFC)
+ {
+ /* pixbuf? can't happen! */
+ }
+ else if (ch == 0)
+ {
+ break;
+ }
+ else
+ {
+ gtk_text_iter_forward_char (&iter);
+ }
+
+ if (gtk_text_iter_toggles_tag (&iter, NULL))
+ break;
+ }
+
+ /* We might have moved too far */
+ if (gtk_text_iter_compare (&iter, end) > 0)
+ iter = *end;
+
+ /* Append the text */
+ tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+ escaped_text = g_markup_escape_text (tmp_text, -1);
+ g_free (tmp_text);
+
+ g_string_append (string, escaped_text);
+ g_free (escaped_text);
+ }
+ while (! gtk_text_iter_equal (&iter, end));
+
+ /* Close any open tags */
+ for (tag_list = active_tags; tag_list; tag_list = tag_list->next)
+ close_tag (GIMP_TEXT_BUFFER (content_buffer), string, tag_list->data);
+
+ g_slist_free (active_tags);
+
+ g_string_append (string, "</markup>");
+
+ *length = string->len;
+
+ return (guint8 *) g_string_free (string, FALSE);
+}
+
+
+/* deserialize */
+
+typedef enum
+{
+ STATE_START,
+ STATE_MARKUP,
+ STATE_TAG,
+ STATE_UNKNOWN
+} ParseState;
+
+typedef struct
+{
+ GSList *states;
+ GtkTextBuffer *buffer;
+ GSList *tag_stack;
+ GList *spans;
+} ParseInfo;
+
+typedef struct
+{
+ gchar *text;
+ GSList *tags;
+} TextSpan;
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ gint line, ch;
+ va_list args;
+ gchar *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ ("Line %d character %d: %s"),
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+parse_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ GtkTextTag *tag;
+
+ g_assert (peek_state (info) == STATE_MARKUP ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_UNKNOWN);
+
+ tag = gimp_text_buffer_name_to_tag (GIMP_TEXT_BUFFER (info->buffer),
+ element_name);
+
+ if (tag)
+ {
+ info->tag_stack = g_slist_prepend (info->tag_stack, tag);
+
+ push_state (info, STATE_TAG);
+ }
+ else
+ {
+ push_state (info, STATE_UNKNOWN);
+ }
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ if (! strcmp (element_name, "markup"))
+ {
+ if (! check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_MARKUP);
+ break;
+ }
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Outermost element in text must be <markup> not <%s>"),
+ element_name);
+ }
+ break;
+
+ case STATE_MARKUP:
+ case STATE_TAG:
+ case STATE_UNKNOWN:
+ parse_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_UNKNOWN:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_UNKNOWN ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_MARKUP);
+ break;
+
+ case STATE_TAG:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_UNKNOWN ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_MARKUP);
+
+ /* Pop tag */
+ info->tag_stack = g_slist_delete_link (info->tag_stack,
+ info->tag_stack);
+ break;
+
+ case STATE_MARKUP:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_START);
+
+ info->spans = g_list_reverse (info->spans);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+all_whitespace (const char *text,
+ gint text_len)
+{
+ const char *p = text;
+ const char *end = text + text_len;
+
+ while (p != end)
+ {
+ if (! g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+ TextSpan *span;
+
+ if (all_whitespace (text, text_len) &&
+ peek_state (info) != STATE_MARKUP &&
+ peek_state (info) != STATE_TAG &&
+ peek_state (info) != STATE_UNKNOWN)
+ return;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ g_assert_not_reached (); /* gmarkup shouldn't do this */
+ break;
+
+ case STATE_MARKUP:
+ case STATE_TAG:
+ case STATE_UNKNOWN:
+ if (text_len == 0)
+ return;
+
+ span = g_new0 (TextSpan, 1);
+ span->text = g_strndup (text, text_len);
+ span->tags = g_slist_copy (info->tag_stack);
+
+ info->spans = g_list_prepend (info->spans, span);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+parse_info_init (ParseInfo *info,
+ GtkTextBuffer *buffer)
+{
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+ info->tag_stack = NULL;
+ info->spans = NULL;
+ info->buffer = buffer;
+}
+
+static void
+text_span_free (TextSpan *span)
+{
+ g_free (span->text);
+ g_slist_free (span->tags);
+ g_free (span);
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ g_slist_free (info->tag_stack);
+ g_slist_free (info->states);
+
+ g_list_foreach (info->spans, (GFunc) text_span_free, NULL);
+ g_list_free (info->spans);
+}
+
+static void
+insert_text (ParseInfo *info,
+ GtkTextIter *iter)
+{
+ GtkTextIter start_iter;
+ GtkTextMark *mark;
+ GList *tmp;
+ GSList *tags;
+
+ start_iter = *iter;
+
+ mark = gtk_text_buffer_create_mark (info->buffer, "deserialize-insert-point",
+ &start_iter, TRUE);
+
+ for (tmp = info->spans; tmp; tmp = tmp->next)
+ {
+ TextSpan *span = tmp->data;
+
+ if (span->text)
+ gtk_text_buffer_insert (info->buffer, iter, span->text, -1);
+
+ gtk_text_buffer_get_iter_at_mark (info->buffer, &start_iter, mark);
+
+ /* Apply tags */
+ for (tags = span->tags; tags; tags = tags->next)
+ {
+ GtkTextTag *tag = tags->data;
+
+ gtk_text_buffer_apply_tag (info->buffer, tag,
+ &start_iter, iter);
+ }
+
+ gtk_text_buffer_move_mark (info->buffer, mark, iter);
+ }
+
+ gtk_text_buffer_delete_mark (info->buffer, mark);
+}
+
+gboolean
+gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *text,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error)
+{
+ GMarkupParseContext *context;
+ ParseInfo info;
+ gboolean retval = FALSE;
+
+ static const GMarkupParser markup_parser =
+ {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+ };
+
+ parse_info_init (&info, content_buffer);
+
+ context = g_markup_parse_context_new (&markup_parser, 0, &info, NULL);
+
+ if (! g_markup_parse_context_parse (context,
+ (const gchar *) text,
+ length,
+ error))
+ goto out;
+
+ if (! g_markup_parse_context_end_parse (context, error))
+ goto out;
+
+ retval = TRUE;
+
+ insert_text (&info, iter);
+
+ out:
+ parse_info_free (&info);
+
+ g_markup_parse_context_free (context);
+
+ return retval;
+}
diff --git a/app/widgets/gimptextbuffer-serialize.h b/app/widgets/gimptextbuffer-serialize.h
new file mode 100644
index 0000000..b2857c6
--- /dev/null
+++ b/app/widgets/gimptextbuffer-serialize.h
@@ -0,0 +1,46 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextBuffer-serialize
+ * Copyright (C) 2010 Michael Natterer <mitch gimp org>
+ *
+ * inspired by
+ * gtktextbufferserialize.h
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * 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 __GIMP_TEXT_BUFFER_SERIALIZE_H__
+#define __GIMP_TEXT_BUFFER_SERIALIZE_H__
+
+
+guint8 * gimp_text_buffer_serialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data);
+
+gboolean gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *data,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error);
+
+
+#endif /* __GIMP_TEXT_BUFFER_SERIALIZE_H__ */
diff --git a/app/widgets/gimptextbuffer.c b/app/widgets/gimptextbuffer.c
index 36d5a22..c133a93 100644
--- a/app/widgets/gimptextbuffer.c
+++ b/app/widgets/gimptextbuffer.c
@@ -45,6 +45,7 @@
#include "widgets-types.h"
#include "gimptextbuffer.h"
+#include "gimptextbuffer-serialize.h"
#include "gimp-intl.h"
@@ -76,6 +77,16 @@ gimp_text_buffer_class_init (GimpTextBufferClass *klass)
static void
gimp_text_buffer_init (GimpTextBuffer *buffer)
{
+ buffer->markup_atom =
+ gtk_text_buffer_register_serialize_format (GTK_TEXT_BUFFER (buffer),
+ "application/x-gimp-pango-markup",
+ gimp_text_buffer_serialize,
+ NULL, NULL);
+
+ gtk_text_buffer_register_deserialize_format (GTK_TEXT_BUFFER (buffer),
+ "application/x-gimp-pango-markup",
+ gimp_text_buffer_deserialize,
+ NULL, NULL);
}
static GObject *
@@ -92,6 +103,26 @@ gimp_text_buffer_constructor (GType type,
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), "", -1);
+ buffer->bold_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+ "bold",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+
+ buffer->italic_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+ "italic",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+
+ buffer->underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+ "underline",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+
+ buffer->strikethrough_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+ "strikethrough",
+ "strikethrough", TRUE,
+ NULL);
+
return object;
}
@@ -145,6 +176,105 @@ gimp_text_buffer_get_text (GimpTextBuffer *buffer)
&start, &end, TRUE);
}
+void
+gimp_text_buffer_set_markup (GimpTextBuffer *buffer,
+ const gchar *markup)
+{
+ g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer));
+
+ gimp_text_buffer_set_text (buffer, NULL);
+
+ if (markup)
+ {
+ GtkTextIter start;
+ GError *error = NULL;
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start);
+
+ if (! gtk_text_buffer_deserialize (GTK_TEXT_BUFFER (buffer),
+ GTK_TEXT_BUFFER (buffer),
+ buffer->markup_atom,
+ &start,
+ (const guint8 *) markup, -1,
+ &error))
+ {
+ g_printerr ("EEK: %s\n", error->message);
+ g_clear_error (&error);
+ }
+ }
+}
+
+gchar *
+gimp_text_buffer_get_markup (GimpTextBuffer *buffer)
+{
+ GtkTextIter start, end;
+ gsize length;
+
+ g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
+
+ return (gchar *) gtk_text_buffer_serialize (GTK_TEXT_BUFFER (buffer),
+ GTK_TEXT_BUFFER (buffer),
+ buffer->markup_atom,
+ &start, &end,
+ &length);
+}
+
+const gchar *
+gimp_text_buffer_tag_to_name (GimpTextBuffer *buffer,
+ GtkTextTag *tag)
+{
+ g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), NULL);
+
+ if (tag == buffer->bold_tag)
+ {
+ return "b";
+ }
+ else if (tag == buffer->italic_tag)
+ {
+ return "i";
+ }
+ else if (tag == buffer->underline_tag)
+ {
+ return "u";
+ }
+ else if (tag == buffer->strikethrough_tag)
+ {
+ return "s";
+ }
+
+ return NULL;
+}
+
+GtkTextTag *
+gimp_text_buffer_name_to_tag (GimpTextBuffer *buffer,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (! strcmp (name, "b"))
+ {
+ return buffer->bold_tag;
+ }
+ else if (! strcmp (name, "i"))
+ {
+ return buffer->italic_tag;
+ }
+ else if (! strcmp (name, "u"))
+ {
+ return buffer->underline_tag;
+ }
+ else if (! strcmp (name, "s"))
+ {
+ return buffer->strikethrough_tag;
+ }
+
+ return NULL;
+}
+
gint
gimp_text_buffer_get_iter_index (GimpTextBuffer *buffer,
GtkTextIter *iter)
diff --git a/app/widgets/gimptextbuffer.h b/app/widgets/gimptextbuffer.h
index a7d463f..d34c876 100644
--- a/app/widgets/gimptextbuffer.h
+++ b/app/widgets/gimptextbuffer.h
@@ -34,6 +34,13 @@ typedef struct _GimpTextBufferClass GimpTextBufferClass;
struct _GimpTextBuffer
{
GtkTextBuffer parent_instance;
+
+ GtkTextTag *bold_tag;
+ GtkTextTag *italic_tag;
+ GtkTextTag *underline_tag;
+ GtkTextTag *strikethrough_tag;
+
+ GdkAtom markup_atom;
};
struct _GimpTextBufferClass
@@ -50,6 +57,15 @@ void gimp_text_buffer_set_text (GimpTextBuffer *buffer,
const gchar *text);
gchar * gimp_text_buffer_get_text (GimpTextBuffer *buffer);
+void gimp_text_buffer_set_markup (GimpTextBuffer *buffer,
+ const gchar *markup);
+gchar * gimp_text_buffer_get_markup (GimpTextBuffer *buffer);
+
+const gchar * gimp_text_buffer_tag_to_name (GimpTextBuffer *buffer,
+ GtkTextTag *tag);
+GtkTextTag * gimp_text_buffer_name_to_tag (GimpTextBuffer *buffer,
+ const gchar *name);
+
gint gimp_text_buffer_get_iter_index (GimpTextBuffer *buffer,
GtkTextIter *iter);
diff --git a/app/widgets/gimptextstyleeditor.c b/app/widgets/gimptextstyleeditor.c
new file mode 100644
index 0000000..36b3419
--- /dev/null
+++ b/app/widgets/gimptextstyleeditor.c
@@ -0,0 +1,423 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextStyleEditor
+ * Copyright (C) 2010 Michael Natterer <mitch gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "widgets-types.h"
+
+#include "gimptextbuffer.h"
+#include "gimptextstyleeditor.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER
+};
+
+
+static GObject * gimp_text_style_editor_constructor (GType type,
+ guint n_params,
+ GObjectConstructParam *params);
+static void gimp_text_style_editor_dispose (GObject *object);
+static void gimp_text_style_editor_finalize (GObject *object);
+static void gimp_text_style_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_text_style_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GtkWidget *
+ gimp_text_style_editor_create_toggle (GimpTextStyleEditor *editor,
+ GtkTextTag *tag,
+ const gchar *stock_id);
+
+static void gimp_text_style_editor_clear_tags (GtkButton *button,
+ GimpTextStyleEditor *editor);
+static void gimp_text_style_editor_tag_toggled (GtkToggleButton *toggle,
+ GimpTextStyleEditor *editor);
+
+static void gimp_text_style_editor_mark_set (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ GimpTextStyleEditor *editor);
+
+
+G_DEFINE_TYPE (GimpTextStyleEditor, gimp_text_style_editor,
+ GTK_TYPE_HBOX)
+
+#define parent_class gimp_text_style_editor_parent_class
+
+
+static void
+gimp_text_style_editor_class_init (GimpTextStyleEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructor = gimp_text_style_editor_constructor;
+ object_class->dispose = gimp_text_style_editor_dispose;
+ object_class->finalize = gimp_text_style_editor_finalize;
+ object_class->set_property = gimp_text_style_editor_set_property;
+ object_class->get_property = gimp_text_style_editor_get_property;
+
+ g_object_class_install_property (object_class, PROP_BUFFER,
+ g_param_spec_object ("buffer",
+ NULL, NULL,
+ GIMP_TYPE_TEXT_BUFFER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_text_style_editor_init (GimpTextStyleEditor *editor)
+{
+ GtkWidget *image;
+
+ editor->tag_to_toggle_hash = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ editor->clear_button = gtk_button_new ();
+ gtk_widget_set_can_focus (editor->clear_button, FALSE);
+ gtk_box_pack_start (GTK_BOX (editor), editor->clear_button, FALSE, FALSE, 0);
+ gtk_widget_show (editor->clear_button);
+
+ g_signal_connect (editor->clear_button, "clicked",
+ G_CALLBACK (gimp_text_style_editor_clear_tags),
+ editor);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (editor->clear_button), image);
+ gtk_widget_show (image);
+}
+
+static GObject *
+gimp_text_style_editor_constructor (GType type,
+ guint n_params,
+ GObjectConstructParam *params)
+{
+ GObject *object;
+ GimpTextStyleEditor *editor;
+
+ object = G_OBJECT_CLASS (parent_class)->constructor (type, n_params, params);
+
+ editor = GIMP_TEXT_STYLE_EDITOR (object);
+
+ g_assert (GIMP_IS_TEXT_BUFFER (editor->buffer));
+
+ gimp_text_style_editor_create_toggle (editor, editor->buffer->bold_tag,
+ GTK_STOCK_BOLD);
+ gimp_text_style_editor_create_toggle (editor, editor->buffer->italic_tag,
+ GTK_STOCK_ITALIC);
+ gimp_text_style_editor_create_toggle (editor, editor->buffer->underline_tag,
+ GTK_STOCK_UNDERLINE);
+ gimp_text_style_editor_create_toggle (editor, editor->buffer->strikethrough_tag,
+ GTK_STOCK_STRIKETHROUGH);
+
+ g_signal_connect (editor->buffer, "mark-set",
+ G_CALLBACK (gimp_text_style_editor_mark_set),
+ editor);
+
+ return object;
+}
+
+static void
+gimp_text_style_editor_dispose (GObject *object)
+{
+ GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object);
+
+ if (editor->buffer)
+ {
+ g_signal_handlers_disconnect_by_func (editor->buffer,
+ gimp_text_style_editor_mark_set,
+ editor);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_text_style_editor_finalize (GObject *object)
+{
+ GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object);
+
+ if (editor->tag_to_toggle_hash)
+ {
+ g_hash_table_unref (editor->tag_to_toggle_hash);
+ editor->tag_to_toggle_hash = NULL;
+ }
+
+ if (editor->buffer)
+ {
+ g_object_unref (editor->buffer);
+ editor->buffer = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_text_style_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ editor->buffer = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_text_style_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, editor->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_text_style_editor_new (GimpTextBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);
+
+ return g_object_new (GIMP_TYPE_TEXT_STYLE_EDITOR,
+ "buffer", buffer,
+ NULL);
+}
+
+
+/* private functions */
+
+static GtkWidget *
+gimp_text_style_editor_create_toggle (GimpTextStyleEditor *editor,
+ GtkTextTag *tag,
+ const gchar *stock_id)
+{
+ GtkWidget *toggle;
+ GtkWidget *image;
+
+ toggle = gtk_toggle_button_new ();
+ gtk_widget_set_can_focus (toggle, FALSE);
+ gtk_box_pack_start (GTK_BOX (editor), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_object_set_data (G_OBJECT (toggle), "tag", tag);
+ g_hash_table_insert (editor->tag_to_toggle_hash, tag, toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_text_style_editor_tag_toggled),
+ editor);
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (toggle), image);
+ gtk_widget_show (image);
+
+ return toggle;
+}
+
+static void
+gimp_text_style_editor_clear_tags (GtkButton *button,
+ GimpTextStyleEditor *editor)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer);
+
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+
+ gtk_text_buffer_remove_all_tags (buffer, &start, &end);
+ }
+}
+
+static void
+gimp_text_style_editor_tag_toggled (GtkToggleButton *toggle,
+ GimpTextStyleEditor *editor)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer);
+ GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), "tag");
+
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+
+ if (gtk_toggle_button_get_active (toggle))
+ {
+ gtk_text_buffer_apply_tag (buffer, tag, &start, &end);
+ }
+ else
+ {
+ gtk_text_buffer_remove_tag (buffer, tag, &start, &end);
+ }
+ }
+}
+
+static void
+gimp_text_style_editor_enable_toggle (GtkTextTag *tag,
+ GtkToggleButton *toggle,
+ GimpTextStyleEditor *editor)
+{
+ g_signal_handlers_block_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ editor);
+
+ gtk_toggle_button_set_active (toggle, TRUE);
+
+ g_signal_handlers_unblock_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ editor);
+}
+
+typedef struct
+{
+ GimpTextStyleEditor *editor;
+ GSList *tags;
+ GtkTextIter iter;
+ gboolean any_active;
+} UpdateTogglesData;
+
+static void
+gimp_text_style_editor_update_selection (GtkTextTag *tag,
+ GtkToggleButton *toggle,
+ UpdateTogglesData *data)
+{
+ if (! gtk_text_iter_has_tag (&data->iter, tag))
+ {
+ g_signal_handlers_block_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ data->editor);
+
+ gtk_toggle_button_set_active (toggle, FALSE);
+
+ g_signal_handlers_unblock_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ data->editor);
+ }
+ else
+ {
+ data->any_active = TRUE;
+ }
+}
+
+static void
+gimp_text_style_editor_update_cursor (GtkTextTag *tag,
+ GtkToggleButton *toggle,
+ UpdateTogglesData *data)
+{
+ g_signal_handlers_block_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ data->editor);
+
+ gtk_toggle_button_set_active (toggle,
+ g_slist_find (data->tags, tag) != NULL);
+
+ g_signal_handlers_unblock_by_func (toggle,
+ gimp_text_style_editor_tag_toggled,
+ data->editor);
+}
+
+static void
+gimp_text_style_editor_mark_set (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ GimpTextStyleEditor *editor)
+{
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ GtkTextIter start, end;
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+ gtk_text_iter_order (&start, &end);
+
+ /* first, switch all toggles on */
+ g_hash_table_foreach (editor->tag_to_toggle_hash,
+ (GHFunc) gimp_text_style_editor_enable_toggle,
+ editor);
+
+ for (iter = start;
+ gtk_text_iter_in_range (&iter, &start, &end);
+ gtk_text_iter_forward_cursor_position (&iter))
+ {
+ UpdateTogglesData data;
+
+ data.editor = editor;
+ data.iter = iter;
+ data.any_active = FALSE;
+
+ g_hash_table_foreach (editor->tag_to_toggle_hash,
+ (GHFunc) gimp_text_style_editor_update_selection,
+ &data);
+
+ if (! data.any_active)
+ break;
+ }
+
+ }
+ else
+ {
+ UpdateTogglesData data;
+ GtkTextIter cursor;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+
+ data.editor = editor;
+ data.tags = gtk_text_iter_get_tags (&cursor);
+
+ g_hash_table_foreach (editor->tag_to_toggle_hash,
+ (GHFunc) gimp_text_style_editor_update_cursor,
+ &data);
+
+ g_slist_free (data.tags);
+ }
+}
diff --git a/app/widgets/gimptextstyleeditor.h b/app/widgets/gimptextstyleeditor.h
new file mode 100644
index 0000000..947bf50
--- /dev/null
+++ b/app/widgets/gimptextstyleeditor.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextStyleEditor
+ * Copyright (C) 2010 Michael Natterer <mitch gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TEXT_STYLE_EDITOR_H__
+#define __GIMP_TEXT_STYLE_EDITOR_H__
+
+
+#define GIMP_TYPE_TEXT_STYLE_EDITOR (gimp_text_style_editor_get_type ())
+#define GIMP_TEXT_STYLE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditor))
+#define GIMP_TEXT_STYLE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditorClass))
+#define GIMP_IS_TEXT_STYLE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR))
+#define GIMP_IS_TEXT_STYLE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_STYLE_EDITOR))
+#define GIMP_TEXT_STYLE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditorClass))
+
+
+typedef struct _GimpTextStyleEditorClass GimpTextStyleEditorClass;
+
+struct _GimpTextStyleEditor
+{
+ GtkHBox parent_instance;
+
+ GimpTextBuffer *buffer;
+
+ GtkWidget *clear_button;
+ GtkWidget *bold_toggle;
+ GtkWidget *italic_toggle;
+ GtkWidget *underline_toggle;
+ GtkWidget *strikethrough_toggle;
+
+ GHashTable *tag_to_toggle_hash;
+};
+
+struct _GimpTextStyleEditorClass
+{
+ GtkHBoxClass parent_class;
+};
+
+
+GType gimp_text_style_editor_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_text_style_editor_new (GimpTextBuffer *buffer);
+
+
+#endif /* __GIMP_TEXT_STYLE_EDITOR_H__ */
diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h
index a31bf93..1acedf0 100644
--- a/app/widgets/widgets-types.h
+++ b/app/widgets/widgets-types.h
@@ -190,6 +190,7 @@ typedef struct _GimpStrokeEditor GimpStrokeEditor;
typedef struct _GimpTagEntry GimpTagEntry;
typedef struct _GimpTagPopup GimpTagPopup;
typedef struct _GimpTemplateEditor GimpTemplateEditor;
+typedef struct _GimpTextStyleEditor GimpTextStyleEditor;
typedef struct _GimpThumbBox GimpThumbBox;
typedef struct _GimpTranslationStore GimpTranslationStore;
typedef struct _GimpUnitStore GimpUnitStore;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]