~noskcaj/ubuntu/trusty/gnome-documents/3.10.2

« back to all changes in this revision

Viewing changes to src/lib/gd-tagged-entry.c

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2012-08-22 10:01:18 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20120822100118-3837rqfy72e1op72
Tags: 3.5.90-0ubuntu1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (c) 2011 Red Hat, Inc.
3
 
 *
4
 
 * This program is free software; you can redistribute it and/or modify
5
 
 * it under the terms of the GNU Lesser General Public License as published by 
6
 
 * the Free Software Foundation; either version 2 of the License, or (at your
7
 
 * option) any later version.
8
 
 *
9
 
 * This program is distributed in the hope that it will be useful, but
10
 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11
 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
12
 
 * License for more details.
13
 
 *
14
 
 * You should have received a copy of the GNU Lesser General Public License 
15
 
 * along with this program; if not, write to the Free Software Foundation,
16
 
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
 
 *
18
 
 * Author: Cosimo Cecchi <cosimoc@redhat.com>
19
 
 *
20
 
 */
21
 
 
22
 
#include "gd-tagged-entry.h"
23
 
 
24
 
#include <math.h>
25
 
 
26
 
G_DEFINE_TYPE (GdTaggedEntry, gd_tagged_entry, GTK_TYPE_SEARCH_ENTRY)
27
 
 
28
 
#define BUTTON_INTERNAL_SPACING 6
29
 
 
30
 
typedef struct {
31
 
  GdkWindow *window;
32
 
  PangoLayout *layout;
33
 
 
34
 
  gchar *id;
35
 
  gchar *label;
36
 
 
37
 
  GdkPixbuf *close_pixbuf;
38
 
  GtkStateFlags last_button_state;
39
 
} GdTaggedEntryTag;
40
 
 
41
 
struct _GdTaggedEntryPrivate {
42
 
  GList *tags;
43
 
 
44
 
  GdTaggedEntryTag *in_child;
45
 
  gboolean in_child_button;
46
 
  gboolean in_child_active;
47
 
  gboolean in_child_button_active;
48
 
  gboolean button_visible;
49
 
};
50
 
 
51
 
enum {
52
 
  SIGNAL_TAG_CLICKED,
53
 
  SIGNAL_TAG_BUTTON_CLICKED,
54
 
  LAST_SIGNAL
55
 
};
56
 
 
57
 
enum {
58
 
  PROP_0,
59
 
  PROP_TAG_BUTTON_VISIBLE,
60
 
  NUM_PROPERTIES
61
 
};
62
 
 
63
 
static guint signals[LAST_SIGNAL] = { 0, };
64
 
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
65
 
 
66
 
static void gd_tagged_entry_get_text_area_size (GtkEntry *entry,
67
 
                                                gint *x,
68
 
                                                gint *y,
69
 
                                                gint *width,
70
 
                                                gint *height);
71
 
static gint gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
72
 
                                           GdTaggedEntry *entry);
73
 
static GtkStyleContext * gd_tagged_entry_tag_get_context (GdTaggedEntry *entry);
74
 
 
75
 
static void
76
 
gd_tagged_entry_tag_get_margin (GdTaggedEntry *entry,
77
 
                                GtkBorder *margin)
78
 
{
79
 
  GtkStyleContext *context;
80
 
 
81
 
  context = gd_tagged_entry_tag_get_context (entry);
82
 
  gtk_style_context_get_margin (context, 0, margin);
83
 
  g_object_unref (context);
84
 
}
85
 
 
86
 
static void
87
 
gd_tagged_entry_tag_ensure_close_pixbuf (GdTaggedEntryTag *tag,
88
 
                                         GtkStyleContext *context)
89
 
{
90
 
  GtkIconInfo *info;
91
 
  gint icon_size;
92
 
 
93
 
  if (tag->close_pixbuf != NULL)
94
 
    return;
95
 
 
96
 
  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
97
 
                        &icon_size, NULL);
98
 
 
99
 
  info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
100
 
                                     "window-close-symbolic",
101
 
                                     icon_size,
102
 
                                     GTK_ICON_LOOKUP_GENERIC_FALLBACK);
103
 
 
104
 
  tag->close_pixbuf = 
105
 
    gtk_icon_info_load_symbolic_for_context (info, context,
106
 
                                             NULL, NULL);
107
 
 
108
 
  /* FIXME: we need a fallback icon in case the icon is not found */
109
 
}
110
 
 
111
 
static gint
112
 
gd_tagged_entry_tag_panel_get_height (GdTaggedEntry *entry)
113
 
{
114
 
  GtkWidget *widget = GTK_WIDGET (entry);
115
 
  gint height, req_height;
116
 
  GtkRequisition requisition;
117
 
  GtkAllocation allocation;
118
 
  GtkBorder margin;
119
 
 
120
 
  gtk_widget_get_allocation (widget, &allocation);
121
 
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
122
 
  gd_tagged_entry_tag_get_margin (entry, &margin);
123
 
 
124
 
  /* the tag panel height is the whole entry height, minus the tag margins */
125
 
  req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
126
 
  height = MIN (req_height, allocation.height) - margin.top - margin.bottom;
127
 
 
128
 
  return height;
129
 
}
130
 
 
131
 
static void
132
 
gd_tagged_entry_tag_panel_get_position (GdTaggedEntry *self,
133
 
                                        gint *x_out, 
134
 
                                        gint *y_out)
135
 
{
136
 
  GtkWidget *widget = GTK_WIDGET (self);
137
 
  gint text_x, text_y, text_width, text_height, req_height;
138
 
  GtkAllocation allocation;
139
 
  GtkRequisition requisition;
140
 
  GtkBorder margin;
141
 
 
142
 
  gtk_widget_get_allocation (widget, &allocation);
143
 
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
144
 
  req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
145
 
 
146
 
  gd_tagged_entry_get_text_area_size (GTK_ENTRY (self), &text_x, &text_y, &text_width, &text_height);
147
 
  gd_tagged_entry_tag_get_margin (self, &margin);
148
 
 
149
 
  /* allocate the panel immediately after the text area */
150
 
  if (x_out)
151
 
    *x_out = allocation.x + text_x + text_width;
152
 
  if (y_out)
153
 
    *y_out = allocation.y + margin.top + (gint) floor ((allocation.height - req_height) / 2);
154
 
}
155
 
 
156
 
static gint
157
 
gd_tagged_entry_tag_panel_get_width (GdTaggedEntry *self)
158
 
{
159
 
  GdTaggedEntryTag *tag;
160
 
  gint width;
161
 
  GList *l;
162
 
 
163
 
  width = 0;
164
 
 
165
 
  for (l = self->priv->tags; l != NULL; l = l->next)
166
 
    {
167
 
      tag = l->data;
168
 
      width += gd_tagged_entry_tag_get_width (tag, self);
169
 
    }
170
 
 
171
 
  return width;
172
 
}
173
 
 
174
 
static void
175
 
gd_tagged_entry_tag_ensure_layout (GdTaggedEntryTag *tag,
176
 
                                   GdTaggedEntry *entry)
177
 
{
178
 
  if (tag->layout != NULL)
179
 
    return;
180
 
 
181
 
  tag->layout = pango_layout_new (gtk_widget_get_pango_context (GTK_WIDGET (entry)));
182
 
  pango_layout_set_text (tag->layout, tag->label, -1);
183
 
}
184
 
 
185
 
static GtkStateFlags
186
 
gd_tagged_entry_tag_get_state (GdTaggedEntryTag *tag,
187
 
                               GdTaggedEntry *entry)
188
 
{
189
 
  GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
190
 
 
191
 
  if (entry->priv->in_child == tag)
192
 
    state |= GTK_STATE_FLAG_PRELIGHT;
193
 
 
194
 
  if (entry->priv->in_child_active)
195
 
    state |= GTK_STATE_FLAG_ACTIVE;
196
 
 
197
 
  return state;
198
 
}
199
 
 
200
 
static GtkStateFlags
201
 
gd_tagged_entry_tag_get_button_state (GdTaggedEntryTag *tag,
202
 
                                      GdTaggedEntry *entry)
203
 
{
204
 
  GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
205
 
 
206
 
  if (entry->priv->in_child == tag &&
207
 
      entry->priv->in_child_button)
208
 
    state |= GTK_STATE_FLAG_PRELIGHT;
209
 
 
210
 
  if (entry->priv->in_child_button_active)
211
 
    state |= GTK_STATE_FLAG_ACTIVE;
212
 
 
213
 
  return state;
214
 
}
215
 
 
216
 
static GtkStyleContext *
217
 
gd_tagged_entry_tag_get_context (GdTaggedEntry *entry)
218
 
{
219
 
  GtkWidget *widget = GTK_WIDGET (entry);
220
 
  GtkWidgetPath *path;
221
 
  gint pos;
222
 
  GtkStyleContext *retval;
223
 
 
224
 
  retval = gtk_style_context_new ();
225
 
  path = gtk_widget_path_copy (gtk_widget_get_path (widget));
226
 
 
227
 
  pos = gtk_widget_path_append_type (path, GD_TYPE_TAGGED_ENTRY);
228
 
  gtk_widget_path_iter_add_class (path, pos, "documents-entry-tag");
229
 
 
230
 
  gtk_style_context_set_path (retval, path);
231
 
 
232
 
  gtk_widget_path_unref (path);
233
 
 
234
 
  return retval;
235
 
}
236
 
 
237
 
static gint
238
 
gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
239
 
                               GdTaggedEntry *entry)
240
 
{
241
 
  GtkBorder button_padding, button_border, button_margin;
242
 
  GtkStyleContext *context;
243
 
  GtkStateFlags state;
244
 
  gint layout_width;
245
 
  gint button_width;
246
 
 
247
 
  gd_tagged_entry_tag_ensure_layout (tag, entry);
248
 
  pango_layout_get_pixel_size (tag->layout, &layout_width, NULL);
249
 
 
250
 
  context = gd_tagged_entry_tag_get_context (entry);
251
 
  state = gd_tagged_entry_tag_get_state (tag, entry);
252
 
 
253
 
  gtk_style_context_get_padding (context, state, &button_padding);
254
 
  gtk_style_context_get_border (context, state, &button_border);
255
 
  gtk_style_context_get_margin (context, state, &button_margin);
256
 
 
257
 
  gd_tagged_entry_tag_ensure_close_pixbuf (tag, context);
258
 
 
259
 
  g_object_unref (context);
260
 
 
261
 
  button_width = 0;
262
 
  if (entry->priv->button_visible)
263
 
    button_width = gdk_pixbuf_get_width (tag->close_pixbuf) + BUTTON_INTERNAL_SPACING;
264
 
 
265
 
  return layout_width + button_padding.left + button_padding.right +
266
 
    button_border.left + button_border.right +
267
 
    button_margin.left + button_margin.right +
268
 
    button_width;
269
 
}
270
 
 
271
 
static void
272
 
gd_tagged_entry_tag_get_size (GdTaggedEntryTag *tag,
273
 
                              GdTaggedEntry *entry,
274
 
                              gint *width_out,
275
 
                              gint *height_out)
276
 
{
277
 
  gint width, panel_height;
278
 
 
279
 
  width = gd_tagged_entry_tag_get_width (tag, entry);
280
 
  panel_height = gd_tagged_entry_tag_panel_get_height (entry);
281
 
 
282
 
  if (width_out)
283
 
    *width_out = width;
284
 
  if (height_out)
285
 
    *height_out = panel_height;
286
 
}
287
 
 
288
 
static void
289
 
gd_tagged_entry_tag_get_relative_allocations (GdTaggedEntryTag *tag,
290
 
                                              GdTaggedEntry *entry,
291
 
                                              GtkStyleContext *context,
292
 
                                              GtkAllocation *background_allocation_out,
293
 
                                              GtkAllocation *layout_allocation_out,
294
 
                                              GtkAllocation *button_allocation_out)
295
 
{
296
 
  GtkAllocation background_allocation, layout_allocation, button_allocation;
297
 
  gint width, height, x, y, pix_width, pix_height;
298
 
  gint layout_width, layout_height;
299
 
  GtkBorder padding, border;
300
 
  GtkStateFlags state;
301
 
 
302
 
  width = gdk_window_get_width (tag->window);
303
 
  height = gdk_window_get_height (tag->window);
304
 
 
305
 
  state = gd_tagged_entry_tag_get_state (tag, entry);
306
 
  gtk_style_context_get_margin (context, state, &padding);
307
 
 
308
 
  width -= padding.left + padding.right;
309
 
  height -= padding.top + padding.bottom;
310
 
  x = padding.left;
311
 
  y = padding.top;
312
 
 
313
 
  background_allocation.x = x;
314
 
  background_allocation.y = y;
315
 
  background_allocation.width = width;
316
 
  background_allocation.height = height;
317
 
 
318
 
  layout_allocation = button_allocation = background_allocation;
319
 
 
320
 
  gtk_style_context_get_padding (context, state, &padding);
321
 
  gtk_style_context_get_border (context, state, &border);  
322
 
 
323
 
  gd_tagged_entry_tag_ensure_layout (tag, entry);
324
 
  pango_layout_get_pixel_size (tag->layout, &layout_width, &layout_height);
325
 
 
326
 
  layout_allocation.x += border.left + padding.left;
327
 
  layout_allocation.y += (layout_allocation.height - layout_height) / 2;
328
 
 
329
 
  if (entry->priv->button_visible)
330
 
    {
331
 
      pix_width = gdk_pixbuf_get_width (tag->close_pixbuf);
332
 
      pix_height = gdk_pixbuf_get_height (tag->close_pixbuf);
333
 
    }
334
 
  else
335
 
    {
336
 
      pix_width = 0;
337
 
      pix_height = 0;
338
 
    }
339
 
 
340
 
  button_allocation.x += width - pix_width - border.right - padding.right;
341
 
  button_allocation.y += (height - pix_height) / 2;
342
 
  button_allocation.width = pix_width;
343
 
  button_allocation.height = pix_height;
344
 
 
345
 
  if (background_allocation_out)
346
 
    *background_allocation_out = background_allocation;
347
 
  if (layout_allocation_out)
348
 
    *layout_allocation_out = layout_allocation;
349
 
  if (button_allocation_out)
350
 
    *button_allocation_out = button_allocation;
351
 
}
352
 
 
353
 
static gboolean
354
 
gd_tagged_entry_tag_event_is_button (GdTaggedEntryTag *tag,
355
 
                                     GdTaggedEntry *entry,
356
 
                                     gdouble event_x,
357
 
                                     gdouble event_y)
358
 
{
359
 
  GtkAllocation button_allocation;
360
 
  GtkStyleContext *context;
361
 
 
362
 
  if (!entry->priv->button_visible)
363
 
    return FALSE;
364
 
 
365
 
  context = gd_tagged_entry_tag_get_context (entry);
366
 
  gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, NULL, NULL, &button_allocation);
367
 
 
368
 
  g_object_unref (context);
369
 
 
370
 
  /* see if the event falls into the button allocation */
371
 
  if ((event_x >= button_allocation.x && 
372
 
       event_x <= button_allocation.x + button_allocation.width) &&
373
 
      (event_y >= button_allocation.y &&
374
 
       event_y <= button_allocation.y + button_allocation.height))
375
 
    return TRUE;
376
 
 
377
 
  return FALSE;
378
 
}
379
 
 
380
 
static void
381
 
gd_tagged_entry_tag_draw (GdTaggedEntryTag *tag,
382
 
                          cairo_t *cr,
383
 
                          GdTaggedEntry *entry)
384
 
{
385
 
  GtkStyleContext *context;
386
 
  GtkStateFlags state;
387
 
  GtkAllocation background_allocation, layout_allocation, button_allocation;
388
 
 
389
 
  context = gd_tagged_entry_tag_get_context (entry);
390
 
  gd_tagged_entry_tag_get_relative_allocations (tag, entry, context,
391
 
                                                &background_allocation,
392
 
                                                &layout_allocation,
393
 
                                                &button_allocation);
394
 
 
395
 
  cairo_save (cr);
396
 
  gtk_cairo_transform_to_window (cr, GTK_WIDGET (entry), tag->window);
397
 
 
398
 
  gtk_style_context_save (context);
399
 
 
400
 
  state = gd_tagged_entry_tag_get_state (tag, entry);
401
 
  gtk_style_context_set_state (context, state);
402
 
  gtk_render_background (context, cr,
403
 
                         background_allocation.x, background_allocation.y,
404
 
                         background_allocation.width, background_allocation.height); 
405
 
  gtk_render_frame (context, cr,
406
 
                    background_allocation.x, background_allocation.y,
407
 
                    background_allocation.width, background_allocation.height); 
408
 
 
409
 
  gtk_render_layout (context, cr,
410
 
                     layout_allocation.x, layout_allocation.y,
411
 
                     tag->layout);
412
 
 
413
 
  gtk_style_context_restore (context);
414
 
 
415
 
  if (!entry->priv->button_visible)
416
 
    goto done;
417
 
 
418
 
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
419
 
  state = gd_tagged_entry_tag_get_button_state (tag, entry);
420
 
  gtk_style_context_set_state (context, state);
421
 
 
422
 
  /* if the state changed since last time we draw the pixbuf,
423
 
   * clear and redraw it.
424
 
   */
425
 
  if (state != tag->last_button_state)
426
 
    {
427
 
      g_clear_object (&tag->close_pixbuf);
428
 
      gd_tagged_entry_tag_ensure_close_pixbuf (tag, context);
429
 
 
430
 
      tag->last_button_state = state;
431
 
    }
432
 
 
433
 
  gtk_render_background (context, cr,
434
 
                         button_allocation.x, button_allocation.y,
435
 
                         button_allocation.width, button_allocation.height);
436
 
  gtk_render_frame (context, cr,
437
 
                         button_allocation.x, button_allocation.y,
438
 
                         button_allocation.width, button_allocation.height);
439
 
 
440
 
  gtk_render_icon (context, cr,
441
 
                   tag->close_pixbuf,
442
 
                   button_allocation.x, button_allocation.y);
443
 
 
444
 
done:
445
 
  cairo_restore (cr);
446
 
 
447
 
  g_object_unref (context);
448
 
}
449
 
 
450
 
static void
451
 
gd_tagged_entry_tag_unrealize (GdTaggedEntryTag *tag)
452
 
{
453
 
  if (tag->window == NULL)
454
 
    return;
455
 
 
456
 
  gdk_window_set_user_data (tag->window, NULL);
457
 
  gdk_window_destroy (tag->window);
458
 
  tag->window = NULL;
459
 
}
460
 
 
461
 
static void
462
 
gd_tagged_entry_tag_realize (GdTaggedEntryTag *tag,
463
 
                             GdTaggedEntry *entry)
464
 
{
465
 
  GtkWidget *widget = GTK_WIDGET (entry);
466
 
  GdkWindowAttr attributes;
467
 
  gint attributes_mask;
468
 
  gint tag_width, tag_height;
469
 
 
470
 
  if (tag->window != NULL)
471
 
    return;
472
 
 
473
 
  attributes.window_type = GDK_WINDOW_CHILD;
474
 
  attributes.wclass = GDK_INPUT_ONLY;
475
 
  attributes.event_mask = gtk_widget_get_events (widget);
476
 
  attributes.event_mask |= GDK_BUTTON_PRESS_MASK
477
 
    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
478
 
    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
479
 
 
480
 
  gd_tagged_entry_tag_get_size (tag, entry, &tag_width, &tag_height);
481
 
  attributes.x = 0;
482
 
  attributes.y = 0;
483
 
  attributes.width = tag_width;
484
 
  attributes.height = tag_height;
485
 
 
486
 
  attributes_mask = GDK_WA_X | GDK_WA_Y;
487
 
 
488
 
  tag->window = gdk_window_new (gtk_widget_get_window (widget),
489
 
                                &attributes, attributes_mask);
490
 
  gdk_window_set_user_data (tag->window, widget);
491
 
}
492
 
 
493
 
static GdTaggedEntryTag *
494
 
gd_tagged_entry_tag_new (const gchar *id,
495
 
                         const gchar *label)
496
 
{
497
 
  GdTaggedEntryTag *tag;
498
 
 
499
 
  tag = g_slice_new0 (GdTaggedEntryTag);
500
 
 
501
 
  tag->id = g_strdup (id);
502
 
  tag->label = g_strdup (label);
503
 
  tag->last_button_state = GTK_STATE_FLAG_NORMAL;
504
 
 
505
 
  return tag;
506
 
}
507
 
 
508
 
static void
509
 
gd_tagged_entry_tag_free (gpointer _tag)
510
 
{
511
 
  GdTaggedEntryTag *tag = _tag;
512
 
 
513
 
  if (tag->window != NULL)
514
 
    gd_tagged_entry_tag_unrealize (tag);
515
 
 
516
 
  g_clear_object (&tag->layout);
517
 
  g_clear_object (&tag->close_pixbuf);
518
 
  g_free (tag->id);
519
 
  g_free (tag->label);
520
 
 
521
 
  g_slice_free (GdTaggedEntryTag, tag);
522
 
}
523
 
 
524
 
static gboolean
525
 
gd_tagged_entry_draw (GtkWidget *widget,
526
 
                      cairo_t *cr)
527
 
{
528
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
529
 
  GdTaggedEntryTag *tag;
530
 
  GList *l;
531
 
 
532
 
  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->draw (widget, cr);
533
 
 
534
 
  for (l = self->priv->tags; l != NULL; l = l->next)
535
 
    {
536
 
      tag = l->data;
537
 
      gd_tagged_entry_tag_draw (tag, cr, self);
538
 
    }
539
 
 
540
 
  return FALSE;
541
 
}
542
 
 
543
 
static void
544
 
gd_tagged_entry_map (GtkWidget *widget)
545
 
{
546
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
547
 
  GdTaggedEntryTag *tag;
548
 
  GList *l;
549
 
 
550
 
  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
551
 
    {
552
 
      GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->map (widget);
553
 
 
554
 
      for (l = self->priv->tags; l != NULL; l = l->next)
555
 
        {
556
 
          tag = l->data;
557
 
          gdk_window_show (tag->window);
558
 
        }
559
 
    }
560
 
}
561
 
 
562
 
static void
563
 
gd_tagged_entry_unmap (GtkWidget *widget)
564
 
{
565
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
566
 
  GdTaggedEntryTag *tag;
567
 
  GList *l;
568
 
 
569
 
  if (gtk_widget_get_mapped (widget))
570
 
    {
571
 
      for (l = self->priv->tags; l != NULL; l = l->next)
572
 
        {
573
 
          tag = l->data;
574
 
          gdk_window_hide (tag->window);
575
 
        }
576
 
 
577
 
      GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unmap (widget);
578
 
    }
579
 
}
580
 
 
581
 
static void
582
 
gd_tagged_entry_realize (GtkWidget *widget)
583
 
{
584
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
585
 
  GdTaggedEntryTag *tag;
586
 
  GList *l;
587
 
 
588
 
  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->realize (widget);
589
 
 
590
 
  for (l = self->priv->tags; l != NULL; l = l->next)
591
 
    {
592
 
      tag = l->data;
593
 
      gd_tagged_entry_tag_realize (tag, self);
594
 
    }
595
 
}
596
 
 
597
 
static void
598
 
gd_tagged_entry_unrealize (GtkWidget *widget)
599
 
{
600
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
601
 
  GdTaggedEntryTag *tag;
602
 
  GList *l;
603
 
 
604
 
  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unrealize (widget);
605
 
 
606
 
  for (l = self->priv->tags; l != NULL; l = l->next)
607
 
    {
608
 
      tag = l->data;
609
 
      gd_tagged_entry_tag_unrealize (tag);
610
 
    }
611
 
}
612
 
 
613
 
static void
614
 
gd_tagged_entry_get_text_area_size (GtkEntry *entry,
615
 
                                    gint *x,
616
 
                                    gint *y,
617
 
                                    gint *width,
618
 
                                    gint *height)
619
 
{
620
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (entry);
621
 
  gint tag_panel_width;
622
 
 
623
 
  GTK_ENTRY_CLASS (gd_tagged_entry_parent_class)->get_text_area_size (entry, x, y, width, height);
624
 
 
625
 
  tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
626
 
 
627
 
  if (width)
628
 
    *width -= tag_panel_width;
629
 
}
630
 
 
631
 
static void
632
 
gd_tagged_entry_size_allocate (GtkWidget *widget,
633
 
                               GtkAllocation *allocation)
634
 
{
635
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
636
 
  gint x, y, width, height;
637
 
  GdTaggedEntryTag *tag;
638
 
  GList *l;
639
 
 
640
 
  gtk_widget_set_allocation (widget, allocation);
641
 
  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->size_allocate (widget, allocation);
642
 
 
643
 
  if (gtk_widget_get_realized (widget))
644
 
    {
645
 
      gd_tagged_entry_tag_panel_get_position (self, &x, &y);
646
 
 
647
 
      for (l = self->priv->tags; l != NULL; l = l->next)
648
 
        {
649
 
          tag = l->data;
650
 
          gd_tagged_entry_tag_get_size (tag, self, &width, &height);
651
 
          gdk_window_move_resize (tag->window, x, y, width, height);
652
 
 
653
 
          x += width;
654
 
        }
655
 
 
656
 
      gtk_widget_queue_draw (widget);
657
 
    }
658
 
}
659
 
 
660
 
static void
661
 
gd_tagged_entry_get_preferred_width (GtkWidget *widget,
662
 
                                     gint *minimum,
663
 
                                     gint *natural)
664
 
{
665
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
666
 
  gint tag_panel_width;
667
 
 
668
 
  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->get_preferred_width (widget, minimum, natural);
669
 
 
670
 
  tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
671
 
 
672
 
  if (minimum)
673
 
    *minimum += tag_panel_width;
674
 
  if (natural)
675
 
    *natural += tag_panel_width;
676
 
}
677
 
 
678
 
static void
679
 
gd_tagged_entry_finalize (GObject *obj)
680
 
{
681
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (obj);
682
 
 
683
 
  if (self->priv->tags != NULL)
684
 
    {
685
 
      g_list_free_full (self->priv->tags, gd_tagged_entry_tag_free);
686
 
      self->priv->tags = NULL;
687
 
    }
688
 
 
689
 
  G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj);
690
 
}
691
 
 
692
 
static GdTaggedEntryTag *
693
 
gd_tagged_entry_find_tag_by_id (GdTaggedEntry *self,
694
 
                                const gchar *id)
695
 
{
696
 
  GdTaggedEntryTag *tag = NULL, *elem;
697
 
  GList *l;
698
 
 
699
 
  for (l = self->priv->tags; l != NULL; l = l->next)
700
 
    {
701
 
      elem = l->data;
702
 
      if (g_strcmp0 (elem->id, id) == 0)
703
 
        {
704
 
          tag = elem;
705
 
          break;
706
 
        }
707
 
    }
708
 
 
709
 
  return tag;
710
 
}
711
 
 
712
 
static GdTaggedEntryTag *
713
 
gd_tagged_entry_find_tag_by_window (GdTaggedEntry *self,
714
 
                                    GdkWindow *window)
715
 
{
716
 
  GdTaggedEntryTag *tag = NULL, *elem;
717
 
  GList *l;
718
 
 
719
 
  for (l = self->priv->tags; l != NULL; l = l->next)
720
 
    {
721
 
      elem = l->data;
722
 
      if (elem->window == window)
723
 
        {
724
 
          tag = elem;
725
 
          break;
726
 
        }
727
 
    }
728
 
 
729
 
  return tag;
730
 
}
731
 
 
732
 
static gint
733
 
gd_tagged_entry_enter_notify (GtkWidget        *widget,
734
 
                              GdkEventCrossing *event)
735
 
{
736
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
737
 
  GdTaggedEntryTag *tag;
738
 
 
739
 
  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
740
 
 
741
 
  if (tag != NULL)
742
 
    {
743
 
      self->priv->in_child = tag;
744
 
      gtk_widget_queue_draw (widget);
745
 
    }
746
 
 
747
 
  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->enter_notify_event (widget, event);
748
 
}
749
 
 
750
 
static gint
751
 
gd_tagged_entry_leave_notify (GtkWidget        *widget,
752
 
                              GdkEventCrossing *event)
753
 
{
754
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
755
 
 
756
 
  if (self->priv->in_child != NULL)
757
 
    {
758
 
      self->priv->in_child = NULL;
759
 
      gtk_widget_queue_draw (widget);
760
 
    }
761
 
 
762
 
  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->leave_notify_event (widget, event);
763
 
}
764
 
 
765
 
static gint
766
 
gd_tagged_entry_motion_notify (GtkWidget      *widget,
767
 
                               GdkEventMotion *event)
768
 
{
769
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
770
 
  GdTaggedEntryTag *tag;
771
 
 
772
 
  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
773
 
 
774
 
  if (tag != NULL)
775
 
    {
776
 
      gdk_event_request_motions (event);
777
 
 
778
 
      self->priv->in_child = tag;
779
 
      self->priv->in_child_button = gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y);
780
 
      gtk_widget_queue_draw (widget);
781
 
 
782
 
      return FALSE;
783
 
    }
784
 
 
785
 
  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->motion_notify_event (widget, event);
786
 
}
787
 
 
788
 
static gboolean
789
 
gd_tagged_entry_button_release_event (GtkWidget *widget,
790
 
                                      GdkEventButton *event)
791
 
{
792
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
793
 
  GdTaggedEntryTag *tag;
794
 
  GQuark id_quark;
795
 
 
796
 
  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
797
 
 
798
 
  if (tag != NULL)
799
 
    {
800
 
      id_quark = g_quark_from_string (tag->id);
801
 
      self->priv->in_child_active = FALSE;
802
 
 
803
 
      if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
804
 
        {
805
 
          self->priv->in_child_button_active = FALSE;
806
 
          g_signal_emit (self, signals[SIGNAL_TAG_BUTTON_CLICKED], id_quark, tag->id);
807
 
        }
808
 
      else
809
 
        {
810
 
          g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], id_quark, tag->id);
811
 
        }
812
 
 
813
 
      gtk_widget_queue_draw (widget);
814
 
 
815
 
      return TRUE;
816
 
    }
817
 
 
818
 
  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_release_event (widget, event);
819
 
}
820
 
 
821
 
static gboolean
822
 
gd_tagged_entry_button_press_event (GtkWidget *widget,
823
 
                                    GdkEventButton *event)
824
 
{
825
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
826
 
  GdTaggedEntryTag *tag;
827
 
 
828
 
  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
829
 
 
830
 
  if (tag != NULL)
831
 
    {
832
 
      if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
833
 
        self->priv->in_child_button_active = TRUE;
834
 
      else
835
 
        self->priv->in_child_active = TRUE;
836
 
 
837
 
      gtk_widget_queue_draw (widget);
838
 
 
839
 
      return TRUE;
840
 
    }
841
 
 
842
 
  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_press_event (widget, event);
843
 
}
844
 
 
845
 
static void
846
 
gd_tagged_entry_init (GdTaggedEntry *self)
847
 
{
848
 
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY, GdTaggedEntryPrivate);
849
 
  self->priv->button_visible = TRUE;
850
 
}
851
 
 
852
 
static void
853
 
gd_tagged_entry_get_property (GObject      *object,
854
 
                              guint         property_id,
855
 
                              GValue       *value,
856
 
                              GParamSpec   *pspec)
857
 
{
858
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
859
 
 
860
 
  switch (property_id)
861
 
    {
862
 
      case PROP_TAG_BUTTON_VISIBLE:
863
 
        g_value_set_boolean (value, gd_tagged_entry_get_tag_button_visible (self));
864
 
        break;
865
 
      default:
866
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
867
 
    }
868
 
}
869
 
 
870
 
static void
871
 
gd_tagged_entry_set_property (GObject      *object,
872
 
                              guint         property_id,
873
 
                              const GValue *value,
874
 
                              GParamSpec   *pspec)
875
 
{
876
 
  GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
877
 
 
878
 
  switch (property_id)
879
 
    {
880
 
      case PROP_TAG_BUTTON_VISIBLE:
881
 
        gd_tagged_entry_set_tag_button_visible (self, g_value_get_boolean (value));
882
 
        break;
883
 
      default:
884
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
885
 
    }
886
 
}
887
 
 
888
 
static void
889
 
gd_tagged_entry_class_init (GdTaggedEntryClass *klass)
890
 
{
891
 
  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
892
 
  GtkEntryClass *eclass = GTK_ENTRY_CLASS (klass);
893
 
  GObjectClass *oclass = G_OBJECT_CLASS (klass);
894
 
 
895
 
  oclass->finalize = gd_tagged_entry_finalize;
896
 
  oclass->set_property = gd_tagged_entry_set_property;
897
 
  oclass->get_property = gd_tagged_entry_get_property;
898
 
 
899
 
  wclass->realize = gd_tagged_entry_realize;
900
 
  wclass->unrealize = gd_tagged_entry_unrealize;
901
 
  wclass->map = gd_tagged_entry_map;
902
 
  wclass->unmap = gd_tagged_entry_unmap;
903
 
  wclass->size_allocate = gd_tagged_entry_size_allocate;
904
 
  wclass->get_preferred_width = gd_tagged_entry_get_preferred_width;
905
 
  wclass->draw = gd_tagged_entry_draw;
906
 
  wclass->enter_notify_event = gd_tagged_entry_enter_notify;
907
 
  wclass->leave_notify_event = gd_tagged_entry_leave_notify;
908
 
  wclass->motion_notify_event = gd_tagged_entry_motion_notify;
909
 
  wclass->button_press_event = gd_tagged_entry_button_press_event;
910
 
  wclass->button_release_event = gd_tagged_entry_button_release_event;
911
 
 
912
 
  eclass->get_text_area_size = gd_tagged_entry_get_text_area_size;
913
 
 
914
 
  signals[SIGNAL_TAG_CLICKED] =
915
 
    g_signal_new ("tag-clicked",
916
 
                  GD_TYPE_TAGGED_ENTRY,
917
 
                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
918
 
                  0, NULL, NULL, NULL,
919
 
                  G_TYPE_NONE,
920
 
                  1, G_TYPE_STRING);
921
 
  signals[SIGNAL_TAG_BUTTON_CLICKED] =
922
 
    g_signal_new ("tag-button-clicked",
923
 
                  GD_TYPE_TAGGED_ENTRY,
924
 
                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
925
 
                  0, NULL, NULL, NULL,
926
 
                  G_TYPE_NONE,
927
 
                  1, G_TYPE_STRING);
928
 
 
929
 
  properties[PROP_TAG_BUTTON_VISIBLE] =
930
 
    g_param_spec_boolean ("tag-close-visible", "Tag close icon visibility",
931
 
                          "Whether the close button should be shown in tags.", TRUE,
932
 
                          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
933
 
 
934
 
  g_type_class_add_private (klass, sizeof (GdTaggedEntryPrivate));
935
 
  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
936
 
}
937
 
 
938
 
GdTaggedEntry *
939
 
gd_tagged_entry_new (void)
940
 
{
941
 
  return g_object_new (GD_TYPE_TAGGED_ENTRY, NULL);
942
 
}
943
 
 
944
 
gboolean
945
 
gd_tagged_entry_add_tag (GdTaggedEntry *self,
946
 
                         const gchar *id,
947
 
                         const gchar *name)
948
 
{
949
 
  GdTaggedEntryTag *tag;
950
 
 
951
 
  if (gd_tagged_entry_find_tag_by_id (self, id) != NULL)
952
 
    return FALSE;
953
 
 
954
 
  tag = gd_tagged_entry_tag_new (id, name);
955
 
  self->priv->tags = g_list_append (self->priv->tags, tag);
956
 
 
957
 
  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
958
 
    {
959
 
      gd_tagged_entry_tag_realize (tag, self);
960
 
      gdk_window_show_unraised (tag->window);
961
 
    }
962
 
 
963
 
  gtk_widget_queue_resize (GTK_WIDGET (self));
964
 
 
965
 
  return TRUE;
966
 
}
967
 
 
968
 
gboolean
969
 
gd_tagged_entry_remove_tag (GdTaggedEntry *self,
970
 
                            const gchar *id)
971
 
{
972
 
  GdTaggedEntryTag *tag;
973
 
  gboolean res = FALSE;
974
 
 
975
 
  tag = gd_tagged_entry_find_tag_by_id (self, id);
976
 
 
977
 
  if (tag != NULL)
978
 
    {
979
 
      res = TRUE;
980
 
      self->priv->tags = g_list_remove (self->priv->tags, tag);
981
 
      gd_tagged_entry_tag_free (tag);
982
 
 
983
 
      gtk_widget_queue_resize (GTK_WIDGET (self));
984
 
    }
985
 
 
986
 
  return res;
987
 
}
988
 
 
989
 
gboolean
990
 
gd_tagged_entry_set_tag_label (GdTaggedEntry *self,
991
 
                               const gchar *tag_id,
992
 
                               const gchar *label)
993
 
{
994
 
  GdTaggedEntryTag *tag;
995
 
  gboolean res = FALSE;
996
 
 
997
 
  tag = gd_tagged_entry_find_tag_by_id (self, tag_id);
998
 
 
999
 
  if (tag != NULL)
1000
 
    {
1001
 
      res = TRUE;
1002
 
 
1003
 
      if (g_strcmp0 (tag->label, label) != 0)
1004
 
        {
1005
 
          g_free (tag->label);
1006
 
          tag->label = g_strdup (label);
1007
 
          g_clear_object (&tag->layout);
1008
 
 
1009
 
          gtk_widget_queue_resize (GTK_WIDGET (self));
1010
 
        }
1011
 
    }
1012
 
 
1013
 
  return res;  
1014
 
}
1015
 
 
1016
 
void
1017
 
gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self,
1018
 
                                        gboolean       visible)
1019
 
{
1020
 
  g_return_if_fail (GD_IS_TAGGED_ENTRY (self));
1021
 
 
1022
 
  if (self->priv->button_visible == visible)
1023
 
    return;
1024
 
 
1025
 
  self->priv->button_visible = visible;
1026
 
  gtk_widget_queue_resize (GTK_WIDGET (self));
1027
 
 
1028
 
  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TAG_BUTTON_VISIBLE]);
1029
 
}
1030
 
 
1031
 
gboolean
1032
 
gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self)
1033
 
{
1034
 
  g_return_val_if_fail (GD_IS_TAGGED_ENTRY (self), FALSE);
1035
 
 
1036
 
  return self->priv->button_visible;
1037
 
}