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