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