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