[nautilus/wip/ernestask/gtk4-continued: 22/144] query-editor: Use GTK+ 4 tagged entry



commit 3ea372a70c642df8f89f1b6f5687ef2e576adbca
Author: Ernestas Kulik <ernestask gnome org>
Date:   Mon Jul 2 10:00:17 2018 +0300

    query-editor: Use GTK+ 4 tagged entry

 src/libgd/gd-tagged-entry.c     | 1242 ---------------------------------------
 src/libgd/gd-tagged-entry.h     |  117 ----
 src/meson.build                 |    6 +-
 src/nautilus-query-editor.c     |  152 ++---
 src/nautilus-tagged-entry-tag.c |  499 ++++++++++++++++
 src/nautilus-tagged-entry-tag.h |   38 ++
 src/nautilus-tagged-entry.c     |  311 ++++++++++
 src/nautilus-tagged-entry.h     |   39 ++
 src/resources/css/Adwaita.css   |    5 -
 9 files changed, 977 insertions(+), 1432 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index c863198f4..d2d20a162 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -64,8 +64,6 @@ libnautilus_sources = [
   'gtk/nautilusgtkplacesviewrowprivate.h',
   'libgd/gd-styled-text-renderer.c',
   'libgd/gd-styled-text-renderer.h',
-  'libgd/gd-tagged-entry.c',
-  'libgd/gd-tagged-entry.h',
   'nautilus-application.c',
   'nautilus-application.h',
   'nautilus-bookmark-list.c',
@@ -274,6 +272,10 @@ libnautilus_sources = [
   'nautilus-types.h',
   'nautilus-tracker-utilities.c',
   'nautilus-tracker-utilities.h',
+  'nautilus-tagged-entry.c',
+  'nautilus-tagged-entry.h',
+  'nautilus-tagged-entry-tag.c',
+  'nautilus-tagged-entry-tag.h',
 ]
 
 nautilus_deps = [
diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c
index 382a0eb10..91de11ea8 100644
--- a/src/nautilus-query-editor.c
+++ b/src/nautilus-query-editor.c
@@ -28,13 +28,14 @@
 #include <gtk/gtk.h>
 #include <string.h>
 
-#include "libgd/gd-tagged-entry.h"
 #include "nautilus-file.h"
 #include "nautilus-file-utilities.h"
 #include "nautilus-global-preferences.h"
 #include "nautilus-search-directory.h"
 #include "nautilus-search-popover.h"
 #include "nautilus-mime-actions.h"
+#include "nautilus-tagged-entry.h"
+#include "nautilus-tagged-entry-tag.h"
 #include "nautilus-ui-utilities.h"
 
 struct _NautilusQueryEditor
@@ -45,8 +46,8 @@ struct _NautilusQueryEditor
     GtkWidget *popover;
     GtkWidget *dropdown_button;
 
-    GdTaggedEntryTag *mime_types_tag;
-    GdTaggedEntryTag *date_range_tag;
+    GtkWidget *mime_types_tag;
+    GtkWidget *date_range_tag;
 
     gboolean change_frozen;
 
@@ -217,19 +218,6 @@ nautilus_query_editor_set_property (GObject      *object,
     }
 }
 
-static void
-nautilus_query_editor_finalize (GObject *object)
-{
-    NautilusQueryEditor *editor;
-
-    editor = NAUTILUS_QUERY_EDITOR (object);
-
-    g_clear_object (&editor->date_range_tag);
-    g_clear_object (&editor->mime_types_tag);
-
-    G_OBJECT_CLASS (nautilus_query_editor_parent_class)->finalize (object);
-}
-
 static void
 nautilus_query_editor_class_init (NautilusQueryEditorClass *class)
 {
@@ -237,7 +225,6 @@ nautilus_query_editor_class_init (NautilusQueryEditorClass *class)
     GtkWidgetClass *widget_class;
 
     gobject_class = G_OBJECT_CLASS (class);
-    gobject_class->finalize = nautilus_query_editor_finalize;
     gobject_class->dispose = nautilus_query_editor_dispose;
     gobject_class->get_property = nautilus_query_editor_get_property;
     gobject_class->set_property = nautilus_query_editor_set_property;
@@ -400,12 +387,38 @@ nautilus_query_editor_on_stop_search (GtkWidget           *entry,
 static void
 nautilus_query_editor_init (NautilusQueryEditor *editor)
 {
+    editor->mime_types_tag = NULL;
+    editor->date_range_tag = NULL;
+
     g_signal_connect (nautilus_preferences,
                       "changed::recursive-search",
                       G_CALLBACK (recursive_search_preferences_changed),
                       editor);
 }
 
+static void
+on_date_range_tag_button_clicked (NautilusTaggedEntryTag *tag,
+                                  gpointer                user_data)
+{
+    NautilusQueryEditor *editor;
+
+    editor = user_data;
+
+    nautilus_search_popover_reset_date_range (NAUTILUS_SEARCH_POPOVER (editor->popover));
+}
+
+static void
+entry_tag_clicked (NautilusTaggedEntryTag *tag,
+                   gpointer                user_data)
+{
+    NautilusQueryEditor *editor;
+
+    editor = user_data;
+
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->dropdown_button),
+                                  TRUE);
+}
+
 static void
 search_popover_date_range_changed_cb (NautilusSearchPopover *popover,
                                       GPtrArray             *date_range,
@@ -420,17 +433,25 @@ search_popover_date_range_changed_cb (NautilusSearchPopover *popover,
         create_query (editor);
     }
 
-    gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (editor->entry),
-                                editor->date_range_tag);
+    g_clear_pointer (&editor->date_range_tag, gtk_widget_destroy);
+
     if (date_range)
     {
         g_autofree gchar *text_for_date_range = NULL;
 
         text_for_date_range = get_text_for_date_range (date_range, TRUE);
-        gd_tagged_entry_tag_set_label (editor->date_range_tag,
-                                       text_for_date_range);
-        gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
-                                 GD_TAGGED_ENTRY_TAG (editor->date_range_tag));
+
+        editor->date_range_tag = nautilus_tagged_entry_tag_new (text_for_date_range);
+
+        nautilus_tagged_entry_add_tag (NAUTILUS_TAGGED_ENTRY (editor->entry),
+                                       NAUTILUS_TAGGED_ENTRY_TAG (editor->date_range_tag));
+
+        g_signal_connect (editor->date_range_tag,
+                          "button-clicked", G_CALLBACK (on_date_range_tag_button_clicked),
+                          editor);
+        g_signal_connect (editor->date_range_tag,
+                          "clicked", G_CALLBACK (entry_tag_clicked),
+                          editor);
     }
 
     nautilus_query_set_date_range (editor->query, date_range);
@@ -438,6 +459,17 @@ search_popover_date_range_changed_cb (NautilusSearchPopover *popover,
     nautilus_query_editor_changed (editor);
 }
 
+static void
+on_mime_types_tag_button_clicked (NautilusTaggedEntryTag *tag,
+                                  gpointer                user_data)
+{
+    NautilusQueryEditor *editor;
+
+    editor = user_data;
+
+    nautilus_search_popover_reset_mime_types (NAUTILUS_SEARCH_POPOVER (editor->popover));
+}
+
 static void
 search_popover_mime_type_changed_cb (NautilusSearchPopover *popover,
                                      gint                   mimetype_group,
@@ -454,8 +486,8 @@ search_popover_mime_type_changed_cb (NautilusSearchPopover *popover,
         create_query (editor);
     }
 
-    gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (editor->entry),
-                                editor->mime_types_tag);
+    g_clear_pointer (&editor->mime_types_tag, gtk_widget_destroy);
+
     /* group 0 is anything */
     if (mimetype_group == 0)
     {
@@ -463,11 +495,23 @@ search_popover_mime_type_changed_cb (NautilusSearchPopover *popover,
     }
     else if (mimetype_group > 0)
     {
+        const gchar *label;
+
+        label = nautilus_mime_types_group_get_name (mimetype_group);
+
         mimetypes = nautilus_mime_types_group_get_mimetypes (mimetype_group);
-        gd_tagged_entry_tag_set_label (editor->mime_types_tag,
-                                       nautilus_mime_types_group_get_name (mimetype_group));
-        gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
-                                 GD_TAGGED_ENTRY_TAG (editor->mime_types_tag));
+
+        editor->mime_types_tag = nautilus_tagged_entry_tag_new (label);
+
+        nautilus_tagged_entry_add_tag (NAUTILUS_TAGGED_ENTRY (editor->entry),
+                                       NAUTILUS_TAGGED_ENTRY_TAG (editor->mime_types_tag));
+
+        g_signal_connect (editor->mime_types_tag,
+                          "button-clicked", G_CALLBACK (on_mime_types_tag_button_clicked),
+                          editor);
+        g_signal_connect (editor->mime_types_tag,
+                          "clicked", G_CALLBACK (entry_tag_clicked),
+                          editor);
     }
     else
     {
@@ -476,9 +520,18 @@ search_popover_mime_type_changed_cb (NautilusSearchPopover *popover,
         mimetypes = g_ptr_array_new_full (1, g_free);
         g_ptr_array_add (mimetypes, g_strdup (mimetype));
         display_name = g_content_type_get_description (mimetype);
-        gd_tagged_entry_tag_set_label (editor->mime_types_tag, display_name);
-        gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (editor->entry),
-                                 GD_TAGGED_ENTRY_TAG (editor->mime_types_tag));
+
+        editor->mime_types_tag = nautilus_tagged_entry_tag_new (display_name);
+
+        nautilus_tagged_entry_add_tag (NAUTILUS_TAGGED_ENTRY (editor->entry),
+                                       NAUTILUS_TAGGED_ENTRY_TAG (editor->mime_types_tag));
+
+        g_signal_connect (editor->mime_types_tag,
+                          "button-clicked", G_CALLBACK (on_mime_types_tag_button_clicked),
+                          editor);
+        g_signal_connect (editor->mime_types_tag,
+                          "clicked", G_CALLBACK (entry_tag_clicked),
+                          editor);
     }
     nautilus_query_set_mime_types (editor->query, mimetypes);
 
@@ -524,27 +577,6 @@ search_popover_fts_changed_cb (GObject    *popover,
     nautilus_query_editor_changed (editor);
 }
 
-static void
-entry_tag_clicked (NautilusQueryEditor *editor)
-{
-    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->dropdown_button),
-                                  TRUE);
-}
-
-static void
-entry_tag_close_button_clicked (NautilusQueryEditor *editor,
-                                GdTaggedEntryTag    *tag)
-{
-    if (tag == editor->mime_types_tag)
-    {
-        nautilus_search_popover_reset_mime_types (NAUTILUS_SEARCH_POPOVER (editor->popover));
-    }
-    else
-    {
-        nautilus_search_popover_reset_date_range (NAUTILUS_SEARCH_POPOVER (editor->popover));
-    }
-}
-
 static void
 setup_widgets (NautilusQueryEditor *editor)
 {
@@ -561,23 +593,11 @@ setup_widgets (NautilusQueryEditor *editor)
     gtk_container_add (GTK_CONTAINER (vbox), hbox);
 
     /* create the search entry */
-    editor->entry = GTK_WIDGET (gd_tagged_entry_new ());
+    editor->entry = nautilus_tagged_entry_new ();
     gtk_widget_set_hexpand (editor->entry, TRUE);
 
     gtk_container_add (GTK_CONTAINER (hbox), editor->entry);
 
-    editor->mime_types_tag = gd_tagged_entry_tag_new (NULL);
-    editor->date_range_tag = gd_tagged_entry_tag_new (NULL);
-
-    g_signal_connect_swapped (editor->entry,
-                              "tag-clicked",
-                              G_CALLBACK (entry_tag_clicked),
-                              editor);
-    g_signal_connect_swapped (editor->entry,
-                              "tag-button-clicked",
-                              G_CALLBACK (entry_tag_close_button_clicked),
-                              editor);
-
     /* setup the search popover */
     editor->popover = nautilus_search_popover_new ();
 
diff --git a/src/nautilus-tagged-entry-tag.c b/src/nautilus-tagged-entry-tag.c
new file mode 100644
index 000000000..1254bffa2
--- /dev/null
+++ b/src/nautilus-tagged-entry-tag.c
@@ -0,0 +1,499 @@
+/* GTK+ 4 implementation of GdTaggedEntry for Nautilus
+ * © 2018  Ernestas Kulik <ernestask gnome org>
+ *
+ * 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.1 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tagged-entry-tag.h"
+
+struct _NautilusTaggedEntryTag
+{
+    GtkWidget parent_instance;
+
+    GtkWidget *label;
+    GtkWidget *image;
+
+    bool active;
+};
+
+G_DEFINE_TYPE (NautilusTaggedEntryTag, nautilus_tagged_entry_tag, GTK_TYPE_WIDGET)
+
+#define SPACING 6
+
+enum
+{
+    PROP_0,
+    PROP_LABEL,
+    PROP_SHOW_CLOSE_BUTTON,
+    N_PROPERTIES
+};
+
+enum
+{
+    CLICKED,
+    BUTTON_CLICKED,
+    LAST_SIGNAL
+};
+
+static GParamSpec *properties[N_PROPERTIES];
+static unsigned int signals[LAST_SIGNAL];
+
+static void
+on_multi_press_gesture_pressed (GtkGestureMultiPress *gesture,
+                                int                   n_press,
+                                double                x,
+                                double                y,
+                                gpointer              user_data)
+{
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_multi_press_gesture_released (GtkGestureMultiPress *gesture,
+                                 int                   n_press,
+                                 double                x,
+                                 double                y,
+                                 gpointer              user_data)
+{
+    g_signal_emit (user_data, signals[CLICKED], 0);
+}
+
+static void
+set_image_pressed (NautilusTaggedEntryTag *self,
+                   bool                    pressed)
+{
+    self->active = pressed;
+
+    if (pressed)
+    {
+        gtk_widget_set_state_flags (self->image, GTK_STATE_FLAG_ACTIVE, false);
+    }
+    else
+    {
+        gtk_widget_unset_state_flags (self->image, GTK_STATE_FLAG_ACTIVE);
+    }
+}
+
+static void
+on_image_multi_press_gesture_pressed (GtkGestureMultiPress *gesture,
+                                      int                   n_press,
+                                      double                x,
+                                      double                y,
+                                      gpointer              user_data)
+{
+    set_image_pressed (user_data, true);
+
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+on_image_multi_press_gesture_released (GtkGestureMultiPress *gesture,
+                                       int                   n_press,
+                                       double                x,
+                                       double                y,
+                                       gpointer              user_data)
+{
+    set_image_pressed (user_data, false);
+
+    g_signal_emit (user_data, signals[BUTTON_CLICKED], 0);
+}
+
+static void
+on_image_multi_press_gesture_cancel (GtkGesture       *gesture,
+                                     GdkEventSequence *sequence,
+                                     gpointer          user_data)
+{
+    set_image_pressed (user_data, false);
+}
+
+static void
+on_image_multi_press_gesture_update (GtkGesture       *gesture,
+                                     GdkEventSequence *sequence,
+                                     gpointer          user_data)
+{
+    GtkWidget *widget;
+    double x;
+    double y;
+    bool contains;
+
+    widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+
+    gtk_gesture_get_point (gesture, sequence, &x, &y);
+
+    contains = gtk_widget_contains (widget, x, y);
+    if (contains)
+    {
+        gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, false);
+    }
+    else
+    {
+        gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
+    }
+
+    set_image_pressed (user_data, contains);
+}
+
+static void
+nautilus_tagged_entry_tag_init (NautilusTaggedEntryTag *self)
+{
+    GtkGesture *gesture;
+    GtkStyleContext *context;
+
+    self->label = gtk_label_new (NULL);
+    self->image = gtk_image_new_from_icon_name ("window-close-symbolic");
+    self->active = false;
+
+    gtk_widget_set_has_surface (GTK_WIDGET (self), false);
+
+    gtk_widget_hide (self->label);
+    gtk_widget_insert_after (self->label, GTK_WIDGET (self), NULL);
+
+    gtk_widget_insert_after (self->image, GTK_WIDGET (self), self->label);
+
+    gesture = gtk_gesture_multi_press_new ();
+    gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+    g_signal_connect (gesture,
+                      "pressed", G_CALLBACK (on_multi_press_gesture_pressed),
+                      self);
+    g_signal_connect (gesture,
+                      "released", G_CALLBACK (on_multi_press_gesture_released),
+                      self);
+
+    gesture = gtk_gesture_multi_press_new ();
+    gtk_widget_add_controller (self->image, GTK_EVENT_CONTROLLER (gesture));
+
+    g_signal_connect (gesture,
+                      "pressed", G_CALLBACK (on_image_multi_press_gesture_pressed),
+                      self);
+    g_signal_connect (gesture,
+                      "released", G_CALLBACK (on_image_multi_press_gesture_released),
+                      self);
+    g_signal_connect (gesture,
+                      "cancel", G_CALLBACK (on_image_multi_press_gesture_cancel),
+                      self);
+    g_signal_connect (gesture,
+                      "update", G_CALLBACK (on_image_multi_press_gesture_update),
+                      self);
+
+    context = gtk_widget_get_style_context (GTK_WIDGET (self));
+    gtk_style_context_add_class (context, "entry-tag");
+
+    context = gtk_widget_get_style_context (self->image);
+    gtk_style_context_add_class (context, "entry-tag");
+    gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+}
+
+static void
+set_property (GObject      *object,
+              guint         property_id,
+              const GValue *value,
+              GParamSpec   *pspec)
+{
+    NautilusTaggedEntryTag *self;
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (object);
+
+    switch (property_id)
+    {
+        case PROP_LABEL:
+        {
+            const char *string;
+            bool empty;
+
+            string = g_value_get_string (value);
+            empty = (g_strcmp0 (string, "") == 0);
+
+            gtk_label_set_label (GTK_LABEL (self->label), string);
+            gtk_widget_set_visible (self->label, !empty);
+        }
+        break;
+
+        case PROP_SHOW_CLOSE_BUTTON:
+        {
+            gtk_widget_set_visible (self->image, g_value_get_boolean (value));
+        }
+        break;
+
+        default:
+        {
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        }
+    }
+}
+
+static void
+get_property (GObject    *object,
+              guint       property_id,
+              GValue     *value,
+              GParamSpec *pspec)
+{
+    NautilusTaggedEntryTag *self;
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (object);
+
+    switch (property_id)
+    {
+        case PROP_LABEL:
+        {
+            g_value_set_string (value, gtk_label_get_label (GTK_LABEL (self->label)));
+        };
+        break;
+
+        case PROP_SHOW_CLOSE_BUTTON:
+        {
+            g_value_set_boolean (value, gtk_widget_is_visible (self->image));
+        }
+        break;
+
+        default:
+        {
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        }
+    }
+}
+
+static void
+finalize (GObject *object)
+{
+    NautilusTaggedEntryTag *self;
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (object);
+
+    gtk_widget_unparent (self->label);
+    gtk_widget_unparent (self->image);
+
+    G_OBJECT_CLASS (nautilus_tagged_entry_tag_parent_class)->finalize (object);
+}
+
+static void
+size_allocate (GtkWidget *widget,
+               int        width,
+               int        height,
+               int        baseline)
+{
+    NautilusTaggedEntryTag *self;
+    GtkAllocation label_allocation = { 0 };
+    GtkAllocation image_allocation = { 0 };
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (widget);
+
+    gtk_widget_measure (self->label,
+                        GTK_ORIENTATION_HORIZONTAL,
+                        -1,
+                        &label_allocation.width, NULL,
+                        NULL, NULL);
+    gtk_widget_measure (self->label,
+                        GTK_ORIENTATION_VERTICAL,
+                        label_allocation.width,
+                        &label_allocation.height, NULL,
+                        NULL, NULL);
+
+    label_allocation.y = (height - label_allocation.height) / 2;
+
+    gtk_widget_size_allocate (self->label, &label_allocation, -1);
+
+    image_allocation.x = label_allocation.width + SPACING;
+
+    gtk_widget_measure (self->image,
+                        GTK_ORIENTATION_HORIZONTAL,
+                        -1,
+                        &image_allocation.width, NULL,
+                        NULL, NULL);
+    gtk_widget_measure (self->image,
+                        GTK_ORIENTATION_VERTICAL,
+                        image_allocation.width,
+                        &image_allocation.height, NULL,
+                        NULL, NULL);
+
+    image_allocation.y = (height - image_allocation.height) / 2;
+
+    gtk_widget_size_allocate (self->image, &image_allocation, -1);
+}
+
+static void
+measure (GtkWidget      *widget,
+         GtkOrientation  orientation,
+         int             for_size,
+         int            *minimum,
+         int            *natural,
+         int            *minimum_baseline,
+         int            *natural_baseline)
+{
+    NautilusTaggedEntryTag *self;
+    int label_minimum;
+    int label_natural;
+    int image_minimum;
+    int image_natural;
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (widget);
+
+    gtk_widget_measure (self->label, orientation, for_size,
+                        &label_minimum, &label_natural,
+                        NULL, NULL);
+    gtk_widget_measure (self->image, orientation, for_size,
+                        &image_minimum, &image_natural,
+                        NULL, NULL);
+
+    if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+        if (minimum != NULL)
+        {
+            *minimum = label_minimum + image_minimum;
+
+            if (gtk_widget_is_visible (self->image))
+            {
+                *minimum += SPACING;
+            }
+        }
+        if (natural != NULL)
+        {
+            *natural = label_natural + image_natural;
+
+            if (gtk_widget_is_visible (self->image))
+            {
+                *natural += SPACING;
+            }
+        }
+    }
+    else
+    {
+        if (minimum != NULL)
+        {
+            *minimum = MAX (label_minimum, image_minimum);
+        }
+        if (natural != NULL)
+        {
+            *natural = MAX (label_natural, image_natural);
+        }
+    }
+}
+
+static void
+snapshot (GtkWidget   *widget,
+          GtkSnapshot *snapshot)
+{
+    NautilusTaggedEntryTag *self;
+    GtkWidgetClass *parent_widget_class;
+
+    self = NAUTILUS_TAGGED_ENTRY_TAG (widget);
+    parent_widget_class = GTK_WIDGET_CLASS (nautilus_tagged_entry_tag_parent_class);
+
+    parent_widget_class->snapshot (widget, snapshot);
+
+    gtk_widget_snapshot_child (widget, self->label, snapshot);
+    gtk_widget_snapshot_child (widget, self->image, snapshot);
+}
+
+static void
+nautilus_tagged_entry_tag_class_init (NautilusTaggedEntryTagClass *klass)
+{
+    GObjectClass *object_class;
+    GtkWidgetClass *widget_class;
+    GtkCssProvider *provider;
+    GdkDisplay *display;
+
+    object_class = G_OBJECT_CLASS (klass);
+    widget_class = GTK_WIDGET_CLASS (klass);
+    provider = gtk_css_provider_new ();
+    display = gdk_display_get_default ();
+
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+    object_class->finalize = finalize;
+
+    widget_class->size_allocate = size_allocate;
+    widget_class->measure = measure;
+    widget_class->snapshot = snapshot;
+
+    gtk_css_provider_load_from_data (provider,
+                                     ".entry-tag {"
+                                     "    border-radius: 4px;"
+                                     "    padding: 0 6px 0 6px;"
+                                     "    margin-left: 0;"
+                                     "    margin-right: 0;"
+                                     "}"
+                                     ".entry-tag label {"
+                                     "    color: inherit;"
+                                     "}"
+                                     /* Reserving space for the border
+                                      * to prevent jumping around.
+                                      */
+                                     ".entry-tag.button:not(:hover),"
+                                     ".entry-tag.button:backdrop {"
+                                     "    border: 1px solid transparent;"
+                                     "}"
+                                     ".entry-tag.button {"
+                                     "    margin: 0;"
+                                     "    padding: 0;"
+                                     "}"
+                                     ,
+                                     -1);
+
+    gtk_style_context_add_provider_for_display (display,
+                                                GTK_STYLE_PROVIDER (provider),
+                                                GTK_STYLE_PROVIDER_PRIORITY_USER);
+
+    properties[PROP_LABEL] =
+        g_param_spec_string ("label", "Label", "Label",
+                             NULL,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    properties[PROP_SHOW_CLOSE_BUTTON] =
+        g_param_spec_boolean ("show-close-button", "Show Close Button", "Toggles close button visibility",
+                              true,
+                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+
+    signals[CLICKED] = g_signal_new ("clicked",
+                                     G_TYPE_FROM_CLASS (klass),
+                                     G_SIGNAL_RUN_FIRST,
+                                     0,
+                                     NULL, NULL,
+                                     g_cclosure_marshal_VOID__VOID,
+                                     G_TYPE_NONE,
+                                     0);
+    signals[BUTTON_CLICKED] = g_signal_new ("button-clicked",
+                                            G_TYPE_FROM_CLASS (klass),
+                                            G_SIGNAL_RUN_FIRST,
+                                            0,
+                                            NULL, NULL,
+                                            g_cclosure_marshal_VOID__VOID,
+                                            G_TYPE_NONE,
+                                            0);
+}
+
+void
+nautilus_tagged_entry_tag_set_label (NautilusTaggedEntryTag *self,
+                                     const char             *label)
+{
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY_TAG (self));
+
+    g_object_set (self, "label", label, NULL);
+}
+
+void
+nautilus_tagged_entry_tag_set_show_close_button (NautilusTaggedEntryTag *self,
+                                                 bool                    show_close_button)
+{
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY_TAG (self));
+
+    gtk_widget_set_visible (self->image, show_close_button);
+}
+
+GtkWidget *
+nautilus_tagged_entry_tag_new (const char *label)
+{
+    return g_object_new (NAUTILUS_TYPE_TAGGED_ENTRY_TAG, "label", label, NULL);
+}
diff --git a/src/nautilus-tagged-entry-tag.h b/src/nautilus-tagged-entry-tag.h
new file mode 100644
index 000000000..8641e7d19
--- /dev/null
+++ b/src/nautilus-tagged-entry-tag.h
@@ -0,0 +1,38 @@
+/* GTK+ 4 implementation of GdTaggedEntry for Nautilus
+ * © 2018  Ernestas Kulik <ernestask gnome org>
+ *
+ * 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.1 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <stdbool.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAGGED_ENTRY_TAG (nautilus_tagged_entry_tag_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTaggedEntryTag, nautilus_tagged_entry_tag,
+                      NAUTILUS, TAGGED_ENTRY_TAG,
+                      GtkWidget)
+
+void       nautilus_tagged_entry_tag_set_label             (NautilusTaggedEntryTag *tag,
+                                                            const char             *label);
+void       nautilus_tagged_entry_tag_set_show_close_button (NautilusTaggedEntryTag *tag,
+                                                            bool                    show_close_button);
+
+GtkWidget *nautilus_tagged_entry_tag_new                   (const char             *label);
+
+G_END_DECLS
diff --git a/src/nautilus-tagged-entry.c b/src/nautilus-tagged-entry.c
new file mode 100644
index 000000000..5c029ff91
--- /dev/null
+++ b/src/nautilus-tagged-entry.c
@@ -0,0 +1,311 @@
+/* GTK+ 4 implementation of GdTaggedEntry for Nautilus
+ * © 2018  Ernestas Kulik <ernestask gnome org>
+ *
+ * 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.1 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-tagged-entry.h"
+
+#include "nautilus-tagged-entry-tag.h"
+
+struct _NautilusTaggedEntry
+{
+    GtkSearchEntry parent_instance;
+
+    GtkWidget *box;
+};
+
+G_DEFINE_TYPE (NautilusTaggedEntry, nautilus_tagged_entry, GTK_TYPE_SEARCH_ENTRY)
+
+#define SPACING 3
+
+enum
+{
+    TAG_CLICKED,
+    TAG_BUTTON_CLICKED,
+    LAST_SIGNAL
+};
+
+static unsigned int signals[LAST_SIGNAL];
+
+static void
+nautilus_tagged_entry_init (NautilusTaggedEntry *self)
+{
+    GtkStyleContext *context;
+
+    context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+    gtk_style_context_add_class (context, "tagged-entry");
+
+    self->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING);
+
+    gtk_widget_set_cursor_from_name (self->box, "default");
+    gtk_widget_set_parent (self->box, GTK_WIDGET (self));
+    gtk_widget_set_vexpand (self->box, true);
+}
+
+static void
+finalize (GObject *object)
+{
+    NautilusTaggedEntry *self;
+
+    self = NAUTILUS_TAGGED_ENTRY (object);
+
+    gtk_widget_unparent (self->box);
+
+    G_OBJECT_CLASS (nautilus_tagged_entry_parent_class)->finalize (object);
+}
+
+static void
+size_allocate (GtkWidget *widget,
+               int        width,
+               int        height,
+               int        baseline)
+{
+    NautilusTaggedEntry *self;
+    GtkWidgetClass *parent_widget_class;
+    GtkAllocation box_allocation = { 0 };
+    GtkAllocation entry_allocation = { 0 };
+    g_autoptr (GList) children = NULL;
+
+    self = NAUTILUS_TAGGED_ENTRY (widget);
+    parent_widget_class = GTK_WIDGET_CLASS (nautilus_tagged_entry_parent_class);
+    box_allocation = (GtkAllocation) { 0, 0, width, height };
+    entry_allocation = (GtkAllocation) { 0, 0, width, height };
+    children = gtk_container_get_children (GTK_CONTAINER (self->box));
+
+    gtk_widget_measure (self->box,
+                        GTK_ORIENTATION_HORIZONTAL,
+                        -1,
+                        &box_allocation.width, NULL,
+                        NULL, NULL);
+
+    box_allocation.x = width - box_allocation.width;
+
+    if (children != NULL)
+    {
+        GtkStyleContext *context;
+        GtkBorder padding = { 0 };
+
+        context = gtk_widget_get_style_context (widget);
+
+        gtk_style_context_get_padding (context, &padding);
+
+        if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+        {
+            box_allocation.width += padding.right;
+        }
+        else
+        {
+            box_allocation.width += padding.left;
+        }
+    }
+
+    entry_allocation.width -= box_allocation.width;
+
+    parent_widget_class->size_allocate (widget,
+                                        entry_allocation.width,
+                                        entry_allocation.height,
+                                        -1);
+
+    gtk_widget_size_allocate (self->box, &box_allocation, -1);
+}
+
+static void
+measure (GtkWidget      *widget,
+         GtkOrientation  orientation,
+         int             for_size,
+         int            *minimum,
+         int            *natural,
+         int            *minimum_baseline,
+         int            *natural_baseline)
+{
+    NautilusTaggedEntry *self;
+    GtkWidgetClass *parent_widget_class;
+    int entry_minimum;
+    int entry_natural;
+    int box_minimum;
+    int box_natural;
+
+    self = NAUTILUS_TAGGED_ENTRY (widget);
+    parent_widget_class = GTK_WIDGET_CLASS (nautilus_tagged_entry_parent_class);
+
+    parent_widget_class->measure (widget, orientation, for_size,
+                                  &entry_minimum, &entry_natural,
+                                  NULL, NULL);
+
+    gtk_widget_measure (self->box, orientation, for_size,
+                        &box_minimum, &box_natural,
+                        NULL, NULL);
+
+    if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+        if (minimum != NULL)
+        {
+            *minimum = entry_minimum + box_minimum;
+        }
+        if (natural != NULL)
+        {
+            *natural = entry_natural + box_minimum;
+        }
+    }
+    else
+    {
+        if (minimum != NULL)
+        {
+            *minimum = entry_minimum;
+        }
+        if (natural != NULL)
+        {
+            *natural = entry_natural;
+        }
+    }
+}
+
+static void
+snapshot (GtkWidget   *widget,
+          GtkSnapshot *snapshot)
+{
+    NautilusTaggedEntry *self;
+
+    self = NAUTILUS_TAGGED_ENTRY (widget);
+
+    GTK_WIDGET_CLASS (nautilus_tagged_entry_parent_class)->snapshot (widget, snapshot);
+
+    gtk_widget_snapshot_child (widget, self->box, snapshot);
+}
+
+static void
+nautilus_tagged_entry_class_init (NautilusTaggedEntryClass *klass)
+{
+    GObjectClass *object_class;
+    GtkWidgetClass *widget_class;
+    GtkCssProvider *provider;
+    GdkDisplay *display;
+
+    object_class = G_OBJECT_CLASS (klass);
+    widget_class = GTK_WIDGET_CLASS (klass);
+    provider = gtk_css_provider_new ();
+    display = gdk_display_get_default ();
+
+    object_class->finalize = finalize;
+
+    widget_class->size_allocate = size_allocate;
+    widget_class->measure = measure;
+    widget_class->snapshot = snapshot;
+
+    gtk_widget_class_set_css_name (widget_class, "entry");
+
+    gtk_css_provider_load_from_data (provider,
+                                     ".tagged-entry entry {"
+                                     "    background: transparent;"
+                                     "    border: none;"
+                                     "    box-shadow: none;"
+                                     "}"
+                                     ,
+                                     -1);
+
+    gtk_style_context_add_provider_for_display (display,
+                                                GTK_STYLE_PROVIDER (provider),
+                                                GTK_STYLE_PROVIDER_PRIORITY_USER);
+
+    signals[TAG_CLICKED] = g_signal_new ("tag-clicked",
+                                         G_TYPE_FROM_CLASS (klass),
+                                         G_SIGNAL_RUN_FIRST,
+                                         0,
+                                         NULL, NULL,
+                                         NULL,
+                                         G_TYPE_NONE,
+                                         1,
+                                         NAUTILUS_TYPE_TAGGED_ENTRY);
+    signals[TAG_BUTTON_CLICKED] = g_signal_new ("tag-button-clicked",
+                                                G_TYPE_FROM_CLASS (klass),
+                                                G_SIGNAL_RUN_FIRST,
+                                                0,
+                                                NULL, NULL,
+                                                NULL,
+                                                G_TYPE_NONE,
+                                                1,
+                                                NAUTILUS_TYPE_TAGGED_ENTRY);
+}
+
+static void
+on_tag_clicked (NautilusTaggedEntryTag *tag,
+                gpointer                user_data)
+{
+    g_signal_emit (user_data, signals[TAG_CLICKED], 0, tag);
+}
+
+static void
+on_tag_button_clicked (NautilusTaggedEntryTag *tag,
+                       gpointer                user_data)
+{
+    g_signal_emit (user_data, signals[TAG_BUTTON_CLICKED], 0, tag);
+}
+
+void
+nautilus_tagged_entry_add_tag (NautilusTaggedEntry    *self,
+                               NautilusTaggedEntryTag *tag)
+{
+    unsigned long handler_id;
+
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY (self));
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY_TAG (tag));
+
+    gtk_container_add (GTK_CONTAINER (self->box), GTK_WIDGET (tag));
+
+    handler_id = g_signal_connect (tag,
+                                   "clicked", G_CALLBACK (on_tag_clicked),
+                                   self);
+    g_object_set_data (G_OBJECT (tag),
+                       "clicked-handler-id", GUINT_TO_POINTER (handler_id));
+
+    handler_id = g_signal_connect (tag,
+                                   "button-clicked", G_CALLBACK (on_tag_button_clicked),
+                                   self);
+    g_object_set_data (G_OBJECT (tag),
+                       "button-clicked-handler-id", GUINT_TO_POINTER (handler_id));
+}
+
+void
+nautilus_tagged_entry_remove_tag (NautilusTaggedEntry    *self,
+                                  NautilusTaggedEntryTag *tag)
+{
+    GtkWidget *parent;
+    gpointer data;
+    unsigned long handler_id;
+
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY (self));
+    g_return_if_fail (NAUTILUS_IS_TAGGED_ENTRY_TAG (tag));
+
+    parent = gtk_widget_get_parent (GTK_WIDGET (tag));
+
+    g_return_if_fail (parent != GTK_WIDGET (self));
+
+    data = g_object_get_data (G_OBJECT (tag), "clicked-handler-id");
+    handler_id = GPOINTER_TO_UINT (data);
+    g_signal_handler_disconnect (tag, handler_id);
+
+    data = g_object_get_data (G_OBJECT (tag), "button-clicked-handler-id");
+    handler_id = GPOINTER_TO_UINT (data);
+    g_signal_handler_disconnect (tag, handler_id);
+
+    gtk_container_remove (GTK_CONTAINER (self->box), GTK_WIDGET (tag));
+}
+
+GtkWidget *
+nautilus_tagged_entry_new (void)
+{
+    return g_object_new (NAUTILUS_TYPE_TAGGED_ENTRY, NULL);
+}
diff --git a/src/nautilus-tagged-entry.h b/src/nautilus-tagged-entry.h
new file mode 100644
index 000000000..61721ec4f
--- /dev/null
+++ b/src/nautilus-tagged-entry.h
@@ -0,0 +1,39 @@
+/* GTK+ 4 implementation of GdTaggedEntry for Nautilus
+ * © 2018  Ernestas Kulik <ernestask gnome org>
+ *
+ * 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.1 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _NautilusTaggedEntryTag NautilusTaggedEntryTag;
+
+#define NAUTILUS_TYPE_TAGGED_ENTRY (nautilus_tagged_entry_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTaggedEntry, nautilus_tagged_entry,
+                      NAUTILUS, TAGGED_ENTRY,
+                      GtkSearchEntry)
+
+void       nautilus_tagged_entry_add_tag          (NautilusTaggedEntry    *entry,
+                                                   NautilusTaggedEntryTag *tag);
+void       nautilus_tagged_entry_remove_tag       (NautilusTaggedEntry    *entry,
+                                                   NautilusTaggedEntryTag *tag);
+
+GtkWidget *nautilus_tagged_entry_new              (void);
+
+G_END_DECLS
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
index 9232ba803..c6a74de45 100644
--- a/src/resources/css/Adwaita.css
+++ b/src/resources/css/Adwaita.css
@@ -71,11 +71,6 @@
     border-radius: 0px 0px 0px 0px;
 }
 
-/* Make the tags fit into the box */
-entry.search > * {
-  margin: 5px;
-}
-
 /* Sidebar */
 
 .sidebar-row:selected {



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