1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 */
19
20 /*
21 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
25 */
26
27 #include "config.h"
28 #include "gtktexttagtable.h"
29 #include "gtkbuildable.h"
30 #include "gtkmarshalers.h"
31 #include "gtktextbuffer.h" /* just for the lame notify_will_remove_tag hack */
32 #include "gtkintl.h"
33 #include "gtkalias.h"
34
35 #include <stdlib.h>
36
37 /**
38 * SECTION:gtktexttagtable
39 * @Short_description: Collection of tags that can be used together
40 * @Title: GtkTextTagTable
41 *
42 * You may wish to begin by reading the <link linkend="TextWidget">text widget
43 * conceptual overview</link> which gives an overview of all the objects and data
44 * types related to the text widget and how they work together.
45 *
46 * <refsect2 id="GtkTextTagTable-BUILDER-UI">
47 * <title>GtkTextTagTables as GtkBuildable</title>
48 * <para>
49 * The GtkTextTagTable implementation of the GtkBuildable interface
50 * supports adding tags by specifying "tag" as the "type"
51 * attribute of a <child> element.
52 * </para>
53 * <example>
54 * <title>A UI definition fragment specifying tags</title>
55 * <programlisting><![CDATA[
56 * <object class="GtkTextTagTable">
57 * <child type="tag">
58 * <object class="GtkTextTag"/>
59 * </child>
60 * </object>
61 * ]]></programlisting>
62 * </example>
63 * </refsect2>
64 */
65
66 enum {
67 TAG_CHANGED,
68 TAG_ADDED,
69 TAG_REMOVED,
70 LAST_SIGNAL
71 };
72
73 enum {
74 LAST_ARG
75 };
76
77 static void gtk_text_tag_table_finalize (GObject *object);
78 static void gtk_text_tag_table_set_property (GObject *object,
79 guint prop_id,
80 const GValue *value,
81 GParamSpec *pspec);
82 static void gtk_text_tag_table_get_property (GObject *object,
83 guint prop_id,
84 GValue *value,
85 GParamSpec *pspec);
86
87 static void gtk_text_tag_table_buildable_interface_init (GtkBuildableIface *iface);
88 static void gtk_text_tag_table_buildable_add_child (GtkBuildable *buildable,
89 GtkBuilder *builder,
90 GObject *child,
91 const gchar *type);
92
93 static guint signals[LAST_SIGNAL] = { 0 };
94
G_DEFINE_TYPE_WITH_CODE(GtkTextTagTable,gtk_text_tag_table,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_text_tag_table_buildable_interface_init))95 G_DEFINE_TYPE_WITH_CODE (GtkTextTagTable, gtk_text_tag_table, G_TYPE_OBJECT,
96 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
97 gtk_text_tag_table_buildable_interface_init))
98
99 static void
100 gtk_text_tag_table_class_init (GtkTextTagTableClass *klass)
101 {
102 GObjectClass *object_class = G_OBJECT_CLASS (klass);
103
104 object_class->set_property = gtk_text_tag_table_set_property;
105 object_class->get_property = gtk_text_tag_table_get_property;
106
107 object_class->finalize = gtk_text_tag_table_finalize;
108
109 signals[TAG_CHANGED] =
110 g_signal_new (I_("tag-changed"),
111 G_OBJECT_CLASS_TYPE (object_class),
112 G_SIGNAL_RUN_LAST,
113 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_changed),
114 NULL, NULL,
115 _gtk_marshal_VOID__OBJECT_BOOLEAN,
116 G_TYPE_NONE,
117 2,
118 GTK_TYPE_TEXT_TAG,
119 G_TYPE_BOOLEAN);
120
121 signals[TAG_ADDED] =
122 g_signal_new (I_("tag-added"),
123 G_OBJECT_CLASS_TYPE (object_class),
124 G_SIGNAL_RUN_LAST,
125 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_added),
126 NULL, NULL,
127 _gtk_marshal_VOID__OBJECT,
128 G_TYPE_NONE,
129 1,
130 GTK_TYPE_TEXT_TAG);
131
132 signals[TAG_REMOVED] =
133 g_signal_new (I_("tag-removed"),
134 G_OBJECT_CLASS_TYPE (object_class),
135 G_SIGNAL_RUN_LAST,
136 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_removed),
137 NULL, NULL,
138 _gtk_marshal_VOID__OBJECT,
139 G_TYPE_NONE,
140 1,
141 GTK_TYPE_TEXT_TAG);
142 }
143
144 static void
gtk_text_tag_table_init(GtkTextTagTable * table)145 gtk_text_tag_table_init (GtkTextTagTable *table)
146 {
147 table->hash = g_hash_table_new (g_str_hash, g_str_equal);
148 }
149
150 /**
151 * gtk_text_tag_table_new:
152 *
153 * Creates a new #GtkTextTagTable. The table contains no tags by
154 * default.
155 *
156 * Return value: a new #GtkTextTagTable
157 **/
158 GtkTextTagTable*
gtk_text_tag_table_new(void)159 gtk_text_tag_table_new (void)
160 {
161 GtkTextTagTable *table;
162
163 table = g_object_new (GTK_TYPE_TEXT_TAG_TABLE, NULL);
164
165 return table;
166 }
167
168 static void
foreach_unref(GtkTextTag * tag,gpointer data)169 foreach_unref (GtkTextTag *tag, gpointer data)
170 {
171 GSList *tmp;
172
173 /* We don't want to emit the remove signal here; so we just unparent
174 * and unref the tag.
175 */
176
177 tmp = tag->table->buffers;
178 while (tmp != NULL)
179 {
180 _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (tmp->data),
181 tag);
182
183 tmp = tmp->next;
184 }
185
186 tag->table = NULL;
187 g_object_unref (tag);
188 }
189
190 static void
gtk_text_tag_table_finalize(GObject * object)191 gtk_text_tag_table_finalize (GObject *object)
192 {
193 GtkTextTagTable *table;
194
195 table = GTK_TEXT_TAG_TABLE (object);
196
197 gtk_text_tag_table_foreach (table, foreach_unref, NULL);
198
199 g_hash_table_destroy (table->hash);
200 g_slist_free (table->anonymous);
201
202 g_slist_free (table->buffers);
203
204 G_OBJECT_CLASS (gtk_text_tag_table_parent_class)->finalize (object);
205 }
206 static void
gtk_text_tag_table_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)207 gtk_text_tag_table_set_property (GObject *object,
208 guint prop_id,
209 const GValue *value,
210 GParamSpec *pspec)
211 {
212 switch (prop_id)
213 {
214
215 default:
216 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
217 break;
218 }
219 }
220
221
222 static void
gtk_text_tag_table_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)223 gtk_text_tag_table_get_property (GObject *object,
224 guint prop_id,
225 GValue *value,
226 GParamSpec *pspec)
227 {
228 switch (prop_id)
229 {
230
231 default:
232 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
233 break;
234 }
235 }
236
237 static void
gtk_text_tag_table_buildable_interface_init(GtkBuildableIface * iface)238 gtk_text_tag_table_buildable_interface_init (GtkBuildableIface *iface)
239 {
240 iface->add_child = gtk_text_tag_table_buildable_add_child;
241 }
242
243 static void
gtk_text_tag_table_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * type)244 gtk_text_tag_table_buildable_add_child (GtkBuildable *buildable,
245 GtkBuilder *builder,
246 GObject *child,
247 const gchar *type)
248 {
249 if (type && strcmp (type, "tag") == 0)
250 gtk_text_tag_table_add (GTK_TEXT_TAG_TABLE (buildable),
251 GTK_TEXT_TAG (child));
252 }
253
254 /**
255 * gtk_text_tag_table_add:
256 * @table: a #GtkTextTagTable
257 * @tag: a #GtkTextTag
258 *
259 * Add a tag to the table. The tag is assigned the highest priority
260 * in the table.
261 *
262 * @tag must not be in a tag table already, and may not have
263 * the same name as an already-added tag.
264 **/
265 void
gtk_text_tag_table_add(GtkTextTagTable * table,GtkTextTag * tag)266 gtk_text_tag_table_add (GtkTextTagTable *table,
267 GtkTextTag *tag)
268 {
269 guint size;
270
271 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
272 g_return_if_fail (GTK_IS_TEXT_TAG (tag));
273 g_return_if_fail (tag->table == NULL);
274
275 if (tag->name && g_hash_table_lookup (table->hash, tag->name))
276 {
277 g_warning ("A tag named '%s' is already in the tag table.",
278 tag->name);
279 return;
280 }
281
282 g_object_ref (tag);
283
284 if (tag->name)
285 g_hash_table_insert (table->hash, tag->name, tag);
286 else
287 {
288 table->anonymous = g_slist_prepend (table->anonymous, tag);
289 table->anon_count += 1;
290 }
291
292 tag->table = table;
293
294 /* We get the highest tag priority, as the most-recently-added
295 tag. Note that we do NOT use gtk_text_tag_set_priority,
296 as it assumes the tag is already in the table. */
297 size = gtk_text_tag_table_get_size (table);
298 g_assert (size > 0);
299 tag->priority = size - 1;
300
301 g_signal_emit (table, signals[TAG_ADDED], 0, tag);
302 }
303
304 /**
305 * gtk_text_tag_table_lookup:
306 * @table: a #GtkTextTagTable
307 * @name: name of a tag
308 *
309 * Look up a named tag.
310 *
311 * Return value: (transfer none): The tag, or %NULL if none by that name is in the table.
312 **/
313 GtkTextTag*
gtk_text_tag_table_lookup(GtkTextTagTable * table,const gchar * name)314 gtk_text_tag_table_lookup (GtkTextTagTable *table,
315 const gchar *name)
316 {
317 g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), NULL);
318 g_return_val_if_fail (name != NULL, NULL);
319
320 return g_hash_table_lookup (table->hash, name);
321 }
322
323 /**
324 * gtk_text_tag_table_remove:
325 * @table: a #GtkTextTagTable
326 * @tag: a #GtkTextTag
327 *
328 * Remove a tag from the table. This will remove the table's
329 * reference to the tag, so be careful - the tag will end
330 * up destroyed if you don't have a reference to it.
331 **/
332 void
gtk_text_tag_table_remove(GtkTextTagTable * table,GtkTextTag * tag)333 gtk_text_tag_table_remove (GtkTextTagTable *table,
334 GtkTextTag *tag)
335 {
336 GSList *tmp;
337
338 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
339 g_return_if_fail (GTK_IS_TEXT_TAG (tag));
340 g_return_if_fail (tag->table == table);
341
342 /* Our little bad hack to be sure buffers don't still have the tag
343 * applied to text in the buffer
344 */
345 tmp = table->buffers;
346 while (tmp != NULL)
347 {
348 _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (tmp->data),
349 tag);
350
351 tmp = tmp->next;
352 }
353
354 /* Set ourselves to the highest priority; this means
355 when we're removed, there won't be any gaps in the
356 priorities of the tags in the table. */
357 gtk_text_tag_set_priority (tag, gtk_text_tag_table_get_size (table) - 1);
358
359 tag->table = NULL;
360
361 if (tag->name)
362 g_hash_table_remove (table->hash, tag->name);
363 else
364 {
365 table->anonymous = g_slist_remove (table->anonymous, tag);
366 table->anon_count -= 1;
367 }
368
369 g_signal_emit (table, signals[TAG_REMOVED], 0, tag);
370
371 g_object_unref (tag);
372 }
373
374 struct ForeachData
375 {
376 GtkTextTagTableForeach func;
377 gpointer data;
378 };
379
380 static void
hash_foreach(gpointer key,gpointer value,gpointer data)381 hash_foreach (gpointer key, gpointer value, gpointer data)
382 {
383 struct ForeachData *fd = data;
384
385 g_return_if_fail (GTK_IS_TEXT_TAG (value));
386
387 (* fd->func) (value, fd->data);
388 }
389
390 static void
list_foreach(gpointer data,gpointer user_data)391 list_foreach (gpointer data, gpointer user_data)
392 {
393 struct ForeachData *fd = user_data;
394
395 g_return_if_fail (GTK_IS_TEXT_TAG (data));
396
397 (* fd->func) (data, fd->data);
398 }
399
400 /**
401 * gtk_text_tag_table_foreach:
402 * @table: a #GtkTextTagTable
403 * @func: (scope call): a function to call on each tag
404 * @data: user data
405 *
406 * Calls @func on each tag in @table, with user data @data.
407 * Note that the table may not be modified while iterating
408 * over it (you can't add/remove tags).
409 **/
410 void
gtk_text_tag_table_foreach(GtkTextTagTable * table,GtkTextTagTableForeach func,gpointer data)411 gtk_text_tag_table_foreach (GtkTextTagTable *table,
412 GtkTextTagTableForeach func,
413 gpointer data)
414 {
415 struct ForeachData d;
416
417 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
418 g_return_if_fail (func != NULL);
419
420 d.func = func;
421 d.data = data;
422
423 g_hash_table_foreach (table->hash, hash_foreach, &d);
424 g_slist_foreach (table->anonymous, list_foreach, &d);
425 }
426
427 /**
428 * gtk_text_tag_table_get_size:
429 * @table: a #GtkTextTagTable
430 *
431 * Returns the size of the table (number of tags)
432 *
433 * Return value: number of tags in @table
434 **/
435 gint
gtk_text_tag_table_get_size(GtkTextTagTable * table)436 gtk_text_tag_table_get_size (GtkTextTagTable *table)
437 {
438 g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), 0);
439
440 return g_hash_table_size (table->hash) + table->anon_count;
441 }
442
443 void
_gtk_text_tag_table_add_buffer(GtkTextTagTable * table,gpointer buffer)444 _gtk_text_tag_table_add_buffer (GtkTextTagTable *table,
445 gpointer buffer)
446 {
447 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
448
449 table->buffers = g_slist_prepend (table->buffers, buffer);
450 }
451
452 static void
foreach_remove_tag(GtkTextTag * tag,gpointer data)453 foreach_remove_tag (GtkTextTag *tag, gpointer data)
454 {
455 GtkTextBuffer *buffer;
456
457 buffer = GTK_TEXT_BUFFER (data);
458
459 _gtk_text_buffer_notify_will_remove_tag (buffer, tag);
460 }
461
462 void
_gtk_text_tag_table_remove_buffer(GtkTextTagTable * table,gpointer buffer)463 _gtk_text_tag_table_remove_buffer (GtkTextTagTable *table,
464 gpointer buffer)
465 {
466 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
467
468 gtk_text_tag_table_foreach (table, foreach_remove_tag, buffer);
469
470 table->buffers = g_slist_remove (table->buffers, buffer);
471 }
472
473 #define __GTK_TEXT_TAG_TABLE_C__
474 #include "gtkaliasdef.c"
475