[epiphany/wip/bookmarks] bookmarks: Add properties dialog



commit 32387fdbd0470b429cc067ede9292edb3f02936c
Author: Iulian Radu <iulian radu67 gmail com>
Date:   Tue Jul 26 21:22:58 2016 +0300

    bookmarks: Add properties dialog

 src/Makefile.am                               |    5 +
 src/ephy-bookmark-properties-grid.c           |  336 +++++++++++++++++++++++++
 src/ephy-bookmark-properties-grid.h           |   45 ++++
 src/ephy-bookmark-row.c                       |   38 +++
 src/ephy-bookmark.c                           |   71 +++++-
 src/ephy-bookmark.h                           |   10 +-
 src/ephy-bookmarks-manager.c                  |  107 ++++++---
 src/ephy-bookmarks-manager.h                  |   10 +-
 src/ephy-bookmarks-popover.c                  |   63 ++---
 src/epiphany.gresource.xml                    |    1 +
 src/resources/epiphany.css                    |   36 +++-
 src/resources/epiphany.scss                   |   42 +++
 src/resources/gtk/bookmark-properties-grid.ui |  171 +++++++++++++
 src/resources/gtk/bookmark-row.ui             |   22 ++
 14 files changed, 878 insertions(+), 79 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 47f28a0..660ed41 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,6 +17,7 @@ EXTRA_DIST = \
 
 TYPES_H_FILES = \
        ephy-link.h                             \
+       ephy-bookmark-properties-grid.h         \
        ephy-session.h                          \
        ephy-shell.h                            \
        ephy-window.h                           \
@@ -26,6 +27,8 @@ TYPES_H_FILES = \
 libephymain_la_SOURCES = \
        ephy-bookmark.c                         \
        ephy-bookmark.h                         \
+       ephy-bookmark-properties-grid.c         \
+       ephy-bookmark-properties-grid.c         \
        ephy-bookmark-row.c                     \
        ephy-bookmark-row.h                     \
        ephy-bookmarks-manager.c                \
@@ -131,6 +134,8 @@ RESOURCE_FILES = \
        resources/prefs-dialog.ui                 \
        resources/prefs-lang-dialog.ui            \
        resources/shortcuts-dialog.ui             \
+       resources/gtk/bookmark-properties-grid.ui \
+       resources/gtk/bookmark-row.ui             \
        resources/gtk/bookmarks-popover.ui        \
        resources/gtk/menus.ui                    \
        $(NULL)
diff --git a/src/ephy-bookmark-properties-grid.c b/src/ephy-bookmark-properties-grid.c
new file mode 100644
index 0000000..f1c9fb2
--- /dev/null
+++ b/src/ephy-bookmark-properties-grid.c
@@ -0,0 +1,336 @@
+/* vim: set sw=2 ts=2 sts=2 et: */
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright (C) 2016 Iulian-Gabriel Radu <iulian radu67 gmail com>
+ *
+ * This program 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 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "ephy-bookmark-properties-grid.h"
+
+#include "ephy-bookmarks-manager.h"
+#include "ephy-debug.h"
+#include "ephy-shell.h"
+#include "ephy-type-builtins.h"
+
+#include <libsoup/soup.h>
+#include <string.h>
+
+struct _EphyBookmarkPropertiesGrid {
+  GtkGrid                         parent_instance;
+
+  EphyBookmark                   *bookmark;
+  EphyBookmarkPropertiesGridType  type;
+
+  GtkWidget                      *popover_bookmark_label;
+  GtkWidget                      *name_entry;
+  GtkWidget                      *address_entry;
+  GtkWidget                      *popover_tags_label;
+  GtkWidget                      *tags_box;
+  GtkWidget                      *add_tag_entry;
+  GtkWidget                      *add_tag_button;
+  GtkWidget                      *remove_bookmark_button;
+};
+
+G_DEFINE_TYPE (EphyBookmarkPropertiesGrid, ephy_bookmark_properties_grid, GTK_TYPE_GRID)
+
+enum {
+  PROP_0,
+  PROP_BOOKMARK,
+  PROP_TYPE,
+  LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static void
+ephy_bookmark_properties_grid_tags_box_child_activated_cb (EphyBookmarkPropertiesGrid *self,
+                                                           GtkFlowBoxChild            *child,
+                                                           GtkFlowBox                 *flow_box)
+{
+  GtkStyleContext *context;
+  GtkWidget *box;
+  GtkWidget *label;
+
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (GTK_IS_FLOW_BOX_CHILD (child));
+  g_assert (GTK_IS_FLOW_BOX (flow_box));
+
+  box = gtk_bin_get_child (GTK_BIN (child));
+  label = g_object_get_data (G_OBJECT (box), "label");
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (child));
+  if (gtk_style_context_has_class (context, "bookmark-tag-widget-selected")) {
+    ephy_bookmark_remove_tag (self->bookmark,
+                              gtk_label_get_text (GTK_LABEL (label)));
+    gtk_style_context_remove_class (context, "bookmark-tag-widget-selected");
+  } else {
+    ephy_bookmark_add_tag (self->bookmark,
+                           gtk_label_get_text (GTK_LABEL (label)));
+    gtk_style_context_add_class (context, "bookmark-tag-widget-selected");
+  }
+}
+
+static GtkWidget *
+ephy_bookmark_properties_grid_create_tag_widget (EphyBookmarkPropertiesGrid *self,
+                                                 const char *tag,
+                                                 gboolean selected)
+{
+  GtkWidget *widget;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *button;
+  GtkStyleContext *context;
+
+  widget = gtk_flow_box_child_new ();
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
+  label = gtk_label_new (tag);
+  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+  button = gtk_button_new ();
+  gtk_button_set_image (GTK_BUTTON (button),
+                        gtk_image_new_from_icon_name ("window-close-symbolic",
+                                                      GTK_ICON_SIZE_MENU));
+  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+  gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  g_object_set_data (G_OBJECT (box), "label", label);
+
+  gtk_container_add (GTK_CONTAINER (widget), box);
+
+  context = gtk_widget_get_style_context (widget);
+  gtk_style_context_add_class (context, "bookmark-tag-widget");
+  if (selected) {
+    /* Toggle initial state on child */
+    ephy_bookmark_properties_grid_tags_box_child_activated_cb (EPHY_BOOKMARK_PROPERTIES_GRID (self),
+                                                               GTK_FLOW_BOX_CHILD (GTK_FLOW_BOX_CHILD 
(widget)),
+                                                               GTK_FLOW_BOX (self->tags_box));
+  }
+
+  gtk_widget_show_all (widget);
+
+  return widget;
+}
+
+static void
+ephy_bookmark_properties_grid_add_tag_button_clicked_cb (EphyBookmarkPropertiesGrid *self,
+                                                         GtkButton                  *button)
+{
+  GtkEntryBuffer *buffer;
+  GtkWidget *widget;
+  const char *text;
+
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  buffer = gtk_entry_get_buffer (GTK_ENTRY (self->add_tag_entry));
+  text = gtk_entry_buffer_get_text (buffer);
+  ephy_bookmark_add_tag (self->bookmark, text);
+
+  widget = ephy_bookmark_properties_grid_create_tag_widget (self, text, TRUE);
+  gtk_flow_box_insert (GTK_FLOW_BOX (self->tags_box), widget, -1);
+}
+
+static void
+ephy_bookmark_properties_grid_remove_bookmark_button_clicked_cb (EphyBookmarkPropertiesGrid *self,
+                                                                 GtkButton *button)
+{
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  g_signal_emit_by_name (self->bookmark, "removed");
+
+  gtk_widget_hide (gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (self))));
+  gtk_widget_destroy (gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (self))));
+}
+
+static void
+ephy_bookmark_properties_grid_buffer_text_changed_cb (EphyBookmarkPropertiesGrid *self,
+                                                      GParamSpec                 *pspec,
+                                                      GtkEntryBuffer             *buffer)
+{
+  const char *text;
+
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (GTK_IS_ENTRY_BUFFER (buffer));
+
+  text = gtk_entry_buffer_get_text (buffer);
+  /* TODO: Check if the tag already exists. Before doing this check, come up
+   * with a better way of storing a list of all existing tags. The current way
+   * of iterating over all bookmarks and doing a reunion of their tags is
+   * really slow */
+  if (strlen (text) >= 3)
+    gtk_widget_set_sensitive (self->add_tag_button, TRUE);
+}
+
+static void
+ephy_bookmark_properties_grid_set_property (GObject      *object,
+                                            guint         prop_id,
+                                            const GValue *value,
+                                            GParamSpec   *pspec)
+{
+  EphyBookmarkPropertiesGrid *self = EPHY_BOOKMARK_PROPERTIES_GRID (object);
+
+  switch (prop_id) {
+    case PROP_BOOKMARK:
+      self->bookmark = g_value_dup_object (value);
+      break;
+    case PROP_TYPE:
+      self->type = g_value_get_enum (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_bookmark_properties_grid_constructed (GObject *object)
+{
+  EphyBookmarkPropertiesGrid *self = EPHY_BOOKMARK_PROPERTIES_GRID (object);
+  EphyBookmarksManager *manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  GSequence *tags;
+  GSequence *bookmark_tags;
+  GSequenceIter *iter;
+  SoupURI *uri;
+
+  /* Set text for name entry */
+  gtk_entry_set_text (GTK_ENTRY (self->name_entry),
+                      ephy_bookmark_get_title (self->bookmark));
+
+  /* Set text for address entry */
+  uri = soup_uri_new (ephy_bookmark_get_url (self->bookmark));
+  gtk_entry_set_text (GTK_ENTRY (self->address_entry),
+                      g_strconcat (soup_uri_get_host (uri),
+                                   soup_uri_get_path (uri),
+                                   soup_uri_get_query (uri),
+                                   soup_uri_get_fragment (uri),
+                                   NULL));
+  soup_uri_free (uri);
+
+  /* Create tag widgets */
+  tags = ephy_bookmarks_manager_get_tags (manager);
+  bookmark_tags = ephy_bookmark_get_tags (self->bookmark);
+  g_sequence_sort (bookmark_tags, (GCompareDataFunc)g_strcmp0, NULL);
+  for (iter = g_sequence_get_begin_iter (tags);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter)) {
+    GtkWidget *widget;
+    gboolean selected = FALSE;
+    const char *tag = g_sequence_get (iter);
+
+    if (g_sequence_lookup (bookmark_tags, (gpointer)tag, (GCompareDataFunc)g_strcmp0, NULL))
+      selected = TRUE;
+
+    widget = ephy_bookmark_properties_grid_create_tag_widget (self, tag, selected);
+    gtk_flow_box_insert (GTK_FLOW_BOX (self->tags_box), widget, -1);
+  }
+
+  g_signal_connect_object (self->tags_box, "child-activated",
+                           G_CALLBACK (ephy_bookmark_properties_grid_tags_box_child_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_widget_show_all (self->tags_box);
+
+  /* Connect */
+}
+
+static void
+ephy_bookmark_properties_grid_class_init (EphyBookmarkPropertiesGridClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->set_property = ephy_bookmark_properties_grid_set_property;
+  object_class->constructed = ephy_bookmark_properties_grid_constructed;
+
+  obj_properties[PROP_BOOKMARK] =
+    g_param_spec_object ("bookmark",
+                         "An EphyBookmark object",
+                         "The EphyBookmark whose properties are being displayed",
+                         EPHY_TYPE_BOOKMARK,
+                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_TYPE] =
+    g_param_spec_enum ("type",
+                       "An EphyBookmarkPropertiesGrid object",
+                       "The type of widget the grid will be used for",
+                       EPHY_TYPE_BOOKMARK_PROPERTIES_GRID_TYPE,
+                       EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG,
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/epiphany/gtk/bookmark-properties-grid.ui");
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, popover_bookmark_label);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, name_entry);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, address_entry);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, popover_tags_label);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, tags_box);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, add_tag_entry);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, add_tag_button);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkPropertiesGrid, remove_bookmark_button);
+}
+
+static void
+ephy_bookmark_properties_grid_init (EphyBookmarkPropertiesGrid *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  if (self->type == EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG) {
+    gtk_container_remove (GTK_CONTAINER (self), self->popover_bookmark_label);
+    gtk_container_remove (GTK_CONTAINER (self), self->popover_tags_label);
+  } else if (self->type == EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG) {
+    gtk_grid_remove_column (GTK_GRID (self), 0);
+  }
+
+  g_signal_connect_object (gtk_entry_get_buffer (GTK_ENTRY (self->add_tag_entry)),
+                           "notify::text",
+                           G_CALLBACK (ephy_bookmark_properties_grid_buffer_text_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->add_tag_button,
+                           "clicked",
+                           G_CALLBACK (ephy_bookmark_properties_grid_add_tag_button_clicked_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->remove_bookmark_button,
+                           "clicked",
+                           G_CALLBACK (ephy_bookmark_properties_grid_remove_bookmark_button_clicked_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+GtkWidget *
+ephy_bookmark_properties_grid_new (EphyBookmark *bookmark,
+                                   EphyBookmarkPropertiesGridType type)
+{
+  g_return_val_if_fail (EPHY_IS_BOOKMARK (bookmark), NULL);
+
+  return g_object_new (EPHY_TYPE_BOOKMARK_PROPERTIES_GRID,
+                       "bookmark", bookmark,
+                       "type", type,
+                       NULL);
+}
+
+GtkWidget *
+ephy_bookmark_properties_grid_get_add_tag_button (EphyBookmarkPropertiesGrid *self)
+{
+  g_return_val_if_fail (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self), NULL);
+
+  return self->add_tag_button;
+}
diff --git a/src/ephy-bookmark-properties-grid.h b/src/ephy-bookmark-properties-grid.h
new file mode 100644
index 0000000..389aeae
--- /dev/null
+++ b/src/ephy-bookmark-properties-grid.h
@@ -0,0 +1,45 @@
+/* vim: set sw=2 ts=2 sts=2 et: */
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright (C) 2016 Iulian-Gabriel Radu <iulian radu67 gmail com>
+ *
+ * This program 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 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _EPHY_BOOKMARK_PROPERTIES_GRID_H
+#define _EPHY_BOOKMARK_PROPERTIES_GRID_H
+
+#include "ephy-bookmark.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_BOOKMARK_PROPERTIES_GRID (ephy_bookmark_properties_grid_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyBookmarkPropertiesGrid, ephy_bookmark_properties_grid, EPHY, 
BOOKMARK_PROPERTIES_GRID, GtkGrid)
+
+typedef enum {
+  EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG,
+  EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_POPOVER
+} EphyBookmarkPropertiesGridType;
+
+GtkWidget *ephy_bookmark_properties_grid_new                (EphyBookmark *bookmark,
+                                                             EphyBookmarkPropertiesGridType type);
+
+GtkWidget *ephy_bookmark_properties_grid_get_add_tag_button (EphyBookmarkPropertiesGrid *self);
+
+G_END_DECLS
+
+#endif /* _EPHY_BOOKMARK_PROPERTIES_GRID_H */
diff --git a/src/ephy-bookmark-row.c b/src/ephy-bookmark-row.c
index a5b0fde..4fe2c92 100644
--- a/src/ephy-bookmark-row.c
+++ b/src/ephy-bookmark-row.c
@@ -17,6 +17,7 @@
 
 #include "config.h"
 
+#include "ephy-bookmark-properties-grid.h"
 #include "ephy-bookmark-row.h"
 
 struct _EphyBookmarkRow {
@@ -25,6 +26,7 @@ struct _EphyBookmarkRow {
   EphyBookmark    *bookmark;
 
   GtkWidget       *title_label;
+  GtkWidget       *properties_button;
 };
 
 G_DEFINE_TYPE (EphyBookmarkRow, ephy_bookmark_row, GTK_TYPE_LIST_BOX_ROW)
@@ -38,6 +40,35 @@ enum {
 static GParamSpec *obj_properties[LAST_PROP];
 
 static void
+ephy_bookmark_row_button_clicked_cb (EphyBookmarkRow *row,
+                                     GtkButton       *button)
+{
+  GtkWidget *dialog;
+  GtkWidget *content_area;
+  GtkWidget *grid;
+
+  g_assert (EPHY_IS_BOOKMARK_ROW (row));
+  g_assert (GTK_IS_BUTTON (button));
+
+  dialog = gtk_dialog_new_with_buttons ("Bookmark Properties",
+                                        GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (row))),
+                                        GTK_DIALOG_USE_HEADER_BAR,
+                                        NULL, NULL);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  grid = ephy_bookmark_properties_grid_new (ephy_bookmark_row_get_bookmark (row),
+                                            EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG);
+  gtk_window_set_default (GTK_WINDOW (dialog),
+                          ephy_bookmark_properties_grid_get_add_tag_button (EPHY_BOOKMARK_PROPERTIES_GRID 
(grid)));
+
+  gtk_container_add (GTK_CONTAINER (content_area), grid);
+
+  gtk_widget_show (dialog);
+}
+
+static void
 ephy_bookmark_row_set_property (GObject      *object,
                                 guint         prop_id,
                                 const GValue *value,
@@ -117,12 +148,19 @@ ephy_bookmark_row_class_init (EphyBookmarkRowClass *klass)
 
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/bookmark-row.ui");
   gtk_widget_class_bind_template_child (widget_class, EphyBookmarkRow, title_label);
+  gtk_widget_class_bind_template_child (widget_class, EphyBookmarkRow, properties_button);
 }
 
 static void
 ephy_bookmark_row_init (EphyBookmarkRow *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->properties_button,
+                           "clicked",
+                           G_CALLBACK (ephy_bookmark_row_button_clicked_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
 }
 
 GtkWidget *
diff --git a/src/ephy-bookmark.c b/src/ephy-bookmark.c
index 38ccaa4..3cf08b4 100644
--- a/src/ephy-bookmark.c
+++ b/src/ephy-bookmark.c
@@ -24,7 +24,7 @@ struct _EphyBookmark {
 
   char        *url;
   char        *title;
-  GList       *tags;
+  GSequence   *tags;
 };
 
 G_DEFINE_TYPE (EphyBookmark, ephy_bookmark, G_TYPE_OBJECT)
@@ -36,7 +36,13 @@ enum {
   LAST_PROP
 };
 
+enum {
+  REMOVED,
+  LAST_SIGNAL
+};
+
 static GParamSpec *obj_properties[LAST_PROP];
+static guint       signals[LAST_SIGNAL];
 
 static void
 ephy_bookmark_set_property (GObject      *object,
@@ -68,7 +74,7 @@ ephy_bookmark_get_property (GObject      *object,
 
   switch (prop_id) {
     case PROP_TITLE:
-      g_value_set_string (value, self->title);
+      g_value_set_string (value, ephy_bookmark_get_title (self));
       break;
     case PROP_URL:
       g_value_set_string (value, ephy_bookmark_get_url (self));
@@ -86,8 +92,7 @@ ephy_bookmark_finalize (GObject *object)
   g_clear_pointer (&self->url, g_free);
   g_clear_pointer (&self->title, g_free);
 
-  g_list_free_full (self->tags, g_object_unref);
-  self->tags = NULL;
+  g_sequence_free (self->tags);
 
   G_OBJECT_CLASS (ephy_bookmark_parent_class)->finalize (object);
 }
@@ -116,6 +121,14 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
 
   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+
+  signals[REMOVED] =
+    g_signal_new ("removed",
+                  EPHY_TYPE_BOOKMARK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
 }
 
 static void
@@ -139,15 +152,61 @@ ephy_bookmark_get_url (EphyBookmark *self) {
   return self->url;
 }
 
+const char *
+ephy_bookmark_get_title (EphyBookmark *bookmark)
+{
+  g_return_val_if_fail (EPHY_IS_BOOKMARK (bookmark), NULL);
+
+  return bookmark->title;
+}
+
+void
+ephy_bookmark_add_tag (EphyBookmark *self,
+                       const char   *tag)
+{
+  GSequenceIter *tag_iter;
+  GSequenceIter *prev_tag_iter;
+
+  g_return_if_fail (EPHY_IS_BOOKMARK (self));
+
+  tag_iter = g_sequence_search (self->tags,
+                                (gpointer)tag,
+                                (GCompareDataFunc)g_strcmp0,
+                                NULL);
+
+  prev_tag_iter = g_sequence_iter_prev (tag_iter);
+  if (g_strcmp0 (g_sequence_get (prev_tag_iter), tag) != 0)
+    g_sequence_insert_before (tag_iter, g_strdup (tag));
+}
+
+void
+ephy_bookmark_remove_tag (EphyBookmark *self,
+                          const char   *tag)
+{
+  GSequenceIter *tag_iter;
+
+  g_return_if_fail (EPHY_IS_BOOKMARK (self));
+
+  tag_iter = g_sequence_lookup (self->tags,
+                                (gpointer)tag,
+                                (GCompareDataFunc)g_strcmp0,
+                                NULL);
+
+  g_assert (tag_iter != NULL);
+
+  g_sequence_remove (tag_iter);
+}
+
 void
-ephy_bookmark_set_tags (EphyBookmark *self, GList *tags)
+ephy_bookmark_set_tags (EphyBookmark *self, GSequence *tags)
 {
   g_return_if_fail (EPHY_IS_BOOKMARK (self));
+  g_return_if_fail (tags != NULL);
 
   self->tags = tags;
 }
 
-GList *
+GSequence *
 ephy_bookmark_get_tags (EphyBookmark *self)
 {
   g_return_val_if_fail (EPHY_IS_BOOKMARK (self), NULL);
diff --git a/src/ephy-bookmark.h b/src/ephy-bookmark.h
index 9d5d279..e752f58 100644
--- a/src/ephy-bookmark.h
+++ b/src/ephy-bookmark.h
@@ -29,11 +29,15 @@ G_DECLARE_FINAL_TYPE (EphyBookmark, ephy_bookmark, EPHY, BOOKMARK, GObject)
 EphyBookmark        *ephy_bookmark_new          (char *url,
                                                  char *title);
 
-
 const char          *ephy_bookmark_get_url      (EphyBookmark *self);
+const char          *ephy_bookmark_get_title    (EphyBookmark *self);
+void                 ephy_bookmark_add_tag      (EphyBookmark *self,
+                                                 const char *tag);
+void                 ephy_bookmark_remove_tag   (EphyBookmark *self,
+                                                 const char *tag);
 void                 ephy_bookmark_set_tags     (EphyBookmark *self,
-                                                 GList        *tags);
-GList               *ephy_bookmark_get_tags     (EphyBookmark *self);
+                                                 GSequence    *tags);
+GSequence           *ephy_bookmark_get_tags     (EphyBookmark *self);
 
 G_END_DECLS
 
diff --git a/src/ephy-bookmarks-manager.c b/src/ephy-bookmarks-manager.c
index 9c3735c..4257aaf 100644
--- a/src/ephy-bookmarks-manager.c
+++ b/src/ephy-bookmarks-manager.c
@@ -31,25 +31,14 @@ struct _EphyBookmarksManager {
   gchar      *gvdb_file;
 };
 
-G_DEFINE_TYPE (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT)
+static void list_model_iface_init     (GListModelInterface *iface);
 
-enum {
-  BOOKMARK_ADDED,
-  LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL];
+G_DEFINE_TYPE_EXTENDED (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
 
 static void
 ephy_bookmarks_manager_class_init (EphyBookmarksManagerClass *klass)
 {
-  signals[BOOKMARK_ADDED] =
-    g_signal_new ("bookmark-added",
-                  EPHY_TYPE_BOOKMARKS_MANAGER,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 1,
-                  EPHY_TYPE_BOOKMARK);
 }
 
 static void
@@ -60,6 +49,42 @@ ephy_bookmarks_manager_init (EphyBookmarksManager *self)
                                          NULL);
 }
 
+static GType
+ephy_bookmarks_manager_get_item_type (GListModel *model)
+{
+  return EPHY_TYPE_BOOKMARK;
+}
+
+static guint
+ephy_bookmarks_manager_get_n_items (GListModel *model)
+{
+  EphyBookmarksManager *self = (EphyBookmarksManager *)model;
+
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+
+  return g_list_length (self->bookmarks);
+}
+
+static gpointer
+ephy_bookmarks_manager_get_item (GListModel *model,
+                                 guint       position)
+{
+  EphyBookmarksManager *self = (EphyBookmarksManager *)model;
+
+  g_return_val_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self), NULL);
+  g_return_val_if_fail (position < g_list_length (self->bookmarks), NULL);
+
+  return g_object_ref (g_list_nth_data (self->bookmarks, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ephy_bookmarks_manager_get_item_type;
+  iface->get_n_items = ephy_bookmarks_manager_get_n_items;
+  iface->get_item = ephy_bookmarks_manager_get_item;
+}
+
 void
 ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
                                      EphyBookmark         *bookmark)
@@ -70,9 +95,29 @@ ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
   if (g_list_find (self->bookmarks, bookmark))
     return;
 
+  g_signal_connect_object (bookmark,
+                           "removed",
+                           G_CALLBACK (ephy_bookmarks_manager_remove_bookmark),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   self->bookmarks = g_list_prepend (self->bookmarks, bookmark);
+}
+
+void
+ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
+                                        EphyBookmark         *bookmark)
+{
+  gint position;
 
-  g_signal_emit (self, signals[BOOKMARK_ADDED], 0, bookmark);
+  g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+
+  position = g_list_position (self->bookmarks,
+                              g_list_find (self->bookmarks, bookmark));
+
+  self->bookmarks = g_list_remove (self->bookmarks, bookmark);
+  g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
 }
 
 GList *
@@ -83,29 +128,33 @@ ephy_bookmarks_manager_get_bookmarks (EphyBookmarksManager *self)
   return self->bookmarks;
 }
 
-GList *
+GSequence *
 ephy_bookmarks_manager_get_tags (EphyBookmarksManager *self)
 {
-  GHashTable *tags_set;
   GList *l;
-  GList *tags;
+  GSequence *tags;
 
   g_return_val_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self), NULL);
 
-  tags_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-
+  tags = g_sequence_new (g_free);
   for (l = self->bookmarks; l != NULL; l = l->next) {
     EphyBookmark *bookmark = EPHY_BOOKMARK (l->data);
-    GList *ll;
-
-    for (ll = ephy_bookmark_get_tags (bookmark); ll != NULL; ll = ll->next)
-      g_hash_table_add (tags_set, g_strdup (ll->data));
+    GSequence *bookmark_tags;
+    GSequenceIter *iter;
+
+    bookmark_tags = ephy_bookmark_get_tags (bookmark);
+    for (iter = g_sequence_get_begin_iter (bookmark_tags);
+         !g_sequence_iter_is_end (iter);
+         iter = g_sequence_iter_next (iter)) {
+      char *tag = g_sequence_get (iter);
+
+      if (g_sequence_lookup (tags, tag, (GCompareDataFunc)g_strcmp0, NULL) == NULL)
+        g_sequence_insert_sorted (tags,
+                                  g_strdup (tag),
+                                  (GCompareDataFunc)g_strcmp0,
+                                  NULL);
+    }
   }
 
-  tags = g_list_copy_deep (g_hash_table_get_values (tags_set),
-                           (GCopyFunc)g_strdup,
-                           NULL);
-  g_hash_table_destroy (tags_set);
-
   return tags;
 }
diff --git a/src/ephy-bookmarks-manager.h b/src/ephy-bookmarks-manager.h
index 55c90ff..a284a00 100644
--- a/src/ephy-bookmarks-manager.h
+++ b/src/ephy-bookmarks-manager.h
@@ -26,11 +26,13 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyBookmarksManager, ephy_bookmarks_manager, EPHY, BOOKMARKS_MANAGER, GObject)
 
-void    ephy_bookmarks_manager_add_bookmark         (EphyBookmarksManager *self,
-                                                     EphyBookmark         *bookmark);
+void         ephy_bookmarks_manager_add_bookmark         (EphyBookmarksManager *self,
+                                                          EphyBookmark         *bookmark);
+void         ephy_bookmarks_manager_remove_bookmark      (EphyBookmarksManager *self,
+                                                          EphyBookmark         *bookmark);
 
-GList  *ephy_bookmarks_manager_get_bookmarks        (EphyBookmarksManager *self);
-GList  *ephy_bookmarks_manager_get_tags             (EphyBookmarksManager *manager);
+GList       *ephy_bookmarks_manager_get_bookmarks        (EphyBookmarksManager *self);
+GSequence   *ephy_bookmarks_manager_get_tags             (EphyBookmarksManager *self);
 
 G_END_DECLS
 
diff --git a/src/ephy-bookmarks-popover.c b/src/ephy-bookmarks-popover.c
index e8862ac..3aa2be0 100644
--- a/src/ephy-bookmarks-popover.c
+++ b/src/ephy-bookmarks-popover.c
@@ -45,18 +45,13 @@ enum {
 
 static GParamSpec *obj_properties[LAST_PROP];
 
-static void
-bookmark_added_cb (EphyBookmarksPopover *self,
-                   EphyBookmark         *bookmark)
+static GtkWidget *
+create_bookmark_row (gpointer item,
+                     gpointer user_data)
 {
-  GtkWidget *bookmark_row;
-
-  g_assert (EPHY_IS_BOOKMARKS_POPOVER (self));
-  g_assert (EPHY_IS_BOOKMARK (bookmark));
-
-  bookmark_row = ephy_bookmark_row_new (bookmark);
+  EphyBookmark *bookmark = EPHY_BOOKMARK (item);
 
-  gtk_list_box_prepend (GTK_LIST_BOX (self->bookmarks_list_box), bookmark_row);
+  return ephy_bookmark_row_new (bookmark);
 }
 
 static void
@@ -67,13 +62,12 @@ bookmarks_list_box_row_activated_cb (EphyBookmarksPopover   *self,
   EphyBookmark *bookmark;
   GActionGroup *action_group;
   GAction *action;
-  const gchar *url;
+  const char *url;
 
   g_assert (EPHY_IS_BOOKMARKS_POPOVER (self));
   g_assert (EPHY_IS_BOOKMARK_ROW (row));
   g_assert (GTK_IS_LIST_BOX (box));
 
-
   action_group = gtk_widget_get_action_group (GTK_WIDGET (self->window), "win");
   action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "open-bookmark");
 
@@ -84,7 +78,7 @@ bookmarks_list_box_row_activated_cb (EphyBookmarksPopover   *self,
 }
 
 static GtkWidget *
-build_tag_box (const gchar *tag)
+create_tag_box (const char *tag)
 {
   GtkWidget *box;
   GtkWidget *image;
@@ -162,44 +156,43 @@ static void
 ephy_bookmarks_popover_init (EphyBookmarksPopover *self)
 {
   EphyBookmarksManager *manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  GList *bookmarks;
-  GList *tags = NULL;
-  GList *l;
+  GSequence *tags, *tags1, *tags2;
+  GSequenceIter *iter;
   EphyBookmark *dummy_bookmark;
+  gint i;
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
-  dummy_bookmark = ephy_bookmark_new (g_strdup ("https://duckduckgo.com";), g_strdup ("Test title"));
-  tags = g_list_append (tags, g_strdup ("Fun"));
-  tags = g_list_append (tags, g_strdup ("Work"));
-  ephy_bookmark_set_tags (dummy_bookmark, tags);
+  dummy_bookmark = ephy_bookmark_new (g_strdup ("https://duckduckgo.com/asdasdas/asdas";), g_strdup ("Test 
title"));
+  tags1 = g_sequence_new (g_free);
+  for (i = 0; i < 20; i++)
+    g_sequence_insert_sorted (tags1, g_strdup_printf ("Fun %d", i), (GCompareDataFunc)g_strcmp0, NULL);
+
+  ephy_bookmark_set_tags (dummy_bookmark, tags1);
   ephy_bookmarks_manager_add_bookmark (manager, dummy_bookmark);
 
   dummy_bookmark = ephy_bookmark_new (g_strdup ("https://wikipedia.com";), g_strdup ("wikipedia"));
   ephy_bookmarks_manager_add_bookmark (manager, dummy_bookmark);
+  tags2 = g_sequence_new (g_free);
+  g_sequence_insert_sorted (tags2, g_strdup_printf ("Not Fun %d", 1), (GCompareDataFunc)g_strcmp0, NULL);
+  ephy_bookmark_set_tags (dummy_bookmark, tags2);
 
-  bookmarks = ephy_bookmarks_manager_get_bookmarks (manager);
-  for (l = bookmarks; l != NULL; l = g_list_next (l)) {
-    EphyBookmark *bookmark = (EphyBookmark *)l->data;
-    GtkWidget *bookmark_row;
-
-    bookmark_row = ephy_bookmark_row_new (bookmark);
-    gtk_list_box_prepend (GTK_LIST_BOX (self->bookmarks_list_box), bookmark_row);
-  }
+  gtk_list_box_bind_model (GTK_LIST_BOX (self->bookmarks_list_box),
+                           G_LIST_MODEL (manager),
+                           create_bookmark_row,
+                           NULL, NULL);
 
   tags = ephy_bookmarks_manager_get_tags (manager);
-  for (l = tags; l != NULL; l = g_list_next (l)) {
+  for (iter = g_sequence_get_begin_iter (tags);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter)) {
     GtkWidget *tag_box;
-    gchar *tag = (gchar *)l->data;
+    const char *tag = g_sequence_get (iter);
 
-    tag_box = build_tag_box (tag);
+    tag_box = create_tag_box (tag);
     gtk_list_box_prepend (GTK_LIST_BOX (self->tags_list_box), tag_box);
   }
 
-  g_signal_connect_object (manager, "bookmark-added",
-                           G_CALLBACK (bookmark_added_cb),
-                           self, G_CONNECT_SWAPPED);
-
   g_signal_connect_object (self->bookmarks_list_box, "row-activated",
                            G_CALLBACK (bookmarks_list_box_row_activated_cb),
                            self, G_CONNECT_SWAPPED);
diff --git a/src/epiphany.gresource.xml b/src/epiphany.gresource.xml
index d913534..f8012c7 100644
--- a/src/epiphany.gresource.xml
+++ b/src/epiphany.gresource.xml
@@ -12,6 +12,7 @@
     <file preprocess="xml-stripblanks" compressed="true">history-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">passwords-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">shortcuts-dialog.ui</file>
+    <file preprocess="xml-stripblanks" compressed="true">gtk/bookmark-properties-grid.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/bookmark-row.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/bookmarks-popover.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/menus.ui</file>
diff --git a/src/resources/epiphany.css b/src/resources/epiphany.css
index 4ae71ed..301aed0 100644
--- a/src/resources/epiphany.css
+++ b/src/resources/epiphany.css
@@ -3,7 +3,7 @@ button.active-menu {
   outline-color: rgba(46, 52, 54, 0.3);
   border-color: #b6b6b3;
   background-image: none;
-  background-color: #d4d4d2;
+  background-color: #d9d9d7;
   box-shadow: inset 0 1px rgba(255, 255, 255, 0);
   text-shadow: none;
   -gtk-icon-shadow: none; }
@@ -70,7 +70,7 @@ button.active-menu {
       outline-color: rgba(46, 52, 54, 0.3);
       border-color: #909fae;
       background-image: none;
-      background-color: #b3bec8;
+      background-color: #b9c3cc;
       box-shadow: inset 0 1px rgba(255, 255, 255, 0);
       text-shadow: none;
       -gtk-icon-shadow: none; }
@@ -125,3 +125,35 @@ button.active-menu {
       box-shadow: inset 0 0 0 1px #4a90d9; }
     .incognito-mode.titlebar entry:backdrop {
       box-shadow: none; }
+
+/* Bookmarks */
+.bookmarks-row button {
+  opacity: 0; }
+.bookmarks-row:hover button {
+  opacity: 1; }
+
+.bookmark-tag-widget {
+  padding-left: 8px;
+  padding-right: 8px;
+  background-color: #d7d7d5; }
+  .bookmark-tag-widget label {
+    padding-left: 8px;
+    padding-right: 8px; }
+  .bookmark-tag-widget button {
+    color: white; }
+
+.bookmark-tag-widget-selected {
+  background-color: #4a90d9;
+  color: white; }
+  .bookmark-tag-widget-selected button {
+    color: white; }
+    .bookmark-tag-widget-selected button:hover {
+      color: #2e3436;
+      outline-color: rgba(46, 52, 54, 0.3);
+      border-color: #215d9c;
+      border-bottom-color: #184472;
+      background-image: linear-gradient(to bottom, #63a0de, #4a90d9 60%, #3986d5);
+      text-shadow: 0 1px rgba(255, 255, 255, 0.76923);
+      -gtk-icon-shadow: 0 1px rgba(255, 255, 255, 0.76923);
+      box-shadow: inset 0 1px rgba(255, 255, 255, 0.4);
+      color: white; }
diff --git a/src/resources/epiphany.scss b/src/resources/epiphany.scss
index 18b87fb..35f481d 100644
--- a/src/resources/epiphany.scss
+++ b/src/resources/epiphany.scss
@@ -110,3 +110,45 @@ $edge_color: lighten($incognito_color, 13%);
     }
   }
 }
+
+/* Bookmarks */
+.bookmarks-row {
+  button {
+    opacity: 0;
+  }
+
+  &:hover {
+    button {
+      opacity: 1;
+    }
+  }
+}
+
+.bookmark-tag-widget {
+  padding-left: 8px;
+  padding-right: 8px;
+  background-color: darken($bg_color, 7%);
+
+  label {
+    padding-left: 8px;
+    padding-right: 8px;
+  }
+
+  button {
+    color: white;
+  }
+}
+
+.bookmark-tag-widget-selected {
+  background-color: $selected_bg_color;
+  color: white;
+
+  button {
+    color: white;
+
+    &:hover {
+      @include button(hover, $selected_bg_color);
+      color: white;
+    }
+  }
+}
diff --git a/src/resources/gtk/bookmark-properties-grid.ui b/src/resources/gtk/bookmark-properties-grid.ui
new file mode 100644
index 0000000..5ec3e98
--- /dev/null
+++ b/src/resources/gtk/bookmark-properties-grid.ui
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="EphyBookmarkPropertiesGrid" parent="GtkGrid">
+    <property name="margin">12</property>
+    <!-- <property name="border-width">12</property> -->
+    <property name="column-spacing">12</property>
+    <property name="row-spacing">12</property>
+    <property name="visible">true</property>
+    <child>
+      <object class="GtkLabel" id="popover_bookmark_label">
+        <property name="label" translatable="yes">Bookmark</property>
+        <property name="visible">false</property>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="width">2</property>
+        <property name="top-attach">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="halign">end</property>
+        <property name="valign">center</property>
+        <property name="label" translatable="yes">Name</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="name_entry">
+        <property name="visible">true</property>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="halign">end</property>
+        <property name="valign">center</property>
+        <property name="label" translatable="yes">Address</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="address_entry">
+        <property name="visible">true</property>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="popover_tags_label">
+        <property name="halign">start</property>
+        <property name="valign">center</property>
+        <property name="label" translatable="yes">Tags</property>
+        <property name="visible">false</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">3</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="halign">end</property>
+        <property name="valign">start</property>
+        <property name="label" translatable="yes">Tags</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="valign">start</property>
+        <property name="height-request">100</property>
+        <property name="min-content-height">100</property>
+        <property name="hscrollbar-policy">never</property>
+        <property name="vscrollbar-policy">automatic</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkFlowBox" id="tags_box">
+            <property name="column-spacing">6</property>
+            <property name="row-spacing">6</property>
+            <property name="selection-mode">none</property>
+            <property name="homogeneous">true</property>
+            <property name="valign">start</property>
+            <property name="min-children-per-line">2</property>
+            <property name="max-children-per-line">3</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="linked"/>
+        </style>
+        <child>
+          <object class="GtkEntry" id="add_tag_entry">
+            <property name="placeholder-text">Add Tag…</property>
+            <property name="activates-default">true</property>
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="add_tag_button">
+            <property name="label" translatable="yes">_Add</property>
+            <property name="can-default">true</property>
+            <property name="sensitive">false</property>
+            <property name="use-underline">true</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">5</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="remove_bookmark_button">
+        <property name="label" translatable="yes">_Remove Bookmark</property>
+        <property name="use-underline">true</property>
+        <property name="halign">end</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="destructive-action"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">6</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/src/resources/gtk/bookmark-row.ui b/src/resources/gtk/bookmark-row.ui
index 689924c..d068167 100644
--- a/src/resources/gtk/bookmark-row.ui
+++ b/src/resources/gtk/bookmark-row.ui
@@ -3,14 +3,36 @@
   <!-- interface-requires gtk+ 3.18 -->
   <template class="EphyBookmarkRow" parent="GtkListBoxRow">
     <property name="visible">true</property>
+    <style>
+      <class name="bookmarks-row"/>
+    </style>
     <child>
       <object class="GtkBox">
         <property name="orientation">horizontal</property>
         <property name="visible">true</property>
         <child>
           <object class="GtkLabel" id="title_label">
+            <property name="expand">true</property>
+            <property name="halign">start</property>
             <property name="visible">true</property>
           </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="properties_button">
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">emblem-system-symbolic</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
         </child>
       </object>
     </child>



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