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