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 &lt;child&gt; 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