[nautilus/wip/ernestask/gtk4-continued: 5/118] general: Use an in-tree copy of libgd code



commit 19d1a90a6c4508e0bd5145f5e3021518c31e316c
Author: Ernestas Kulik <ernestask gnome org>
Date:   Fri Jun 22 11:15:11 2018 +0300

    general: Use an in-tree copy of libgd code
    
    This will make it easier to do the GTK+ 4 port, as no external code
    will be built.

 .gitmodules                         |    4 -
 meson.build                         |   11 -
 src/libgd/gd-styled-text-renderer.c |  124 ++++
 src/libgd/gd-styled-text-renderer.h |   79 +++
 src/libgd/gd-tagged-entry.c         | 1242 +++++++++++++++++++++++++++++++++++
 src/libgd/gd-tagged-entry.h         |  117 ++++
 src/meson.build                     |    7 +-
 src/nautilus-list-view.c            |    3 +-
 src/nautilus-query-editor.c         |    2 +-
 src/nautilus-ui-utilities.c         |  109 ++-
 subprojects/libgd                   |    1 -
 11 files changed, 1675 insertions(+), 24 deletions(-)
---
diff --git a/meson.build b/meson.build
index 74a441f8e..c9c43ec74 100644
--- a/meson.build
+++ b/meson.build
@@ -106,17 +106,6 @@ pkgconfig = import('pkgconfig')
 ################
 glib_ver = '>= 2.55.1'
 
-libgd = subproject(
-  'libgd',
-  default_options: [
-    'static=true',
-    'with-gtk-hacks=true',
-    'with-main-view=true',
-    'with-tagged-entry=true'
-  ]
-)
-libgd_dep = libgd.get_variable('libgd_dep')
-
 libm = cc.find_library('m')
 
 if get_option('extensions')
diff --git a/src/libgd/gd-styled-text-renderer.c b/src/libgd/gd-styled-text-renderer.c
new file mode 100644
index 000000000..50a315e21
--- /dev/null
+++ b/src/libgd/gd-styled-text-renderer.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program 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 program 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 License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include "gd-styled-text-renderer.h"
+
+G_DEFINE_TYPE (GdStyledTextRenderer, gd_styled_text_renderer, GTK_TYPE_CELL_RENDERER_TEXT);
+
+struct _GdStyledTextRendererPrivate {
+  GList *style_classes;
+};
+
+static void
+gd_styled_text_renderer_render (GtkCellRenderer      *cell,
+                                cairo_t              *cr,
+                                GtkWidget            *widget,
+                                const GdkRectangle   *background_area,
+                                const GdkRectangle   *cell_area,
+                                GtkCellRendererState  flags)
+{
+  GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (cell);
+  GtkStyleContext *context;
+  const gchar *style_class;
+  GList *l;
+
+  context = gtk_widget_get_style_context (widget);
+  gtk_style_context_save (context);
+
+  for (l = self->priv->style_classes; l != NULL; l = l->next)
+    {
+      style_class = l->data;
+      gtk_style_context_add_class (context, style_class);
+    }
+
+  GTK_CELL_RENDERER_CLASS (gd_styled_text_renderer_parent_class)->render 
+    (cell, cr, widget,
+     background_area, cell_area, flags);
+
+  gtk_style_context_restore (context);
+}
+
+static void
+gd_styled_text_renderer_finalize (GObject *obj)
+{
+  GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (obj);
+
+  if (self->priv->style_classes != NULL)
+    {
+      g_list_free_full (self->priv->style_classes, g_free);
+      self->priv->style_classes = NULL;
+    }
+
+  G_OBJECT_CLASS (gd_styled_text_renderer_parent_class)->finalize (obj);
+}
+
+static void
+gd_styled_text_renderer_class_init (GdStyledTextRendererClass *klass)
+{
+  GtkCellRendererClass *crclass = GTK_CELL_RENDERER_CLASS (klass);
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+  oclass->finalize = gd_styled_text_renderer_finalize;
+  crclass->render = gd_styled_text_renderer_render;
+
+  g_type_class_add_private (klass, sizeof (GdStyledTextRendererPrivate));
+}
+
+static void
+gd_styled_text_renderer_init (GdStyledTextRenderer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_STYLED_TEXT_RENDERER,
+                                            GdStyledTextRendererPrivate);
+}
+
+GtkCellRenderer *
+gd_styled_text_renderer_new (void)
+{
+  return g_object_new (GD_TYPE_STYLED_TEXT_RENDERER,
+                       NULL);
+}
+
+void
+gd_styled_text_renderer_add_class (GdStyledTextRenderer *self,
+                                   const gchar *class)
+{
+  if (g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0))
+    return;
+
+  self->priv->style_classes = g_list_append (self->priv->style_classes, g_strdup (class));
+}
+
+void
+gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self,
+                                      const gchar *class)
+{
+  GList *class_element;
+
+  class_element = g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0);
+
+  if (class_element == NULL)
+    return;
+
+  self->priv->style_classes = g_list_remove_link (self->priv->style_classes,
+                                                  class_element);
+  g_free (class_element->data);
+  g_list_free_1 (class_element);
+}
diff --git a/src/libgd/gd-styled-text-renderer.h b/src/libgd/gd-styled-text-renderer.h
new file mode 100644
index 000000000..fc1995b1c
--- /dev/null
+++ b/src/libgd/gd-styled-text-renderer.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program 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 program 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 License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef _GD_STYLED_TEXT_RENDERER_H
+#define _GD_STYLED_TEXT_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_STYLED_TEXT_RENDERER gd_styled_text_renderer_get_type()
+
+#define GD_STYLED_TEXT_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRenderer))
+
+#define GD_STYLED_TEXT_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass))
+
+#define GD_IS_STYLED_TEXT_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   GD_TYPE_STYLED_TEXT_RENDERER))
+
+#define GD_IS_STYLED_TEXT_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   GD_TYPE_STYLED_TEXT_RENDERER))
+
+#define GD_STYLED_TEXT_RENDERER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass))
+
+typedef struct _GdStyledTextRenderer GdStyledTextRenderer;
+typedef struct _GdStyledTextRendererClass GdStyledTextRendererClass;
+typedef struct _GdStyledTextRendererPrivate GdStyledTextRendererPrivate;
+
+struct _GdStyledTextRenderer
+{
+  GtkCellRendererText parent;
+
+  GdStyledTextRendererPrivate *priv;
+};
+
+struct _GdStyledTextRendererClass
+{
+  GtkCellRendererTextClass parent_class;
+};
+
+GType gd_styled_text_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_styled_text_renderer_new (void);
+void gd_styled_text_renderer_add_class (GdStyledTextRenderer *self,
+                                        const gchar *class);
+void gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self,
+                                           const gchar *class);
+
+G_END_DECLS
+
+#endif /* _GD_STYLED_TEXT_RENDERER_H */
diff --git a/src/libgd/gd-tagged-entry.c b/src/libgd/gd-tagged-entry.c
new file mode 100644
index 000000000..380962c60
--- /dev/null
+++ b/src/libgd/gd-tagged-entry.c
@@ -0,0 +1,1242 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2013 Ignacio Casal Quinteiro
+ *
+ * This program 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 program 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 License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include "gd-tagged-entry.h"
+
+#include <math.h>
+
+#define BUTTON_INTERNAL_SPACING 6
+
+struct _GdTaggedEntryTagPrivate {
+  GdTaggedEntry *entry;
+  GdkWindow *window;
+  PangoLayout *layout;
+
+  gchar *label;
+  gchar *style;
+  gboolean has_close_button;
+
+  cairo_surface_t *close_surface;
+  GtkStateFlags last_button_state;
+};
+
+struct _GdTaggedEntryPrivate {
+  GList *tags;
+
+  GdTaggedEntryTag *in_child;
+  gboolean in_child_button;
+  gboolean in_child_active;
+  gboolean in_child_button_active;
+  gboolean button_visible;
+};
+
+enum {
+  SIGNAL_TAG_CLICKED,
+  SIGNAL_TAG_BUTTON_CLICKED,
+  LAST_SIGNAL
+};
+
+enum {
+  PROP_0,
+  PROP_TAG_BUTTON_VISIBLE,
+  NUM_PROPERTIES
+};
+
+enum {
+  PROP_TAG_0,
+  PROP_TAG_LABEL,
+  PROP_TAG_HAS_CLOSE_BUTTON,
+  PROP_TAG_STYLE,
+  NUM_TAG_PROPERTIES
+};
+
+G_DEFINE_TYPE (GdTaggedEntry, gd_tagged_entry, GTK_TYPE_SEARCH_ENTRY)
+G_DEFINE_TYPE (GdTaggedEntryTag, gd_tagged_entry_tag, G_TYPE_OBJECT)
+
+static guint signals[LAST_SIGNAL] = { 0, };
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static GParamSpec *tag_properties[NUM_TAG_PROPERTIES] = { NULL, };
+
+static void gd_tagged_entry_get_text_area_size (GtkEntry *entry,
+                                                gint *x,
+                                                gint *y,
+                                                gint *width,
+                                                gint *height);
+static gint gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
+                                           GdTaggedEntry *entry);
+static GtkStyleContext * gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag,
+                                                          GdTaggedEntry *entry);
+
+static void
+gd_tagged_entry_tag_get_margin (GdTaggedEntryTag *tag,
+                                GdTaggedEntry *entry,
+                                GtkBorder *margin)
+{
+  GtkStyleContext *context;
+
+  context = gd_tagged_entry_tag_get_context (tag, entry);
+  gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+  gtk_style_context_get_margin (context,
+                                gtk_style_context_get_state (context),
+                                margin);
+  gtk_style_context_restore (context);
+}
+
+static void
+gd_tagged_entry_tag_ensure_close_surface (GdTaggedEntryTag *tag,
+                                          GtkStyleContext *context)
+{
+  GtkIconInfo *info;
+  GdkPixbuf *pixbuf;
+  gint icon_size;
+  gint scale_factor;
+
+  if (tag->priv->close_surface != NULL)
+    return;
+
+  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
+                        &icon_size, NULL);
+  scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (tag->priv->entry));
+
+  info = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
+                                               "window-close-symbolic",
+                                               icon_size, scale_factor,
+                                               GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+
+  /* FIXME: we need a fallback icon in case the icon is not found */
+  pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL);
+  tag->priv->close_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, tag->priv->window);
+
+  g_object_unref (info);
+  g_object_unref (pixbuf);
+}
+
+static gint
+gd_tagged_entry_tag_panel_get_height (GdTaggedEntryTag *tag,
+                                      GdTaggedEntry *entry)
+{
+  GtkWidget *widget = GTK_WIDGET (entry);
+  gint height, req_height;
+  GtkRequisition requisition;
+  GtkAllocation allocation;
+  GtkBorder margin;
+
+  gtk_widget_get_allocation (widget, &allocation);
+  gtk_widget_get_preferred_size (widget, &requisition, NULL);
+  gd_tagged_entry_tag_get_margin (tag, entry, &margin);
+
+  /* the tag panel height is the whole entry height, minus the tag margins */
+  req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom 
(widget);
+  height = MIN (req_height, allocation.height) - margin.top - margin.bottom;
+
+  return height;
+}
+
+static void
+gd_tagged_entry_tag_panel_get_position (GdTaggedEntry *self,
+                                        gint *x_out, 
+                                        gint *y_out)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  gint text_x, text_y, text_width, text_height, req_height;
+  GtkAllocation allocation;
+  GtkRequisition requisition;
+
+  gtk_widget_get_allocation (widget, &allocation);
+  gtk_widget_get_preferred_size (widget, &requisition, NULL);
+  req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom 
(widget);
+
+  gd_tagged_entry_get_text_area_size (GTK_ENTRY (self), &text_x, &text_y, &text_width, &text_height);
+
+  /* allocate the panel immediately after the text area */
+  if (x_out)
+    *x_out = allocation.x + text_x + text_width;
+  if (y_out)
+    *y_out = allocation.y + (gint) floor ((allocation.height - req_height) / 2);
+}
+
+static gint
+gd_tagged_entry_tag_panel_get_width (GdTaggedEntry *self)
+{
+  GdTaggedEntryTag *tag;
+  gint width;
+  GList *l;
+
+  width = 0;
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      tag = l->data;
+      width += gd_tagged_entry_tag_get_width (tag, self);
+    }
+
+  return width;
+}
+
+static void
+gd_tagged_entry_tag_ensure_layout (GdTaggedEntryTag *tag,
+                                   GdTaggedEntry *entry)
+{
+  if (tag->priv->layout != NULL)
+    return;
+
+  tag->priv->layout = pango_layout_new (gtk_widget_get_pango_context (GTK_WIDGET (entry)));
+  pango_layout_set_text (tag->priv->layout, tag->priv->label, -1);
+}
+
+static GtkStateFlags
+gd_tagged_entry_tag_get_state (GdTaggedEntryTag *tag,
+                               GdTaggedEntry *entry)
+{
+  GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
+
+  if (entry->priv->in_child == tag)
+    state |= GTK_STATE_FLAG_PRELIGHT;
+
+  if (entry->priv->in_child_active)
+    state |= GTK_STATE_FLAG_ACTIVE;
+
+  return state;
+}
+
+static GtkStateFlags
+gd_tagged_entry_tag_get_button_state (GdTaggedEntryTag *tag,
+                                      GdTaggedEntry *entry)
+{
+  GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
+
+  if (entry->priv->in_child == tag)
+    {
+      if (entry->priv->in_child_button_active)
+        state |= GTK_STATE_FLAG_ACTIVE;
+
+      else if (entry->priv->in_child_button)
+        state |= GTK_STATE_FLAG_PRELIGHT;
+    }
+
+  return state;
+}
+
+static GtkStyleContext *
+gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag,
+                                 GdTaggedEntry    *entry)
+{
+  GtkWidget *widget = GTK_WIDGET (entry);
+  GtkStyleContext *retval;
+  GList *l, *list;
+
+  retval = gtk_widget_get_style_context (widget);
+  gtk_style_context_save (retval);
+
+  list = gtk_style_context_list_classes (retval);
+  for (l = list; l; l = l->next)
+    gtk_style_context_remove_class (retval, l->data);
+  g_list_free (list);
+  gtk_style_context_add_class (retval, tag->priv->style);
+
+  return retval;
+}
+
+static gint
+gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
+                               GdTaggedEntry *entry)
+{
+  GtkBorder button_padding, button_border, button_margin;
+  GtkStyleContext *context;
+  GtkStateFlags state;
+  gint layout_width;
+  gint button_width;
+  gint scale_factor;
+
+  gd_tagged_entry_tag_ensure_layout (tag, entry);
+  pango_layout_get_pixel_size (tag->priv->layout, &layout_width, NULL);
+
+  context = gd_tagged_entry_tag_get_context (tag, entry);
+  state = gd_tagged_entry_tag_get_state (tag, entry);
+
+  gtk_style_context_set_state (context, state);
+  gtk_style_context_get_padding (context,
+                                 gtk_style_context_get_state (context),
+                                 &button_padding);
+  gtk_style_context_get_border (context,
+                                gtk_style_context_get_state (context),
+                                &button_border);
+  gtk_style_context_get_margin (context,
+                                gtk_style_context_get_state (context),
+                                &button_margin);
+
+  gd_tagged_entry_tag_ensure_close_surface (tag, context);
+
+  gtk_style_context_restore (context);
+
+  button_width = 0;
+  if (entry->priv->button_visible && tag->priv->has_close_button)
+    {
+      scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (entry));
+      button_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor +
+        BUTTON_INTERNAL_SPACING;
+    }
+
+  return layout_width + button_padding.left + button_padding.right +
+    button_border.left + button_border.right +
+    button_margin.left + button_margin.right +
+    button_width;
+}
+
+static void
+gd_tagged_entry_tag_get_size (GdTaggedEntryTag *tag,
+                              GdTaggedEntry *entry,
+                              gint *width_out,
+                              gint *height_out)
+{
+  gint width, panel_height;
+
+  width = gd_tagged_entry_tag_get_width (tag, entry);
+  panel_height = gd_tagged_entry_tag_panel_get_height (tag, entry);
+
+  if (width_out)
+    *width_out = width;
+  if (height_out)
+    *height_out = panel_height;
+}
+
+static void
+gd_tagged_entry_tag_get_relative_allocations (GdTaggedEntryTag *tag,
+                                              GdTaggedEntry *entry,
+                                              GtkStyleContext *context,
+                                              GtkAllocation *background_allocation_out,
+                                              GtkAllocation *layout_allocation_out,
+                                              GtkAllocation *button_allocation_out)
+{
+  GtkAllocation background_allocation, layout_allocation, button_allocation;
+  gint width, height, x, y, pix_width, pix_height;
+  gint layout_width, layout_height;
+  gint scale_factor;
+  GtkBorder padding, border;
+  GtkStateFlags state;
+
+  width = gdk_window_get_width (tag->priv->window);
+  height = gdk_window_get_height (tag->priv->window);
+  scale_factor = gdk_window_get_scale_factor (tag->priv->window);
+
+  state = gd_tagged_entry_tag_get_state (tag, entry);
+  gtk_style_context_save (context);
+  gtk_style_context_set_state (context, state);
+  gtk_style_context_get_margin (context,
+                                gtk_style_context_get_state (context),
+                                &padding);
+  gtk_style_context_restore (context);
+
+  width -= padding.left + padding.right;
+  height -= padding.top + padding.bottom;
+  x = padding.left;
+  y = padding.top;
+
+  background_allocation.x = x;
+  background_allocation.y = y;
+  background_allocation.width = width;
+  background_allocation.height = height;
+
+  layout_allocation = button_allocation = background_allocation;
+
+  gtk_style_context_save (context);
+  gtk_style_context_set_state (context, state);
+  gtk_style_context_get_padding (context,
+                                 gtk_style_context_get_state (context),
+                                 &padding);
+  gtk_style_context_get_border (context,
+                                gtk_style_context_get_state (context),
+                                &border);
+  gtk_style_context_restore (context);
+
+  gd_tagged_entry_tag_ensure_layout (tag, entry);
+  pango_layout_get_pixel_size (tag->priv->layout, &layout_width, &layout_height);
+
+  layout_allocation.x += border.left + padding.left;
+  layout_allocation.y += (layout_allocation.height - layout_height) / 2;
+
+  if (entry->priv->button_visible && tag->priv->has_close_button)
+    {
+      pix_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor;
+      pix_height = cairo_image_surface_get_height (tag->priv->close_surface) / scale_factor;
+    }
+  else
+    {
+      pix_width = 0;
+      pix_height = 0;
+    }
+
+  button_allocation.x += width - pix_width - border.right - padding.right;
+  button_allocation.y += (height - pix_height) / 2;
+  button_allocation.width = pix_width;
+  button_allocation.height = pix_height;
+
+  if (background_allocation_out)
+    *background_allocation_out = background_allocation;
+  if (layout_allocation_out)
+    *layout_allocation_out = layout_allocation;
+  if (button_allocation_out)
+    *button_allocation_out = button_allocation;
+}
+
+static gboolean
+gd_tagged_entry_tag_event_is_button (GdTaggedEntryTag *tag,
+                                     GdTaggedEntry *entry,
+                                     gdouble event_x,
+                                     gdouble event_y)
+{
+  GtkAllocation button_allocation;
+  GtkStyleContext *context;
+
+  if (!entry->priv->button_visible || !tag->priv->has_close_button)
+    return FALSE;
+
+  context = gd_tagged_entry_tag_get_context (tag, entry);
+  gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, NULL, NULL, &button_allocation);
+
+  gtk_style_context_restore (context);
+
+  /* see if the event falls into the button allocation */
+  if ((event_x >= button_allocation.x && 
+       event_x <= button_allocation.x + button_allocation.width) &&
+      (event_y >= button_allocation.y &&
+       event_y <= button_allocation.y + button_allocation.height))
+    return TRUE;
+
+  return FALSE;
+}
+
+gboolean
+gd_tagged_entry_tag_get_area (GdTaggedEntryTag      *tag,
+                              cairo_rectangle_int_t *rect)
+{
+  GtkStyleContext *context;
+  GtkAllocation background_allocation;
+  int window_x, window_y;
+  GtkAllocation alloc;
+
+  g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE);
+  g_return_val_if_fail (rect != NULL, FALSE);
+
+  gdk_window_get_position (tag->priv->window, &window_x, &window_y);
+  gtk_widget_get_allocation (GTK_WIDGET (tag->priv->entry), &alloc);
+  context = gd_tagged_entry_tag_get_context (tag, tag->priv->entry);
+  gd_tagged_entry_tag_get_relative_allocations (tag, tag->priv->entry, context,
+                                                &background_allocation,
+                                                NULL, NULL);
+  gtk_style_context_restore (context);
+
+  rect->x = window_x - alloc.x + background_allocation.x;
+  rect->y = window_y - alloc.y + background_allocation.y;
+  rect->width = background_allocation.width;
+  rect->height = background_allocation.height;
+
+  return TRUE;
+}
+
+static void
+gd_tagged_entry_tag_draw (GdTaggedEntryTag *tag,
+                          cairo_t *cr,
+                          GdTaggedEntry *entry)
+{
+  GtkStyleContext *context;
+  GtkStateFlags state;
+  GtkAllocation background_allocation, layout_allocation, button_allocation;
+
+  context = gd_tagged_entry_tag_get_context (tag, entry);
+  gd_tagged_entry_tag_get_relative_allocations (tag, entry, context,
+                                                &background_allocation,
+                                                &layout_allocation,
+                                                &button_allocation);
+
+  cairo_save (cr);
+  gtk_cairo_transform_to_window (cr, GTK_WIDGET (entry), tag->priv->window);
+
+  gtk_style_context_save (context);
+
+  state = gd_tagged_entry_tag_get_state (tag, entry);
+  gtk_style_context_set_state (context, state);
+  gtk_render_background (context, cr,
+                         background_allocation.x, background_allocation.y,
+                         background_allocation.width, background_allocation.height); 
+  gtk_render_frame (context, cr,
+                    background_allocation.x, background_allocation.y,
+                    background_allocation.width, background_allocation.height); 
+
+  gtk_render_layout (context, cr,
+                     layout_allocation.x, layout_allocation.y,
+                     tag->priv->layout);
+
+  gtk_style_context_restore (context);
+
+  if (!entry->priv->button_visible || !tag->priv->has_close_button)
+    goto done;
+
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+  state = gd_tagged_entry_tag_get_button_state (tag, entry);
+  gtk_style_context_set_state (context, state);
+
+  /* if the state changed since last time we draw the pixbuf,
+   * clear and redraw it.
+   */
+  if (state != tag->priv->last_button_state)
+    {
+      g_clear_pointer (&tag->priv->close_surface, cairo_surface_destroy);
+      gd_tagged_entry_tag_ensure_close_surface (tag, context);
+
+      tag->priv->last_button_state = state;
+    }
+
+  gtk_render_background (context, cr,
+                         button_allocation.x, button_allocation.y,
+                         button_allocation.width, button_allocation.height);
+  gtk_render_frame (context, cr,
+                         button_allocation.x, button_allocation.y,
+                         button_allocation.width, button_allocation.height);
+
+  gtk_render_icon_surface (context, cr,
+                           tag->priv->close_surface,
+                           button_allocation.x, button_allocation.y);
+
+done:
+  gtk_style_context_restore (context);
+
+  cairo_restore (cr);
+}
+
+static void
+gd_tagged_entry_tag_unrealize (GdTaggedEntryTag *tag)
+{
+  if (tag->priv->window == NULL)
+    return;
+
+  gdk_window_set_user_data (tag->priv->window, NULL);
+  gdk_window_destroy (tag->priv->window);
+  tag->priv->window = NULL;
+}
+
+static void
+gd_tagged_entry_tag_realize (GdTaggedEntryTag *tag,
+                             GdTaggedEntry *entry)
+{
+  GtkWidget *widget = GTK_WIDGET (entry);
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  gint tag_width, tag_height;
+
+  if (tag->priv->window != NULL)
+    return;
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= GDK_BUTTON_PRESS_MASK
+    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
+    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
+
+  gd_tagged_entry_tag_get_size (tag, entry, &tag_width, &tag_height);
+  attributes.x = 0;
+  attributes.y = 0;
+  attributes.width = tag_width;
+  attributes.height = tag_height;
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+  tag->priv->window = gdk_window_new (gtk_widget_get_window (widget),
+                                &attributes, attributes_mask);
+  gdk_window_set_user_data (tag->priv->window, widget);
+}
+
+static gboolean
+gd_tagged_entry_draw (GtkWidget *widget,
+                      cairo_t *cr)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->draw (widget, cr);
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      tag = l->data;
+      gd_tagged_entry_tag_draw (tag, cr, self);
+    }
+
+  return FALSE;
+}
+
+static void
+gd_tagged_entry_map (GtkWidget *widget)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
+    {
+      GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->map (widget);
+
+      for (l = self->priv->tags; l != NULL; l = l->next)
+        {
+          tag = l->data;
+          gdk_window_show (tag->priv->window);
+        }
+    }
+}
+
+static void
+gd_tagged_entry_unmap (GtkWidget *widget)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  if (gtk_widget_get_mapped (widget))
+    {
+      for (l = self->priv->tags; l != NULL; l = l->next)
+        {
+          tag = l->data;
+          gdk_window_hide (tag->priv->window);
+        }
+
+      GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unmap (widget);
+    }
+}
+
+static void
+gd_tagged_entry_realize (GtkWidget *widget)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->realize (widget);
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      tag = l->data;
+      gd_tagged_entry_tag_realize (tag, self);
+    }
+}
+
+static void
+gd_tagged_entry_unrealize (GtkWidget *widget)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unrealize (widget);
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      tag = l->data;
+      gd_tagged_entry_tag_unrealize (tag);
+    }
+}
+
+static void
+gd_tagged_entry_get_text_area_size (GtkEntry *entry,
+                                    gint *x,
+                                    gint *y,
+                                    gint *width,
+                                    gint *height)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (entry);
+  gint tag_panel_width;
+
+  GTK_ENTRY_CLASS (gd_tagged_entry_parent_class)->get_text_area_size (entry, x, y, width, height);
+
+  tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
+
+  if (width)
+    *width -= tag_panel_width;
+}
+
+static void
+gd_tagged_entry_size_allocate (GtkWidget *widget,
+                               GtkAllocation *allocation)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  gint x, y, width, height;
+  GdTaggedEntryTag *tag;
+  GList *l;
+
+  gtk_widget_set_allocation (widget, allocation);
+  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->size_allocate (widget, allocation);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      gd_tagged_entry_tag_panel_get_position (self, &x, &y);
+
+      for (l = self->priv->tags; l != NULL; l = l->next)
+        {
+          GtkBorder margin;
+
+          tag = l->data;
+          gd_tagged_entry_tag_get_size (tag, self, &width, &height);
+          gd_tagged_entry_tag_get_margin (tag, self, &margin);
+          gdk_window_move_resize (tag->priv->window, x, y + margin.top, width, height);
+
+          x += width;
+        }
+
+      gtk_widget_queue_draw (widget);
+    }
+}
+
+static void
+gd_tagged_entry_get_preferred_width (GtkWidget *widget,
+                                     gint *minimum,
+                                     gint *natural)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  gint tag_panel_width;
+
+  GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->get_preferred_width (widget, minimum, natural);
+
+  tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
+
+  if (minimum)
+    *minimum += tag_panel_width;
+  if (natural)
+    *natural += tag_panel_width;
+}
+
+static void
+gd_tagged_entry_finalize (GObject *obj)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (obj);
+
+  if (self->priv->tags != NULL)
+    {
+      g_list_free_full (self->priv->tags, g_object_unref);
+      self->priv->tags = NULL;
+    }
+
+  G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj);
+}
+
+static GdTaggedEntryTag *
+gd_tagged_entry_find_tag_by_window (GdTaggedEntry *self,
+                                    GdkWindow *window)
+{
+  GdTaggedEntryTag *tag = NULL, *elem;
+  GList *l;
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      elem = l->data;
+      if (elem->priv->window == window)
+        {
+          tag = elem;
+          break;
+        }
+    }
+
+  return tag;
+}
+
+static gint
+gd_tagged_entry_enter_notify (GtkWidget        *widget,
+                              GdkEventCrossing *event)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+
+  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+  if (tag != NULL)
+    {
+      self->priv->in_child = tag;
+      gtk_widget_queue_draw (widget);
+    }
+
+  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->enter_notify_event (widget, event);
+}
+
+static gint
+gd_tagged_entry_leave_notify (GtkWidget        *widget,
+                              GdkEventCrossing *event)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+
+  if (self->priv->in_child != NULL)
+    {
+      self->priv->in_child = NULL;
+      gtk_widget_queue_draw (widget);
+    }
+
+  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->leave_notify_event (widget, event);
+}
+
+static gint
+gd_tagged_entry_motion_notify (GtkWidget      *widget,
+                               GdkEventMotion *event)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+
+  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+  if (tag != NULL)
+    {
+      gdk_event_request_motions (event);
+
+      self->priv->in_child = tag;
+      self->priv->in_child_button = gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y);
+      gtk_widget_queue_draw (widget);
+
+      return FALSE;
+    }
+
+  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->motion_notify_event (widget, event);
+}
+
+static gboolean
+gd_tagged_entry_button_release_event (GtkWidget *widget,
+                                      GdkEventButton *event)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+
+  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+  if (tag != NULL)
+    {
+      self->priv->in_child_active = FALSE;
+
+      if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
+        {
+          self->priv->in_child_button_active = FALSE;
+          g_signal_emit (self, signals[SIGNAL_TAG_BUTTON_CLICKED], 0, tag);
+        }
+      else
+        {
+          g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], 0, tag);
+        }
+
+      gtk_widget_queue_draw (widget);
+
+      return TRUE;
+    }
+
+  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_release_event (widget, event);
+}
+
+static gboolean
+gd_tagged_entry_button_press_event (GtkWidget *widget,
+                                    GdkEventButton *event)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+  GdTaggedEntryTag *tag;
+
+  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+  if (tag != NULL)
+    {
+      if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
+        self->priv->in_child_button_active = TRUE;
+      else
+        self->priv->in_child_active = TRUE;
+
+      gtk_widget_queue_draw (widget);
+
+      return TRUE;
+    }
+
+  return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_press_event (widget, event);
+}
+
+static void
+gd_tagged_entry_init (GdTaggedEntry *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY, GdTaggedEntryPrivate);
+  self->priv->button_visible = TRUE;
+}
+
+static void
+gd_tagged_entry_get_property (GObject      *object,
+                              guint         property_id,
+                              GValue       *value,
+                              GParamSpec   *pspec)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
+
+  switch (property_id)
+    {
+      case PROP_TAG_BUTTON_VISIBLE:
+        g_value_set_boolean (value, gd_tagged_entry_get_tag_button_visible (self));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+gd_tagged_entry_set_property (GObject      *object,
+                              guint         property_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
+
+  switch (property_id)
+    {
+      case PROP_TAG_BUTTON_VISIBLE:
+        gd_tagged_entry_set_tag_button_visible (self, g_value_get_boolean (value));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+gd_tagged_entry_class_init (GdTaggedEntryClass *klass)
+{
+  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+  GtkEntryClass *eclass = GTK_ENTRY_CLASS (klass);
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+  oclass->finalize = gd_tagged_entry_finalize;
+  oclass->set_property = gd_tagged_entry_set_property;
+  oclass->get_property = gd_tagged_entry_get_property;
+
+  wclass->realize = gd_tagged_entry_realize;
+  wclass->unrealize = gd_tagged_entry_unrealize;
+  wclass->map = gd_tagged_entry_map;
+  wclass->unmap = gd_tagged_entry_unmap;
+  wclass->size_allocate = gd_tagged_entry_size_allocate;
+  wclass->get_preferred_width = gd_tagged_entry_get_preferred_width;
+  wclass->draw = gd_tagged_entry_draw;
+  wclass->enter_notify_event = gd_tagged_entry_enter_notify;
+  wclass->leave_notify_event = gd_tagged_entry_leave_notify;
+  wclass->motion_notify_event = gd_tagged_entry_motion_notify;
+  wclass->button_press_event = gd_tagged_entry_button_press_event;
+  wclass->button_release_event = gd_tagged_entry_button_release_event;
+
+  eclass->get_text_area_size = gd_tagged_entry_get_text_area_size;
+
+  signals[SIGNAL_TAG_CLICKED] =
+    g_signal_new ("tag-clicked",
+                  GD_TYPE_TAGGED_ENTRY,
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1, GD_TYPE_TAGGED_ENTRY_TAG);
+  signals[SIGNAL_TAG_BUTTON_CLICKED] =
+    g_signal_new ("tag-button-clicked",
+                  GD_TYPE_TAGGED_ENTRY,
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1, GD_TYPE_TAGGED_ENTRY_TAG);
+
+  properties[PROP_TAG_BUTTON_VISIBLE] =
+    g_param_spec_boolean ("tag-close-visible", "Tag close icon visibility",
+                          "Whether the close button should be shown in tags.", TRUE,
+                          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+  g_type_class_add_private (klass, sizeof (GdTaggedEntryPrivate));
+  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_tagged_entry_tag_init (GdTaggedEntryTag *self)
+{
+  GdTaggedEntryTagPrivate *priv;
+
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagPrivate);
+  priv = self->priv;
+
+  priv->last_button_state = GTK_STATE_FLAG_NORMAL;
+}
+
+static void
+gd_tagged_entry_tag_finalize (GObject *obj)
+{
+  GdTaggedEntryTag *tag = GD_TAGGED_ENTRY_TAG (obj);
+  GdTaggedEntryTagPrivate *priv = tag->priv;
+
+  if (priv->window != NULL)
+    gd_tagged_entry_tag_unrealize (tag);
+
+  g_clear_object (&priv->layout);
+  g_clear_pointer (&priv->close_surface, cairo_surface_destroy);
+  g_free (priv->label);
+  g_free (priv->style);
+
+  G_OBJECT_CLASS (gd_tagged_entry_tag_parent_class)->finalize (obj);
+}
+
+static void
+gd_tagged_entry_tag_get_property (GObject      *object,
+                                  guint         property_id,
+                                  GValue       *value,
+                                  GParamSpec   *pspec)
+{
+  GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object);
+
+  switch (property_id)
+    {
+      case PROP_TAG_LABEL:
+        g_value_set_string (value, gd_tagged_entry_tag_get_label (self));
+        break;
+      case PROP_TAG_HAS_CLOSE_BUTTON:
+        g_value_set_boolean (value, gd_tagged_entry_tag_get_has_close_button (self));
+        break;
+      case PROP_TAG_STYLE:
+        g_value_set_string (value, gd_tagged_entry_tag_get_style (self));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+gd_tagged_entry_tag_set_property (GObject      *object,
+                                  guint         property_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object);
+
+  switch (property_id)
+    {
+      case PROP_TAG_LABEL:
+        gd_tagged_entry_tag_set_label (self, g_value_get_string (value));
+        break;
+      case PROP_TAG_HAS_CLOSE_BUTTON:
+        gd_tagged_entry_tag_set_has_close_button (self, g_value_get_boolean (value));
+        break;
+      case PROP_TAG_STYLE:
+        gd_tagged_entry_tag_set_style (self, g_value_get_string (value));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+gd_tagged_entry_tag_class_init (GdTaggedEntryTagClass *klass)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+  oclass->finalize = gd_tagged_entry_tag_finalize;
+  oclass->set_property = gd_tagged_entry_tag_set_property;
+  oclass->get_property = gd_tagged_entry_tag_get_property;
+
+  tag_properties[PROP_TAG_LABEL] =
+    g_param_spec_string ("label", "Label",
+                         "Text to show on the tag.", NULL,
+                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  tag_properties[PROP_TAG_HAS_CLOSE_BUTTON] =
+    g_param_spec_boolean ("has-close-button", "Tag has a close button",
+                          "Whether the tag has a close button.", TRUE,
+                          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  tag_properties[PROP_TAG_STYLE] =
+    g_param_spec_string ("style", "Style",
+                         "Style of the tag.", "entry-tag",
+                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_type_class_add_private (klass, sizeof (GdTaggedEntryTagPrivate));
+  g_object_class_install_properties (oclass, NUM_TAG_PROPERTIES, tag_properties);
+}
+
+GdTaggedEntry *
+gd_tagged_entry_new (void)
+{
+  return g_object_new (GD_TYPE_TAGGED_ENTRY, NULL);
+}
+
+gboolean
+gd_tagged_entry_insert_tag (GdTaggedEntry    *self,
+                            GdTaggedEntryTag *tag,
+                            gint              position)
+{
+  if (g_list_find (self->priv->tags, tag) != NULL)
+    return FALSE;
+
+  tag->priv->entry = self;
+
+  self->priv->tags = g_list_insert (self->priv->tags, g_object_ref (tag), position);
+
+  if (gtk_widget_get_realized (GTK_WIDGET (self)))
+    gd_tagged_entry_tag_realize (tag, self);
+
+  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+    gdk_window_show_unraised (tag->priv->window);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  return TRUE;
+}
+
+gboolean
+gd_tagged_entry_add_tag (GdTaggedEntry    *self,
+                         GdTaggedEntryTag *tag)
+{
+  return gd_tagged_entry_insert_tag (self, tag, -1);
+}
+
+gboolean
+gd_tagged_entry_remove_tag (GdTaggedEntry    *self,
+                            GdTaggedEntryTag *tag)
+{
+  if (!g_list_find (self->priv->tags, tag))
+    return FALSE;
+
+  gd_tagged_entry_tag_unrealize (tag);
+
+  self->priv->tags = g_list_remove (self->priv->tags, tag);
+  g_object_unref (tag);
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  return TRUE;
+}
+
+GdTaggedEntryTag *
+gd_tagged_entry_tag_new (const gchar *label)
+{
+  return g_object_new (GD_TYPE_TAGGED_ENTRY_TAG, "label", label, NULL);
+}
+
+void
+gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag,
+                               const gchar *label)
+{
+  GdTaggedEntryTagPrivate *priv;
+
+  g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+  priv = tag->priv;
+
+  if (g_strcmp0 (priv->label, label) != 0)
+    {
+      GtkWidget *entry;
+
+      g_free (priv->label);
+      priv->label = g_strdup (label);
+      g_clear_object (&priv->layout);
+
+      entry = GTK_WIDGET (tag->priv->entry);
+      if (entry)
+        gtk_widget_queue_resize (entry);
+    }
+}
+
+const gchar *
+gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag)
+{
+  g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL);
+
+  return tag->priv->label;
+}
+
+void
+gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag,
+                                          gboolean has_close_button)
+{
+  GdTaggedEntryTagPrivate *priv;
+
+  g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+  priv = tag->priv;
+
+  has_close_button = has_close_button != FALSE;
+  if (priv->has_close_button != has_close_button)
+    {
+      GtkWidget *entry;
+
+      priv->has_close_button = has_close_button;
+      g_clear_object (&priv->layout);
+
+      entry = GTK_WIDGET (priv->entry);
+      if (entry)
+        gtk_widget_queue_resize (entry);
+    }
+}
+
+gboolean
+gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag)
+{
+  g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE);
+
+  return tag->priv->has_close_button;
+}
+
+void
+gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag,
+                               const gchar *style)
+{
+  GdTaggedEntryTagPrivate *priv;
+
+  g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+  priv = tag->priv;
+
+  if (g_strcmp0 (priv->style, style) != 0)
+    {
+      GtkWidget *entry;
+
+      g_free (priv->style);
+      priv->style = g_strdup (style);
+      g_clear_object (&priv->layout);
+
+      entry = GTK_WIDGET (tag->priv->entry);
+      if (entry)
+        gtk_widget_queue_resize (entry);
+    }
+}
+
+const gchar *
+gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag)
+{
+  g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL);
+
+  return tag->priv->style;
+}
+
+void
+gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self,
+                                        gboolean       visible)
+{
+  g_return_if_fail (GD_IS_TAGGED_ENTRY (self));
+
+  if (self->priv->button_visible == visible)
+    return;
+
+  self->priv->button_visible = visible;
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TAG_BUTTON_VISIBLE]);
+}
+
+gboolean
+gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self)
+{
+  g_return_val_if_fail (GD_IS_TAGGED_ENTRY (self), FALSE);
+
+  return self->priv->button_visible;
+}
diff --git a/src/libgd/gd-tagged-entry.h b/src/libgd/gd-tagged-entry.h
new file mode 100644
index 000000000..ba9f67314
--- /dev/null
+++ b/src/libgd/gd-tagged-entry.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2013 Ignacio Casal Quinteiro
+ *
+ * This program 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 program 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 License 
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef __GD_TAGGED_ENTRY_H__
+#define __GD_TAGGED_ENTRY_H__
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TAGGED_ENTRY gd_tagged_entry_get_type()
+#define GD_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY, GdTaggedEntry))
+#define GD_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY, 
GdTaggedEntryClass))
+#define GD_IS_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY))
+#define GD_IS_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY))
+#define GD_TAGGED_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY, 
GdTaggedEntryClass))
+
+typedef struct _GdTaggedEntry GdTaggedEntry;
+typedef struct _GdTaggedEntryClass GdTaggedEntryClass;
+typedef struct _GdTaggedEntryPrivate GdTaggedEntryPrivate;
+
+typedef struct _GdTaggedEntryTag GdTaggedEntryTag;
+typedef struct _GdTaggedEntryTagClass GdTaggedEntryTagClass;
+typedef struct _GdTaggedEntryTagPrivate GdTaggedEntryTagPrivate;
+
+struct _GdTaggedEntry
+{
+  GtkSearchEntry parent;
+
+  GdTaggedEntryPrivate *priv;
+};
+
+struct _GdTaggedEntryClass
+{
+  GtkSearchEntryClass parent_class;
+};
+
+#define GD_TYPE_TAGGED_ENTRY_TAG gd_tagged_entry_tag_get_type()
+#define GD_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY_TAG, 
GdTaggedEntryTag))
+#define GD_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY_TAG, 
GdTaggedEntryTagClass))
+#define GD_IS_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY_TAG))
+#define GD_IS_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY_TAG))
+#define GD_TAGGED_ENTRY_TAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY_TAG, 
GdTaggedEntryTagClass))
+
+struct _GdTaggedEntryTag
+{
+  GObject parent;
+
+  GdTaggedEntryTagPrivate *priv;
+};
+
+struct _GdTaggedEntryTagClass
+{
+  GObjectClass parent_class;
+};
+
+GType gd_tagged_entry_get_type (void) G_GNUC_CONST;
+
+GdTaggedEntry *gd_tagged_entry_new (void);
+
+void     gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self,
+                                                 gboolean       visible);
+gboolean gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self);
+
+gboolean gd_tagged_entry_insert_tag (GdTaggedEntry    *self,
+                                     GdTaggedEntryTag *tag,
+                                     gint              position);
+
+gboolean gd_tagged_entry_add_tag (GdTaggedEntry    *self,
+                                  GdTaggedEntryTag *tag);
+
+gboolean gd_tagged_entry_remove_tag (GdTaggedEntry *self,
+                                     GdTaggedEntryTag *tag);
+
+GType gd_tagged_entry_tag_get_type (void) G_GNUC_CONST;
+
+GdTaggedEntryTag *gd_tagged_entry_tag_new (const gchar *label);
+
+void gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag,
+                                    const gchar *label);
+const gchar *gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag);
+
+void gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag,
+                                               gboolean has_close_button);
+gboolean gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag);
+
+void gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag,
+                                    const gchar *style);
+const gchar *gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag);
+
+gboolean gd_tagged_entry_tag_get_area (GdTaggedEntryTag      *tag,
+                                       cairo_rectangle_int_t *rect);
+
+G_END_DECLS
+
+#endif /* __GD_TAGGED_ENTRY_H__ */
diff --git a/src/meson.build b/src/meson.build
index a87c51414..1dfd7f68b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -62,6 +62,10 @@ libnautilus_sources = [
   'gtk/nautilusgtkplacesviewprivate.h',
   'gtk/nautilusgtkplacesviewrow.c',
   'gtk/nautilusgtkplacesviewrowprivate.h',
+  'libgd/gd-styled-text-renderer.c',
+  'libgd/gd-styled-text-renderer.h',
+  'libgd/gd-tagged-entry.c',
+  'libgd/gd-tagged-entry.h',
   'nautilus-application.c',
   'nautilus-application.h',
   'nautilus-bookmark-list.c',
@@ -267,7 +271,7 @@ libnautilus_sources = [
   'nautilus-starred-directory.c',
   'nautilus-starred-directory.h',
   'nautilus-enums.h',
-  'nautilus-types.h'
+  'nautilus-types.h',
 ]
 
 nautilus_deps = [
@@ -277,7 +281,6 @@ nautilus_deps = [
   gmodule,
   gnome_autoar,
   gsettings_desktop_schemas,
-  libgd_dep,
   nautilus_extension,
   seccomp,
   selinux,
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index b118c785a..7e204899f 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -30,9 +30,10 @@
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
-#include <libgd/gd.h>
 #include <string.h>
 
+#include "libgd/gd-styled-text-renderer.h"
+
 #define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
 #include "nautilus-debug.h"
 
diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c
index f4e380a1e..89b32ff86 100644
--- a/src/nautilus-query-editor.c
+++ b/src/nautilus-query-editor.c
@@ -26,9 +26,9 @@
 #include <gio/gio.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
-#include <libgd/gd.h>
 #include <string.h>
 
+#include "libgd/gd-tagged-entry.h"
 #include "nautilus-file.h"
 #include "nautilus-file-utilities.h"
 #include "nautilus-global-preferences.h"
diff --git a/src/nautilus-ui-utilities.c b/src/nautilus-ui-utilities.c
index 1c84ee113..812199cbb 100644
--- a/src/nautilus-ui-utilities.c
+++ b/src/nautilus-ui-utilities.c
@@ -28,7 +28,7 @@
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
-#include <libgd/gd.h>
+#include <math.h>
 #include <string.h>
 #include <glib/gi18n.h>
 
@@ -174,6 +174,107 @@ nautilus_gmenu_add_item_in_submodel (GMenu       *menu,
 #define NAUTILUS_THUMBNAIL_FRAME_RIGHT 3
 #define NAUTILUS_THUMBNAIL_FRAME_BOTTOM 3
 
+static cairo_surface_t *
+embed_surface_in_frame (cairo_surface_t *source_image,
+                        const gchar     *frame_image_url,
+                        GtkBorder       *slice_width,
+                        GtkBorder       *border_width)
+{
+    cairo_surface_t *surface;
+    cairo_t *cr;
+    gint source_width;
+    gint source_height;
+    g_autofree gchar *css_str = NULL;
+    g_autoptr (GtkCssProvider) provider = NULL;
+    g_autoptr (GtkStyleContext) context = NULL;
+    g_autoptr (GError) error = NULL;
+    g_autoptr (GtkWidgetPath) path = NULL;
+    gdouble scale_x;
+    gdouble scale_y;
+
+    cairo_surface_get_device_scale (source_image, &scale_x, &scale_y);
+
+    source_width = cairo_image_surface_get_width (source_image) / (gint) floor (scale_x);
+    source_height = cairo_image_surface_get_height (source_image) / (gint) floor (scale_y);
+
+    css_str = g_strdup_printf (".embedded-image { border-image: url(\"%s\") %d %d %d %d / %dpx %dpx %dpx 
%dpx }",
+                               frame_image_url,
+                               slice_width->top, slice_width->right, slice_width->bottom, slice_width->left,
+                               border_width->top, border_width->right, border_width->bottom, 
border_width->left);
+    provider = gtk_css_provider_new ();
+    gtk_css_provider_load_from_data (provider, css_str, -1, &error);
+
+    if (error != NULL)
+    {
+        g_warning ("Unable to create the thumbnail frame image: %s", error->message);
+
+        return g_object_ref (source_image);
+    }
+
+    surface = cairo_surface_create_similar (source_image,
+                                            CAIRO_CONTENT_COLOR_ALPHA,
+                                            source_width, source_height);
+    cr = cairo_create (surface);
+
+    context = gtk_style_context_new ();
+    path = gtk_widget_path_new ();
+    gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
+
+    gtk_style_context_set_path (context, path);
+    gtk_style_context_add_provider (context,
+                                    GTK_STYLE_PROVIDER (provider),
+                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+    cairo_save (cr);
+    cairo_rectangle (cr,
+                     border_width->left,
+                     border_width->top,
+                     source_width - border_width->left - border_width->right,
+                     source_height - border_width->top - border_width->bottom);
+    cairo_clip (cr);
+    gtk_render_icon_surface (context, cr, source_image, 0, 0);
+    cairo_restore (cr);
+
+    gtk_style_context_save (context);
+    gtk_style_context_add_class (context, "embedded-image");
+
+    gtk_render_frame (context, cr, 0, 0, source_width, source_height);
+
+    gtk_style_context_restore (context);
+    cairo_destroy (cr);
+
+    return surface;
+}
+
+static GdkPixbuf *
+embed_image_in_frame (GdkPixbuf   *source_image,
+                      const gchar *frame_image_url,
+                      GtkBorder   *slice_width,
+                      GtkBorder   *border_width)
+{
+  cairo_surface_t *surface;
+  cairo_surface_t *embedded_surface;
+  GdkPixbuf *retval;
+
+  surface = gdk_cairo_surface_create_from_pixbuf (source_image, 0, NULL);
+
+  /* Force the device scale to 1.0, since pixbufs are always in unscaled
+   * dimensions.
+   */
+  cairo_surface_set_device_scale (surface, 1.0, 1.0);
+  embedded_surface = embed_surface_in_frame (surface, frame_image_url,
+                                             slice_width, border_width);
+  retval = gdk_pixbuf_get_from_surface (embedded_surface,
+                                        0, 0,
+                                        cairo_image_surface_get_width (embedded_surface),
+                                        cairo_image_surface_get_height (embedded_surface));
+
+  cairo_surface_destroy (embedded_surface);
+  cairo_surface_destroy (surface);
+
+  return retval;
+}
+
 void
 nautilus_ui_frame_image (GdkPixbuf **pixbuf)
 {
@@ -185,9 +286,9 @@ nautilus_ui_frame_image (GdkPixbuf **pixbuf)
     border.right = NAUTILUS_THUMBNAIL_FRAME_RIGHT;
     border.bottom = NAUTILUS_THUMBNAIL_FRAME_BOTTOM;
 
-    pixbuf_with_frame = gd_embed_image_in_frame (*pixbuf,
-                                                 "resource:///org/gnome/nautilus/icons/thumbnail_frame.png",
-                                                 &border, &border);
+    pixbuf_with_frame = embed_image_in_frame (*pixbuf,
+                                              "resource:///org/gnome/nautilus/icons/thumbnail_frame.png",
+                                              &border, &border);
     g_object_unref (*pixbuf);
 
     *pixbuf = pixbuf_with_frame;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]