1 /*
2 
3 Copyright (c) 2001-2007 Michael Terry
4 Copyright (c) 2011 Sergei Riaguzov
5 Copyright (c) 2013-2014 Arthur Borsboom
6 
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11 
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 
21 */
22 
23 #include "../config.h"
24 
25 #include <gtk/gtk.h>
26 #include <gtksourceview/gtksource.h>
27 
28 #include "xpad-text-buffer.h"
29 #include "xpad-pad.h"
30 #include "xpad-undo.h"
31 
32 struct XpadTextBufferPrivate
33 {
34 	XpadUndo *undo;
35 	XpadPad *pad;
36 };
37 
38 G_DEFINE_TYPE_WITH_PRIVATE (XpadTextBuffer, xpad_text_buffer, GTK_SOURCE_TYPE_BUFFER)
39 
40 /* Unicode chars in the Private Use Area. */
41 static gunichar TAG_CHAR = 0xe000;
42 
43 static GtkTextTagTable *create_tag_table (void);
44 
45 enum
46 {
47 	PROP_0,
48 	PROP_PAD,
49 	LAST_PROP
50 };
51 
52 static void xpad_text_buffer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
53 static void xpad_text_buffer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
54 static void xpad_text_buffer_dispose (GObject *object);
55 static void xpad_text_buffer_finalize (GObject *object);
56 
57 XpadTextBuffer *
xpad_text_buffer_new(XpadPad * pad)58 xpad_text_buffer_new (XpadPad *pad)
59 {
60 	return g_object_new (XPAD_TYPE_TEXT_BUFFER, "tag_table", create_tag_table(), "pad", pad, NULL);
61 }
62 
63 static void
xpad_text_buffer_class_init(XpadTextBufferClass * klass)64 xpad_text_buffer_class_init (XpadTextBufferClass *klass)
65 {
66 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
67 
68 	gobject_class->dispose = xpad_text_buffer_dispose;
69 	gobject_class->finalize = xpad_text_buffer_finalize;
70 	gobject_class->set_property = xpad_text_buffer_set_property;
71 	gobject_class->get_property = xpad_text_buffer_get_property;
72 
73 	g_object_class_install_property (gobject_class,
74 					 PROP_PAD,
75 					 g_param_spec_pointer ("pad",
76 							       "Pad",
77 							       "Pad connected to this buffer",
78 							       G_PARAM_READWRITE));
79 }
80 
81 static void
xpad_text_buffer_init(XpadTextBuffer * buffer)82 xpad_text_buffer_init (XpadTextBuffer *buffer)
83 {
84 	buffer->priv = xpad_text_buffer_get_instance_private (buffer);
85 
86 	buffer->priv->undo = xpad_undo_new (buffer);
87 }
88 
89 static void
xpad_text_buffer_dispose(GObject * object)90 xpad_text_buffer_dispose (GObject *object)
91 {
92 	XpadTextBuffer *buffer = XPAD_TEXT_BUFFER (object);
93 
94 	g_clear_object (&buffer->priv->pad);
95 	g_clear_object (&buffer->priv->undo);
96 
97 	G_OBJECT_CLASS (xpad_text_buffer_parent_class)->dispose (object);
98 }
99 
100 static void
xpad_text_buffer_finalize(GObject * object)101 xpad_text_buffer_finalize (GObject *object)
102 {
103 	G_OBJECT_CLASS (xpad_text_buffer_parent_class)->finalize (object);
104 }
105 
106 static void
xpad_text_buffer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)107 xpad_text_buffer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
108 {
109 	XpadTextBuffer *buffer = XPAD_TEXT_BUFFER (object);
110 
111 	switch (prop_id)
112 	{
113 	case PROP_PAD:
114 		buffer->priv->pad = g_value_get_pointer (value);
115 		g_object_ref (buffer->priv->pad);
116 		break;
117 
118 	default:
119 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
120 		break;
121 	}
122 }
123 
124 static void
xpad_text_buffer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)125 xpad_text_buffer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
126 {
127 	XpadTextBuffer *buffer = XPAD_TEXT_BUFFER (object);
128 
129 	switch (prop_id)
130 	{
131 	case PROP_PAD:
132 		g_value_set_pointer (value, buffer->priv->pad);
133 		break;
134 
135 	default:
136 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137 		break;
138 	}
139 }
140 
141 void
xpad_text_buffer_set_text_with_tags(XpadTextBuffer * buffer,const gchar * text)142 xpad_text_buffer_set_text_with_tags (XpadTextBuffer *buffer, const gchar *text)
143 {
144 	GtkTextIter start, end;
145 	GList *tags = NULL;
146 	gchar **tokens;
147 	gint count;
148 	gchar tag_char_utf8[7] = {0};
149 
150 	if (!text)
151 		return;
152 
153 	GtkSourceBuffer *buffer_tb = GTK_SOURCE_BUFFER (buffer);
154 
155 	gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer_tb));
156 
157 	gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer_tb), &start, &end);
158 	gtk_text_buffer_delete (GTK_TEXT_BUFFER (buffer_tb), &start, &end);
159 	gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer_tb), &start, &end);
160 
161 	g_unichar_to_utf8 (TAG_CHAR, tag_char_utf8);
162 
163 	tokens = g_strsplit (text, tag_char_utf8, 0);
164 
165 	for (count = 0; tokens[count]; count++)
166 	{
167 		if (count % 2 == 0)
168 		{
169 			gint offset;
170 			GList *j;
171 
172 			offset = gtk_text_iter_get_offset (&end);
173 			gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer_tb), &end, tokens[count], -1);
174 			gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer_tb), &start, offset);
175 
176 			for (j = tags; j; j = j->next)
177 			{
178 				gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (buffer_tb), j->data, &start, &end);
179 			}
180 		}
181 		else
182 		{
183 			if (tokens[count][0] != '/')
184 			{
185 				tags = g_list_prepend (tags, tokens[count]);
186 			}
187 			else
188 			{
189 				GList *element = g_list_find_custom (tags, &(tokens[count][1]), (GCompareFunc) g_ascii_strcasecmp);
190 
191 				if (element)
192 				{
193 					tags = g_list_delete_link (tags, element);
194 				}
195 			}
196 		}
197 	}
198 
199 	gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer_tb));
200 
201 	g_strfreev (tokens);
202 }
203 
204 
205 gchar *
xpad_text_buffer_get_text_with_tags(XpadTextBuffer * buffer)206 xpad_text_buffer_get_text_with_tags (XpadTextBuffer *buffer)
207 {
208 	GtkTextIter start, prev;
209 	GSList *tags = NULL, *i;
210 	gchar tag_char_utf8[7] = {0};
211 	gchar *text = g_strdup (""), *oldtext = NULL, *tmp;
212 	gboolean done = FALSE;
213 	GtkSourceBuffer *buffer_tb = GTK_SOURCE_BUFFER (buffer);
214 
215 	gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer_tb), &start);
216 
217 	g_unichar_to_utf8 (TAG_CHAR, tag_char_utf8);
218 
219 	prev = start;
220 
221 	while (!done)
222 	{
223 		tmp = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer_tb), &prev, &start, TRUE);
224 		oldtext = text;
225 		text = g_strconcat (text, tmp, NULL);
226 		g_free (oldtext);
227 		g_free (tmp);
228 
229 		tags = gtk_text_iter_get_toggled_tags (&start, TRUE);
230 		for (i = tags; i; i = i->next)
231 		{
232 			gchar *name;
233 			g_object_get (G_OBJECT (i->data), "name", &name, NULL);
234 
235 			if (name) {
236 				oldtext = text;
237 				text = g_strconcat (text, tag_char_utf8, name, tag_char_utf8, NULL);
238 				g_free (oldtext);
239 			}
240 
241 			g_free (name);
242 		}
243 		g_slist_free (tags);
244 
245 		tags = gtk_text_iter_get_toggled_tags (&start, FALSE);
246 		for (i = tags; i; i = i->next)
247 		{
248 			gchar *name;
249 			g_object_get (G_OBJECT (i->data), "name", &name, NULL);
250 
251 			if (name) {
252 				oldtext = text;
253 				text = g_strconcat (text, tag_char_utf8, "/", name, tag_char_utf8, NULL);
254 				g_free (oldtext);
255 			}
256 
257 			g_free (name);
258 		}
259 		g_slist_free (tags);
260 
261 		if (gtk_text_iter_is_end (&start))
262 			done = TRUE;
263 		prev = start;
264 		gtk_text_iter_forward_to_tag_toggle (&start, NULL);
265 	}
266 
267 	return text;
268 }
269 
270 void
xpad_text_buffer_insert_text(XpadTextBuffer * buffer,gint pos,const gchar * text,gint len)271 xpad_text_buffer_insert_text (XpadTextBuffer *buffer, gint pos, const gchar *text, gint len)
272 {
273 	GtkSourceBuffer *parent = (GtkSourceBuffer*) buffer;
274 	GtkTextIter iter;
275 	gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (parent), &iter, pos);
276 	gtk_text_buffer_insert (GTK_TEXT_BUFFER (parent), &iter, text, len);
277 	gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (parent), &iter);
278 }
279 
280 void
xpad_text_buffer_delete_range(XpadTextBuffer * buffer,gint start,gint end)281 xpad_text_buffer_delete_range (XpadTextBuffer *buffer, gint start, gint end)
282 {
283 	GtkSourceBuffer *parent = (GtkSourceBuffer*) buffer;
284 
285 	GtkTextIter start_iter;
286 	GtkTextIter end_iter;
287 
288 	gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (parent), &start_iter, start);
289 
290 	if (end < 0)
291 		gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (parent), &end_iter);
292 	else
293 		gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (parent), &end_iter, end);
294 
295 	gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (parent), &start_iter);
296 	gtk_text_buffer_delete (GTK_TEXT_BUFFER (parent), &start_iter, &end_iter);
297 }
298 
299 void
xpad_text_buffer_toggle_tag(XpadTextBuffer * buffer,const gchar * name)300 xpad_text_buffer_toggle_tag (XpadTextBuffer *buffer, const gchar *name)
301 {
302 	GtkTextTagTable *table;
303 	GtkTextTag *tag;
304 	GtkTextIter start, end, i;
305 	gboolean all_tagged;
306 	GtkSourceBuffer *buffer_tb = GTK_SOURCE_BUFFER (buffer);
307 
308 	table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer_tb));
309 	tag = gtk_text_tag_table_lookup (table, name);
310 	gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer_tb), &start, &end);
311 
312 	if (!tag)
313 	{
314 		g_printerr ("Tag not found in table %p\n", (void *) table);
315 		return;
316 	}
317 
318 	for (all_tagged = TRUE, i = start; !gtk_text_iter_equal (&i, &end); gtk_text_iter_forward_char (&i))
319 	{
320 		if (!gtk_text_iter_has_tag (&i, tag))
321 		{
322 			all_tagged = FALSE;
323 			break;
324 		}
325 	}
326 
327 	if (all_tagged)
328 	{
329 		gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer_tb), tag, &start, &end);
330 		xpad_undo_remove_tag (buffer->priv->undo, name, &start, &end);
331 	}
332 	else
333 	{
334 		gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer_tb), tag, &start, &end);
335 		xpad_undo_apply_tag (buffer->priv->undo, name, &start, &end);
336 	}
337 }
338 
339 static GtkTextTagTable *
create_tag_table(void)340 create_tag_table (void)
341 {
342 	GtkTextTagTable *table;
343 	GtkTextTag *tag;
344 
345 	table = gtk_text_tag_table_new ();
346 
347 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "bold", "weight", PANGO_WEIGHT_BOLD, NULL));
348 	gtk_text_tag_table_add (table, tag);
349 	g_clear_object (&tag);
350 
351 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "italic", "style", PANGO_STYLE_ITALIC, NULL));
352 	gtk_text_tag_table_add (table, tag);
353 	g_clear_object (&tag);
354 
355 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "strikethrough", "strikethrough", TRUE, NULL));
356 	gtk_text_tag_table_add (table, tag);
357 	g_clear_object (&tag);
358 
359 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "underline", "underline", PANGO_UNDERLINE_SINGLE, NULL));
360 	gtk_text_tag_table_add (table, tag);
361 	g_clear_object (&tag);
362 
363 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "small-xx", "scale", PANGO_SCALE_XX_SMALL, NULL));
364 	gtk_text_tag_table_add (table, tag);
365 	g_clear_object (&tag);
366 
367 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "small-x", "scale", PANGO_SCALE_X_SMALL, NULL));
368 	gtk_text_tag_table_add (table, tag);
369 	g_clear_object (&tag);
370 
371 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "small", "scale", PANGO_SCALE_SMALL, NULL));
372 	gtk_text_tag_table_add (table, tag);
373 	g_clear_object (&tag);
374 
375 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "medium", "scale", PANGO_SCALE_MEDIUM, NULL));
376 	gtk_text_tag_table_add (table, tag);
377 	g_clear_object (&tag);
378 
379 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "large", "scale", PANGO_SCALE_LARGE, NULL));
380 	gtk_text_tag_table_add (table, tag);
381 	g_clear_object (&tag);
382 
383 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "large-x", "scale", PANGO_SCALE_X_LARGE, NULL));
384 	gtk_text_tag_table_add (table, tag);
385 	g_clear_object (&tag);
386 
387 	tag = GTK_TEXT_TAG (g_object_new (GTK_TYPE_TEXT_TAG, "name", "large-xx", "scale", PANGO_SCALE_XX_LARGE, NULL));
388 	gtk_text_tag_table_add (table, tag);
389 	g_clear_object (&tag);
390 
391 	return table;
392 }
393 
394 gboolean
xpad_text_buffer_undo_available(XpadTextBuffer * buffer)395 xpad_text_buffer_undo_available (XpadTextBuffer *buffer)
396 {
397 	return xpad_undo_undo_available (buffer->priv->undo);
398 }
399 
400 gboolean
xpad_text_buffer_redo_available(XpadTextBuffer * buffer)401 xpad_text_buffer_redo_available (XpadTextBuffer *buffer)
402 {
403 	return xpad_undo_redo_available (buffer->priv->undo);
404 }
405 
406 void
xpad_text_buffer_undo(XpadTextBuffer * buffer)407 xpad_text_buffer_undo (XpadTextBuffer *buffer)
408 {
409 	xpad_undo_exec_undo (buffer->priv->undo);
410 }
411 
412 void
xpad_text_buffer_redo(XpadTextBuffer * buffer)413 xpad_text_buffer_redo (XpadTextBuffer *buffer)
414 {
415 	xpad_undo_exec_redo (buffer->priv->undo);
416 }
417 
xpad_text_buffer_freeze_undo(XpadTextBuffer * buffer)418 void xpad_text_buffer_freeze_undo (XpadTextBuffer *buffer)
419 {
420 	xpad_undo_freeze (buffer->priv->undo);
421 }
422 
xpad_text_buffer_thaw_undo(XpadTextBuffer * buffer)423 void xpad_text_buffer_thaw_undo (XpadTextBuffer *buffer)
424 {
425 	xpad_undo_thaw (buffer->priv->undo);
426 }
427 
xpad_text_buffer_get_pad(XpadTextBuffer * buffer)428 XpadPad *xpad_text_buffer_get_pad (XpadTextBuffer *buffer)
429 {
430 	if (buffer == NULL)
431 		return NULL;
432 
433 	XpadPad *pad = NULL;
434 	g_object_get (G_OBJECT (buffer), "pad", &pad, NULL);
435 	return pad;
436 }
437 
xpad_text_buffer_set_pad(XpadTextBuffer * buffer,XpadPad * pad)438 void xpad_text_buffer_set_pad (XpadTextBuffer *buffer, XpadPad *pad)
439 {
440 	g_return_if_fail (buffer);
441 
442 	g_object_set (G_OBJECT (buffer), "pad", pad, NULL);
443 }
444