gtktexttagtable.c 12.2 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4 5 6 7 8 9 10 11 12 13 14
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17 18 19 20 21 22 23
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */
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 36

#include <stdlib.h>

37

38 39 40 41 42 43
/**
 * SECTION:gtktexttagtable
 * @Short_description: Collection of tags that can be used together
 * @Title: GtkTextTagTable
 *
 * You may wish to begin by reading the <link linkend="TextWidget">text widget
Matthias Clasen's avatar
Matthias Clasen committed
44 45
 * conceptual overview</link> which gives an overview of all the objects and
 * data types related to the text widget and how they work together.
46
 *
47
 * # GtkTextTagTables as GtkBuildable
48
 *
49
 * The GtkTextTagTable implementation of the GtkBuildable interface
Matthias Clasen's avatar
Matthias Clasen committed
50
 * supports adding tags by specifying "tag" as the "type"
51
 * attribute of a &lt;child&gt; element.
Matthias Clasen's avatar
Matthias Clasen committed
52
 *
53
 * An example of a UI definition fragment specifying tags:
54
 * |[
55 56 57 58 59
 * <object class="GtkTextTagTable">
 *  <child type="tag">
 *    <object class="GtkTextTag"/>
 *  </child>
 * </object>
60
 * ]|
61 62
 */

63
struct _GtkTextTagTablePrivate
64 65 66 67 68 69 70 71
{
  GHashTable *hash;
  GSList     *anonymous;
  GSList     *buffers;

  gint anon_count;
};

72 73 74 75 76 77 78
enum {
  TAG_CHANGED,
  TAG_ADDED,
  TAG_REMOVED,
  LAST_SIGNAL
};

79
static void gtk_text_tag_table_finalize                 (GObject             *object);
80

81 82 83 84 85 86
static void gtk_text_tag_table_buildable_interface_init (GtkBuildableIface   *iface);
static void gtk_text_tag_table_buildable_add_child      (GtkBuildable        *buildable,
							 GtkBuilder          *builder,
							 GObject             *child,
							 const gchar         *type);

87 88
static guint signals[LAST_SIGNAL] = { 0 };

89
G_DEFINE_TYPE_WITH_CODE (GtkTextTagTable, gtk_text_tag_table, G_TYPE_OBJECT,
90
                         G_ADD_PRIVATE (GtkTextTagTable)
91 92
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_text_tag_table_buildable_interface_init))
93 94 95 96

static void
gtk_text_tag_table_class_init (GtkTextTagTableClass *klass)
{
97
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
98

99
  object_class->finalize = gtk_text_tag_table_finalize;
100 101 102 103 104 105 106

  /**
   * GtkTextTagTable::tag-changed:
   * @texttagtable: the object which received the signal.
   * @tag: the changed tag.
   * @size_changed: whether the size has been changed.
   */
107
  signals[TAG_CHANGED] =
108
    g_signal_new (I_("tag-changed"),
109 110 111 112
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextTagTableClass, tag_changed),
                  NULL, NULL,
113
                  _gtk_marshal_VOID__OBJECT_BOOLEAN,
114 115
                  G_TYPE_NONE,
                  2,
116
                  GTK_TYPE_TEXT_TAG,
117
                  G_TYPE_BOOLEAN);  
118

119 120 121 122 123
  /**
   * GtkTextTagTable::tag-added:
   * @texttagtable: the object which received the signal.
   * @tag: the added tag.
   */
124
  signals[TAG_ADDED] =
125
    g_signal_new (I_("tag-added"),
Manish Singh's avatar
Manish Singh committed
126
                  G_OBJECT_CLASS_TYPE (object_class),
127 128 129
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextTagTableClass, tag_added),
                  NULL, NULL,
130
                  _gtk_marshal_VOID__OBJECT,
Manish Singh's avatar
Manish Singh committed
131
                  G_TYPE_NONE,
132
                  1,
133
                  GTK_TYPE_TEXT_TAG);
134

135 136 137 138 139
  /**
   * GtkTextTagTable::tag-removed:
   * @texttagtable: the object which received the signal.
   * @tag: the removed tag.
   */
140
  signals[TAG_REMOVED] =
141
    g_signal_new (I_("tag-removed"),  
Manish Singh's avatar
Manish Singh committed
142
                  G_OBJECT_CLASS_TYPE (object_class),
143 144 145
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextTagTableClass, tag_removed),
                  NULL, NULL,
146
                  _gtk_marshal_VOID__OBJECT,
Manish Singh's avatar
Manish Singh committed
147
                  G_TYPE_NONE,
148
                  1,
149
                  GTK_TYPE_TEXT_TAG);
150 151
}

152
static void
153 154
gtk_text_tag_table_init (GtkTextTagTable *table)
{
155 156
  table->priv = gtk_text_tag_table_get_instance_private (table);
  table->priv->hash = g_hash_table_new (g_str_hash, g_str_equal);
157 158
}

159 160
/**
 * gtk_text_tag_table_new:
161
 * 
162 163 164 165 166
 * Creates a new #GtkTextTagTable. The table contains no tags by
 * default.
 * 
 * Return value: a new #GtkTextTagTable
 **/
167 168 169 170 171
GtkTextTagTable*
gtk_text_tag_table_new (void)
{
  GtkTextTagTable *table;

Manish Singh's avatar
Manish Singh committed
172
  table = g_object_new (GTK_TYPE_TEXT_TAG_TABLE, NULL);
173

174 175 176
  return table;
}

177 178 179
static void
foreach_unref (GtkTextTag *tag, gpointer data)
{
180
  GtkTextTagTable *table = GTK_TEXT_TAG_TABLE (tag->priv->table);
181
  GtkTextTagTablePrivate *priv = table->priv;
182 183
  GSList *l;

184 185 186
  /* We don't want to emit the remove signal here; so we just unparent
   * and unref the tag.
   */
187

188 189 190 191
  for (l = priv->buffers; l != NULL; l = l->next)
    _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (l->data),
                                             tag);

192
  tag->priv->table = NULL;
Manish Singh's avatar
Manish Singh committed
193
  g_object_unref (tag);
194 195
}

196 197 198
static void
gtk_text_tag_table_finalize (GObject *object)
{
199
  GtkTextTagTable *table = GTK_TEXT_TAG_TABLE (object);
200
  GtkTextTagTablePrivate *priv = table->priv;
201

202
  gtk_text_tag_table_foreach (table, foreach_unref, NULL);
203

204 205 206
  g_hash_table_destroy (priv->hash);
  g_slist_free (priv->anonymous);
  g_slist_free (priv->buffers);
207 208

  G_OBJECT_CLASS (gtk_text_tag_table_parent_class)->finalize (object);
209 210
}

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
static void
gtk_text_tag_table_buildable_interface_init (GtkBuildableIface   *iface)
{
  iface->add_child = gtk_text_tag_table_buildable_add_child;
}

static void
gtk_text_tag_table_buildable_add_child (GtkBuildable        *buildable,
					GtkBuilder          *builder,
					GObject             *child,
					const gchar         *type)
{
  if (type && strcmp (type, "tag") == 0)
    gtk_text_tag_table_add (GTK_TEXT_TAG_TABLE (buildable),
			    GTK_TEXT_TAG (child));
}

228 229 230 231 232 233 234
/**
 * gtk_text_tag_table_add:
 * @table: a #GtkTextTagTable
 * @tag: a #GtkTextTag
 *
 * Add a tag to the table. The tag is assigned the highest priority
 * in the table.
Havoc Pennington's avatar
Havoc Pennington committed
235 236 237
 *
 * @tag must not be in a tag table already, and may not have
 * the same name as an already-added tag.
238
 **/
239
void
240 241
gtk_text_tag_table_add (GtkTextTagTable *table,
                        GtkTextTag      *tag)
242
{
243
  GtkTextTagTablePrivate *priv;
244
  guint size;
245

246 247
  g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
  g_return_if_fail (GTK_IS_TEXT_TAG (tag));
248
  g_return_if_fail (tag->priv->table == NULL);
249

250 251
  priv = table->priv;

252
  if (tag->priv->name && g_hash_table_lookup (priv->hash, tag->priv->name))
Havoc Pennington's avatar
Havoc Pennington committed
253 254
    {
      g_warning ("A tag named '%s' is already in the tag table.",
255
                 tag->priv->name);
Havoc Pennington's avatar
Havoc Pennington committed
256 257 258
      return;
    }
  
Manish Singh's avatar
Manish Singh committed
259
  g_object_ref (tag);
260

261 262
  if (tag->priv->name)
    g_hash_table_insert (priv->hash, tag->priv->name, tag);
263 264
  else
    {
265
      priv->anonymous = g_slist_prepend (priv->anonymous, tag);
266
      priv->anon_count++;
267
    }
268

269
  tag->priv->table = table;
270 271 272 273

  /* We get the highest tag priority, as the most-recently-added
     tag. Note that we do NOT use gtk_text_tag_set_priority,
     as it assumes the tag is already in the table. */
274
  size = gtk_text_tag_table_get_size (table);
275
  g_assert (size > 0);
276
  tag->priv->priority = size - 1;
277

Manish Singh's avatar
Manish Singh committed
278
  g_signal_emit (table, signals[TAG_ADDED], 0, tag);
279 280
}

281 282 283 284 285 286 287
/**
 * gtk_text_tag_table_lookup:
 * @table: a #GtkTextTagTable 
 * @name: name of a tag
 * 
 * Look up a named tag.
 * 
288
 * Return value: (transfer none): The tag, or %NULL if none by that name is in the table.
289
 **/
290
GtkTextTag*
291 292
gtk_text_tag_table_lookup (GtkTextTagTable *table,
                           const gchar     *name)
293
{
294
  GtkTextTagTablePrivate *priv;
295

296 297 298
  g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), NULL);
  g_return_val_if_fail (name != NULL, NULL);

299 300 301
  priv = table->priv;

  return g_hash_table_lookup (priv->hash, name);
302 303
}

304 305 306 307
/**
 * gtk_text_tag_table_remove:
 * @table: a #GtkTextTagTable
 * @tag: a #GtkTextTag
308 309 310 311 312
 *
 * Remove a tag from the table. If a #GtkTextBuffer has @table as its tag table,
 * the tag is removed from the buffer. The table's reference to the tag is
 * removed, so the tag will end up destroyed if you don't have a reference to
 * it.
313
 **/
314
void
315 316
gtk_text_tag_table_remove (GtkTextTagTable *table,
                           GtkTextTag      *tag)
317
{
318
  GtkTextTagTablePrivate *priv;
319 320
  GSList *l;

321
  g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
322
  g_return_if_fail (GTK_IS_TEXT_TAG (tag));
323
  g_return_if_fail (tag->priv->table == table);
324

325 326
  priv = table->priv;

327 328 329
  /* Our little bad hack to be sure buffers don't still have the tag
   * applied to text in the buffer
   */
330 331 332 333
  for (l = priv->buffers; l != NULL; l = l->next)
    _gtk_text_buffer_notify_will_remove_tag (GTK_TEXT_BUFFER (l->data),
                                             tag);

334 335 336
  /* Set ourselves to the highest priority; this means
     when we're removed, there won't be any gaps in the
     priorities of the tags in the table. */
337
  gtk_text_tag_set_priority (tag, gtk_text_tag_table_get_size (table) - 1);
338

339
  tag->priv->table = NULL;
340

341 342
  if (tag->priv->name)
    g_hash_table_remove (priv->hash, tag->priv->name);
343 344
  else
    {
345
      priv->anonymous = g_slist_remove (priv->anonymous, tag);
346
      priv->anon_count--;
347
    }
348

Manish Singh's avatar
Manish Singh committed
349
  g_signal_emit (table, signals[TAG_REMOVED], 0, tag);
350

Manish Singh's avatar
Manish Singh committed
351
  g_object_unref (tag);
352 353
}

354 355 356 357 358 359 360 361 362 363 364
struct ForeachData
{
  GtkTextTagTableForeach func;
  gpointer data;
};

static void
hash_foreach (gpointer key, gpointer value, gpointer data)
{
  struct ForeachData *fd = data;

365
  g_return_if_fail (GTK_IS_TEXT_TAG (value));
366

367 368 369
  (* fd->func) (value, fd->data);
}

370 371 372 373 374 375
static void
list_foreach (gpointer data, gpointer user_data)
{
  struct ForeachData *fd = user_data;

  g_return_if_fail (GTK_IS_TEXT_TAG (data));
376

377 378 379
  (* fd->func) (data, fd->data);
}

380 381 382
/**
 * gtk_text_tag_table_foreach:
 * @table: a #GtkTextTagTable
383
 * @func: (scope call): a function to call on each tag
384 385 386
 * @data: user data
 *
 * Calls @func on each tag in @table, with user data @data.
387 388
 * Note that the table may not be modified while iterating 
 * over it (you can't add/remove tags).
389
 **/
390
void
391 392 393
gtk_text_tag_table_foreach (GtkTextTagTable       *table,
                            GtkTextTagTableForeach func,
                            gpointer               data)
394
{
395
  GtkTextTagTablePrivate *priv;
396
  struct ForeachData d;
397 398 399

  g_return_if_fail (GTK_IS_TEXT_TAG_TABLE (table));
  g_return_if_fail (func != NULL);
400

401 402
  priv = table->priv;

403 404
  d.func = func;
  d.data = data;
405

406 407
  g_hash_table_foreach (priv->hash, hash_foreach, &d);
  g_slist_foreach (priv->anonymous, list_foreach, &d);
408 409
}

410
/**
411
 * gtk_text_tag_table_get_size:
412 413 414 415 416 417 418
 * @table: a #GtkTextTagTable
 * 
 * Returns the size of the table (number of tags)
 * 
 * Return value: number of tags in @table
 **/
gint
419
gtk_text_tag_table_get_size (GtkTextTagTable *table)
420
{
421
  GtkTextTagTablePrivate *priv;
422

423
  g_return_val_if_fail (GTK_IS_TEXT_TAG_TABLE (table), 0);
424

425 426 427
  priv = table->priv;

  return g_hash_table_size (priv->hash) + priv->anon_count;
428
}
429 430 431 432 433

void
_gtk_text_tag_table_add_buffer (GtkTextTagTable *table,
                                gpointer         buffer)
{
434
  GtkTextTagTablePrivate *priv = table->priv;
435

436
  priv->buffers = g_slist_prepend (priv->buffers, buffer);
437 438
}

439 440 441 442 443 444 445 446 447 448
static void
foreach_remove_tag (GtkTextTag *tag, gpointer data)
{
  GtkTextBuffer *buffer;

  buffer = GTK_TEXT_BUFFER (data);

  _gtk_text_buffer_notify_will_remove_tag (buffer, tag);
}

449 450 451 452
void
_gtk_text_tag_table_remove_buffer (GtkTextTagTable *table,
                                   gpointer         buffer)
{
453
  GtkTextTagTablePrivate *priv = table->priv;
454

455
  gtk_text_tag_table_foreach (table, foreach_remove_tag, buffer);
456 457

  priv->buffers = g_slist_remove (priv->buffers, buffer);
458
}