1 /*
2  * Copyright (C) 2010 - 2012 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301, USA.
19  */
20 
21 #include <string.h>
22 #include <glib/gi18n-lib.h>
23 #include <gdk-pixbuf/gdk-pixdata.h>
24 #include "bullet.h"
25 #include "bulleth.h"
26 #define MAX_BULLETS 2
27 //gchar * bullet_strings[] = {"•", "◦"};
28 #include <libgda-ui.h>
29 
30 GdkPixbuf *bullet_pix[MAX_BULLETS] = {NULL};
31 gchar     *lists_tokens[MAX_BULLETS] = {"- ", " - "};
32 
33 static void gdaui_rt_editor_class_init (GdauiRtEditorClass *klass);
34 static void gdaui_rt_editor_init (GdauiRtEditor *wid);
35 static void gdaui_rt_editor_dispose (GObject *object);
36 
37 static void gdaui_rt_editor_set_property (GObject *object,
38 					  guint param_id,
39 					  const GValue *value,
40 					  GParamSpec *pspec);
41 static void gdaui_rt_editor_get_property (GObject *object,
42 					  guint param_id,
43 					  GValue *value,
44 					  GParamSpec *pspec);
45 static void gdaui_rt_editor_show_all (GtkWidget *widget);
46 
47 static void _gdaui_rt_editor_set_show_markup (GdauiRtEditor *editor, gboolean show_markup);
48 
49 /* tag types */
50 enum {
51 	TEXT_TAG_ITALIC,
52 	TEXT_TAG_BOLD,
53 	TEXT_TAG_TT,
54 	TEXT_TAG_VERBATIM,
55 	TEXT_TAG_UNDERLINE,
56 	TEXT_TAG_STRIKE,
57 	TEXT_TAG_TITLE1,
58 	TEXT_TAG_TITLE2,
59 	TEXT_TAG_LIST1,
60 	TEXT_TAG_LIST2,
61 
62 	TEXT_TAG_LAST
63 };
64 
65 typedef struct {
66 	GtkTextTag *tag;
67 	gchar      *action_name;
68 } TagData;
69 
70 struct _GdauiRtEditorPriv
71 {
72 	GtkTextView    *textview;
73 	gdouble         vadj_value;
74 	GtkTextBuffer  *textbuffer;
75 	GtkWidget      *toolbar;
76 	GtkActionGroup *actions_group;
77 	GtkUIManager   *uimanager;
78 	TagData         tags[TEXT_TAG_LAST];
79 	gboolean        selection_changing;
80 	gboolean        show_markup;
81 	gchar          *saved_for_help;
82 	gboolean        enable_changed_signal;
83 	gboolean        no_background;
84 	gint            insert_offset;
85 
86 	gboolean        contents_setting; /* TRUE if whole contents is being changed */
87 	GtkWidget      *sw; /* swrolled window in which the contents is */
88 };
89 
90 /* get a pointer to the parents to be able to call their destructor */
91 static GObjectClass *parent_class = NULL;
92 static gchar *help_str=N_("\"\"\"= Title level 1 =\n"
93 			  "== Title level 2 ==\n"
94 			  "\"\"\"= Title level 1 =\n"
95 			  "== Title level 2 ==\n\n"
96 			  "\"\"\""
97 			  "For beautifiers we have **bold**\n"
98 			  "and //italic//.\n"
99 			  "There is also __underline__, --strike--\n"
100 			  "and ``monospaced``.\n"
101 			  "\"\"\"\n"
102 			  "For beautifiers we have **bold**\n"
103 			  "and //italic//.\n"
104 			  "There is also __underline__, --strike--\n"
105 			  "and ``monospaced``.\n\n"
106 			  "\"\"\""
107 			  "- This is a list of items\n"
108 			  "- Just use hyphens\n"
109 			  " - And starting space for indenting\n"
110 			  "\"\"\"- This is a list of items\n"
111 			  "- Just use hyphens\n"
112 			  " - And starting space for indenting\n"
113 			  "\nRaw areas are enclosed inside three doublequotes and no markup is interpreted");
114 
115 /* signals */
116 enum {
117 	CHANGED,
118 	LAST_SIGNAL
119 };
120 
121 static gint gdaui_rt_editor_signals[LAST_SIGNAL] = { 0 };
122 
123 /* properties */
124 enum {
125 	PROP_0,
126 	PROP_NO_BACKGROUND,
127 	PROP_SHOW_MARKUP,
128 	PROP_TEXTBUFFER,
129 	PROP_SCROLLED_WINDOW
130 };
131 
132 /* global pixbufs */
133 static gint spaces_since_start_of_line (GtkTextIter *iter);
134 static gchar *real_gdaui_rt_editor_get_contents (GdauiRtEditor *editor);
135 
136 static GtkTextTag *iter_begins_list (GdauiRtEditor *rte, GtkTextIter *iter, gint *out_list_level);
137 static void mark_set_cb (GtkTextBuffer *textbuffer, GtkTextIter *location,
138 			 GtkTextMark *mark, GdauiRtEditor *rte);
139 static void insert_text_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, GdauiRtEditor *rte);
140 static void insert_text_after_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, GdauiRtEditor *rte);
141 static void text_buffer_changed_cb (GtkTextBuffer *textbuffer, GdauiRtEditor *rte);
142 static void populate_popup_cb (GtkTextView *entry, GtkMenu *menu, GdauiRtEditor *rte);
143 
144 static void show_hide_toolbar (GdauiRtEditor *editor);
145 
146 static gchar *add_newlines_to_base64 (gchar *base64);
147 static gchar *remove_newlines_from_base64 (gchar *base64);
148 
149 static void italic_cb (GtkToggleAction *action, GdauiRtEditor *rte);
150 static void strike_cb (GtkToggleAction *action, GdauiRtEditor *rte);
151 static void underline_cb (GtkToggleAction *action, GdauiRtEditor *rte);
152 static void bold_cb (GtkToggleAction *action, GdauiRtEditor *rte);
153 static void reset_all_cb (GtkAction *action, GdauiRtEditor *rte);
154 static void add_image_cb (GtkAction *action, GdauiRtEditor *rte);
155 static void help_cb (GtkToggleAction *action, GdauiRtEditor *rte);
156 
157 static const GtkToggleActionEntry ui_toggle_actions [] =
158 {
159         { "ActionBold", GTK_STOCK_BOLD, N_("_Bold"), NULL, N_("Bold text"), G_CALLBACK (bold_cb), FALSE},
160         { "ActionItalic", GTK_STOCK_ITALIC, N_("_Italic"), NULL, N_("Italic text"), G_CALLBACK (italic_cb), FALSE},
161         { "ActionUnderline", GTK_STOCK_UNDERLINE, N_("_Underline"), NULL, N_("Underline text"), G_CALLBACK (underline_cb), FALSE},
162         { "ActionStrike", GTK_STOCK_STRIKETHROUGH, N_("_Strike through"), NULL, N_("Strike through text"), G_CALLBACK (strike_cb), FALSE},
163         { "ActionHelp", GTK_STOCK_HELP, N_("_Syntax help"), NULL, N_("Show syntax help"), G_CALLBACK (help_cb), FALSE}
164 };
165 
166 static const GtkActionEntry ui_actions[] = {
167         { "ActionAddImage", "insert-image", N_("_Add image"), NULL, N_("Insert image"), G_CALLBACK (add_image_cb)},
168         { "ActionReset", GTK_STOCK_CLEAR, N_("_Normal text"), NULL, N_("Reset to normal text"), G_CALLBACK (reset_all_cb)},
169 };
170 
171 static const gchar *ui_actions_info =
172         "<ui>"
173         "  <toolbar name='ToolBar'>"
174         "    <toolitem action='ActionBold'/>"
175         "    <toolitem action='ActionItalic'/>"
176         "    <toolitem action='ActionUnderline'/>"
177         "    <toolitem action='ActionStrike'/>"
178         "    <toolitem action='ActionAddImage'/>"
179         "    <toolitem action='ActionReset'/>"
180         "    <toolitem action='ActionHelp'/>"
181         "  </toolbar>"
182         "</ui>";
183 
184 GType
gdaui_rt_editor_get_type(void)185 gdaui_rt_editor_get_type (void)
186 {
187 	static GType type = 0;
188 
189 	if (G_UNLIKELY (type == 0)) {
190 		static const GTypeInfo info = {
191 			sizeof (GdauiRtEditorClass),
192 			(GBaseInitFunc) NULL,
193 			(GBaseFinalizeFunc) NULL,
194 			(GClassInitFunc) gdaui_rt_editor_class_init,
195 			NULL,
196 			NULL,
197 			sizeof (GdauiRtEditor),
198 			0,
199 			(GInstanceInitFunc) gdaui_rt_editor_init,
200 			0
201 		};
202 
203 		type = g_type_register_static (GTK_TYPE_BOX, "GdauiRtEditor", &info, 0);
204 	}
205 
206 	return type;
207 }
208 
209 static void
gdaui_rt_editor_class_init(GdauiRtEditorClass * klass)210 gdaui_rt_editor_class_init (GdauiRtEditorClass *klass)
211 {
212 	GObjectClass  *object_class = G_OBJECT_CLASS (klass);
213 	parent_class = g_type_class_peek_parent (klass);
214 
215 	object_class->dispose = gdaui_rt_editor_dispose;
216 
217 	GTK_WIDGET_CLASS (klass)->show_all = gdaui_rt_editor_show_all;
218 
219 	/* signals */
220 	gdaui_rt_editor_signals[CHANGED] =
221                 g_signal_new ("changed",
222                               G_TYPE_FROM_CLASS (object_class),
223                               G_SIGNAL_RUN_LAST,
224                               G_STRUCT_OFFSET (GdauiRtEditorClass, changed),
225                               NULL, NULL,
226                               g_cclosure_marshal_VOID__VOID,
227                               G_TYPE_NONE, 0);
228 
229 	/* Properties */
230         object_class->set_property = gdaui_rt_editor_set_property;
231         object_class->get_property = gdaui_rt_editor_get_property;
232 	/**
233 	 * GdauiRtEditor:no-background:
234 	 *
235 	 * If set to %TRUE, then the default text background is removed
236 	 * and thus the textbackground is the default widget's background.
237 	 *
238 	 * This property has to be set before the widget is realized, and is taken into account only
239 	 * if the widget is not editable (when it's realized).
240 	 **/
241 	g_object_class_install_property (object_class, PROP_NO_BACKGROUND,
242                                          g_param_spec_boolean ("no-background",
243                                                                _("Don't display a specific background for the text"),
244                                                                NULL, FALSE,
245                                                                G_PARAM_READABLE | G_PARAM_WRITABLE));
246 	/**
247 	 * GdauiRtEditor:show-markup:
248 	 *
249 	 * Instead of showing the formatted text, display the raw text (in the txt2tags syntax)
250 	 **/
251 	g_object_class_install_property (object_class, PROP_SHOW_MARKUP,
252 					 g_param_spec_boolean ("show-markup",
253                                                                _("Display raw markup text instead of formatted text"),
254                                                                NULL, FALSE,
255                                                                G_PARAM_READABLE | G_PARAM_WRITABLE));
256 
257 	/**
258 	 * GdauiRtEditor:buffer:
259 	 *
260 	 * Get access to the actual #GtkTextBuffer used. Do not modify it!
261 	 **/
262 	g_object_class_install_property (object_class, PROP_TEXTBUFFER,
263 					 g_param_spec_object ("buffer",
264 							      _("The buffer which is displayed"),
265 							      NULL, GTK_TYPE_TEXT_BUFFER,
266 							      G_PARAM_READABLE));
267 
268 	/**
269 	 * GdauiRtEditor:in-scrolled-window:
270 	 *
271 	 * Determines if the contents of the widget appears in a scrolled window or not.
272 	 **/
273 	g_object_class_install_property (object_class, PROP_SCROLLED_WINDOW,
274 					 g_param_spec_boolean ("in-scrolled-window",
275                                                                _("Determines if the contents appears in a scrolled window"),
276                                                                NULL, TRUE,
277                                                                G_PARAM_READABLE | G_PARAM_WRITABLE));
278 }
279 
280 static void
text_view_realized_cb(GtkWidget * tv,GdauiRtEditor * rte)281 text_view_realized_cb (GtkWidget *tv, GdauiRtEditor *rte)
282 {
283 	if (rte->priv->no_background && ! gtk_text_view_get_editable (GTK_TEXT_VIEW (tv))) {
284 		GdkWindow *win;
285 		GtkStyleContext* style_context = gtk_widget_get_style_context (tv);
286                 GdkRGBA color;
287 		win = gtk_text_view_get_window (GTK_TEXT_VIEW (tv), GTK_TEXT_WINDOW_TEXT);
288                 gtk_style_context_get_background_color (style_context, GTK_STATE_FLAG_NORMAL, &color);
289 		gdk_window_set_background_rgba (win, &color);
290 	}
291 }
292 
293 static gboolean
focus_changed_cb(GtkWidget * textview,GdkEventFocus * ev,GdauiRtEditor * rte)294 focus_changed_cb (GtkWidget *textview, GdkEventFocus *ev, GdauiRtEditor *rte)
295 {
296 	show_hide_toolbar (rte);
297 	return FALSE;
298 }
299 
300 static void
gdaui_rt_editor_init(GdauiRtEditor * rte)301 gdaui_rt_editor_init (GdauiRtEditor *rte)
302 {
303 	GtkWidget *sw, *textview, *toolbar;
304 
305 	gtk_orientable_set_orientation (GTK_ORIENTABLE (rte), GTK_ORIENTATION_VERTICAL);
306 
307 	sw = gtk_scrolled_window_new (NULL, NULL);
308 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
309 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
310 	gtk_box_pack_end (GTK_BOX (rte), sw, TRUE, TRUE, 0);
311 
312 	textview = gtk_text_view_new ();
313 	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), GTK_WRAP_WORD);
314 	gtk_container_add (GTK_CONTAINER (sw), textview);
315 	g_signal_connect (textview, "realize",
316 			  G_CALLBACK (text_view_realized_cb), rte);
317 
318 	gtk_widget_show_all (sw);
319 
320 	rte->priv = g_new0 (GdauiRtEditorPriv, 1);
321 	rte->priv->sw = sw;
322 	rte->priv->vadj_value = 0.;
323 	rte->priv->saved_for_help = NULL;
324 	rte->priv->enable_changed_signal = TRUE;
325 	rte->priv->no_background = FALSE;
326 	rte->priv->insert_offset = -1;
327 	rte->priv->textview = GTK_TEXT_VIEW (textview);
328 	rte->priv->textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
329 	rte->priv->contents_setting = FALSE;
330 	g_signal_connect (rte->priv->textbuffer, "changed",
331 			  G_CALLBACK (text_buffer_changed_cb), rte);
332 	g_signal_connect (rte->priv->textbuffer, "mark-set",
333 			  G_CALLBACK (mark_set_cb), rte);
334 	g_signal_connect (rte->priv->textbuffer, "insert-text",
335 			  G_CALLBACK (insert_text_cb), rte);
336 	g_signal_connect_after (rte->priv->textbuffer, "insert-text",
337 				G_CALLBACK (insert_text_after_cb), rte);
338 	g_signal_connect (rte->priv->textview, "populate-popup",
339 			  G_CALLBACK (populate_popup_cb), rte);
340 	g_signal_connect (rte->priv->textview, "focus-in-event",
341 			  G_CALLBACK (focus_changed_cb), rte);
342 	g_signal_connect (rte->priv->textview, "focus-out-event",
343 			  G_CALLBACK (focus_changed_cb), rte);
344 
345 	/* tags. REM: leave the LIST* and BULLET tags defined 1st as they will be with less priority
346 	 * and it affects the result returned by gtk_text_iter_get_tags() */
347 	rte->priv->show_markup = FALSE;
348 	memset (rte->priv->tags, 0, sizeof (rte->priv->tags));
349 
350 	rte->priv->tags[TEXT_TAG_LIST1].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
351 									  "indent", -5,
352 									  "left_margin", 15,
353 									  /*"background", "#cbbcbc",*/
354 									  NULL);
355 	rte->priv->tags[TEXT_TAG_LIST2].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
356 									  "indent", -10,
357 									  "left_margin", 25,
358 									  /*"background", "#dcbcbc",*/
359 									  NULL);
360 	rte->priv->tags[TEXT_TAG_ITALIC].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
361 									   "style", PANGO_STYLE_ITALIC, NULL);
362 	rte->priv->tags[TEXT_TAG_ITALIC].action_name = "/ToolBar/ActionItalic";
363 
364 	rte->priv->tags[TEXT_TAG_BOLD].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
365 									 "weight", PANGO_WEIGHT_BOLD, NULL);
366 	rte->priv->tags[TEXT_TAG_BOLD].action_name = "/ToolBar/ActionBold";
367 
368 	rte->priv->tags[TEXT_TAG_TT].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
369 								       "family", "Monospace", NULL);
370 	rte->priv->tags[TEXT_TAG_VERBATIM].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
371 									     "background", "#e5e2e2",
372 									     NULL);
373 
374 	rte->priv->tags[TEXT_TAG_UNDERLINE].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
375 									      "underline", PANGO_UNDERLINE_SINGLE, NULL);
376 	rte->priv->tags[TEXT_TAG_UNDERLINE].action_name = "/ToolBar/ActionUnderline";
377 
378 	rte->priv->tags[TEXT_TAG_STRIKE].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
379 									   "strikethrough", TRUE, NULL);
380 	rte->priv->tags[TEXT_TAG_STRIKE].action_name = "/ToolBar/ActionStrike";
381 
382 	rte->priv->tags[TEXT_TAG_TITLE1].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
383 									   "size", 15 * PANGO_SCALE,
384 									   "weight", PANGO_WEIGHT_SEMIBOLD,
385 									   NULL);
386 	rte->priv->tags[TEXT_TAG_TITLE2].tag = gtk_text_buffer_create_tag (rte->priv->textbuffer, NULL,
387 									   "size", 13 * PANGO_SCALE,
388 									   "weight", PANGO_WEIGHT_SEMIBOLD,
389 									   NULL);
390 
391 	/* action group */
392 	rte->priv->actions_group = gtk_action_group_new ("Actions");
393 	gtk_action_group_set_translation_domain (rte->priv->actions_group, GETTEXT_PACKAGE);
394 	gtk_action_group_add_toggle_actions (rte->priv->actions_group, ui_toggle_actions,
395 					     G_N_ELEMENTS (ui_toggle_actions), rte);
396         gtk_action_group_add_actions (rte->priv->actions_group, ui_actions, G_N_ELEMENTS (ui_actions),
397 				      rte);
398 
399 	/* ui manager */
400 	rte->priv->uimanager = gtk_ui_manager_new ();
401         gtk_ui_manager_insert_action_group (rte->priv->uimanager, rte->priv->actions_group, 0);
402         gtk_ui_manager_add_ui_from_string (rte->priv->uimanager, ui_actions_info, -1, NULL);
403 
404 	/* toolbar */
405 	toolbar = gtk_ui_manager_get_widget (rte->priv->uimanager, "/ToolBar");
406 	gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_MENU);
407 	rte->priv->toolbar = toolbar;
408 	gtk_box_pack_end (GTK_BOX (rte), toolbar, FALSE, FALSE, 0);
409 
410 	show_hide_toolbar (rte);
411 }
412 
413 /**
414  * gdaui_rt_editor_new:
415  *
416  * Creates a new #GdauiRtEditor widget
417  *
418  * Returns: (transfer full): the new widget
419  *
420  * Since: 4.2.2
421  */
422 GtkWidget *
gdaui_rt_editor_new()423 gdaui_rt_editor_new ()
424 {
425 	GtkWidget *rte;
426 
427 	rte = (GtkWidget *) g_object_new (GDAUI_TYPE_RT_EDITOR, NULL);
428 
429 	return rte;
430 }
431 
432 static void
gdaui_rt_editor_dispose(GObject * object)433 gdaui_rt_editor_dispose (GObject *object)
434 {
435 	GdauiRtEditor *rte;
436 
437 	g_return_if_fail (GDAUI_IS_RT_EDITOR (object));
438 	rte = GDAUI_RT_EDITOR (object);
439 
440 	if (rte->priv) {
441 		if (rte->priv->actions_group) {
442 			g_object_unref (G_OBJECT (rte->priv->actions_group));
443 			rte->priv->actions_group = NULL;
444 		}
445 
446 		if (rte->priv->uimanager)
447 			g_object_unref (rte->priv->uimanager);
448 
449 		g_free (rte->priv->saved_for_help);
450 		/* NB: GtkTextTags are owned by the GtkTextBuffer's text tags table */
451 
452 		/* the private area itself */
453 		g_free (rte->priv);
454 		rte->priv = NULL;
455 	}
456 
457 	/* for the parent class */
458 	parent_class->dispose (object);
459 }
460 
461 static void
gdaui_rt_editor_show_all(GtkWidget * widget)462 gdaui_rt_editor_show_all (GtkWidget *widget)
463 {
464 	GdauiRtEditor *editor;
465 	editor = GDAUI_RT_EDITOR (widget);
466 	GTK_WIDGET_CLASS (parent_class)->show_all (widget);
467 	show_hide_toolbar (editor);
468 }
469 
470 static void
gdaui_rt_editor_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)471 gdaui_rt_editor_set_property (GObject *object,
472 			      guint param_id,
473 			      const GValue *value,
474 			      GParamSpec *pspec)
475 {
476 	GdauiRtEditor *editor;
477 
478         editor = GDAUI_RT_EDITOR (object);
479         if (editor->priv) {
480                 switch (param_id) {
481 		case PROP_NO_BACKGROUND:
482 			editor->priv->no_background = g_value_get_boolean (value);
483 			break;
484 		case PROP_SHOW_MARKUP:
485 			_gdaui_rt_editor_set_show_markup (editor, g_value_get_boolean (value));
486 			break;
487 		case PROP_SCROLLED_WINDOW: {
488 			gboolean setting;
489 			setting = g_value_get_boolean (value);
490 			if ((editor->priv->sw && setting) ||
491 			    (! editor->priv->sw && ! setting))
492 				break; /* nothing to change */
493 			if (editor->priv->sw) {
494 				/* remove scrolled window */
495 				gtk_widget_reparent (GTK_WIDGET (editor->priv->textview), GTK_WIDGET (editor));
496 				gtk_widget_destroy (editor->priv->sw);
497 				editor->priv->sw = NULL;
498 			}
499 			else {
500 				/* add scrolled window */
501 				GtkWidget *sw;
502 				sw = gtk_scrolled_window_new (NULL, NULL);
503 				gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
504 								GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
505 				gtk_box_pack_end (GTK_BOX (editor), sw, TRUE, TRUE, 0);
506 				editor->priv->sw = sw;
507 				gtk_widget_show (sw);
508 				gtk_widget_reparent (GTK_WIDGET (editor->priv->textview), GTK_WIDGET (sw));
509 			}
510 			break;
511 		}
512 		default:
513 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
514 			break;
515                 }
516         }
517 }
518 
519 static void
gdaui_rt_editor_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)520 gdaui_rt_editor_get_property (GObject *object,
521 			     guint param_id,
522 			     GValue *value,
523 			     GParamSpec *pspec)
524 {
525 	GdauiRtEditor *editor;
526 
527         editor = GDAUI_RT_EDITOR (object);
528         if (editor->priv) {
529                 switch (param_id) {
530 		case PROP_NO_BACKGROUND:
531 			g_value_set_boolean (value, editor->priv->no_background);
532 			break;
533 		case PROP_SHOW_MARKUP:
534 			g_value_set_boolean (value, editor->priv->show_markup);
535 			break;
536 		case PROP_TEXTBUFFER:
537 			g_value_set_object (value, editor->priv->textbuffer);
538 			break;
539 		case PROP_SCROLLED_WINDOW:
540 			g_value_set_boolean (value, editor->priv->sw ? TRUE : FALSE);
541 			break;
542 		default:
543 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
544 			break;
545                 }
546         }
547 }
548 
549 static GtkTextTag *
iter_begins_list(GdauiRtEditor * rte,GtkTextIter * iter,gint * out_list_level)550 iter_begins_list (GdauiRtEditor *rte, GtkTextIter *iter, gint *out_list_level)
551 {
552 	gint idx = -1;
553 	GtkTextTag *tag = NULL;
554 	if (gtk_text_iter_has_tag (iter, rte->priv->tags[TEXT_TAG_LIST1].tag)) {
555 		tag = rte->priv->tags[TEXT_TAG_LIST1].tag;
556 		idx = 0;
557 	}
558 	else if (gtk_text_iter_has_tag (iter, rte->priv->tags[TEXT_TAG_LIST2].tag)) {
559 		tag = rte->priv->tags[TEXT_TAG_LIST2].tag;
560 		idx = 1;
561 	}
562 	if (out_list_level)
563 		*out_list_level = idx;
564 	return tag;
565 }
566 
567 /* tags management */
568 static void
apply_tag(GdauiRtEditor * rte,gboolean reverse,GtkTextTag * tag)569 apply_tag (GdauiRtEditor *rte, gboolean reverse, GtkTextTag *tag)
570 {
571 	GtkTextIter start;
572 	GtkTextIter end;
573 
574 	g_return_if_fail (rte->priv->textbuffer);
575 
576 	if (rte->priv->selection_changing)
577 		return;
578 
579 	if (gtk_text_buffer_get_selection_bounds (rte->priv->textbuffer, &start, &end)) {
580 		if (tag) {
581 			if (reverse)
582 				gtk_text_buffer_remove_tag (rte->priv->textbuffer, tag, &start, &end);
583 			else {
584 				gtk_text_buffer_apply_tag (rte->priv->textbuffer, tag, &start, &end);
585 				/* if there are LIST tags, then remove the applied tag */
586 				GtkTextIter iter;
587 				for (iter = start; gtk_text_iter_compare (&iter, &end) < 0; ) {
588 					GtkTextTag *ltag;
589 					gint idx;
590 					ltag = iter_begins_list (rte, &iter, &idx);
591 					if (ltag) {
592 						GtkTextIter liter;
593 						liter = iter;
594 						gtk_text_iter_forward_to_tag_toggle (&liter, ltag);
595 						gtk_text_iter_backward_char (&iter);
596 						gtk_text_buffer_remove_tag (rte->priv->textbuffer,
597 									    tag, &iter, &liter);
598 						gtk_text_iter_forward_char (&iter);
599 					}
600 					if (! gtk_text_iter_forward_char (&iter))
601 						break;
602 				}
603 			}
604 		}
605 		else {
606 			GtkTextIter iter;
607 			for (iter = start; gtk_text_iter_compare (&iter, &end) < 0; ) {
608 
609 				GSList *tags, *list;
610 				tags = gtk_text_iter_get_tags (&iter);
611 				if (tags) {
612 					for (list = tags; list; list = list->next) {
613 						GtkTextTag *tag = (GtkTextTag *) list->data;
614 						if ((tag != rte->priv->tags[TEXT_TAG_LIST1].tag) &&
615 						    (tag != rte->priv->tags[TEXT_TAG_LIST2].tag))
616 							gtk_text_buffer_remove_tag (rte->priv->textbuffer,
617 										    tag, &start, &end);
618 					}
619 					g_slist_free (tags);
620 				}
621 				if (! gtk_text_iter_forward_char (&iter))
622 					break;
623 			}
624 		}
625 	}
626 
627 	if (rte->priv->enable_changed_signal)
628 		g_signal_emit (rte, gdaui_rt_editor_signals[CHANGED], 0, NULL);
629 
630 }
631 
632 static void
help_cb(GtkToggleAction * action,GdauiRtEditor * rte)633 help_cb (GtkToggleAction *action, GdauiRtEditor *rte)
634 {
635 	if (gtk_toggle_action_get_active (action)) {
636 		rte->priv->enable_changed_signal = FALSE;
637 		g_free (rte->priv->saved_for_help);
638 		rte->priv->saved_for_help = gdaui_rt_editor_get_contents (rte);
639 		gdaui_rt_editor_set_contents (rte, help_str, -1);
640 	}
641 	else {
642 		gdaui_rt_editor_set_contents (rte, rte->priv->saved_for_help, -1);
643 		rte->priv->enable_changed_signal = TRUE;
644 		g_free (rte->priv->saved_for_help);
645 		rte->priv->saved_for_help = NULL;
646 	}
647 }
648 
649 static void
italic_cb(GtkToggleAction * action,GdauiRtEditor * rte)650 italic_cb (GtkToggleAction *action, GdauiRtEditor *rte)
651 {
652 	apply_tag (rte, gtk_toggle_action_get_active (action) ? FALSE : TRUE,
653 		   rte->priv->tags [TEXT_TAG_ITALIC].tag);
654 }
655 
656 static void
bold_cb(GtkToggleAction * action,GdauiRtEditor * rte)657 bold_cb (GtkToggleAction *action, GdauiRtEditor *rte)
658 {
659 	apply_tag (rte, gtk_toggle_action_get_active (action) ? FALSE : TRUE,
660 		   rte->priv->tags [TEXT_TAG_BOLD].tag);
661 }
662 
663 static void
strike_cb(GtkToggleAction * action,GdauiRtEditor * rte)664 strike_cb (GtkToggleAction *action, GdauiRtEditor *rte)
665 {
666 	apply_tag (rte, gtk_toggle_action_get_active (action) ? FALSE : TRUE,
667 		   rte->priv->tags [TEXT_TAG_STRIKE].tag);
668 }
669 
670 static void
underline_cb(GtkToggleAction * action,GdauiRtEditor * rte)671 underline_cb (GtkToggleAction *action, GdauiRtEditor *rte)
672 {
673 	apply_tag (rte, gtk_toggle_action_get_active (action) ? FALSE : TRUE,
674 		   rte->priv->tags [TEXT_TAG_UNDERLINE].tag);
675 }
676 
677 static void
reset_all_cb(G_GNUC_UNUSED GtkAction * action,GdauiRtEditor * rte)678 reset_all_cb (G_GNUC_UNUSED GtkAction *action, GdauiRtEditor *rte)
679 {
680 	apply_tag (rte, FALSE, NULL);
681 }
682 
683 static void
add_image_cb(G_GNUC_UNUSED GtkAction * action,GdauiRtEditor * rte)684 add_image_cb (G_GNUC_UNUSED GtkAction *action, GdauiRtEditor *rte)
685 {
686 	GtkWidget *dlg;
687         GtkFileFilter *filter;
688 
689         dlg = gtk_file_chooser_dialog_new (_("Select image to load"),
690                                            GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) rte)),
691                                            GTK_FILE_CHOOSER_ACTION_OPEN,
692                                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
693                                            GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
694                                            NULL);
695 	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg),
696 					     gdaui_get_default_path ());
697         filter = gtk_file_filter_new ();
698         gtk_file_filter_add_pixbuf_formats (filter);
699         gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dlg), filter);
700 
701         if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT) {
702                 char *filename;
703                 GError *error = NULL;
704 
705                 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));
706 		gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg)));
707 
708 		GdkPixbuf *pixbuf;
709 		pixbuf = gdk_pixbuf_new_from_file (filename, &error);
710 		if (pixbuf) {
711 			GtkTextIter start;
712 			GtkTextIter end;
713 			gboolean ret = FALSE;
714 
715 			ret = gtk_text_buffer_get_selection_bounds (rte->priv->textbuffer, &start, &end);
716 			if (ret)
717 				gtk_text_buffer_delete (rte->priv->textbuffer, &start, &end);
718 
719 			gtk_text_buffer_get_iter_at_mark (rte->priv->textbuffer, &start,
720 							  gtk_text_buffer_get_insert (rte->priv->textbuffer));
721 			gtk_text_buffer_insert_pixbuf (rte->priv->textbuffer, &start, pixbuf);
722 			g_object_unref (pixbuf);
723 		}
724 		else {
725 			GtkWidget *msg;
726 
727                         msg = gtk_message_dialog_new_with_markup (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) rte)),
728                                                                   GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
729                                                                   GTK_BUTTONS_CLOSE,
730                                                                   _("Could not load the contents of '%s':\n %s"),
731                                                                   filename,
732                                                                   error && error->message ? error->message : _("No detail"));
733 			g_clear_error (&error);
734                         gtk_widget_destroy (dlg);
735                         dlg = NULL;
736 
737                         gtk_dialog_run (GTK_DIALOG (msg));
738                         gtk_widget_destroy (msg);
739 		}
740 	}
741 
742 	gtk_widget_destroy (dlg);
743 }
744 
745 static void
mark_set_cb(GtkTextBuffer * textbuffer,GtkTextIter * location,GtkTextMark * mark,GdauiRtEditor * rte)746 mark_set_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, GtkTextMark *mark, GdauiRtEditor *rte)
747 {
748 	if (mark == gtk_text_buffer_get_insert (textbuffer)) {
749 		GtkAction *action;
750 		gboolean act;
751 		gint i;
752 
753 		rte->priv->selection_changing = TRUE;
754 
755 		for (i = 0; i < TEXT_TAG_LAST; i++) {
756 			if (! rte->priv->tags[i].action_name)
757 				continue;
758 
759 			action = gtk_ui_manager_get_action (rte->priv->uimanager,
760 							    rte->priv->tags[i].action_name);
761 			if (gtk_text_buffer_get_has_selection (textbuffer))
762 				act = FALSE;
763 			else
764 				act = gtk_text_iter_has_tag (location, rte->priv->tags [i].tag);
765 			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), act);
766 		}
767 
768 		rte->priv->selection_changing = FALSE;
769 	}
770 }
771 
772 static void
insert_text_cb(GtkTextBuffer * textbuffer,GtkTextIter * location,G_GNUC_UNUSED gchar * text,G_GNUC_UNUSED gint len,GdauiRtEditor * rte)773 insert_text_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, G_GNUC_UNUSED gchar *text, G_GNUC_UNUSED gint len, GdauiRtEditor *rte)
774 {
775 	/* if inserting is before a bullet, then insert right after */
776 	GtkTextTag *tag;
777 	tag = iter_begins_list (rte, location, NULL);
778 	if (tag) {
779 		gtk_text_iter_forward_char (location);
780 		gtk_text_buffer_place_cursor (textbuffer, location);
781 	}
782 	rte->priv->insert_offset = gtk_text_iter_get_offset (location);
783 }
784 
785 static void
insert_text_after_cb(GtkTextBuffer * textbuffer,GtkTextIter * location,gchar * text,G_GNUC_UNUSED gint len,GdauiRtEditor * rte)786 insert_text_after_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, G_GNUC_UNUSED gint len, GdauiRtEditor *rte)
787 {
788 	GtkTextIter start, end;
789 
790 	if ((rte->priv->insert_offset < 0) || rte->priv->show_markup)
791 		return;
792 
793 	/* disable any extra modification while text is being set using gdaui_rt_editor_set_contents() */
794 	if (rte->priv->contents_setting)
795 		return;
796 
797 	/* apply selected tag in toolbar if any */
798 	gtk_text_buffer_get_iter_at_offset (textbuffer, &start, rte->priv->insert_offset);
799 	end = *location;
800 	if (gtk_text_iter_backward_chars (&end, g_utf8_strlen (text, -1))) {
801 		gint i;
802 		for (i = 0; i < TEXT_TAG_LAST; i++) {
803 			GtkAction *action;
804 			if (! rte->priv->tags[i].action_name)
805 				continue;
806 			action = gtk_ui_manager_get_action (rte->priv->uimanager,
807 							    rte->priv->tags[i].action_name);
808 			if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
809 				gtk_text_buffer_apply_tag (rte->priv->textbuffer,
810 							   rte->priv->tags[i].tag, location, &end);
811 		}
812 	}
813 	rte->priv->insert_offset = -1;
814 
815 	gtk_text_iter_set_line_offset (&start, 0);
816 	/* add new bullet if already in list */
817 	if (*text == '\n') {
818 		gchar *text_to_insert = NULL;
819 		GtkTextTag *tag;
820 		gint index;
821 		tag = iter_begins_list (rte, &start, &index);
822 		if (tag)
823 			text_to_insert = lists_tokens [index];
824 
825 		if (text_to_insert) {
826 			if (! gtk_text_iter_forward_char (&end))
827 				gtk_text_buffer_get_end_iter (textbuffer, &end);
828 			gtk_text_buffer_insert (textbuffer, &end, text_to_insert, -1);
829 		}
830 	}
831 	else {
832 		GtkTextTag *tag = NULL;
833 		end = *location;
834 
835 		if (gtk_text_iter_begins_tag (&start, rte->priv->tags[TEXT_TAG_TITLE1].tag))
836 			tag = rte->priv->tags[TEXT_TAG_TITLE1].tag;
837 		else if (gtk_text_iter_begins_tag (&start, rte->priv->tags[TEXT_TAG_TITLE2].tag))
838 			tag = rte->priv->tags[TEXT_TAG_TITLE2].tag;
839 		if (tag)
840 			gtk_text_buffer_apply_tag (rte->priv->textbuffer, tag, &start, &end);
841 	}
842 }
843 
844 static void
show_markup_item_activate_cb(GtkCheckMenuItem * checkmenuitem,GdauiRtEditor * rte)845 show_markup_item_activate_cb (GtkCheckMenuItem *checkmenuitem, GdauiRtEditor *rte)
846 {
847 	gboolean show;
848 	show = gtk_check_menu_item_get_active (checkmenuitem);
849 	_gdaui_rt_editor_set_show_markup (rte, show);
850 }
851 
852 static void
bigger_font_item_activate_cb(G_GNUC_UNUSED GtkCheckMenuItem * checkmenuitem,GdauiRtEditor * rte)853 bigger_font_item_activate_cb (G_GNUC_UNUSED GtkCheckMenuItem *checkmenuitem,
854 			      GdauiRtEditor *rte)
855 {
856 	PangoContext *pcontext;
857 	PangoFontDescription *fd, *nfd;
858 	pcontext = gtk_widget_get_pango_context (GTK_WIDGET (rte->priv->textview));
859 	fd = pango_context_get_font_description (pcontext);
860 	nfd = pango_font_description_copy_static (fd);
861 	pango_font_description_set_size (nfd, pango_font_description_get_size (fd) * 1.2);
862 	gtk_widget_override_font (GTK_WIDGET (rte->priv->textview), nfd);
863 	pango_font_description_free (nfd);
864 }
865 
866 static void
smaller_font_item_activate_cb(G_GNUC_UNUSED GtkCheckMenuItem * checkmenuitem,GdauiRtEditor * rte)867 smaller_font_item_activate_cb (G_GNUC_UNUSED GtkCheckMenuItem *checkmenuitem,
868 			       GdauiRtEditor *rte)
869 {
870 	PangoContext *pcontext;
871 	PangoFontDescription *fd, *nfd;
872 	pcontext = gtk_widget_get_pango_context (GTK_WIDGET (rte->priv->textview));
873 	fd = pango_context_get_font_description (pcontext);
874 	nfd = pango_font_description_copy_static (fd);
875 	pango_font_description_set_size (nfd, pango_font_description_get_size (fd) / 1.2);
876 	gtk_widget_override_font (GTK_WIDGET (rte->priv->textview), nfd);
877 	pango_font_description_free (nfd);
878 }
879 
880 static void
reset_font_item_activate_cb(G_GNUC_UNUSED GtkCheckMenuItem * checkmenuitem,GdauiRtEditor * rte)881 reset_font_item_activate_cb (G_GNUC_UNUSED GtkCheckMenuItem *checkmenuitem,
882 			     GdauiRtEditor *rte)
883 {
884 	gtk_widget_override_font (GTK_WIDGET (rte->priv->textview), NULL);
885 }
886 
887 static void
populate_popup_cb(G_GNUC_UNUSED GtkTextView * entry,GtkMenu * menu,GdauiRtEditor * rte)888 populate_popup_cb (G_GNUC_UNUSED GtkTextView *entry, GtkMenu *menu, GdauiRtEditor *rte)
889 {
890 	GtkWidget *item;
891 
892 	item = gtk_separator_menu_item_new ();
893         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
894         gtk_widget_show (item);
895 
896 	item = gtk_menu_item_new_with_label (_("Reset font size"));
897         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
898         g_signal_connect (G_OBJECT (item), "activate",
899                           G_CALLBACK (reset_font_item_activate_cb), rte);
900         gtk_widget_show (item);
901 
902 	item = gtk_menu_item_new_with_label (_("Decrease font size (zoom out)"));
903         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
904         g_signal_connect (G_OBJECT (item), "activate",
905                           G_CALLBACK (smaller_font_item_activate_cb), rte);
906         gtk_widget_show (item);
907 
908 	item = gtk_menu_item_new_with_label (_("Increase font size (zoom in)"));
909         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
910         g_signal_connect (G_OBJECT (item), "activate",
911                           G_CALLBACK (bigger_font_item_activate_cb), rte);
912         gtk_widget_show (item);
913 
914 	item = gtk_check_menu_item_new_with_label (_("Show source markup"));
915         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
916 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), rte->priv->show_markup);
917         g_signal_connect (G_OBJECT (item), "toggled",
918                           G_CALLBACK (show_markup_item_activate_cb), rte);
919         gtk_widget_show (item);
920 }
921 
922 /* RTE markup analysis */
923 typedef enum {
924 	MARKUP_NONE,      /* 0 */
925 	MARKUP_BOLD,
926 	MARKUP_TT,
927 	MARKUP_VERBATIM,
928 	MARKUP_ITALIC,
929 	MARKUP_STRIKE,    /* 5 */
930 	MARKUP_UNDERLINE,
931 
932 	MARKUP_TITLE1_S,
933 	MARKUP_TITLE1_E,
934 	MARKUP_TITLE2_S,
935 	MARKUP_TITLE2_E,  /* 10 */
936 
937 	MARKUP_PICTURE_S,
938 	MARKUP_PICTURE_E,
939 
940 	MARKUP_LIST_S,
941 	MARKUP_LIST_E,
942 
943 	MARKUP_EOF
944 } MarkupTag;
945 
946 typedef struct {
947 	GtkTextMark *m_start;
948 	GtkTextMark *m_end;
949 	gboolean init;
950 	MarkupTag markup;
951 } TextTag;
952 
953 
954 static MarkupTag get_markup_token (GtkTextIter *iter, gint *out_nb_spaces_before, GtkTextIter *out_end,
955 				   TextTag *start_tag, GdauiRtEditor *rte);
956 static MarkupTag get_token (GtkTextIter *iter, gint *out_nb_spaces_before, GtkTextIter *out_end,
957 			    TextTag *start_tag, GdauiRtEditor *rte);
958 static gchar get_char_at_iter (GtkTextIter *iter, gboolean move_forward_first);
959 static gboolean markup_tag_match (MarkupTag tag1, gint tagline1, MarkupTag tag2, gint tagline2);
960 
961 static void
apply_markup(GdauiRtEditor * rte,GtkTextBuffer * textbuffer,TextTag * current,GtkTextMark * mark_start,GtkTextMark * mark_end)962 apply_markup (GdauiRtEditor *rte, GtkTextBuffer *textbuffer, TextTag *current, GtkTextMark *mark_start, GtkTextMark *mark_end)
963 {
964 	gint ssol;
965 	GtkTextIter start, end;
966 
967 	rte->priv->insert_offset = -1;
968 
969 	gtk_text_buffer_get_iter_at_mark (textbuffer, &start, mark_start);
970 	gtk_text_buffer_get_iter_at_mark (textbuffer, &end, mark_end);
971 
972 	/* apply markup */
973 	GtkTextIter astart;
974 	gtk_text_buffer_get_iter_at_mark (textbuffer, &astart, current->m_start);
975 	switch (current->markup) {
976 	case MARKUP_BOLD:
977 		gtk_text_buffer_apply_tag (textbuffer,
978 					   rte->priv->tags[TEXT_TAG_BOLD].tag,
979 					   &astart, &end);
980 		break;
981 	case MARKUP_VERBATIM:
982 		gtk_text_buffer_apply_tag (textbuffer,
983 					   rte->priv->tags[TEXT_TAG_VERBATIM].tag,
984 					   &astart, &end);
985 		break;
986 	case MARKUP_TT:
987 		gtk_text_buffer_apply_tag (textbuffer,
988 					   rte->priv->tags[TEXT_TAG_TT].tag,
989 					   &astart, &end);
990 		break;
991 	case MARKUP_ITALIC:
992 		gtk_text_buffer_apply_tag (textbuffer,
993 					   rte->priv->tags[TEXT_TAG_ITALIC].tag,
994 					   &astart, &end);
995 		break;
996 	case MARKUP_STRIKE:
997 		gtk_text_buffer_apply_tag (textbuffer,
998 					   rte->priv->tags[TEXT_TAG_STRIKE].tag,
999 					   &astart, &end);
1000 		break;
1001 	case MARKUP_UNDERLINE:
1002 		gtk_text_buffer_apply_tag (textbuffer,
1003 					   rte->priv->tags[TEXT_TAG_UNDERLINE].tag,
1004 					   &astart, &end);
1005 		break;
1006 	case MARKUP_TITLE1_S:
1007 		gtk_text_buffer_apply_tag (textbuffer,
1008 					   rte->priv->tags[TEXT_TAG_TITLE1].tag,
1009 					   &astart, &end);
1010 		break;
1011 	case MARKUP_TITLE2_S:
1012 		gtk_text_buffer_apply_tag (textbuffer,
1013 					   rte->priv->tags[TEXT_TAG_TITLE2].tag,
1014 					   &astart, &end);
1015 		break;
1016 	case MARKUP_LIST_S: {
1017 		GtkTextIter ps, pe;
1018 		gtk_text_buffer_get_iter_at_mark (textbuffer, &ps, current->m_start);
1019 		ssol = spaces_since_start_of_line (&ps);
1020 		if (ssol > 0) {
1021 			GtkTextIter diter;
1022 			diter = ps;
1023 			if (gtk_text_iter_backward_chars (&diter, ssol))
1024 				gtk_text_buffer_delete (textbuffer, &diter, &ps);
1025 		}
1026 
1027 		GtkTextTag *tag;
1028 		gint bindex = 0;
1029 		gtk_text_buffer_get_iter_at_mark (textbuffer, &ps, current->m_end);
1030 		if (ssol > 0) {
1031 			bindex = 1;
1032 			tag = rte->priv->tags[TEXT_TAG_LIST2].tag;
1033 		}
1034 		else
1035 			tag = rte->priv->tags[TEXT_TAG_LIST1].tag;
1036 
1037 		if (! bullet_pix[0]) {
1038 			bullet_pix[0] = gdk_pixbuf_new_from_inline (-1, bullet_pixdata,
1039 								    FALSE, NULL);
1040 			bullet_pix[1] = gdk_pixbuf_new_from_inline (-1, bulleth_pixdata,
1041 								    FALSE, NULL);
1042 		}
1043 		gtk_text_buffer_insert_pixbuf (textbuffer, &ps, bullet_pix[bindex]);
1044 		gtk_text_buffer_get_iter_at_mark (textbuffer, &ps, current->m_end);
1045 		pe = ps;
1046 		gtk_text_iter_forward_char (&pe);
1047 		gtk_text_buffer_apply_tag (rte->priv->textbuffer, tag, &ps, &pe);
1048 
1049 		/* remove all other tags */
1050 		gint i;
1051 		gtk_text_iter_set_line_index (&ps, 0);
1052 		gtk_text_iter_backward_char (&ps); /* to catch the previous line's '\n' */
1053 		for (i = 0; i < TEXT_TAG_LAST; i++) {
1054 			if (rte->priv->tags[i].tag == tag)
1055 				continue;
1056 			else
1057 				gtk_text_buffer_remove_tag (rte->priv->textbuffer,
1058 							    rte->priv->tags[i].tag, &ps, &pe);
1059 		}
1060 
1061 		break;
1062 	}
1063 	case MARKUP_PICTURE_S: {
1064 		gchar *data;
1065 		GtkTextIter ps, pe;
1066 		gsize length;
1067 		GdkPixdata pixdata;
1068 		gtk_text_buffer_get_iter_at_mark (textbuffer, &ps, current->m_end);
1069 		pe = start;
1070 		data = remove_newlines_from_base64 (gtk_text_buffer_get_text (textbuffer, &ps, &pe, FALSE));
1071 		/*g_print ("{{{%s}}}\n", data);*/
1072 		g_base64_decode_inplace (data, &length);
1073 		if (gdk_pixdata_deserialize (&pixdata, length, (guint8*) data, NULL)) {
1074 			GdkPixbuf *pixbuf;
1075 			pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, NULL);
1076 			if (pixbuf) {
1077 				gtk_text_buffer_delete (textbuffer, &ps, &pe);
1078 				gtk_text_buffer_get_iter_at_mark (textbuffer, &ps, mark_end);
1079 				gtk_text_buffer_insert_pixbuf (textbuffer, &ps, pixbuf);
1080 				g_object_unref (pixbuf);
1081 			}
1082 		}
1083 		g_free (data);
1084 		break;
1085 	}
1086 	default:
1087 		g_warning ("Unhandled marker (type %d)", current->markup);
1088 		break;
1089 	}
1090 	/* remove markup text */
1091 	gtk_text_buffer_get_iter_at_mark (textbuffer, &start, mark_start);
1092 	gtk_text_buffer_get_iter_at_mark (textbuffer, &end, mark_end);
1093 	if (! gtk_text_iter_equal (&start, &end))
1094 		gtk_text_buffer_delete (textbuffer, &start, &end);
1095 	gtk_text_buffer_get_iter_at_mark (textbuffer, &start, current->m_start);
1096 	gtk_text_buffer_get_iter_at_mark (textbuffer, &end, current->m_end);
1097 	if (! gtk_text_iter_equal (&start, &end))
1098 		gtk_text_buffer_delete (textbuffer, &start, &end);
1099 }
1100 
1101 static void
text_buffer_changed_cb(GtkTextBuffer * textbuffer,GdauiRtEditor * rte)1102 text_buffer_changed_cb (GtkTextBuffer *textbuffer, GdauiRtEditor *rte)
1103 {
1104 	GSList *queue = NULL;
1105 	MarkupTag mt;
1106 	TextTag *current = NULL;
1107 	GtkTextIter start, end;
1108 	gint ssol;
1109 
1110 	if (rte->priv->show_markup) {
1111 		if (rte->priv->enable_changed_signal)
1112 			g_signal_emit (rte, gdaui_rt_editor_signals[CHANGED], 0, NULL);
1113 		return;
1114 	}
1115 
1116 	gtk_text_buffer_get_start_iter (textbuffer, &start);
1117 
1118 	g_signal_handlers_block_by_func (textbuffer,
1119 					 G_CALLBACK (text_buffer_changed_cb), rte);
1120 
1121 	for (mt = get_token (&start, &ssol, &end, current, rte);
1122 	     mt != MARKUP_EOF;
1123 	     mt = get_token (&start, &ssol, &end, current, rte)) {
1124 		/*
1125 		gchar *text= gtk_text_iter_get_text (&start, &end);
1126 		g_print ("Token %d [%s] with SSOL %d\n", mt, text, ssol);
1127 		g_free (text);
1128 		*/
1129 		if (mt == MARKUP_NONE) {
1130 			start = end;
1131 			continue;
1132 		}
1133 		if (! current) {
1134 			current = g_new (TextTag, 1);
1135 			current->markup = mt;
1136 			current->m_start = gtk_text_buffer_create_mark (textbuffer, NULL, &start, TRUE);
1137 			current->m_end = gtk_text_buffer_create_mark (textbuffer, NULL, &end, TRUE);
1138 
1139 			queue = g_slist_prepend (queue, current);
1140 		}
1141 		else {
1142 			GtkTextIter liter;
1143 			gtk_text_buffer_get_iter_at_mark (textbuffer, &liter, current->m_start);
1144 			if (markup_tag_match (current->markup, gtk_text_iter_get_line (&liter),
1145 					      mt, gtk_text_iter_get_line (&start))) {
1146 				/* save iters as marks */
1147 				GtkTextMark *mark_start, *mark_end;
1148 				mark_start = gtk_text_buffer_create_mark (textbuffer, NULL, &start, TRUE);
1149 				mark_end = gtk_text_buffer_create_mark (textbuffer, NULL, &end, TRUE);
1150 
1151 				/* apply markup */
1152 				apply_markup (rte, textbuffer, current, mark_start, mark_end);
1153 
1154 				/* get rid of @current */
1155 				gtk_text_buffer_delete_mark (textbuffer, current->m_start);
1156 				gtk_text_buffer_delete_mark (textbuffer, current->m_end);
1157 				g_free (current);
1158 				queue = g_slist_remove (queue, current);
1159 				current = NULL;
1160 
1161 				if (queue) {
1162 					current = (TextTag*) queue->data;
1163 					gtk_text_buffer_get_iter_at_mark (textbuffer, &end, current->m_end);
1164 				}
1165 				else {
1166 					/* restore iter from marks */
1167 					gtk_text_buffer_get_iter_at_mark (textbuffer, &end, mark_end);
1168 				}
1169 
1170 				/* delete marks */
1171 				gtk_text_buffer_delete_mark (textbuffer, mark_end);
1172 				gtk_text_buffer_delete_mark (textbuffer, mark_start);
1173 			}
1174 			else {
1175 				current = g_new (TextTag, 1);
1176 				current->markup = mt;
1177 				current->m_start = gtk_text_buffer_create_mark (textbuffer, NULL, &start,
1178 										TRUE);
1179 				current->m_end = gtk_text_buffer_create_mark (textbuffer, NULL, &end, TRUE);
1180 
1181 				queue = g_slist_prepend (queue, current);
1182 			}
1183 		}
1184 
1185 		start = end;
1186 	}
1187 
1188 	while (queue) {
1189 		current = (TextTag*) queue->data;
1190 		gtk_text_buffer_delete_mark (textbuffer, current->m_start);
1191 		gtk_text_buffer_delete_mark (textbuffer, current->m_end);
1192 		g_free (current);
1193 		queue = g_slist_delete_link (queue, queue);
1194 	}
1195 
1196 	g_signal_handlers_unblock_by_func (textbuffer,
1197 					   G_CALLBACK (text_buffer_changed_cb), rte);
1198 
1199 	if (rte->priv->enable_changed_signal)
1200 		g_signal_emit (rte, gdaui_rt_editor_signals[CHANGED], 0, NULL);
1201 }
1202 
1203 /*
1204  * get_token
1205  *
1206  * returns the token type starting from @iter, and positions @out_end to the last used position
1207  * position.
1208  *
1209  * Returns: a #MarkupTag
1210  */
1211 static MarkupTag
get_token(GtkTextIter * iter,gint * out_nb_spaces_before,GtkTextIter * out_end,TextTag * start_tag,GdauiRtEditor * rte)1212 get_token (GtkTextIter *iter, gint *out_nb_spaces_before, GtkTextIter *out_end,
1213 	   TextTag *start_tag, GdauiRtEditor *rte)
1214 {
1215 	MarkupTag retval;
1216 	GtkTextIter inti;
1217 	inti = *iter;
1218 
1219 	retval = get_markup_token (&inti, out_nb_spaces_before, out_end, start_tag, rte);
1220 	if ((retval != MARKUP_NONE) || (retval == MARKUP_EOF))
1221 		return retval;
1222 
1223 	for (; gtk_text_iter_forward_char (&inti);) {
1224 		retval = get_markup_token (&inti, NULL, NULL, start_tag, rte);
1225 		if ((retval != MARKUP_NONE) || (retval == MARKUP_EOF))
1226 			break;
1227 	}
1228 	*out_end = inti;
1229 	return MARKUP_NONE;
1230 }
1231 
1232 /*
1233  * spaces_since_start_of_line
1234  * @iter: an iterator, __not modified__
1235  *
1236  * Computes the number of spaces since start of line in case there is no other
1237  * character on the line except for spaces
1238  *
1239  * Returns: number of spaces, or -1 if there is not only some spaces on the line before @iter
1240  */
1241 static gint
spaces_since_start_of_line(GtkTextIter * iter)1242 spaces_since_start_of_line (GtkTextIter *iter)
1243 {
1244 	gint i = 0;
1245 	GtkTextIter inti = *iter;
1246 	gunichar u;
1247 	for (; !gtk_text_iter_starts_line (&inti) && gtk_text_iter_backward_char (&inti); i++) {
1248 		u = gtk_text_iter_get_char (&inti);
1249 		if (! g_unichar_isspace (u))
1250 			return -1;
1251 	}
1252 
1253 #ifdef GDA_DEBUG_NO
1254 	gchar *data;
1255 	data = gtk_text_buffer_get_slice (gtk_text_iter_get_buffer (iter), &inti, iter, TRUE);
1256 	g_print ("FOUND %d spaces for [%s]\n", i, data);
1257 	g_free (data);
1258 #endif
1259 	return i;
1260 }
1261 
1262 /**
1263  * get_markup_token:
1264  * @iter: starting position
1265  * @out_nb_spaces_before: a place to set the value returned by spaces_since_start_of_line() if called
1266  * @out_end: (allow-none): place to put the last used position, or %NULL
1267  * @rte: the #GdauiRtEditor
1268  *
1269  * Parses marking tokens, nothing else
1270  *
1271  * Returns: a markup token, or MARKUP_NONE or MARKUP_EOF otherwise
1272  */
1273 static MarkupTag
get_markup_token(GtkTextIter * iter,gint * out_nb_spaces_before,GtkTextIter * out_end,TextTag * start_tag,GdauiRtEditor * rte)1274 get_markup_token (GtkTextIter *iter, gint *out_nb_spaces_before, GtkTextIter *out_end,
1275 		  TextTag *start_tag, GdauiRtEditor *rte)
1276 {
1277 	GtkTextIter inti;
1278 	gchar c;
1279 	gint ssol = -1; /* spaces since start of line */
1280 	MarkupTag start_markup = MARKUP_EOF;
1281 	gint linestart = 0;
1282 
1283 #define SET_OUT \
1284 	if (out_end) {							\
1285 		gtk_text_iter_forward_char (&inti);			\
1286 		*out_end = inti;					\
1287 	}								\
1288 	if (out_nb_spaces_before)					\
1289 		*out_nb_spaces_before = ssol
1290 
1291 	if (start_tag) {
1292 		start_markup = start_tag->markup;
1293 		gtk_text_buffer_get_iter_at_mark (gtk_text_iter_get_buffer (iter), &inti, start_tag->m_start);
1294 		linestart = gtk_text_iter_get_line (&inti);
1295 	}
1296 
1297 	inti = *iter;
1298 	if (out_end)
1299 		*out_end = inti;
1300 
1301 	c = get_char_at_iter (&inti, FALSE);
1302 
1303 	/* tests involving starting markup before anything else */
1304 	if (start_markup == MARKUP_PICTURE_S) {
1305 		if (c == ']') {
1306 			c = get_char_at_iter (&inti, TRUE);
1307 			if (c == ']') {
1308 				c = get_char_at_iter (&inti, TRUE);
1309 				if (c == ']') {
1310 					SET_OUT;
1311 					return MARKUP_PICTURE_E;
1312 				}
1313 			}
1314 		}
1315 		if (!c)
1316 			return MARKUP_EOF;
1317 		else
1318 			return MARKUP_NONE;
1319 	}
1320 	else if (start_markup == MARKUP_VERBATIM) {
1321 		if (c == '"') {
1322 			c = get_char_at_iter (&inti, TRUE);
1323 			if (c == '"') {
1324 				c = get_char_at_iter (&inti, TRUE);
1325 				if (c == '"') {
1326 					SET_OUT;
1327 					return MARKUP_VERBATIM;
1328 				}
1329 			}
1330 		}
1331 		if (!c)
1332 			return MARKUP_EOF;
1333 		else
1334 			return MARKUP_NONE;
1335 	}
1336 	else if (gtk_text_iter_has_tag (&inti, rte->priv->tags[TEXT_TAG_VERBATIM].tag)) {
1337 		if (!c)
1338 			return MARKUP_EOF;
1339 		else
1340 			return MARKUP_NONE;
1341 	}
1342 
1343 	if (gtk_text_iter_ends_line (&inti) && (start_markup == MARKUP_LIST_S) &&
1344 	    (linestart == gtk_text_iter_get_line (&inti)))
1345 		return MARKUP_LIST_E;
1346 
1347 	if (!c)
1348 		return MARKUP_EOF;
1349 
1350 	/* other tests */
1351 	ssol = spaces_since_start_of_line (&inti);
1352 	if (ssol >= 0) {
1353 		/* we are on a line with only spaces since its start */
1354 		if (c == '=') {
1355 			c = get_char_at_iter (&inti, TRUE);
1356 			if (c == ' ') {
1357 				SET_OUT;
1358 				return MARKUP_TITLE1_S;
1359 			}
1360 			else if (c == '=') {
1361 				c = get_char_at_iter (&inti, TRUE);
1362 				if (c == ' ') {
1363 					SET_OUT;
1364 					return MARKUP_TITLE2_S;
1365 				}
1366 			}
1367 		}
1368 		else if (c == '-') {
1369 			c = get_char_at_iter (&inti, TRUE);
1370 			if (c == ' ') {
1371 				SET_OUT;
1372 				return MARKUP_LIST_S;
1373 			}
1374 		}
1375 	}
1376 
1377 	if (c == '*') {
1378 		c = get_char_at_iter (&inti, TRUE);
1379 		if (c == '*') {
1380 			SET_OUT;
1381 			return MARKUP_BOLD;
1382 		}
1383 	}
1384 	else if (c == '/') {
1385 		c = get_char_at_iter (&inti, TRUE);
1386 		if (c == '/') {
1387 			GtkTextIter previ;
1388 			previ = inti;
1389 			if (gtk_text_iter_backward_char (&previ) &&
1390 			    gtk_text_iter_backward_char (&previ) &&
1391 			    (get_char_at_iter (&previ, FALSE) == ':')) {}
1392 			else {
1393 				SET_OUT;
1394 				return MARKUP_ITALIC;
1395 			}
1396 		}
1397 	}
1398 	else if (c == '_') {
1399 		c = get_char_at_iter (&inti, TRUE);
1400 		if (c == '_') {
1401 			SET_OUT;
1402 			return MARKUP_UNDERLINE;
1403 		}
1404 	}
1405 	else if (c == '-') {
1406 		c = get_char_at_iter (&inti, TRUE);
1407 		if (c == '-') {
1408 			SET_OUT;
1409 			return MARKUP_STRIKE;
1410 		}
1411 	}
1412 	else if (c == '`') {
1413 		c = get_char_at_iter (&inti, TRUE);
1414 		if (c == '`') {
1415 			SET_OUT;
1416 			return MARKUP_TT;
1417 		}
1418 	}
1419 	else if (c == '"') {
1420 		c = get_char_at_iter (&inti, TRUE);
1421 		if (c == '"') {
1422 			c = get_char_at_iter (&inti, TRUE);
1423 			if (c == '"') {
1424 				SET_OUT;
1425 				return MARKUP_VERBATIM;
1426 			}
1427 		}
1428 	}
1429 	else if (c == ' ') {
1430 		gtk_text_iter_starts_line (&inti);
1431 
1432 		c = get_char_at_iter (&inti, TRUE);
1433 		if (c == '=') {
1434 			if (start_markup == MARKUP_TITLE1_S) {
1435 				GtkTextIter it = inti;
1436 				gtk_text_iter_forward_char (&it);
1437 				if (gtk_text_iter_ends_line (&it) &&
1438 				    (linestart == gtk_text_iter_get_line (&inti))) {
1439 					SET_OUT;
1440 					return MARKUP_TITLE1_E;
1441 				}
1442 			}
1443 			else {
1444 				c = get_char_at_iter (&inti, TRUE);
1445 				if (c == '=') {
1446 					GtkTextIter it = inti;
1447 					gtk_text_iter_forward_char (&it);
1448 					if ((start_markup == MARKUP_TITLE2_S) && gtk_text_iter_ends_line (&it) &&
1449 					    (linestart == gtk_text_iter_get_line (&inti))) {
1450 						SET_OUT;
1451 						return MARKUP_TITLE2_E;
1452 					}
1453 				}
1454 			}
1455 		}
1456 	}
1457 	else if (c == '[') {
1458 		c = get_char_at_iter (&inti, TRUE);
1459 		if (c == '[') {
1460 			c = get_char_at_iter (&inti, TRUE);
1461 			if (c == '[') {
1462 				SET_OUT;
1463 				return MARKUP_PICTURE_S;
1464 			}
1465 		}
1466 	}
1467 	return MARKUP_NONE;
1468 }
1469 
1470 /**
1471  * get_char_at_iter:
1472  * @iter: an iter
1473  * @move_forward_first: %TRUE if @iter should be moved forward first
1474  *
1475  * Returns: 0 for EOF, 1 for the "unknown" unicode char (usually pixbufs) or for chars represented
1476  * by more than one byte
1477  */
1478 static gchar
get_char_at_iter(GtkTextIter * iter,gboolean move_forward_first)1479 get_char_at_iter (GtkTextIter *iter, gboolean move_forward_first)
1480 {
1481 	if (!move_forward_first ||
1482 	    (move_forward_first	&& gtk_text_iter_forward_char (iter))) {
1483 		gunichar uc;
1484 		gchar buf1[6];
1485 		uc = gtk_text_iter_get_char (iter);
1486 		if (!uc)
1487 			return 0;
1488 		if (g_unichar_to_utf8 (uc, buf1) == 1)
1489 			return *buf1;
1490 		return 1;
1491 	}
1492 	return 0;
1493 }
1494 
1495 static gboolean
markup_tag_match(MarkupTag tag1,gint tagline1,MarkupTag tag2,gint tagline2)1496 markup_tag_match (MarkupTag tag1, gint tagline1, MarkupTag tag2, gint tagline2)
1497 {
1498 	gboolean retval;
1499 	switch (tag1) {
1500 	case MARKUP_BOLD:
1501 	case MARKUP_TT:
1502 	case MARKUP_VERBATIM:
1503 	case MARKUP_ITALIC:
1504 	case MARKUP_STRIKE:
1505 	case MARKUP_UNDERLINE:
1506 		retval = (tag1 == tag2) ? TRUE : FALSE;
1507 		break;
1508 	case MARKUP_TITLE1_S:
1509 		retval = (tag2 == MARKUP_TITLE1_E) ? TRUE : FALSE;
1510 		break;
1511 	case MARKUP_TITLE2_S:
1512 		retval = (tag2 == MARKUP_TITLE2_E) ? TRUE : FALSE;
1513 		break;
1514 	case MARKUP_PICTURE_S:
1515 		retval = (tag2 == MARKUP_PICTURE_E) ? TRUE : FALSE;
1516 		break;
1517 	case MARKUP_LIST_S:
1518 		retval = (tag2 == MARKUP_LIST_E) ? TRUE : FALSE;
1519 		break;
1520 	default:
1521 		retval = FALSE;
1522 		break;
1523 	}
1524 
1525 	if (retval) {
1526 		if ((tag1 != MARKUP_PICTURE_S) && (tag1 != MARKUP_VERBATIM))
1527 			retval = (tagline1 == tagline2) ? TRUE : FALSE;
1528 	}
1529 	return retval;
1530 }
1531 
1532 static guint8 *serialize_as_txt2tag (GtkTextBuffer     *register_buffer,
1533 				     GtkTextBuffer     *content_buffer,
1534 				     const GtkTextIter *start,
1535 				     const GtkTextIter *end,
1536 				     gsize             *length,
1537 				     GdauiRtEditor     *editor);
1538 
1539 /**
1540  * gdaui_rt_editor_get_contents:
1541  * @editor: a #GdauiRtEditor
1542  *
1543  * Get the contents of @editor, using the markup syntax
1544  *
1545  * Returns: (transfer full): a new string, or %NULL if there was an error
1546  *
1547  * Since: 4.2.2
1548  */
1549 gchar *
gdaui_rt_editor_get_contents(GdauiRtEditor * editor)1550 gdaui_rt_editor_get_contents (GdauiRtEditor *editor)
1551 {
1552 	g_return_val_if_fail (GDAUI_IS_RT_EDITOR (editor), NULL);
1553 
1554 	if (editor->priv->saved_for_help)
1555 		return g_strdup (editor->priv->saved_for_help);
1556 	else
1557 		return real_gdaui_rt_editor_get_contents (editor);
1558 }
1559 
1560 static gchar *
real_gdaui_rt_editor_get_contents(GdauiRtEditor * editor)1561 real_gdaui_rt_editor_get_contents (GdauiRtEditor *editor)
1562 {
1563 	GtkTextIter start, end;
1564 
1565 	gtk_text_buffer_get_bounds (editor->priv->textbuffer, &start, &end);
1566 
1567 	if (editor->priv->show_markup)
1568 		return gtk_text_buffer_get_text (editor->priv->textbuffer, &start, &end, FALSE);
1569 	else {
1570 		GdkAtom format;
1571 		guint8 *data;
1572 		gsize length;
1573 		format = gtk_text_buffer_register_serialize_format (editor->priv->textbuffer, "txt/rte",
1574 								    (GtkTextBufferSerializeFunc) serialize_as_txt2tag,
1575 								    editor, NULL);
1576 		data = gtk_text_buffer_serialize (editor->priv->textbuffer, editor->priv->textbuffer, format,
1577 						  &start, &end, &length);
1578 		return (gchar*) data;
1579 	}
1580 }
1581 
1582 /*
1583  * Serialization as txt2tag
1584  */
1585 typedef struct
1586 {
1587 	GString *text_str;
1588 	GHashTable *tags;
1589 	GtkTextIter start, end;
1590 
1591 	gint tag_id;
1592 	GHashTable *tag_id_tags;
1593 } SerializationContext;
1594 
1595 /*
1596  * serialize_tag:
1597  * @tag: a #GtkTextTag
1598  * @starting: %TRUE if serialization has to be done for the opening part
1599  *
1600  * Returns: a static string, never %NULL
1601  */
1602 static const gchar *
serialize_tag(GtkTextTag * tag,gboolean starting,GdauiRtEditor * editor)1603 serialize_tag (GtkTextTag *tag, gboolean starting, GdauiRtEditor *editor)
1604 {
1605 	if (tag == editor->priv->tags[TEXT_TAG_ITALIC].tag)
1606 		return "//";
1607 	else if (tag == editor->priv->tags[TEXT_TAG_BOLD].tag)
1608 		return "**";
1609 	else if (tag == editor->priv->tags[TEXT_TAG_UNDERLINE].tag)
1610 		return "__";
1611 	else if (tag == editor->priv->tags[TEXT_TAG_STRIKE].tag)
1612 		return "--";
1613 	else if (tag == editor->priv->tags[TEXT_TAG_TT].tag)
1614 		return "``";
1615 	else if (tag == editor->priv->tags[TEXT_TAG_VERBATIM].tag)
1616 		return "\"\"\"";
1617 	else if (tag == editor->priv->tags[TEXT_TAG_TITLE1].tag) {
1618 		if (starting)
1619 			return "= ";
1620 		else
1621 			return " =";
1622 	}
1623 	else if (tag == editor->priv->tags[TEXT_TAG_TITLE2].tag) {
1624 		if (starting)
1625 			return "== ";
1626 		else
1627 			return " ==";
1628 	}
1629 	else if (tag == editor->priv->tags[TEXT_TAG_LIST1].tag) {
1630 		if (starting)
1631 			return lists_tokens [0];
1632 		else
1633 			return "";
1634 	}
1635 	else if (tag == editor->priv->tags[TEXT_TAG_LIST2].tag) {
1636 		if (starting)
1637 			return lists_tokens [1];
1638 		else
1639 			return "";
1640 	}
1641 	else {
1642 		gchar *tagname;
1643 		g_object_get ((GObject*) tag, "name", &tagname, NULL);
1644 		g_warning ("Unknown tag '%s'\n", tagname);
1645 		g_free (tagname);
1646 		return "";
1647 	}
1648 }
1649 
1650 /*
1651  * steals @base64
1652  */
1653 static gchar *
add_newlines_to_base64(gchar * base64)1654 add_newlines_to_base64 (gchar *base64)
1655 {
1656 	GString *string;
1657 	gint i;
1658 	gchar *ptr;
1659 	string = g_string_new ("");
1660 	for (i = 0, ptr = base64; *ptr; i++, ptr++) {
1661 		if (i && ! (i % 100))
1662 			g_string_append_c (string, '\n');
1663 		g_string_append_c (string, *ptr);
1664 	}
1665 	g_free (base64);
1666 	return g_string_free (string, FALSE);
1667 }
1668 
1669 /*
1670  * steals @base64
1671  */
1672 static gchar *
remove_newlines_from_base64(gchar * base64)1673 remove_newlines_from_base64 (gchar *base64)
1674 {
1675 	GString *string;
1676 	gchar *ptr;
1677 	string = g_string_new ("");
1678 	for (ptr = base64; *ptr; ptr++) {
1679 		if (*ptr != '\n')
1680 			g_string_append_c (string, *ptr);
1681 	}
1682 	g_free (base64);
1683 	return g_string_free (string, FALSE);
1684 }
1685 
1686 static void
serialize_text(G_GNUC_UNUSED GtkTextBuffer * buffer,SerializationContext * context,GdauiRtEditor * editor)1687 serialize_text (G_GNUC_UNUSED GtkTextBuffer *buffer, SerializationContext *context,
1688 		GdauiRtEditor *editor)
1689 {
1690 	GtkTextIter iter, old_iter;
1691 	GList *opened_tags = NULL; /* 1st element of the list is the last opened tag (ie. the one
1692 				    * which should be closed 1st */
1693 
1694 	/*g_string_append (context->text_str, "###");*/
1695 	iter = context->start;
1696 	do {
1697 		GSList *new_tag_list, *list;
1698 		GList *dlist;
1699 		gboolean tags_needs_reopened = TRUE;
1700 		new_tag_list = gtk_text_iter_get_tags (&iter);
1701 
1702 		/*
1703 		 * Close tags which need closing
1704 		 */
1705 		/* Find the last element in @opened_tags which is not opened anymore */
1706 		for (dlist = g_list_last (opened_tags); dlist; dlist = dlist->prev) {
1707 			if (! g_slist_find (new_tag_list, dlist->data))
1708 				break;
1709 		}
1710 
1711 		if (dlist) {
1712 			/* close all the tags up to dlist->data and at the same time remove them
1713 			 from @opened_tags */
1714 			for (; opened_tags != dlist;
1715 			     opened_tags = g_list_delete_link (opened_tags, opened_tags)) {
1716 				g_string_append (context->text_str,
1717 						 serialize_tag ((GtkTextTag*) opened_tags->data,
1718 								FALSE, editor));
1719 			}
1720 			g_string_append (context->text_str,
1721 					 serialize_tag ((GtkTextTag*) opened_tags->data,
1722 							FALSE, editor));
1723 			opened_tags = g_list_delete_link (opened_tags, opened_tags);
1724 		}
1725 
1726 		/* Now try to go to either the next tag toggle, or if a pixbuf appears */
1727 		old_iter = iter;
1728 		while (1) {
1729 			gunichar ch = gtk_text_iter_get_char (&iter);
1730 
1731 			if (ch == 0xFFFC) {
1732 				GdkPixbuf *pixbuf = gtk_text_iter_get_pixbuf (&iter);
1733 
1734 				if (pixbuf) {
1735 					/* Append the text before the pixbuf */
1736 					gchar *tmp_text;
1737 					tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
1738 					g_string_append (context->text_str, tmp_text);
1739 					g_free (tmp_text);
1740 
1741 					if ((pixbuf != bullet_pix[0]) &&
1742 					    (pixbuf != bullet_pix[1])) {
1743 						GdkPixdata pixdata;
1744 						guint8 *tmp;
1745 						guint len;
1746 						gchar *data;
1747 
1748 						gdk_pixdata_from_pixbuf (&pixdata, pixbuf, FALSE);
1749 						tmp = gdk_pixdata_serialize (&pixdata, &len);
1750 						data = add_newlines_to_base64 (g_base64_encode (tmp, len));
1751 						g_free (tmp);
1752 						g_string_append (context->text_str, "[[[");
1753 						g_string_append (context->text_str, data);
1754 						g_free (data);
1755 						g_string_append (context->text_str, "]]]");
1756 					}
1757 
1758 					/* Forward so we don't get the 0xfffc char */
1759 					gtk_text_iter_forward_char (&iter);
1760 					old_iter = iter;
1761 				}
1762 			}
1763 			else if (ch == 0) {
1764 				break;
1765 			}
1766 			else
1767 				gtk_text_iter_forward_char (&iter);
1768 
1769 			if (gtk_text_iter_toggles_tag (&iter, NULL)) {
1770 				/*g_print ("Toggle @pos %d:%d\n", gtk_text_iter_get_line (&iter),
1771 				  gtk_text_iter_get_line_offset (&iter));*/
1772 				break;
1773 			}
1774 		}
1775 
1776 		/* We might have moved too far */
1777 		if (gtk_text_iter_compare (&iter, &context->end) > 0)
1778 			iter = context->end;
1779 
1780 		/* Append the text, except if there is the TEXT_TAG_BULLET tag */
1781 		GtkTextTag *ltag;
1782 		ltag = iter_begins_list (editor, &old_iter, NULL);
1783 		if (! ltag) {
1784 			gint i;
1785 			gchar *tmp_text;
1786 			tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
1787 #ifdef NONO
1788 			g_string_append (context->text_str, tmp_text);
1789 #endif
1790 			for (i = 0; tmp_text[i]; i++) {
1791 				if (tmp_text[i] != '\n') {
1792 					if (tags_needs_reopened) {
1793 						/*
1794 						 * (re)open tags which are still there
1795 						 */
1796 						for (list = new_tag_list; list; list = list->next) {
1797 							if (! g_list_find (opened_tags, list->data)) {
1798 								opened_tags = g_list_prepend (opened_tags, list->data);
1799 								g_string_append (context->text_str,
1800 										 serialize_tag ((GtkTextTag*) opened_tags->data,
1801 												TRUE, editor));
1802 							}
1803 						}
1804 						tags_needs_reopened = FALSE;
1805 					}
1806 
1807 					g_string_append_c (context->text_str, tmp_text[i]);
1808 					continue;
1809 				}
1810 				/* close all tags and re-open them after the newline */
1811 				for (dlist = opened_tags; dlist; dlist = dlist->next) {
1812 					g_string_append (context->text_str,
1813 							 serialize_tag (GTK_TEXT_TAG (dlist->data),
1814 									FALSE, editor));
1815 				}
1816 				g_string_append_c (context->text_str, '\n');
1817 				/* re-open tags */
1818 				for (dlist = g_list_last (opened_tags); dlist; dlist = dlist->prev) {
1819 					g_string_append (context->text_str,
1820 							 serialize_tag (GTK_TEXT_TAG (dlist->data),
1821 									TRUE, editor));
1822 				}
1823 			}
1824 			g_free (tmp_text);
1825 		}
1826 
1827 		if (tags_needs_reopened) {
1828 			/*
1829 			 * (re)open tags which are still there
1830 			 */
1831 			for (list = new_tag_list; list; list = list->next) {
1832 				if (! g_list_find (opened_tags, list->data)) {
1833 					opened_tags = g_list_prepend (opened_tags, list->data);
1834 					g_string_append (context->text_str,
1835 							 serialize_tag ((GtkTextTag*) opened_tags->data,
1836 									TRUE, editor));
1837 				}
1838 			}
1839 			tags_needs_reopened = FALSE;
1840 		}
1841 
1842 
1843 		g_slist_free (new_tag_list);
1844 	}
1845 	while (!gtk_text_iter_equal (&iter, &context->end));
1846 
1847 	/* Close any open tags */
1848 	for (; opened_tags;
1849 	     opened_tags = g_list_delete_link (opened_tags, opened_tags)) {
1850 		g_string_append (context->text_str,
1851 				 serialize_tag ((GtkTextTag*) opened_tags->data,
1852 						FALSE, editor));
1853 	}
1854 
1855 	/*g_string_append (context->text_str, "###");*/
1856 }
1857 
1858 /*
1859  * serialize_as_txt2tag:
1860  */
1861 static guint8 *
serialize_as_txt2tag(G_GNUC_UNUSED GtkTextBuffer * register_buffer,GtkTextBuffer * content_buffer,const GtkTextIter * start,const GtkTextIter * end,gsize * length,GdauiRtEditor * editor)1862 serialize_as_txt2tag (G_GNUC_UNUSED  GtkTextBuffer *register_buffer,
1863 		      GtkTextBuffer     *content_buffer,
1864 		      const GtkTextIter *start,
1865 		      const GtkTextIter *end,
1866 		      gsize             *length,
1867 		      GdauiRtEditor     *editor)
1868 {
1869 	SerializationContext context;
1870 	GString *text;
1871 
1872 	context.tags = g_hash_table_new (NULL, NULL);
1873 	context.text_str = g_string_new (NULL);
1874 	context.start = *start;
1875 	context.end = *end;
1876 	context.tag_id = 0;
1877 	context.tag_id_tags = g_hash_table_new (NULL, NULL);
1878 
1879 	serialize_text (content_buffer, &context, editor);
1880 
1881 	text = g_string_new (NULL);
1882 
1883 	g_string_append_len (text, context.text_str->str, context.text_str->len);
1884 
1885 	g_hash_table_destroy (context.tags);
1886 	g_string_free (context.text_str, TRUE);
1887 	g_hash_table_destroy (context.tag_id_tags);
1888 
1889 	*length = text->len;
1890 
1891 	return (guint8 *) g_string_free (text, FALSE);
1892 }
1893 
1894 
1895 /**
1896  * gdaui_rt_editor_set_contents:
1897  * @editor: a #GdauiRtEditor
1898  * @markup: the text to set in @editor, using the markup syntax (must be valid UTF-8)
1899  * @length: length of text in bytes.
1900  *
1901  * Set @editor's contents. If @length is -1, @markup must be nul-terminated
1902  *
1903  * Since: 4.2.2
1904  */
1905 void
gdaui_rt_editor_set_contents(GdauiRtEditor * editor,const gchar * markup,gint length)1906 gdaui_rt_editor_set_contents (GdauiRtEditor *editor, const gchar *markup, gint length)
1907 {
1908 	g_return_if_fail (GDAUI_IS_RT_EDITOR (editor));
1909 
1910 	editor->priv->contents_setting = TRUE;
1911 	gtk_text_buffer_set_text (editor->priv->textbuffer, markup, length);
1912 	editor->priv->contents_setting = FALSE;
1913 }
1914 
1915 /**
1916  * gdaui_rt_editor_set_editable:
1917  * @editor: a #GdauiRtEditor
1918  * @editable: whether it's editable
1919  *
1920  * Set @editor's editability
1921  *
1922  * Since: 4.2.2
1923  */
1924 void
gdaui_rt_editor_set_editable(GdauiRtEditor * editor,gboolean editable)1925 gdaui_rt_editor_set_editable (GdauiRtEditor *editor, gboolean editable)
1926 {
1927 	g_return_if_fail (GDAUI_IS_RT_EDITOR (editor));
1928 	gtk_text_view_set_editable (editor->priv->textview, editable);
1929 	gtk_text_view_set_cursor_visible (editor->priv->textview, editable);
1930 	show_hide_toolbar (editor);
1931 }
1932 
1933 /*
1934  * _gdaui_rt_editor_set_show_markup
1935  * @editor: a #GdauiRtEditor
1936  * @show_markup: whether @editor shows markup of applies it
1937  *
1938  * If @show_markup is %FALSE, then @editor displays text with tags applied (bold text, ...); and if it's
1939  * %TRUE then it shows markup text instead
1940  *
1941  * Since: 4.2.2
1942  */
1943 static void
_gdaui_rt_editor_set_show_markup(GdauiRtEditor * editor,gboolean show_markup)1944 _gdaui_rt_editor_set_show_markup (GdauiRtEditor *editor, gboolean show_markup)
1945 {
1946 	gchar *data;
1947 	gint cursor_pos;
1948 	GtkTextIter iter;
1949 
1950 	g_return_if_fail (GDAUI_IS_RT_EDITOR (editor));
1951 	if (editor->priv->show_markup == show_markup)
1952 		return;
1953 
1954 	g_object_get (editor->priv->textbuffer, "cursor-position", &cursor_pos, NULL);
1955 	data = real_gdaui_rt_editor_get_contents (editor);
1956 	editor->priv->show_markup = show_markup;
1957 	gdaui_rt_editor_set_contents (editor, data, -1);
1958 	g_free (data);
1959 
1960 	gtk_text_buffer_get_iter_at_offset (editor->priv->textbuffer, &iter, cursor_pos);
1961 	gtk_text_buffer_place_cursor (editor->priv->textbuffer, &iter);
1962 
1963 	show_hide_toolbar (editor);
1964 }
1965 
1966 static void
show_hide_toolbar(GdauiRtEditor * editor)1967 show_hide_toolbar (GdauiRtEditor *editor)
1968 {
1969 	gboolean enable_markup = TRUE;
1970 	GtkAction *action;
1971 	gboolean doshow = FALSE;
1972 
1973 	if (gtk_text_view_get_editable (editor->priv->textview) &&
1974 	    gtk_widget_has_focus (GTK_WIDGET (editor->priv->textview)))
1975 		doshow = TRUE;
1976 
1977 	if (doshow) {
1978 		gtk_widget_show (editor->priv->toolbar);
1979 		if (editor->priv->sw && (editor->priv->vadj_value != 0.)) {
1980 			GtkAdjustment *adj;
1981 			adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (editor->priv->sw));
1982 			while (gtk_events_pending ())
1983 				gtk_main_iteration ();
1984 			gtk_adjustment_set_value (adj, editor->priv->vadj_value);
1985 		}
1986 	}
1987 	else {
1988 		if (editor->priv->sw) {
1989 			GtkAdjustment *adj;
1990 			adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (editor->priv->sw));
1991 			editor->priv->vadj_value = gtk_adjustment_get_value (adj);
1992 		}
1993 		gtk_widget_hide (editor->priv->toolbar);
1994 	}
1995 
1996 	if (editor->priv->show_markup ||
1997 	    ! gtk_text_view_get_editable (editor->priv->textview))
1998 		enable_markup = FALSE;
1999 
2000 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionBold");
2001 	gtk_action_set_sensitive (action, enable_markup);
2002 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionItalic");
2003 	gtk_action_set_sensitive (action, enable_markup);
2004 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionUnderline");
2005 	gtk_action_set_sensitive (action, enable_markup);
2006 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionStrike");
2007 	gtk_action_set_sensitive (action, enable_markup);
2008 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionAddImage");
2009 	gtk_action_set_sensitive (action, enable_markup);
2010 	action = gtk_ui_manager_get_action (editor->priv->uimanager, "/ToolBar/ActionReset");
2011 	gtk_action_set_sensitive (action, enable_markup);
2012 }
2013