[gtk/tagged-entry] wip: Add a tagged entry



commit c2429bfa9c034ad7b3ac5495a07b41532ed4db6d
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Feb 22 12:29:22 2019 -0500

    wip: Add a tagged entry
    
    Not polished in any form, yet.

 gtk/gtk.h                      |   1 +
 gtk/gtktaggedentry.c           | 524 +++++++++++++++++++++++++++++++++++++++++
 gtk/gtktaggedentry.h           | 104 ++++++++
 gtk/meson.build                |   1 +
 gtk/theme/Adwaita/_common.scss |  27 +++
 tests/test-tagged-entry.c      | 113 +++++++++
 6 files changed, 770 insertions(+)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 62a3bb3d7f..209dc4a486 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -212,6 +212,7 @@
 #include <gtk/gtkstylecontext.h>
 #include <gtk/gtkstyleprovider.h>
 #include <gtk/gtkswitch.h>
+#include <gtk/gtktaggedentry.h>
 #include <gtk/gtktext.h>
 #include <gtk/gtktextbuffer.h>
 #include <gtk/gtktextchild.h>
diff --git a/gtk/gtktaggedentry.c b/gtk/gtktaggedentry.c
new file mode 100644
index 0000000000..f384f5eccc
--- /dev/null
+++ b/gtk/gtktaggedentry.c
@@ -0,0 +1,524 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Authors:
+ * - Matthias Clasen <mclasen redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtktaggedentry.h"
+
+#include "gtkaccessible.h"
+#include "gtktextprivate.h"
+#include "gtkeditable.h"
+#include "gtklabel.h"
+#include "gtkbutton.h"
+#include "gtkbox.h"
+#include "gtkimage.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+#include "gtkmarshalers.h"
+#include "gtkstylecontext.h"
+#include "gtkgesturemultipress.h"
+
+#include "a11y/gtkentryaccessible.h"
+
+/**
+ * SECTION:gtktaggedhentry
+ * @Short_description: An entry that can show tags
+ * @Title: GtkTaggedEntry
+ *
+ * #GtkTaggedEntry is an entry that can show tags in
+ * addition to text.
+ */
+
+typedef struct {
+  GtkWidget *box;
+  GtkWidget *entry;
+} GtkTaggedEntryPrivate;
+
+static void gtk_tagged_entry_editable_init (GtkEditableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkTaggedEntry, gtk_tagged_entry, GTK_TYPE_WIDGET,
+                         G_ADD_PRIVATE (GtkTaggedEntry)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_tagged_entry_editable_init))
+
+static void
+gtk_tagged_entry_init (GtkTaggedEntry *entry)
+{
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  gtk_widget_set_has_surface (GTK_WIDGET (entry), FALSE);
+
+  priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_widget_set_parent (priv->box, GTK_WIDGET (entry));
+
+  priv->entry = gtk_text_new ();
+  gtk_widget_set_hexpand (priv->entry, TRUE);
+  gtk_widget_set_vexpand (priv->entry, TRUE);
+  gtk_widget_set_hexpand (priv->box, FALSE);
+  gtk_widget_set_vexpand (priv->box, FALSE);
+  gtk_container_add (GTK_CONTAINER (priv->box), priv->entry);
+  gtk_editable_init_delegate (GTK_EDITABLE (entry));
+}
+
+static void
+gtk_tagged_entry_dispose (GObject *object)
+{
+  GtkTaggedEntry *entry = GTK_TAGGED_ENTRY (object);
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  if (priv->entry)
+    gtk_editable_finish_delegate (GTK_EDITABLE (entry));
+
+  g_clear_pointer (&priv->entry, gtk_widget_unparent);
+  g_clear_pointer (&priv->box, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_tagged_entry_parent_class)->dispose (object);
+}
+
+static void
+gtk_tagged_entry_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gtk_tagged_entry_parent_class)->finalize (object);
+}
+
+static void
+gtk_tagged_entry_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
+    return;
+
+  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+gtk_tagged_entry_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
+    return;
+
+  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+gtk_tagged_entry_measure (GtkWidget      *widget,
+                          GtkOrientation  orientation,
+                          int             for_size,
+                          int            *minimum,
+                          int            *natural,
+                          int            *minimum_baseline,
+                          int            *natural_baseline)
+{
+  GtkTaggedEntry *entry = GTK_TAGGED_ENTRY (widget);
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  gtk_widget_measure (priv->box, orientation, for_size,
+                      minimum, natural,
+                      minimum_baseline, natural_baseline);
+}
+
+static void
+gtk_tagged_entry_size_allocate (GtkWidget *widget,
+                                int        width,
+                                int        height,
+                                int        baseline)
+{
+  GtkTaggedEntry *entry = GTK_TAGGED_ENTRY (widget);
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  gtk_widget_size_allocate (priv->box,
+                            &(GtkAllocation) { 0, 0, width, height },
+                            baseline);
+}
+
+static void
+gtk_tagged_entry_grab_focus (GtkWidget *widget)
+{
+  GtkTaggedEntry *entry = GTK_TAGGED_ENTRY (widget);
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  gtk_widget_grab_focus (priv->entry);
+}
+
+static void
+gtk_tagged_entry_class_init (GtkTaggedEntryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gtk_tagged_entry_dispose;
+  object_class->finalize = gtk_tagged_entry_finalize;
+  object_class->get_property = gtk_tagged_entry_get_property;
+  object_class->set_property = gtk_tagged_entry_set_property;
+
+  widget_class->measure = gtk_tagged_entry_measure;
+  widget_class->size_allocate = gtk_tagged_entry_size_allocate;
+  widget_class->grab_focus = gtk_tagged_entry_grab_focus;
+ 
+  gtk_editable_install_properties (object_class, 1);
+
+  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE);
+  gtk_widget_class_set_css_name (widget_class, I_("entry"));
+}
+
+static GtkEditable *
+gtk_tagged_entry_get_delegate (GtkEditable *editable)
+{
+  GtkTaggedEntry *entry = GTK_TAGGED_ENTRY (editable);
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  return GTK_EDITABLE (priv->entry);
+}
+
+static void
+gtk_tagged_entry_editable_init (GtkEditableInterface *iface)
+{
+  iface->get_delegate = gtk_tagged_entry_get_delegate;
+}
+
+/**
+ * gtk_tagged_entry_new:
+ *
+ * Creates a #GtkTaggedEntry.
+ *
+ * Returns: a new #GtkTaggedEntry
+ */
+GtkWidget *
+gtk_tagged_entry_new (void)
+{
+  return GTK_WIDGET (g_object_new (GTK_TYPE_TAGGED_ENTRY, NULL));
+}
+
+void
+gtk_tagged_entry_add_tag (GtkTaggedEntry *entry,
+                          GtkWidget      *tag)
+{
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  g_return_if_fail (GTK_IS_TAGGED_ENTRY (entry));
+
+  gtk_container_add (GTK_CONTAINER (priv->box), tag);
+}
+
+void
+gtk_tagged_entry_insert_tag (GtkTaggedEntry *entry,
+                             GtkWidget      *tag,
+                             int             position)
+{
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  g_return_if_fail (GTK_IS_TAGGED_ENTRY (entry));
+
+  if (position == -1)
+    gtk_container_add (GTK_CONTAINER (priv->box), tag);
+  else
+    {
+      GList *children = gtk_container_get_children (GTK_CONTAINER (priv->box));
+      GtkWidget *sibling = g_list_nth_data (children, position);
+      gtk_box_insert_child_after (GTK_BOX (priv->box), tag, sibling); 
+      g_list_free (children);
+    }
+}
+
+void
+gtk_tagged_entry_remove_tag (GtkTaggedEntry *entry,
+                             GtkWidget      *tag)
+{
+  GtkTaggedEntryPrivate *priv = gtk_tagged_entry_get_instance_private (entry);
+
+  g_return_if_fail (GTK_IS_TAGGED_ENTRY (entry));
+
+  gtk_container_remove (GTK_CONTAINER (priv->box), tag);
+}
+
+#define GTK_TYPE_ENTRY_TAG                (gtk_entry_tag_get_type ())
+#define GTK_ENTRY_TAG(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ENTRY_TAG, 
GtkEntryTag))
+#define GTK_ENTRY_TAG_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ENTRY_TAG, 
GtkEntryTag))
+#define GTK_IS_ENTRY_TAG(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ENTRY_TAG))
+#define GTK_IS_ENTRY_TAG_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ENTRY_TAG))
+#define GTK_ENTRY_TAG_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ENTRY_TAG, 
GtkEntryTagClass))
+
+typedef struct _GtkEntryTag       GtkEntryTag;
+typedef struct _GtkEntryTagClass  GtkEntryTagClass;
+
+struct _GtkEntryTag
+{
+  GtkWidget parent;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *button;
+
+  gboolean has_close_button;
+  char *style;
+};
+
+struct _GtkEntryTagClass
+{
+  GtkWidgetClass parent_class;
+};
+
+enum {
+  PROP_0,
+  PROP_LABEL,
+  PROP_HAS_CLOSE_BUTTON,
+};
+
+enum {
+  SIGNAL_CLICKED,
+  SIGNAL_BUTTON_CLICKED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (GtkEntryTag, gtk_entry_tag, GTK_TYPE_WIDGET)
+
+static void
+on_released (GtkGestureMultiPress *gesture,
+             int                   n_press,
+             double                x,
+             double                y,
+             GtkEntryTag          *tag)
+{
+  g_signal_emit (tag, signals[SIGNAL_CLICKED], 0);
+}
+
+static void
+gtk_entry_tag_init (GtkEntryTag *tag)
+{
+  GtkGesture *gesture;
+
+  gtk_widget_set_has_surface (GTK_WIDGET (tag), FALSE);
+
+  tag->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_widget_set_parent (tag->box, GTK_WIDGET (tag));
+  tag->label = gtk_label_new ("");
+  gtk_container_add (GTK_CONTAINER (tag->box), tag->label);
+
+  gesture = gtk_gesture_multi_press_new ();
+  g_signal_connect (gesture, "released", G_CALLBACK (on_released), tag);
+  gtk_widget_add_controller (GTK_WIDGET (tag), GTK_EVENT_CONTROLLER (gesture));
+}
+
+static void
+gtk_entry_tag_dispose (GObject *object)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (object);
+
+  g_clear_pointer (&tag->box, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_entry_tag_parent_class)->dispose (object);
+}
+
+static void
+gtk_entry_tag_finalize (GObject *object)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (object);
+
+  g_clear_pointer (&tag->box, gtk_widget_unparent);
+  g_clear_pointer (&tag->style, g_free);
+
+  G_OBJECT_CLASS (gtk_entry_tag_parent_class)->finalize (object);
+}
+
+static void
+gtk_entry_tag_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (object);
+
+  switch (prop_id)
+    {
+    case PROP_LABEL:
+      gtk_entry_tag_set_label (tag, g_value_get_string (value));
+      break;
+
+    case PROP_HAS_CLOSE_BUTTON:
+      gtk_entry_tag_set_has_close_button (tag, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_entry_tag_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (object);
+
+  switch (prop_id)
+    {
+    case PROP_LABEL:
+      g_value_set_string (value, gtk_entry_tag_get_label (tag));
+      break;
+
+    case PROP_HAS_CLOSE_BUTTON:
+      g_value_set_boolean (value, gtk_entry_tag_get_has_close_button (tag));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_entry_tag_measure (GtkWidget      *widget,
+                       GtkOrientation  orientation,
+                       int             for_size,
+                       int            *minimum,
+                       int            *natural,
+                       int            *minimum_baseline,
+                       int            *natural_baseline)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (widget);
+
+  gtk_widget_measure (tag->box, orientation, for_size,
+                      minimum, natural,
+                      minimum_baseline, natural_baseline);
+}
+
+static void
+gtk_entry_tag_size_allocate (GtkWidget *widget,
+                             int        width,
+                             int        height,
+                             int        baseline)
+{
+  GtkEntryTag *tag = GTK_ENTRY_TAG (widget);
+
+  gtk_widget_size_allocate (tag->box,
+                            &(GtkAllocation) { 0, 0, width, height },
+                            baseline);
+}
+
+static void
+gtk_entry_tag_class_init (GtkEntryTagClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->dispose = gtk_entry_tag_dispose;
+  object_class->finalize = gtk_entry_tag_finalize;
+  object_class->set_property = gtk_entry_tag_set_property;
+  object_class->get_property = gtk_entry_tag_get_property;
+
+  widget_class->measure = gtk_entry_tag_measure;
+  widget_class->size_allocate = gtk_entry_tag_size_allocate;
+
+  signals[SIGNAL_CLICKED] =
+    g_signal_new ("clicked",
+                  GTK_TYPE_ENTRY_TAG,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+  signals[SIGNAL_BUTTON_CLICKED] =
+    g_signal_new ("button-clicked",
+                  GTK_TYPE_ENTRY_TAG,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  g_object_class_install_property (object_class, PROP_LABEL,
+      g_param_spec_string ("label", "Label", "Label",
+                           NULL, GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class, PROP_HAS_CLOSE_BUTTON,
+      g_param_spec_boolean ("has-close-button", "Has close button", "Whether this tag has a close button",
+                            FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
+
+  gtk_widget_class_set_css_name (widget_class, I_("tag"));
+}
+
+GtkEntryTag *
+gtk_entry_tag_new (const char *label)
+{
+  return GTK_ENTRY_TAG (g_object_new (GTK_TYPE_ENTRY_TAG, "label", label, NULL));
+}
+
+const char *
+gtk_entry_tag_get_label (GtkEntryTag *tag)
+{
+  g_return_val_if_fail (GTK_IS_ENTRY_TAG (tag), NULL);
+
+  return gtk_label_get_label (GTK_LABEL (tag->label));
+}
+
+void
+gtk_entry_tag_set_label (GtkEntryTag *tag,
+                         const char  *label)
+{
+  g_return_if_fail (GTK_IS_ENTRY_TAG (tag));
+
+  gtk_label_set_label (GTK_LABEL (tag->label), label);
+}
+
+static void
+on_button_clicked (GtkButton *button, GtkEntryTag *tag)
+{
+  g_signal_emit (tag, signals[SIGNAL_BUTTON_CLICKED], 0);
+}
+
+void
+gtk_entry_tag_set_has_close_button (GtkEntryTag *tag,
+                                    gboolean     has_close_button)
+{
+  g_return_if_fail (GTK_IS_ENTRY_TAG (tag));
+
+  if ((tag->button != NULL) == has_close_button)
+    return;
+
+  if (!has_close_button && tag->button)
+    {
+      gtk_container_remove (GTK_CONTAINER (tag->box), tag->button);
+      tag->button = NULL;
+    }
+  else if (has_close_button && tag->button == NULL)
+    {
+      GtkWidget *image;
+
+      image = gtk_image_new_from_icon_name ("window-close-symbolic");
+      gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
+      tag->button = gtk_button_new ();
+      gtk_container_add (GTK_CONTAINER (tag->button), image);
+      gtk_button_set_relief (GTK_BUTTON (tag->button), GTK_RELIEF_NONE);
+      gtk_container_add (GTK_CONTAINER (tag->box), tag->button);
+      g_signal_connect (tag->button, "clicked", G_CALLBACK (on_button_clicked), tag);
+    }
+
+  g_object_notify (G_OBJECT (tag), "has-close-button");
+}
+
+gboolean
+gtk_entry_tag_get_has_close_button (GtkEntryTag *tag)
+{
+  g_return_val_if_fail (GTK_IS_ENTRY_TAG (tag), FALSE);
+
+  return tag->button != NULL;
+}
diff --git a/gtk/gtktaggedentry.h b/gtk/gtktaggedentry.h
new file mode 100644
index 0000000000..e7a98df968
--- /dev/null
+++ b/gtk/gtktaggedentry.h
@@ -0,0 +1,104 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Authors:
+ * - MAtthias Clasen <mclasen redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_TAGGED_ENTRY_H__
+#define __GTK_TAGGED_ENTRY_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkentry.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TAGGED_ENTRY                 (gtk_tagged_entry_get_type ())
+#define GTK_TAGGED_ENTRY(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TAGGED_ENTRY, 
GtkTaggedEntry))
+#define GTK_TAGGED_ENTRY_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TAGGED_ENTRY, 
GtkTaggedEntryClass))
+#define GTK_IS_TAGGED_ENTRY(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TAGGED_ENTRY))
+#define GTK_IS_TAGGED_ENTRY_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TAGGED_ENTRY))
+#define GTK_TAGGED_ENTRY_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TAGGED_ENTRY, 
GtkTaggedEntryClass))
+
+typedef struct _GtkTaggedEntry       GtkTaggedEntry;
+typedef struct _GtkTaggedEntryClass  GtkTaggedEntryClass;
+
+struct _GtkTaggedEntry
+{
+  GtkWidget parent;
+};
+
+struct _GtkTaggedEntryClass
+{
+  GtkWidgetClass parent_class;
+};
+
+#define GTK_TYPE_ENTRY_TAG                (gtk_entry_tag_get_type ())
+#define GTK_ENTRY_TAG(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ENTRY_TAG, 
GtkEntryTag))
+#define GTK_ENTRY_TAG_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ENTRY_TAG, 
GtkEntryTag))
+#define GTK_IS_ENTRY_TAG(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ENTRY_TAG))
+#define GTK_IS_ENTRY_TAG_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ENTRY_TAG))
+#define GTK_ENTRY_TAG_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ENTRY_TAG, 
GtkEntryTagClass))
+
+typedef struct _GtkEntryTag       GtkEntryTag;
+typedef struct _GtkEntryTagClass  GtkEntryTagClass;
+
+
+GDK_AVAILABLE_IN_ALL
+GType           gtk_tagged_entry_get_type (void) G_GNUC_CONST;
+GDK_AVAILABLE_IN_ALL
+GType           gtk_entry_tag_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget *     gtk_tagged_entry_new      (void);
+
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_tagged_entry_add_tag (GtkTaggedEntry *entry,
+                                          GtkWidget      *widget);
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_tagged_entry_insert_tag (GtkTaggedEntry *entry,
+                                             GtkWidget      *widget,
+                                             int             position);
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_tagged_entry_remove_tag (GtkTaggedEntry *entry,
+                                             GtkWidget      *widget);
+
+GDK_AVAILABLE_IN_ALL
+GtkEntryTag *   gtk_entry_tag_new (const char *label);
+
+GDK_AVAILABLE_IN_ALL
+const char *    gtk_entry_tag_get_label (GtkEntryTag *tag);
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_entry_tag_set_label (GtkEntryTag *tag,
+                                         const char  *label);
+
+GDK_AVAILABLE_IN_ALL
+gboolean        gtk_entry_tag_get_has_close_button (GtkEntryTag *tag);
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_entry_tag_set_has_close_button (GtkEntryTag *tag,
+                                                    gboolean     has_close_button);
+
+G_END_DECLS
+
+#endif /* __GTK_TAGGED_ENTRY_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 85f75c8c51..ffdbf0274e 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -352,6 +352,7 @@ gtk_public_sources = files([
   'gtkstylecontext.c',
   'gtkstyleprovider.c',
   'gtkswitch.c',
+  'gtktaggedentry.c',
   'gtktestutils.c',
   'gtktext.c',
   'gtktextattributes.c',
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index aa8acc88e5..52e1fdddbc 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -4704,3 +4704,30 @@ popover.emoji-completion contents row box {
 popover.emoji-completion .emoji:hover {
   background-color: $popover_hover_color;
 }
+
+entry tag {
+  background: blue;
+  border-radius: 12px;
+  margin: 6px 3px;
+}
+
+entry tag label {
+  color: white;
+}
+
+entry tag button {
+  color: white;
+  padding: 0;
+}
+
+entry tag box {
+  border-spacing: 4px;
+}
+
+entry tag label.first-child {
+  margin-left: 10px;
+}
+
+entry tag label.last-child {
+  margin-right: 10px;
+}
diff --git a/tests/test-tagged-entry.c b/tests/test-tagged-entry.c
new file mode 100644
index 0000000000..045fe8a255
--- /dev/null
+++ b/tests/test-tagged-entry.c
@@ -0,0 +1,113 @@
+#include <gtk/gtk.h>
+
+static GtkEntryTag *toggle_tag;
+
+static void
+on_tag_clicked (GtkEntryTag *tag,
+                gpointer useless)
+{
+  g_print ("tag clicked: %s\n", gtk_entry_tag_get_label (tag));
+}
+
+static void
+on_tag_button_clicked (GtkEntryTag *tag,
+                       GtkTaggedEntry *entry)
+{
+  g_print ("tag button clicked: %s\n", gtk_entry_tag_get_label (tag));
+  gtk_tagged_entry_remove_tag (entry, tag);
+}
+
+static void
+on_toggle_visible (GtkButton *button,
+                   GtkWidget *entry)
+{
+  gboolean active;
+
+  active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+  g_print ("%s tagged entry\n", active ? "show" : "hide");
+  gtk_widget_set_visible (entry, active);
+}
+
+static void
+on_toggle_tag (GtkButton *button,
+               GtkTaggedEntry *entry)
+{
+  gboolean active;
+
+  active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+  if (active)
+    {
+      g_print ("adding tag 'Toggle Tag'\n");
+      gtk_tagged_entry_insert_tag (entry, toggle_tag, 0);
+    }
+  else
+    {
+      g_print ("removing tag 'Toggle Tag'\n");
+      gtk_tagged_entry_remove_tag (entry, toggle_tag);
+    }
+}
+
+gint
+main (gint argc,
+      gchar ** argv)
+{
+  GtkWidget *window, *box, *entry, *toggle_visible_button, *toggle_tag_button;
+  GtkEntryTag *tag;
+
+  gtk_init ();
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_size_request (window, 300, 20);
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_container_add (GTK_CONTAINER (window), box);
+
+  entry = GTK_WIDGET (gtk_tagged_entry_new ());
+  gtk_container_add (GTK_CONTAINER (box), entry);
+
+  tag = gtk_entry_tag_new ("Blah1");
+  g_object_set (tag, "has-close-button", TRUE, NULL);
+  g_signal_connect(tag, "clicked", G_CALLBACK (on_tag_clicked), NULL);
+  g_signal_connect(tag, "button-clicked", G_CALLBACK (on_tag_button_clicked), entry);
+
+  gtk_tagged_entry_add_tag (GTK_TAGGED_ENTRY (entry), tag);
+
+  tag = gtk_entry_tag_new ("Blah2");
+  g_object_set (tag, "has-close-button", TRUE, NULL);
+  g_signal_connect(tag, "clicked", G_CALLBACK (on_tag_clicked), NULL);
+  g_signal_connect(tag, "button-clicked", G_CALLBACK (on_tag_button_clicked), entry);
+  gtk_tagged_entry_insert_tag (GTK_TAGGED_ENTRY (entry), tag, -1);
+
+  tag = gtk_entry_tag_new ("Blah3");
+  g_signal_connect(tag, "clicked", G_CALLBACK (on_tag_clicked), NULL);
+  g_signal_connect(tag, "button-clicked", G_CALLBACK (on_tag_button_clicked), entry);
+  gtk_tagged_entry_insert_tag (GTK_TAGGED_ENTRY (entry), tag, 0);
+
+  toggle_visible_button = gtk_toggle_button_new_with_label ("Visible");
+  gtk_widget_set_vexpand (toggle_visible_button, TRUE);
+  gtk_widget_set_valign (toggle_visible_button, GTK_ALIGN_END);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_visible_button), TRUE);
+  g_signal_connect (toggle_visible_button, "toggled",
+                    G_CALLBACK (on_toggle_visible), entry);
+  gtk_container_add (GTK_CONTAINER (box), toggle_visible_button);
+
+  gtk_tagged_entry_add_tag (entry, g_object_new (GTK_TYPE_SPINNER, "active", TRUE, NULL));
+  toggle_tag = gtk_entry_tag_new ("Toggle Tag");
+  g_signal_connect(toggle_tag, "clicked", G_CALLBACK (on_tag_clicked), NULL);
+  g_signal_connect(toggle_tag, "button-clicked", G_CALLBACK (on_tag_button_clicked), NULL);
+  g_object_ref_sink (toggle_tag);
+
+  toggle_tag_button = gtk_toggle_button_new_with_label ("Toggle Tag");
+  g_signal_connect (toggle_tag_button, "toggled",
+                    G_CALLBACK (on_toggle_tag), entry);
+  gtk_container_add (GTK_CONTAINER (box), toggle_tag_button);
+
+  gtk_widget_show (window);
+  gtk_main ();
+
+  gtk_widget_destroy (window);
+
+  return 0;
+}


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