[gnome-boxes] libgd: import some widgets from gnome-documents



commit 61084473033ce5d36b2b39d5f046d9043774c43a
Author: Marc-Andrà Lureau <marcandre lureau gmail com>
Date:   Wed Jul 25 14:50:44 2012 +0200

    libgd: import some widgets from gnome-documents
    
    These files should be kept in sync with upstream, if possible.
    That's why I also left the trailing white spaces.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681089

 Makefile.am                 |    2 +-
 configure.ac                |   12 +-
 libgd/Makefile.am           |   66 +++
 libgd/gd-entry-focus-hack.c |   87 ++++
 libgd/gd-entry-focus-hack.h |   30 ++
 libgd/gd-margin-container.c |  379 ++++++++++++++++
 libgd/gd-margin-container.h |   75 ++++
 libgd/gd-tagged-entry.c     | 1037 +++++++++++++++++++++++++++++++++++++++++++
 libgd/gd-tagged-entry.h     |   90 ++++
 libgd/gd.h                  |   35 ++
 10 files changed, 1811 insertions(+), 2 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index cccf884..bb278e7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
 ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 NULL =
 
-SUBDIRS = data src po vapi
+SUBDIRS = data libgd src po vapi
 
 INTLTOOL_FILES =				\
 	intltool-extract.in			\
diff --git a/configure.ac b/configure.ac
index 78d9349..a637f5b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,7 +70,6 @@ PKG_CHECK_MODULES(BOXES, [
   tracker-sparql-0.14 >= $TRACKER_SPARQL
 ])
 
-
 VALA_ADD_STAMP([src/gnome_boxes_vala.stamp])
 
 VALA_CHECK([0.14.0], [
@@ -89,6 +88,16 @@ VALA_CHECK([0.14.0], [
   spice-client-gtk-3.0
 ])
 
+dnl libgd
+AC_CHECK_LIBM
+AC_SUBST(LIBM)
+
+PKG_CHECK_MODULES(LIBGD, [
+  glib-2.0 >= $GLIB_MIN_VERSION
+  gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
+  gtk+-3.0 >= $GTK_MIN_VERSION
+])
+
 dnl Strict compiler
 AC_ARG_ENABLE([strict-cc],
   AS_HELP_STRING([--enable-strict-cc],[Enable strict C compiler]))
@@ -112,6 +121,7 @@ AC_CONFIG_FILES([
   data/Makefile
   data/gnome-boxes.desktop.in
   data/icons/Makefile
+  libgd/Makefile
   po/Makefile.in
   src/Makefile
   vapi/Makefile
diff --git a/libgd/Makefile.am b/libgd/Makefile.am
new file mode 100644
index 0000000..96c058f
--- /dev/null
+++ b/libgd/Makefile.am
@@ -0,0 +1,66 @@
+NULL =
+
+gdprivate_cflags =				\
+	-I$(top_srcdir)				\
+	-DPREFIX=\"$(prefix)\"			\
+	-DLIBDIR=\"$(libdir)\"			\
+	-DG_LOG_DOMAIN=\"Gdprivate\"		\
+	-DG_DISABLE_DEPRECATED			\
+	$(LIBGD_CFLAGS)				\
+	$(NULL)
+
+gdprivate_source_h =				\
+	gd.h					\
+	gd-entry-focus-hack.h			\
+	gd-margin-container.h			\
+	gd-tagged-entry.h			\
+	$(NULL)
+
+gdprivate_source_c =				\
+	gd-entry-focus-hack.c			\
+	gd-margin-container.c			\
+	gd-tagged-entry.c			\
+	$(NULL)
+
+pkglib_LTLIBRARIES = libgdprivate-1.0.la
+
+libgdprivate_1_0_la_LIBADD =			\
+	$(LIBGD_LIBS)				\
+	$(LIBM)					\
+	$(NULL)
+
+libgdprivate_1_0_la_LDFLAGS =			\
+	-avoid-version
+
+libgdprivate_1_0_la_CPPFLAGS =			\
+	$(gdprivate_cflags)
+
+libgdprivate_1_0_la_SOURCES =			\
+	$(gdprivate_source_h)			\
+	$(gdprivate_source_c)			\
+	$(NULL)
+
+include $(INTROSPECTION_MAKEFILE)
+
+INTROSPECTION_GIRS = Gd-1.0.gir
+
+Gd-1.0.gir: libgdprivate-1.0.la Makefile
+Gd_1_0_gir_NAMESPACE = Gd
+Gd_1_0_gir_VERSION = 1.0
+Gd_1_0_gir_LIBS = libgdprivate-1.0.la
+Gd_1_0_gir_CFLAGS = $(AM_CPPFLAGS) $(gdprivate_cflags)
+Gd_1_0_gir_SCANNERFLAGS =				\
+	--warn-all					\
+	--symbol-prefix=gd				\
+	--identifier-prefix=Gd				\
+        --c-include="libgd/gd.h"			\
+	$(NULL)
+
+Gd_1_0_gir_INCLUDES =				\
+	Gtk-3.0					\
+	$(NULL)
+
+Gd_1_0_gir_FILES =				\
+	$(gdprivate_source_h:%=$(srcdir)/%)	\
+	$(gdprivate_source_c:%=$(srcdir)/%)	\
+	$(NULL)
diff --git a/libgd/gd-entry-focus-hack.c b/libgd/gd-entry-focus-hack.c
new file mode 100644
index 0000000..156e3f3
--- /dev/null
+++ b/libgd/gd-entry-focus-hack.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011, 2012 Red Hat, Inc.
+ *
+ * Gnome Documents is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Gnome Documents 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 General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Gnome Documents; 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-entry-focus-hack.h"
+
+/* taken from gtk/gtktreeview.c */
+static void
+send_focus_change (GtkWidget *widget,
+                   GdkDevice *device,
+		   gboolean   in)
+{
+  GdkDeviceManager *device_manager;
+  GList *devices, *d;
+
+  device_manager = gdk_display_get_device_manager (gtk_widget_get_display (widget));
+  devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
+  devices = g_list_concat (devices, gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_SLAVE));
+  devices = g_list_concat (devices, gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_FLOATING));
+
+  for (d = devices; d; d = d->next)
+    {
+      GdkDevice *dev = d->data;
+      GdkEvent *fevent;
+      GdkWindow *window;
+
+      if (gdk_device_get_source (dev) != GDK_SOURCE_KEYBOARD)
+        continue;
+
+      window = gtk_widget_get_window (widget);
+
+      /* Skip non-master keyboards that haven't
+       * selected for events from this window
+       */
+      if (gdk_device_get_device_type (dev) != GDK_DEVICE_TYPE_MASTER &&
+          !gdk_window_get_device_events (window, dev))
+        continue;
+
+      fevent = gdk_event_new (GDK_FOCUS_CHANGE);
+
+      fevent->focus_change.type = GDK_FOCUS_CHANGE;
+      fevent->focus_change.window = g_object_ref (window);
+      fevent->focus_change.in = in;
+      gdk_event_set_device (fevent, device);
+
+      gtk_widget_send_focus_change (widget, fevent);
+
+      gdk_event_free (fevent);
+    }
+
+  g_list_free (devices);
+}
+
+void
+gd_entry_focus_hack (GtkWidget *entry,
+                     GdkDevice *device)
+{
+  GtkEntryClass *entry_class;
+  GtkWidgetClass *entry_parent_class;
+
+  /* Grab focus will select all the text.  We don't want that to happen, so we
+   * call the parent instance and bypass the selection change.  This is probably
+   * really non-kosher. */
+  entry_class = g_type_class_peek (GTK_TYPE_ENTRY);
+  entry_parent_class = g_type_class_peek_parent (entry_class);
+  (entry_parent_class->grab_focus) (entry);
+
+  /* send focus-in event */
+  send_focus_change (entry, device, TRUE);
+}
diff --git a/libgd/gd-entry-focus-hack.h b/libgd/gd-entry-focus-hack.h
new file mode 100644
index 0000000..8daa559
--- /dev/null
+++ b/libgd/gd-entry-focus-hack.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011, 2012 Red Hat, Inc.
+ *
+ * Gnome Documents is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Gnome Documents 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 General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Gnome Documents; 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_ENTRY_FOCUS_HACK_H__
+#define __GD_ENTRY_FOCUS_HACK_H__
+
+#include <gtk/gtk.h>
+
+void   gd_entry_focus_hack (GtkWidget *entry,
+                            GdkDevice *device);
+
+#endif/* __GD_ENTRY_FOCUS_HACK_H__ */
diff --git a/libgd/gd-margin-container.c b/libgd/gd-margin-container.c
new file mode 100644
index 0000000..215cf4f
--- /dev/null
+++ b/libgd/gd-margin-container.c
@@ -0,0 +1,379 @@
+/*
+ * 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 "config.h"
+
+#include "gd-margin-container.h"
+
+G_DEFINE_TYPE_WITH_CODE (GdMarginContainer, gd_margin_container, GTK_TYPE_BIN,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
+                                                NULL))
+
+struct _GdMarginContainerPrivate {
+  gint min_margin;
+  gint max_margin;
+
+  GtkOrientation orientation;
+};
+
+enum {
+  PROP_MIN_MARGIN = 1,
+  PROP_MAX_MARGIN,
+  PROP_ORIENTATION,
+  NUM_PROPERTIES
+};
+
+static void
+gd_margin_container_queue_redraw (GdMarginContainer *self)
+{
+  GtkWidget *child;
+
+  /* Make sure that the widget and children are redrawn with the new setting: */
+  child = gtk_bin_get_child (GTK_BIN (self));
+  if (child)
+    gtk_widget_queue_resize (child);
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gd_margin_container_set_orientation (GdMarginContainer *self,
+                                     GtkOrientation orientation)
+{
+  if (self->priv->orientation != orientation)
+    {
+      self->priv->orientation = orientation;
+      g_object_notify (G_OBJECT (self), "orientation");
+
+      gd_margin_container_queue_redraw (self);
+    }
+}
+
+static void
+gd_margin_container_set_min_margin (GdMarginContainer *self,
+                                    gint min_margin)
+{
+  if (self->priv->min_margin != min_margin)
+    {
+      self->priv->min_margin = min_margin;
+      g_object_notify (G_OBJECT (self), "min-margin");
+
+      gd_margin_container_queue_redraw (self);
+    }
+}
+
+static void
+gd_margin_container_set_max_margin (GdMarginContainer *self,
+                                    gint max_margin)
+{
+  if (self->priv->max_margin != max_margin)
+    {
+      self->priv->max_margin = max_margin;
+      g_object_notify (G_OBJECT (self), "max-margin");
+
+      gd_margin_container_queue_redraw (self);
+    }
+}
+
+static void
+gd_margin_container_set_property (GObject      *object,
+                                  guint         property_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GdMarginContainer *self = GD_MARGIN_CONTAINER (object);
+
+  switch (property_id)
+    {
+    case PROP_MIN_MARGIN:
+      gd_margin_container_set_min_margin (self, g_value_get_int (value));
+      break;
+    case PROP_MAX_MARGIN:
+      gd_margin_container_set_max_margin (self, g_value_get_int (value));
+      break;
+    case PROP_ORIENTATION:
+      gd_margin_container_set_orientation (self, g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_margin_container_get_property (GObject    *object,
+                                  guint       property_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec) 
+{
+  GdMarginContainer *self = GD_MARGIN_CONTAINER (object);
+
+  switch (property_id)
+    {
+    case PROP_MIN_MARGIN: 
+      g_value_set_int (value, self->priv->min_margin);
+      break;
+    case PROP_MAX_MARGIN:
+      g_value_set_int (value, self->priv->max_margin);
+      break;
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, self->priv->orientation);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_margin_container_size_allocate (GtkWidget *widget,
+                                   GtkAllocation *allocation)
+{
+  GdMarginContainer *self = GD_MARGIN_CONTAINER (widget);
+  GtkWidget *child;
+  GtkAllocation child_allocation;
+  gint avail_width, avail_height;
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+  gtk_widget_set_allocation (widget, allocation);
+
+  if (child && gtk_widget_get_visible (child))
+    {
+      gint child_nat_width;
+      gint child_nat_height;
+      gint child_width, child_height;
+      gint offset;
+
+      /* available */
+      avail_width = allocation->width;
+      avail_height = allocation->height;
+
+      if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+        avail_width  = MAX (1, avail_width - 2 * self->priv->min_margin);
+      else
+        avail_height = MAX (1, avail_height - 2 * self->priv->min_margin);
+
+      if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+	{
+	  gtk_widget_get_preferred_width (child, NULL, &child_nat_width);
+	  child_width = MIN (avail_width, child_nat_width);
+
+	  gtk_widget_get_preferred_height_for_width (child, child_width, NULL, &child_nat_height);
+	  child_height = MIN (avail_height, child_nat_height);
+
+          offset = MIN ((gint) ((avail_height - child_height) / 2), self->priv->max_margin);
+
+          if (offset > 0)
+            child_allocation.height = avail_height  - (offset * 2);
+          else
+            child_allocation.height = avail_height;
+
+          child_allocation.width = MIN (avail_width, child_nat_width);
+	}
+      else
+	{
+	  gtk_widget_get_preferred_height (child, NULL, &child_nat_height);
+	  child_height = MIN (avail_height, child_nat_height);
+
+	  gtk_widget_get_preferred_width_for_height (child, child_height, NULL, &child_nat_width);
+	  child_width = MIN (avail_width, child_nat_width);
+
+          offset = MIN ((gint) ((avail_width - child_width) / 2), self->priv->max_margin);
+
+          if (offset > 0)
+            child_allocation.width = avail_width - (offset * 2);
+          else
+            child_allocation.width = avail_width;
+
+          child_allocation.height = MIN (avail_height, child_nat_height);
+	}
+
+      child_allocation.x = offset + allocation->x;
+      child_allocation.y = (avail_height - child_allocation.height) + allocation->y;
+
+      if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+        child_allocation.x += self->priv->min_margin;
+      else
+        child_allocation.y += self->priv->min_margin;
+
+      gtk_widget_size_allocate (child, &child_allocation);
+    }
+}
+
+static void
+gd_margin_container_get_preferred_size (GtkWidget *widget,
+                                        GtkOrientation orientation,
+                                        gint for_size,
+                                        gint *minimum_size,
+                                        gint *natural_size)
+{
+  GdMarginContainer *self = GD_MARGIN_CONTAINER (widget);
+  guint natural, minimum;
+  GtkWidget *child;
+
+  if (orientation == self->priv->orientation)
+    {
+      minimum = self->priv->min_margin * 2;
+      natural = self->priv->max_margin * 2;
+    }
+  else
+    {
+      minimum = 0;
+      natural = 0;
+    }
+
+  if ((child = gtk_bin_get_child (GTK_BIN (widget))) && gtk_widget_get_visible (child))
+    {
+      gint child_min, child_nat;
+
+      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+	  if (for_size < 0)
+	    gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+	  else
+	    {
+	      gint min_height;
+
+	      gtk_widget_get_preferred_height (child, &min_height, NULL);
+	      for_size -= 2 * self->priv->min_margin;
+
+	      gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat);
+	    }
+        }
+      else
+        {
+	  if (for_size < 0)
+	    gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+	  else
+	    {
+	      gint min_width;
+
+	      gtk_widget_get_preferred_width (child, &min_width, NULL);
+	      for_size -= 2 * self->priv->min_margin;
+
+	      gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat);
+	    }
+        }
+
+      natural += child_nat;
+
+      if (orientation != self->priv->orientation)
+        minimum += child_min;
+    }
+
+  if (minimum_size != NULL)
+    *minimum_size = minimum;
+  if (natural_size != NULL)
+    *natural_size = natural;
+}
+
+static void
+gd_margin_container_get_preferred_width (GtkWidget *widget,
+                                         gint      *minimum_size,
+                                         gint      *natural_size)
+{
+  gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL,
+                                          -1, minimum_size, natural_size);
+}
+
+static void
+gd_margin_container_get_preferred_height (GtkWidget *widget,
+                                          gint      *minimum_size,
+                                          gint      *natural_size)
+{
+  gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL,
+                                          -1, minimum_size, natural_size);
+}
+
+static void 
+gd_margin_container_get_preferred_width_for_height (GtkWidget *widget,
+                                                    gint       for_size,
+                                                    gint      *minimum_size,
+                                                    gint      *natural_size)
+{
+  gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL,
+                                          for_size, minimum_size, natural_size);
+}
+
+static void 
+gd_margin_container_get_preferred_height_for_width (GtkWidget *widget,
+                                                    gint       for_size,
+                                                    gint      *minimum_size,
+                                                    gint      *natural_size)
+{
+  gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL,
+                                          for_size, minimum_size, natural_size);
+}
+
+GdMarginContainer *
+gd_margin_container_new (void)
+{
+    return g_object_new (GD_TYPE_MARGIN_CONTAINER, NULL);
+}
+
+static void
+gd_margin_container_init (GdMarginContainer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MARGIN_CONTAINER,
+                                            GdMarginContainerPrivate);
+
+  self->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+  gtk_widget_set_redraw_on_allocate (GTK_WIDGET (self), FALSE);
+}
+
+static void
+gd_margin_container_class_init (GdMarginContainerClass *klass)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+  oclass->get_property = gd_margin_container_get_property;
+  oclass->set_property = gd_margin_container_set_property;
+
+  wclass->size_allocate = gd_margin_container_size_allocate;
+  wclass->get_preferred_width = gd_margin_container_get_preferred_width;
+  wclass->get_preferred_height = gd_margin_container_get_preferred_height;
+  wclass->get_preferred_width_for_height = gd_margin_container_get_preferred_width_for_height;
+  wclass->get_preferred_height_for_width = gd_margin_container_get_preferred_height_for_width;
+
+  gtk_container_class_handle_border_width (GTK_CONTAINER_CLASS (klass));
+
+  g_object_class_install_property (oclass, PROP_MIN_MARGIN,
+                                   g_param_spec_int ("min-margin",
+                                                     "Min margin",
+                                                     "Minimum margin around the child",
+                                                     0, G_MAXINT, 6,
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT));
+  g_object_class_install_property (oclass, PROP_MAX_MARGIN,
+                                   g_param_spec_int ("max-margin",
+                                                     "Max margin",
+                                                     "Maximum margin around the child",
+                                                     0, G_MAXINT, 6,
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT));
+  g_object_class_override_property (oclass, PROP_ORIENTATION,
+                                    "orientation");
+;
+  g_type_class_add_private (klass, sizeof (GdMarginContainerPrivate));
+}
diff --git a/libgd/gd-margin-container.h b/libgd/gd-margin-container.h
new file mode 100644
index 0000000..3937ea7
--- /dev/null
+++ b/libgd/gd-margin-container.h
@@ -0,0 +1,75 @@
+/*
+ * 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_MARGIN_CONTAINER_H
+#define _GD_MARGIN_CONTAINER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MARGIN_CONTAINER gd_margin_container_get_type()
+
+#define GD_MARGIN_CONTAINER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   GD_TYPE_MARGIN_CONTAINER, GdMarginContainer))
+
+#define GD_MARGIN_CONTAINER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   GD_TYPE_MARGIN_CONTAINER, GdMarginContainerClass))
+
+#define GD_IS_MARGIN_CONTAINER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   GD_TYPE_MARGIN_CONTAINER))
+
+#define GD_IS_MARGIN_CONTAINER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   GD_TYPE_MARGIN_CONTAINER))
+
+#define GD_MARGIN_CONTAINER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   GD_TYPE_MARGIN_CONTAINER, GdMarginContainerClass))
+
+typedef struct _GdMarginContainer GdMarginContainer;
+typedef struct _GdMarginContainerClass GdMarginContainerClass;
+typedef struct _GdMarginContainerPrivate GdMarginContainerPrivate;
+
+struct _GdMarginContainer
+{
+  GtkBin parent;
+
+  GdMarginContainerPrivate *priv;
+};
+
+struct _GdMarginContainerClass
+{
+  GtkBinClass parent_class;
+};
+
+GType gd_margin_container_get_type (void) G_GNUC_CONST;
+
+GdMarginContainer *gd_margin_container_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_MARGIN_CONTAINER_H */
diff --git a/libgd/gd-tagged-entry.c b/libgd/gd-tagged-entry.c
new file mode 100644
index 0000000..8baff53
--- /dev/null
+++ b/libgd/gd-tagged-entry.c
@@ -0,0 +1,1037 @@
+/*
+ * 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-tagged-entry.h"
+
+#include <math.h>
+
+G_DEFINE_TYPE (GdTaggedEntry, gd_tagged_entry, GTK_TYPE_SEARCH_ENTRY)
+
+#define BUTTON_INTERNAL_SPACING 6
+
+typedef struct {
+  GdkWindow *window;
+  PangoLayout *layout;
+
+  gchar *id;
+  gchar *label;
+
+  GdkPixbuf *close_pixbuf;
+  GtkStateFlags last_button_state;
+} GdTaggedEntryTag;
+
+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
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+static GParamSpec *properties[NUM_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 (GdTaggedEntry *entry);
+
+static void
+gd_tagged_entry_tag_get_margin (GdTaggedEntry *entry,
+                                GtkBorder *margin)
+{
+  GtkStyleContext *context;
+
+  context = gd_tagged_entry_tag_get_context (entry);
+  gtk_style_context_get_margin (context, 0, margin);
+  g_object_unref (context);
+}
+
+static void
+gd_tagged_entry_tag_ensure_close_pixbuf (GdTaggedEntryTag *tag,
+                                         GtkStyleContext *context)
+{
+  GtkIconInfo *info;
+  gint icon_size;
+
+  if (tag->close_pixbuf != NULL)
+    return;
+
+  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
+                        &icon_size, NULL);
+
+  info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
+                                     "window-close-symbolic",
+                                     icon_size,
+                                     GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+
+  tag->close_pixbuf = 
+    gtk_icon_info_load_symbolic_for_context (info, context,
+                                             NULL, NULL);
+
+  /* FIXME: we need a fallback icon in case the icon is not found */
+}
+
+static gint
+gd_tagged_entry_tag_panel_get_height (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 (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;
+  GtkBorder margin;
+
+  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);
+  gd_tagged_entry_tag_get_margin (self, &margin);
+
+  /* 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 + margin.top + (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->layout != NULL)
+    return;
+
+  tag->layout = pango_layout_new (gtk_widget_get_pango_context (GTK_WIDGET (entry)));
+  pango_layout_set_text (tag->layout, tag->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 &&
+      entry->priv->in_child_button)
+    state |= GTK_STATE_FLAG_PRELIGHT;
+
+  if (entry->priv->in_child_button_active)
+    state |= GTK_STATE_FLAG_ACTIVE;
+
+  return state;
+}
+
+static GtkStyleContext *
+gd_tagged_entry_tag_get_context (GdTaggedEntry *entry)
+{
+  GtkWidget *widget = GTK_WIDGET (entry);
+  GtkWidgetPath *path;
+  gint pos;
+  GtkStyleContext *retval;
+
+  retval = gtk_style_context_new ();
+  path = gtk_widget_path_copy (gtk_widget_get_path (widget));
+
+  pos = gtk_widget_path_append_type (path, GD_TYPE_TAGGED_ENTRY);
+  gtk_widget_path_iter_add_class (path, pos, "documents-entry-tag");
+
+  gtk_style_context_set_path (retval, path);
+
+  gtk_widget_path_unref (path);
+
+  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;
+
+  gd_tagged_entry_tag_ensure_layout (tag, entry);
+  pango_layout_get_pixel_size (tag->layout, &layout_width, NULL);
+
+  context = gd_tagged_entry_tag_get_context (entry);
+  state = gd_tagged_entry_tag_get_state (tag, entry);
+
+  gtk_style_context_get_padding (context, state, &button_padding);
+  gtk_style_context_get_border (context, state, &button_border);
+  gtk_style_context_get_margin (context, state, &button_margin);
+
+  gd_tagged_entry_tag_ensure_close_pixbuf (tag, context);
+
+  g_object_unref (context);
+
+  button_width = 0;
+  if (entry->priv->button_visible)
+    button_width = gdk_pixbuf_get_width (tag->close_pixbuf) + 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 (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;
+  GtkBorder padding, border;
+  GtkStateFlags state;
+
+  width = gdk_window_get_width (tag->window);
+  height = gdk_window_get_height (tag->window);
+
+  state = gd_tagged_entry_tag_get_state (tag, entry);
+  gtk_style_context_get_margin (context, state, &padding);
+
+  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_get_padding (context, state, &padding);
+  gtk_style_context_get_border (context, state, &border);  
+
+  gd_tagged_entry_tag_ensure_layout (tag, entry);
+  pango_layout_get_pixel_size (tag->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)
+    {
+      pix_width = gdk_pixbuf_get_width (tag->close_pixbuf);
+      pix_height = gdk_pixbuf_get_height (tag->close_pixbuf);
+    }
+  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)
+    return FALSE;
+
+  context = gd_tagged_entry_tag_get_context (entry);
+  gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, NULL, NULL, &button_allocation);
+
+  g_object_unref (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;
+}
+
+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 (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->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->layout);
+
+  gtk_style_context_restore (context);
+
+  if (!entry->priv->button_visible)
+    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->last_button_state)
+    {
+      g_clear_object (&tag->close_pixbuf);
+      gd_tagged_entry_tag_ensure_close_pixbuf (tag, context);
+
+      tag->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 (context, cr,
+                   tag->close_pixbuf,
+                   button_allocation.x, button_allocation.y);
+
+done:
+  cairo_restore (cr);
+
+  g_object_unref (context);
+}
+
+static void
+gd_tagged_entry_tag_unrealize (GdTaggedEntryTag *tag)
+{
+  if (tag->window == NULL)
+    return;
+
+  gdk_window_set_user_data (tag->window, NULL);
+  gdk_window_destroy (tag->window);
+  tag->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->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->window = gdk_window_new (gtk_widget_get_window (widget),
+                                &attributes, attributes_mask);
+  gdk_window_set_user_data (tag->window, widget);
+}
+
+static GdTaggedEntryTag *
+gd_tagged_entry_tag_new (const gchar *id,
+                         const gchar *label)
+{
+  GdTaggedEntryTag *tag;
+
+  tag = g_slice_new0 (GdTaggedEntryTag);
+
+  tag->id = g_strdup (id);
+  tag->label = g_strdup (label);
+  tag->last_button_state = GTK_STATE_FLAG_NORMAL;
+
+  return tag;
+}
+
+static void
+gd_tagged_entry_tag_free (gpointer _tag)
+{
+  GdTaggedEntryTag *tag = _tag;
+
+  if (tag->window != NULL)
+    gd_tagged_entry_tag_unrealize (tag);
+
+  g_clear_object (&tag->layout);
+  g_clear_object (&tag->close_pixbuf);
+  g_free (tag->id);
+  g_free (tag->label);
+
+  g_slice_free (GdTaggedEntryTag, tag);
+}
+
+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->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->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)
+        {
+          tag = l->data;
+          gd_tagged_entry_tag_get_size (tag, self, &width, &height);
+          gdk_window_move_resize (tag->window, x, y, 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, gd_tagged_entry_tag_free);
+      self->priv->tags = NULL;
+    }
+
+  G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj);
+}
+
+static GdTaggedEntryTag *
+gd_tagged_entry_find_tag_by_id (GdTaggedEntry *self,
+                                const gchar *id)
+{
+  GdTaggedEntryTag *tag = NULL, *elem;
+  GList *l;
+
+  for (l = self->priv->tags; l != NULL; l = l->next)
+    {
+      elem = l->data;
+      if (g_strcmp0 (elem->id, id) == 0)
+        {
+          tag = elem;
+          break;
+        }
+    }
+
+  return tag;
+}
+
+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->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;
+  GQuark id_quark;
+
+  tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+  if (tag != NULL)
+    {
+      id_quark = g_quark_from_string (tag->id);
+      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], id_quark, tag->id);
+        }
+      else
+        {
+          g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], id_quark, tag->id);
+        }
+
+      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, G_TYPE_STRING);
+  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, G_TYPE_STRING);
+
+  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);
+}
+
+GdTaggedEntry *
+gd_tagged_entry_new (void)
+{
+  return g_object_new (GD_TYPE_TAGGED_ENTRY, NULL);
+}
+
+gboolean
+gd_tagged_entry_add_tag (GdTaggedEntry *self,
+                         const gchar *id,
+                         const gchar *name)
+{
+  GdTaggedEntryTag *tag;
+
+  if (gd_tagged_entry_find_tag_by_id (self, id) != NULL)
+    return FALSE;
+
+  tag = gd_tagged_entry_tag_new (id, name);
+  self->priv->tags = g_list_append (self->priv->tags, tag);
+
+  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+    {
+      gd_tagged_entry_tag_realize (tag, self);
+      gdk_window_show_unraised (tag->window);
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  return TRUE;
+}
+
+gboolean
+gd_tagged_entry_remove_tag (GdTaggedEntry *self,
+                            const gchar *id)
+{
+  GdTaggedEntryTag *tag;
+  gboolean res = FALSE;
+
+  tag = gd_tagged_entry_find_tag_by_id (self, id);
+
+  if (tag != NULL)
+    {
+      res = TRUE;
+      self->priv->tags = g_list_remove (self->priv->tags, tag);
+      gd_tagged_entry_tag_free (tag);
+
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+
+  return res;
+}
+
+gboolean
+gd_tagged_entry_set_tag_label (GdTaggedEntry *self,
+                               const gchar *tag_id,
+                               const gchar *label)
+{
+  GdTaggedEntryTag *tag;
+  gboolean res = FALSE;
+
+  tag = gd_tagged_entry_find_tag_by_id (self, tag_id);
+
+  if (tag != NULL)
+    {
+      res = TRUE;
+
+      if (g_strcmp0 (tag->label, label) != 0)
+        {
+          g_free (tag->label);
+          tag->label = g_strdup (label);
+          g_clear_object (&tag->layout);
+
+          gtk_widget_queue_resize (GTK_WIDGET (self));
+        }
+    }
+
+  return res;  
+}
+
+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/libgd/gd-tagged-entry.h b/libgd/gd-tagged-entry.h
new file mode 100644
index 0000000..7a29140
--- /dev/null
+++ b/libgd/gd-tagged-entry.h
@@ -0,0 +1,90 @@
+/*
+ * 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_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;
+
+struct _GdTaggedEntry
+{
+  GtkSearchEntry parent;
+
+  GdTaggedEntryPrivate *priv;
+};
+
+struct _GdTaggedEntryClass
+{
+  GtkSearchEntryClass 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_add_tag (GdTaggedEntry *entry,
+                                  const gchar *id,
+                                  const gchar *label);
+
+gboolean gd_tagged_entry_remove_tag (GdTaggedEntry *self,
+                                     const gchar *id);
+
+gboolean gd_tagged_entry_set_tag_label (GdTaggedEntry *self,
+                                        const gchar *tag_id,
+                                        const gchar *label);
+
+G_END_DECLS
+
+#endif /* __GD_TAGGED_ENTRY_H__ */
diff --git a/libgd/gd.h b/libgd/gd.h
new file mode 100644
index 0000000..2a4a57b
--- /dev/null
+++ b/libgd/gd.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012 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_H__
+#define __GD_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#include <libgd/gd-entry-focus-hack.h>
+#include <libgd/gd-margin-container.h>
+#include <libgd/gd-tagged-entry.h>
+
+G_END_DECLS
+
+#endif /* __GD_H__ */



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