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, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtktexttagtable.h"
28#include "gtktexttagtableprivate.h"
29
30#include "gtkbuildable.h"
31#include "gtktexttagprivate.h"
32#include "gtkmarshalers.h"
33#include "gtktextbufferprivate.h" /* just for the lame notify_will_remove_tag hack */
34#include "gtkintl.h"
35
36#include <stdlib.h>
37
38
39/**
40 * GtkTextTagTable:
41 *
42 * The collection of tags in a `GtkTextBuffer`
43 *
44 * You may wish to begin by reading the
45 * [text widget conceptual overview](section-text-widget.html),
46 * which gives an overview of all the objects and data types
47 * related to the text widget and how they work together.
48 *
49 * # GtkTextTagTables as GtkBuildable
50 *
51 * The `GtkTextTagTable` implementation of the `GtkBuildable` interface
52 * supports adding tags by specifying “tag” as the “type” attribute
53 * of a <child> element.
54 *
55 * An example of a UI definition fragment specifying tags:
56 * ```xml
57 * <object class="GtkTextTagTable">
58 * <child type="tag">
59 * <object class="GtkTextTag"/>
60 * </child>
61 * </object>
62 * ```
63 */
64
65typedef struct _GtkTextTagTablePrivate GtkTextTagTablePrivate;
66typedef struct _GtkTextTagTableClass GtkTextTagTableClass;
67
68struct _GtkTextTagTable
69{
70 GObject parent_instance;
71
72 GtkTextTagTablePrivate *priv;
73};
74
75struct _GtkTextTagTableClass
76{
77 GObjectClass parent_class;
78
79 void (* tag_changed) (GtkTextTagTable *table, GtkTextTag *tag, gboolean size_changed);
80 void (* tag_added) (GtkTextTagTable *table, GtkTextTag *tag);
81 void (* tag_removed) (GtkTextTagTable *table, GtkTextTag *tag);
82};
83
84struct _GtkTextTagTablePrivate
85{
86 GHashTable *hash;
87 GSList *anonymous;
88 GSList *buffers;
89
90 int anon_count;
91
92 guint seen_invisible : 1;
93};
94
95enum {
96 TAG_CHANGED,
97 TAG_ADDED,
98 TAG_REMOVED,
99 LAST_SIGNAL
100};
101
102static void gtk_text_tag_table_finalize (GObject *object);
103
104static void gtk_text_tag_table_buildable_interface_init (GtkBuildableIface *iface);
105static void gtk_text_tag_table_buildable_add_child (GtkBuildable *buildable,
106 GtkBuilder *builder,
107 GObject *child,
108 const char *type);
109
110static guint signals[LAST_SIGNAL] = { 0 };
111
112G_DEFINE_TYPE_WITH_CODE (GtkTextTagTable, gtk_text_tag_table, G_TYPE_OBJECT,
113 G_ADD_PRIVATE (GtkTextTagTable)
114 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
115 gtk_text_tag_table_buildable_interface_init))
116
117static void
118gtk_text_tag_table_class_init (GtkTextTagTableClass *klass)
119{
120 GObjectClass *object_class = G_OBJECT_CLASS (klass);
121
122 object_class->finalize = gtk_text_tag_table_finalize;
123
124 /**
125 * GtkTextTagTable::tag-changed:
126 * @texttagtable: the object which received the signal.
127 * @tag: the changed tag.
128 * @size_changed: whether the change affects the `GtkTextView` layout.
129 *
130 * Emitted every time a tag in the `GtkTextTagTable` changes.
131 */
132 signals[TAG_CHANGED] =
133 g_signal_new (I_("tag-changed"),
134 G_OBJECT_CLASS_TYPE (object_class),
135 signal_flags: G_SIGNAL_RUN_LAST,
136 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_changed),
137 NULL, NULL,
138 c_marshaller: _gtk_marshal_VOID__OBJECT_BOOLEAN,
139 G_TYPE_NONE,
140 n_params: 2,
141 GTK_TYPE_TEXT_TAG,
142 G_TYPE_BOOLEAN);
143 g_signal_set_va_marshaller (signal_id: signals[TAG_CHANGED],
144 G_OBJECT_CLASS_TYPE (object_class),
145 va_marshaller: _gtk_marshal_VOID__OBJECT_BOOLEANv);
146
147 /**
148 * GtkTextTagTable::tag-added:
149 * @texttagtable: the object which received the signal.
150 * @tag: the added tag.
151 *
152 * Emitted every time a new tag is added in the `GtkTextTagTable`.
153 */
154 signals[TAG_ADDED] =
155 g_signal_new (I_("tag-added"),
156 G_OBJECT_CLASS_TYPE (object_class),
157 signal_flags: G_SIGNAL_RUN_LAST,
158 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_added),
159 NULL, NULL,
160 NULL,
161 G_TYPE_NONE,
162 n_params: 1,
163 GTK_TYPE_TEXT_TAG);
164
165 /**
166 * GtkTextTagTable::tag-removed:
167 * @texttagtable: the object which received the signal.
168 * @tag: the removed tag.
169 *
170 * Emitted every time a tag is removed from the `GtkTextTagTable`.
171 *
172 * The @tag is still valid by the time the signal is emitted, but
173 * it is not associated with a tag table any more.
174 */
175 signals[TAG_REMOVED] =
176 g_signal_new (I_("tag-removed"),
177 G_OBJECT_CLASS_TYPE (object_class),
178 signal_flags: G_SIGNAL_RUN_LAST,
179 G_STRUCT_OFFSET (GtkTextTagTableClass, tag_removed),
180 NULL, NULL,
181 NULL,
182 G_TYPE_NONE,
183 n_params: 1,
184 GTK_TYPE_TEXT_TAG);
185}
186
187static void
188gtk_text_tag_table_init (GtkTextTagTable *table)
189{
190 table->priv = gtk_text_tag_table_get_instance_private (self: table);
191 table->priv->hash = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
192}
193
194static void
195check_visible (GtkTextTagTable *table,
196 GtkTextTag *tag)
197{
198 if (table->priv->seen_invisible)
199 return;
200
201 if (tag->priv->invisible_set)
202 {
203 gboolean invisible;
204
205 g_object_get (object: tag, first_property_name: "invisible", &invisible, NULL);
206 table->priv->seen_invisible = invisible;
207 }
208}
209
210/**
211 * gtk_text_tag_table_new:
212 *
213 * Creates a new `GtkTextTagTable`.
214 *
215 * The table contains no tags by default.
216 *
217 * Returns: a new `GtkTextTagTable`
218 */
219GtkTextTagTable*
220gtk_text_tag_table_new (void)
221{
222 GtkTextTagTable *table;
223
224 table = g_object_new (GTK_TYPE_TEXT_TAG_TABLE, NULL);
225
226 return table;
227}
228
229static void
230foreach_unref (GtkTextTag *tag, gpointer data)
231{
232 GtkTextTagTable *table = GTK_TEXT_TAG_TABLE (tag->priv->table);
233 GtkTextTagTablePrivate *priv = table->priv;
234 GSList *l;
235
236 /* We don't want to emit the remove signal here; so we just unparent
237 * and unref the tag.
238 */
239
240 for (l = priv->buffers; l != NULL; l = l->next)
241 _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (l->data),
242 tag);
243
244 tag->priv->table = NULL;
245 g_object_unref (object: tag);
246}
247
248static void
249gtk_text_tag_table_finalize (GObject *object)
250{
251 GtkTextTagTable *table = GTK_TEXT_TAG_TABLE (object);
252 GtkTextTagTablePrivate *priv = table->priv;
253
254 gtk_text_tag_table_foreach (table, func: foreach_unref, NULL);
255
256 g_hash_table_destroy (hash_table: priv->hash);
257 g_slist_free (list: priv->anonymous);
258 g_slist_free (list: priv->buffers);
259
260 G_OBJECT_CLASS (gtk_text_tag_table_parent_class)->finalize (object);
261}
262
263static void
264gtk_text_tag_table_buildable_interface_init (GtkBuildableIface *iface)
265{
266 iface->add_child = gtk_text_tag_table_buildable_add_child;
267}
268
269static void
270gtk_text_tag_table_buildable_add_child (GtkBuildable *buildable,
271 GtkBuilder *builder,
272 GObject *child,
273 const char *type)
274{
275 if (type && strcmp (s1: type, s2: "tag") == 0)
276 gtk_text_tag_table_add (GTK_TEXT_TAG_TABLE (buildable),
277 GTK_TEXT_TAG (child));
278}
279
280/**
281 * gtk_text_tag_table_add:
282 * @table: a `GtkTextTagTable`
283 * @tag: a `GtkTextTag`
284 *
285 * Add a tag to the table.
286 *
287 * The tag is assigned the highest priority in the table.
288 *
289 * @tag must not be in a tag table already, and may not have
290 * the same name as an already-added tag.
291 *
292 * Returns: %TRUE on success.
293 */
294gboolean
295gtk_text_tag_table_add (GtkTextTagTable *table,
296 GtkTextTag *tag)
297{
298 GtkTextTagTablePrivate *priv;
299 guint size;
300
301 g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), FALSE);
302 g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), FALSE);
303 g_return_val_if_fail (tag->priv->table == NULL, FALSE);
304
305 priv = table->priv;
306
307 if (tag->priv->name && g_hash_table_lookup (hash_table: priv->hash, key: tag->priv->name))
308 {
309 g_warning ("A tag named '%s' is already in the tag table.",
310 tag->priv->name);
311 return FALSE;
312 }
313
314 g_object_ref (tag);
315
316 if (tag->priv->name)
317 g_hash_table_insert (hash_table: priv->hash, key: tag->priv->name, value: tag);
318 else
319 {
320 priv->anonymous = g_slist_prepend (list: priv->anonymous, data: tag);
321 priv->anon_count++;
322 }
323
324 tag->priv->table = table;
325
326 /* We get the highest tag priority, as the most-recently-added
327 tag. Note that we do NOT use gtk_text_tag_set_priority,
328 as it assumes the tag is already in the table. */
329 size = gtk_text_tag_table_get_size (table);
330 g_assert (size > 0);
331 tag->priv->priority = size - 1;
332
333 check_visible (table, tag);
334
335 g_signal_emit (instance: table, signal_id: signals[TAG_ADDED], detail: 0, tag);
336 return TRUE;
337}
338
339/**
340 * gtk_text_tag_table_lookup:
341 * @table: a `GtkTextTagTable`
342 * @name: name of a tag
343 *
344 * Look up a named tag.
345 *
346 * Returns: (nullable) (transfer none): The tag
347 */
348GtkTextTag*
349gtk_text_tag_table_lookup (GtkTextTagTable *table,
350 const char *name)
351{
352 GtkTextTagTablePrivate *priv;
353
354 g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), NULL);
355 g_return_val_if_fail (name != NULL, NULL);
356
357 priv = table->priv;
358
359 return g_hash_table_lookup (hash_table: priv->hash, key: name);
360}
361
362/**
363 * gtk_text_tag_table_remove:
364 * @table: a `GtkTextTagTable`
365 * @tag: a `GtkTextTag`
366 *
367 * Remove a tag from the table.
368 *
369 * If a `GtkTextBuffer` has @table as its tag table, the tag is
370 * removed from the buffer. The table’s reference to the tag is
371 * removed, so the tag will end up destroyed if you don’t have
372 * a reference to it.
373 */
374void
375gtk_text_tag_table_remove (GtkTextTagTable *table,
376 GtkTextTag *tag)
377{
378 GtkTextTagTablePrivate *priv;
379 GSList *l;
380
381 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
382 g_return_if_fail (GTK_IS_TEXT_TAG (tag));
383 g_return_if_fail (tag->priv->table == table);
384
385 priv = table->priv;
386
387 /* Our little bad hack to be sure buffers don't still have the tag
388 * applied to text in the buffer
389 */
390 for (l = priv->buffers; l != NULL; l = l->next)
391 _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (l->data),
392 tag);
393
394 /* Set ourselves to the highest priority; this means
395 when we're removed, there won't be any gaps in the
396 priorities of the tags in the table. */
397 gtk_text_tag_set_priority (tag, priority: gtk_text_tag_table_get_size (table) - 1);
398
399 tag->priv->table = NULL;
400
401 if (tag->priv->name)
402 g_hash_table_remove (hash_table: priv->hash, key: tag->priv->name);
403 else
404 {
405 priv->anonymous = g_slist_remove (list: priv->anonymous, data: tag);
406 priv->anon_count--;
407 }
408
409 g_signal_emit (instance: table, signal_id: signals[TAG_REMOVED], detail: 0, tag);
410
411 g_object_unref (object: tag);
412}
413
414struct ForeachData
415{
416 GtkTextTagTableForeach func;
417 gpointer data;
418};
419
420static void
421hash_foreach (gpointer key, gpointer value, gpointer data)
422{
423 struct ForeachData *fd = data;
424
425 g_return_if_fail (GTK_IS_TEXT_TAG (value));
426
427 (* fd->func) (value, fd->data);
428}
429
430static void
431list_foreach (gpointer data, gpointer user_data)
432{
433 struct ForeachData *fd = user_data;
434
435 g_return_if_fail (GTK_IS_TEXT_TAG (data));
436
437 (* fd->func) (data, fd->data);
438}
439
440/**
441 * gtk_text_tag_table_foreach:
442 * @table: a `GtkTextTagTable`
443 * @func: (scope call): a function to call on each tag
444 * @data: user data
445 *
446 * Calls @func on each tag in @table, with user data @data.
447 *
448 * Note that the table may not be modified while iterating
449 * over it (you can’t add/remove tags).
450 */
451void
452gtk_text_tag_table_foreach (GtkTextTagTable *table,
453 GtkTextTagTableForeach func,
454 gpointer data)
455{
456 GtkTextTagTablePrivate *priv;
457 struct ForeachData d;
458
459 g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
460 g_return_if_fail (func != NULL);
461
462 priv = table->priv;
463
464 d.func = func;
465 d.data = data;
466
467 g_hash_table_foreach (hash_table: priv->hash, func: hash_foreach, user_data: &d);
468 g_slist_foreach (list: priv->anonymous, func: list_foreach, user_data: &d);
469}
470
471/**
472 * gtk_text_tag_table_get_size:
473 * @table: a `GtkTextTagTable`
474 *
475 * Returns the size of the table (number of tags)
476 *
477 * Returns: number of tags in @table
478 */
479int
480gtk_text_tag_table_get_size (GtkTextTagTable *table)
481{
482 GtkTextTagTablePrivate *priv;
483
484 g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), 0);
485
486 priv = table->priv;
487
488 return g_hash_table_size (hash_table: priv->hash) + priv->anon_count;
489}
490
491void
492_gtk_text_tag_table_add_buffer (GtkTextTagTable *table,
493 gpointer buffer)
494{
495 GtkTextTagTablePrivate *priv = table->priv;
496
497 priv->buffers = g_slist_prepend (list: priv->buffers, data: buffer);
498}
499
500static void
501foreach_remove_tag (GtkTextTag *tag, gpointer data)
502{
503 GtkTextBuffer *buffer;
504
505 buffer = GTK_TEXT_BUFFER (data);
506
507 _gtk_text_buffer_notify_will_remove_tag (buffer, tag);
508}
509
510void
511_gtk_text_tag_table_remove_buffer (GtkTextTagTable *table,
512 gpointer buffer)
513{
514 GtkTextTagTablePrivate *priv = table->priv;
515
516 gtk_text_tag_table_foreach (table, func: foreach_remove_tag, data: buffer);
517
518 priv->buffers = g_slist_remove (list: priv->buffers, data: buffer);
519}
520
521void
522_gtk_text_tag_table_tag_changed (GtkTextTagTable *table,
523 GtkTextTag *tag,
524 gboolean size_changed)
525{
526 check_visible (table, tag);
527 g_signal_emit (instance: table, signal_id: signals[TAG_CHANGED], detail: 0, tag, size_changed);
528}
529
530gboolean
531_gtk_text_tag_table_affects_visibility (GtkTextTagTable *table)
532{
533 return table->priv->seen_invisible;
534}
535

source code of gtk/gtk/gtktexttagtable.c