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