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