[gnome-boxes] libgd: import some widgets from gnome-documents
- From: Marc-Andre Lureau <malureau src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes] libgd: import some widgets from gnome-documents
- Date: Thu, 9 Aug 2012 12:01:58 +0000 (UTC)
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]