+#include <gtk/gtk.h>
+#include "wx/gtk/private.h"
+#include "wx/gtk/private/gtk2-compat.h"
+
+// ----------------------------------------------------------------------------
+// helpers
+// ----------------------------------------------------------------------------
+
+extern "C" {
+static void wxGtkOnRemoveTag(GtkTextBuffer *buffer,
+ GtkTextTag *tag,
+ GtkTextIter * WXUNUSED(start),
+ GtkTextIter * WXUNUSED(end),
+ char *prefix)
+{
+ gchar *name;
+ g_object_get (tag, "name", &name, NULL);
+
+ if (!name || strncmp(name, prefix, strlen(prefix)))
+ // anonymous tag or not starting with prefix - don't remove
+ g_signal_stop_emission_by_name (buffer, "remove_tag");
+
+ g_free(name);
+}
+}
+
+// remove all tags starting with the given prefix from the start..end range
+static void
+wxGtkTextRemoveTagsWithPrefix(GtkTextBuffer *text_buffer,
+ const char *prefix,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ gulong remove_handler_id = g_signal_connect
+ (
+ text_buffer,
+ "remove_tag",
+ G_CALLBACK(wxGtkOnRemoveTag),
+ gpointer(prefix)
+ );
+ gtk_text_buffer_remove_all_tags(text_buffer, start, end);
+ g_signal_handler_disconnect(text_buffer, remove_handler_id);
+}
+
+static void wxGtkTextApplyTagsFromAttr(GtkWidget *text,
+ GtkTextBuffer *text_buffer,
+ const wxTextAttr& attr,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ static gchar buf[1024];
+ GtkTextTag *tag;
+
+ if (attr.HasFont())
+ {
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXFONT", start, end);
+
+ wxFont font(attr.GetFont());
+
+ PangoFontDescription *font_description = font.GetNativeFontInfo()->description;
+ wxGtkString font_string(pango_font_description_to_string(font_description));
+ g_snprintf(buf, sizeof(buf), "WXFONT %s", font_string.c_str());
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "font-desc", font_description,
+ NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
+
+ if (font.GetUnderlined())
+ {
+ g_snprintf(buf, sizeof(buf), "WXFONTUNDERLINE");
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "underline-set", TRUE,
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
+ }
+ if ( font.GetStrikethrough() )
+ {
+ g_snprintf(buf, sizeof(buf), "WXFONTSTRIKETHROUGH");
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "strikethrough-set", TRUE,
+ "strikethrough", TRUE,
+ NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
+ }
+ }
+
+ if (attr.HasTextColour())
+ {
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXFORECOLOR", start, end);
+
+ const GdkColor *colFg = attr.GetTextColour().GetColor();
+ g_snprintf(buf, sizeof(buf), "WXFORECOLOR %d %d %d",
+ colFg->red, colFg->green, colFg->blue);
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "foreground-gdk", colFg, NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
+ }
+
+ if (attr.HasBackgroundColour())
+ {
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXBACKCOLOR", start, end);
+
+ const GdkColor *colBg = attr.GetBackgroundColour().GetColor();
+ g_snprintf(buf, sizeof(buf), "WXBACKCOLOR %d %d %d",
+ colBg->red, colBg->green, colBg->blue);
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "background-gdk", colBg, NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
+ }
+
+ if (attr.HasAlignment())
+ {
+ GtkTextIter para_start, para_end = *end;
+ gtk_text_buffer_get_iter_at_line( text_buffer,
+ ¶_start,
+ gtk_text_iter_get_line(start) );
+ gtk_text_iter_forward_line(¶_end);
+
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXALIGNMENT", ¶_start, ¶_end);
+
+ GtkJustification align;
+ switch (attr.GetAlignment())
+ {
+ case wxTEXT_ALIGNMENT_RIGHT:
+ align = GTK_JUSTIFY_RIGHT;
+ break;
+ case wxTEXT_ALIGNMENT_CENTER:
+ align = GTK_JUSTIFY_CENTER;
+ break;
+ case wxTEXT_ALIGNMENT_JUSTIFIED:
+#ifdef __WXGTK3__
+ align = GTK_JUSTIFY_FILL;
+ break;
+#elif GTK_CHECK_VERSION(2,11,0)
+// gtk+ doesn't support justify before gtk+-2.11.0 with pango-1.17 being available
+// (but if new enough pango isn't available it's a mere gtk warning)
+ if (!gtk_check_version(2,11,0))
+ {
+ align = GTK_JUSTIFY_FILL;
+ break;
+ }
+ // fallthrough
+#endif
+ default:
+ align = GTK_JUSTIFY_LEFT;
+ break;
+ }
+
+ g_snprintf(buf, sizeof(buf), "WXALIGNMENT %d", align);
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "justification", align, NULL );
+ gtk_text_buffer_apply_tag( text_buffer, tag, ¶_start, ¶_end );
+ }
+
+ if (attr.HasLeftIndent())
+ {
+ // Indentation attribute
+
+ // Clear old indentation tags
+ GtkTextIter para_start, para_end = *end;
+ gtk_text_buffer_get_iter_at_line( text_buffer,
+ ¶_start,
+ gtk_text_iter_get_line(start) );
+ gtk_text_iter_forward_line(¶_end);
+
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXINDENT", ¶_start, ¶_end);
+
+ // Convert indent from 1/10th of a mm into pixels
+ float factor =
+ (float)gdk_screen_get_width(gtk_widget_get_screen(text)) /
+ gdk_screen_get_width_mm(gtk_widget_get_screen(text)) / 10;
+
+ const int indent = (int)(factor * attr.GetLeftIndent());
+ const int subIndent = (int)(factor * attr.GetLeftSubIndent());
+
+ gint gindent;
+ gint gsubindent;
+
+ if (subIndent >= 0)
+ {
+ gindent = indent;
+ gsubindent = -subIndent;
+ }
+ else
+ {
+ gindent = -subIndent;
+ gsubindent = indent;
+ }
+
+ g_snprintf(buf, sizeof(buf), "WXINDENT %d %d", gindent, gsubindent);
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buf );
+ if (!tag)
+ tag = gtk_text_buffer_create_tag( text_buffer, buf,
+ "left-margin", gindent, "indent", gsubindent, NULL );
+ gtk_text_buffer_apply_tag (text_buffer, tag, ¶_start, ¶_end);
+ }
+
+ if (attr.HasTabs())
+ {
+ // Set tab stops
+
+ // Clear old tabs
+ GtkTextIter para_start, para_end = *end;
+ gtk_text_buffer_get_iter_at_line( text_buffer,
+ ¶_start,
+ gtk_text_iter_get_line(start) );
+ gtk_text_iter_forward_line(¶_end);
+
+ wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXTABS", ¶_start, ¶_end);
+
+ const wxArrayInt& tabs = attr.GetTabs();
+
+ wxString tagname = wxT("WXTABS");
+ g_snprintf(buf, sizeof(buf), "WXTABS");
+ for (size_t i = 0; i < tabs.GetCount(); i++)
+ tagname += wxString::Format(wxT(" %d"), tabs[i]);
+
+ const wxWX2MBbuf buftag = tagname.utf8_str();
+
+ tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
+ buftag );
+ if (!tag)
+ {
+ // Factor to convert from 1/10th of a mm into pixels
+ float factor =
+ (float)gdk_screen_get_width(gtk_widget_get_screen(text)) /
+ gdk_screen_get_width_mm(gtk_widget_get_screen(text)) / 10;
+
+ PangoTabArray* tabArray = pango_tab_array_new(tabs.GetCount(), TRUE);
+ for (size_t i = 0; i < tabs.GetCount(); i++)
+ pango_tab_array_set_tab(tabArray, i, PANGO_TAB_LEFT, (gint)(tabs[i] * factor));
+ tag = gtk_text_buffer_create_tag( text_buffer, buftag,
+ "tabs", tabArray, NULL );
+ pango_tab_array_free(tabArray);
+ }
+ gtk_text_buffer_apply_tag (text_buffer, tag, ¶_start, ¶_end);
+ }
+}
+
+static void wxGtkTextInsert(GtkWidget *text,
+ GtkTextBuffer *text_buffer,
+ const wxTextAttr& attr,
+ const wxCharBuffer& buffer)
+
+{
+ gint start_offset;
+ GtkTextIter iter, start;
+
+ gtk_text_buffer_get_iter_at_mark( text_buffer, &iter,
+ gtk_text_buffer_get_insert (text_buffer) );
+ start_offset = gtk_text_iter_get_offset (&iter);
+ gtk_text_buffer_insert( text_buffer, &iter, buffer, strlen(buffer) );
+
+ gtk_text_buffer_get_iter_at_offset (text_buffer, &start, start_offset);
+
+ wxGtkTextApplyTagsFromAttr(text, text_buffer, attr, &start, &iter);
+}
+
+// Implementation of wxTE_AUTO_URL for wxGTK2 by Mart Raudsepp,
+
+extern "C" {
+static void
+au_apply_tag_callback(GtkTextBuffer *buffer,
+ GtkTextTag *tag,
+ GtkTextIter * WXUNUSED(start),
+ GtkTextIter * WXUNUSED(end),
+ gpointer WXUNUSED(textctrl))
+{
+ if(tag == gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl"))
+ g_signal_stop_emission_by_name (buffer, "apply_tag");
+}
+}
+
+//-----------------------------------------------------------------------------
+// GtkTextCharPredicates for gtk_text_iter_*_find_char
+//-----------------------------------------------------------------------------
+
+extern "C" {
+static gboolean
+pred_whitespace(gunichar ch, gpointer WXUNUSED(user_data))
+{
+ return g_unichar_isspace(ch);
+}
+}
+
+extern "C" {
+static gboolean
+pred_non_whitespace (gunichar ch, gpointer WXUNUSED(user_data))
+{
+ return !g_unichar_isspace(ch);
+}
+}
+
+extern "C" {
+static gboolean
+pred_nonpunct (gunichar ch, gpointer WXUNUSED(user_data))
+{
+ return !g_unichar_ispunct(ch);
+}
+}
+
+extern "C" {
+static gboolean
+pred_nonpunct_or_slash (gunichar ch, gpointer WXUNUSED(user_data))
+{
+ return !g_unichar_ispunct(ch) || ch == '/';
+}
+}
+
+//-----------------------------------------------------------------------------
+// Check for links between s and e and correct tags as necessary
+//-----------------------------------------------------------------------------
+
+// This function should be made match better while being efficient at one point.
+// Most probably with a row of regular expressions.
+extern "C" {
+static void
+au_check_word( GtkTextIter *s, GtkTextIter *e )
+{
+ static const char *const URIPrefixes[] =
+ {
+ "http://",
+ "ftp://",
+ "www.",
+ "ftp.",
+ "mailto://",
+ "https://",
+ "file://",
+ "nntp://",
+ "news://",
+ "telnet://",
+ "mms://",
+ "gopher://",
+ "prospero://",
+ "wais://",
+ };
+
+ GtkTextIter start = *s, end = *e;
+ GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
+
+ // Get our special link tag
+ GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
+
+ // Get rid of punctuation from beginning and end.
+ // Might want to move this to au_check_range if an improved link checking doesn't
+ // use some intelligent punctuation checking itself (beware of undesired iter modifications).
+ if(g_unichar_ispunct( gtk_text_iter_get_char( &start ) ) )
+ gtk_text_iter_forward_find_char( &start, pred_nonpunct, NULL, e );
+
+ gtk_text_iter_backward_find_char( &end, pred_nonpunct_or_slash, NULL, &start );
+ gtk_text_iter_forward_char(&end);
+
+ wxGtkString text(gtk_text_iter_get_text( &start, &end ));
+ size_t len = strlen(text), prefix_len;
+ size_t n;
+
+ for( n = 0; n < WXSIZEOF(URIPrefixes); ++n )
+ {
+ prefix_len = strlen(URIPrefixes[n]);
+ if((len > prefix_len) && !wxStrnicmp(text, URIPrefixes[n], prefix_len))
+ break;
+ }
+
+ if(n < WXSIZEOF(URIPrefixes))
+ {
+ gulong signal_id = g_signal_handler_find (buffer,
+ (GSignalMatchType) (G_SIGNAL_MATCH_FUNC),
+ 0, 0, NULL,
+ (gpointer)au_apply_tag_callback, NULL);
+
+ g_signal_handler_block (buffer, signal_id);
+ gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
+ g_signal_handler_unblock (buffer, signal_id);
+ }
+}
+}
+
+extern "C" {
+static void
+au_check_range(GtkTextIter *s,
+ GtkTextIter *range_end)
+{
+ GtkTextIter range_start = *s;
+ GtkTextIter word_end;
+ GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
+ GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
+
+ gtk_text_buffer_remove_tag(buffer, tag, s, range_end);
+
+ if(g_unichar_isspace(gtk_text_iter_get_char(&range_start)))
+ gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
+
+ while(!gtk_text_iter_equal(&range_start, range_end))
+ {
+ word_end = range_start;
+ gtk_text_iter_forward_find_char(&word_end, pred_whitespace, NULL, range_end);
+
+ // Now we should have a word delimited by range_start and word_end, correct link tags
+ au_check_word(&range_start, &word_end);
+
+ range_start = word_end;
+ gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
+ }
+}
+}
+
+//-----------------------------------------------------------------------------
+// "insert-text" for GtkTextBuffer
+//-----------------------------------------------------------------------------
+
+extern "C" {
+static void
+au_insert_text_callback(GtkTextBuffer * WXUNUSED(buffer),
+ GtkTextIter *end,
+ gchar *text,
+ gint len,
+ wxTextCtrl *win)
+{
+ if (!len || !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
+ return;
+
+ GtkTextIter start = *end;
+ gtk_text_iter_backward_chars(&start, g_utf8_strlen(text, len));
+
+ GtkTextIter line_start = start;
+ GtkTextIter line_end = *end;
+ GtkTextIter words_start = start;
+ GtkTextIter words_end = *end;
+
+ gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(&start));
+ gtk_text_iter_forward_to_line_end(&line_end);
+ gtk_text_iter_backward_find_char(&words_start, pred_whitespace, NULL, &line_start);
+ gtk_text_iter_forward_find_char(&words_end, pred_whitespace, NULL, &line_end);
+
+ au_check_range(&words_start, &words_end);
+}
+}
+
+//-----------------------------------------------------------------------------
+// "delete-range" for GtkTextBuffer
+//-----------------------------------------------------------------------------
+
+extern "C" {
+static void
+au_delete_range_callback(GtkTextBuffer * WXUNUSED(buffer),
+ GtkTextIter *start,
+ GtkTextIter *end,
+ wxTextCtrl *win)
+{
+ if( !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
+ return;
+
+ GtkTextIter line_start = *start, line_end = *end;
+
+ gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(start));
+ gtk_text_iter_forward_to_line_end(&line_end);
+ gtk_text_iter_backward_find_char(start, pred_whitespace, NULL, &line_start);
+ gtk_text_iter_forward_find_char(end, pred_whitespace, NULL, &line_end);
+
+ au_check_range(start, end);
+}
+}