[gnome-builder/wip/chergert/docs] use custom model and single listbox



commit 2fc60dc7759f9b1b33f872b1f4dcf107a278e198
Author: Christian Hergert <chergert redhat com>
Date:   Thu Jul 11 17:13:19 2019 -0700

    use custom model and single listbox

 src/libide/docs/ide-docs-item.c                 |  22 ++
 src/libide/docs/ide-docs-item.h                 |   3 +
 src/libide/docs/ide-docs-search-model.c         | 282 ++++++++++++++++++++++++
 src/libide/docs/ide-docs-search-model.h         |  39 ++++
 src/libide/docs/ide-docs-search-row.c           |  37 +++-
 src/libide/docs/ide-docs-search-section.c       |  51 ++---
 src/libide/docs/meson.build                     |   1 +
 src/libide/themes/themes/shared/shared-docs.css |  21 +-
 src/plugins/devhelp/gbp-devhelp-docs-provider.c |   1 +
 9 files changed, 412 insertions(+), 45 deletions(-)
---
diff --git a/src/libide/docs/ide-docs-item.c b/src/libide/docs/ide-docs-item.c
index e485460d9..ff86cbc6e 100644
--- a/src/libide/docs/ide-docs-item.c
+++ b/src/libide/docs/ide-docs-item.c
@@ -684,3 +684,25 @@ ide_docs_item_truncate (IdeDocsItem *self,
   while (priv->children.length > max_items)
     ide_docs_item_remove (self, priv->children.tail->data);
 }
+
+/**
+ * ide_docs_item_get_nth_child:
+ * @self: a #IdeDocsItem
+ * @nth: the index (starting from zero) of the child
+ *
+ * Gets the @nth item from the children.
+ *
+ * Returns: (transfer none) (nullable): an #IdeDocsItem or %NULL
+ *
+ * Since: 3.34
+ */
+IdeDocsItem *
+ide_docs_item_get_nth_child (IdeDocsItem *self,
+                             guint        nth)
+{
+  IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+  return g_list_nth_data (priv->children.head, nth);
+}
diff --git a/src/libide/docs/ide-docs-item.h b/src/libide/docs/ide-docs-item.h
index 6d228cf2b..0ad2ae762 100644
--- a/src/libide/docs/ide-docs-item.h
+++ b/src/libide/docs/ide-docs-item.h
@@ -113,6 +113,9 @@ void             ide_docs_item_remove           (IdeDocsItem     *self,
 IDE_AVAILABLE_IN_3_34
 gboolean         ide_docs_item_has_child        (IdeDocsItem     *self);
 IDE_AVAILABLE_IN_3_34
+IdeDocsItem     *ide_docs_item_get_nth_child    (IdeDocsItem     *self,
+                                                 guint            nth);
+IDE_AVAILABLE_IN_3_34
 guint            ide_docs_item_get_n_children   (IdeDocsItem     *self);
 IDE_AVAILABLE_IN_3_34
 const GList     *ide_docs_item_get_children     (IdeDocsItem     *self);
diff --git a/src/libide/docs/ide-docs-search-model.c b/src/libide/docs/ide-docs-search-model.c
new file mode 100644
index 000000000..c6a327716
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-model.c
@@ -0,0 +1,282 @@
+/* ide-docs-search-model.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-search-model"
+
+#include "config.h"
+
+#include "ide-docs-search-model.h"
+
+#define DEFAULT_MAX_CHILDREN 3
+
+struct _IdeDocsSearchModel
+{
+  GObject  parent_instance;
+  GArray  *groups;
+};
+
+typedef struct
+{
+  IdeDocsItem *group;
+  guint        expanded : 1;
+} Group;
+
+static GType
+ide_docs_search_model_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_DOCS_ITEM;
+}
+
+static guint
+ide_docs_search_model_get_n_items (GListModel *model)
+{
+  IdeDocsSearchModel *self = (IdeDocsSearchModel *)model;
+  guint n_items = 0;
+
+  g_assert (IDE_IS_DOCS_SEARCH_MODEL (self));
+
+  for (guint i = 0; i < self->groups->len; i++)
+    {
+      const Group *g = &g_array_index (self->groups, Group, i);
+      guint n_children = ide_docs_item_get_n_children (g->group);
+
+      /* Add the group title */
+      n_items++;
+
+      /* Add the items (depending on expanded state) */
+      if (g->expanded)
+        n_items += n_children;
+      else
+        n_items += MIN (n_children, DEFAULT_MAX_CHILDREN);
+    }
+
+  return n_items;
+}
+
+static gpointer
+ide_docs_search_model_get_item (GListModel *model,
+                                guint       position)
+{
+  IdeDocsSearchModel *self = (IdeDocsSearchModel *)model;
+
+  g_assert (IDE_IS_DOCS_SEARCH_MODEL (self));
+  g_assert (position < ide_docs_search_model_get_n_items (model));
+
+  for (guint i = 0; i < self->groups->len; i++)
+    {
+      const Group *g = &g_array_index (self->groups, Group, i);
+      guint n_children = ide_docs_item_get_n_children (g->group);
+
+      if (position == 0)
+        return g_object_ref (g->group);
+
+      position--;
+
+      if (!g->expanded)
+        n_children = MIN (n_children, DEFAULT_MAX_CHILDREN);
+
+      if (position >= n_children)
+        {
+          position -= n_children;
+          continue;
+        }
+
+      return g_object_ref (ide_docs_item_get_nth_child (g->group, position));
+    }
+
+  g_return_val_if_reached (NULL);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ide_docs_search_model_get_item_type;
+  iface->get_n_items = ide_docs_search_model_get_n_items;
+  iface->get_item = ide_docs_search_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IdeDocsSearchModel, ide_docs_search_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+clear_group_cb (gpointer data)
+{
+  Group *g = data;
+  g_clear_object (&g->group);
+}
+
+static void
+ide_docs_search_model_finalize (GObject *object)
+{
+  IdeDocsSearchModel *self = (IdeDocsSearchModel *)object;
+
+  g_clear_pointer (&self->groups, g_array_unref);
+
+  G_OBJECT_CLASS (ide_docs_search_model_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_search_model_class_init (IdeDocsSearchModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_docs_search_model_finalize;
+}
+
+static void
+ide_docs_search_model_init (IdeDocsSearchModel *self)
+{
+  self->groups = g_array_new (FALSE, FALSE, sizeof (Group));
+  g_array_set_clear_func (self->groups, clear_group_cb);
+}
+
+IdeDocsSearchModel *
+ide_docs_search_model_new (void)
+{
+  return g_object_new (IDE_TYPE_DOCS_SEARCH_MODEL, NULL);
+}
+
+void
+ide_docs_search_model_add_group (IdeDocsSearchModel *self,
+                                 IdeDocsItem        *group)
+{
+  Group to_add = {0};
+  guint position = 0;
+  guint added;
+  gint priority;
+
+  g_return_if_fail (IDE_IS_DOCS_SEARCH_MODEL (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (group));
+
+  if (ide_docs_item_get_n_children (group) == 0)
+    return;
+
+  to_add.group = g_object_ref (group);
+  to_add.expanded = FALSE;
+
+  priority = ide_docs_item_get_priority (group);
+  added = ide_docs_item_get_n_children (group);
+  if (added > DEFAULT_MAX_CHILDREN)
+    added = DEFAULT_MAX_CHILDREN;
+
+  /* Add the group header */
+  added++;
+
+  for (guint i = 0; i < self->groups->len; i++)
+    {
+      const Group *g = &g_array_index (self->groups, Group, i);
+      guint n_children;
+
+      if (ide_docs_item_get_priority (g->group) > priority)
+        {
+          g_array_insert_val (self->groups, i, to_add);
+          g_list_model_items_changed (G_LIST_MODEL (self), position, 0, added);
+          return;
+        }
+
+      /* Skip the group header */
+      position++;
+
+      n_children = ide_docs_item_get_n_children (g->group);
+
+      if (g->expanded)
+        position += n_children;
+      else
+        position += MIN (n_children, DEFAULT_MAX_CHILDREN);
+    }
+
+  g_assert (position == ide_docs_search_model_get_n_items (G_LIST_MODEL (self)));
+
+  g_array_append_val (self->groups, to_add);
+  g_list_model_items_changed (G_LIST_MODEL (self), position, 0, added);
+}
+
+static void
+ide_docs_search_model_toggle (IdeDocsSearchModel *self,
+                              IdeDocsItem        *group,
+                              gboolean            expanded)
+{
+  guint position = 0;
+
+  g_return_if_fail (IDE_IS_DOCS_SEARCH_MODEL (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (group));
+
+  for (guint i = 0; i < self->groups->len; i++)
+    {
+      Group *g = &g_array_index (self->groups, Group, i);
+      guint n_children = ide_docs_item_get_n_children (g->group);
+      guint removed = 0;
+      guint added = 0;
+
+      /* Skip the group header */
+      position++;
+
+      if (g->group != group)
+        {
+          if (g->expanded)
+            position += n_children;
+          else
+            position += MIN (DEFAULT_MAX_CHILDREN, n_children);
+
+          continue;
+        }
+
+      if (g->expanded == expanded)
+        return;
+
+      g->expanded = !g->expanded;
+
+      if (g->expanded)
+        {
+          /* expanding */
+          removed = MIN (DEFAULT_MAX_CHILDREN, n_children);
+          added = n_children;
+        }
+      else
+        {
+          /* collapsing */
+          removed = n_children;
+          added = MIN (DEFAULT_MAX_CHILDREN, n_children);
+        }
+
+      g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+      break;
+    }
+}
+
+void
+ide_docs_search_model_collapse_group (IdeDocsSearchModel *self,
+                                      IdeDocsItem        *group)
+{
+  g_return_if_fail (IDE_IS_DOCS_SEARCH_MODEL (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (group));
+
+  ide_docs_search_model_toggle (self, group, FALSE);
+}
+
+void
+ide_docs_search_model_expand_group (IdeDocsSearchModel *self,
+                                    IdeDocsItem        *group)
+{
+  g_return_if_fail (IDE_IS_DOCS_SEARCH_MODEL (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (group));
+
+  ide_docs_search_model_toggle (self, group, TRUE);
+}
diff --git a/src/libide/docs/ide-docs-search-model.h b/src/libide/docs/ide-docs-search-model.h
new file mode 100644
index 000000000..eb2a825e7
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-model.h
@@ -0,0 +1,39 @@
+/* ide-docs-search-model.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-docs-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_SEARCH_MODEL (ide_docs_search_model_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsSearchModel, ide_docs_search_model, IDE, DOCS_SEARCH_MODEL, GObject)
+
+IdeDocsSearchModel *ide_docs_search_model_new            (void);
+void                ide_docs_search_model_add_group      (IdeDocsSearchModel *self,
+                                                          IdeDocsItem        *group);
+void                ide_docs_search_model_collapse_group (IdeDocsSearchModel *self,
+                                                          IdeDocsItem        *group);
+void                ide_docs_search_model_expand_group   (IdeDocsSearchModel *self,
+                                                          IdeDocsItem        *group);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-row.c b/src/libide/docs/ide-docs-search-row.c
index e07407183..bd4db79fd 100644
--- a/src/libide/docs/ide-docs-search-row.c
+++ b/src/libide/docs/ide-docs-search-row.c
@@ -24,6 +24,8 @@
 
 #include "ide-docs-search-row.h"
 
+#define DEFAULT_MAX_CHILDREN 3
+
 struct _IdeDocsSearchRow
 {
   DzlListBoxRow parent_instance;
@@ -49,8 +51,11 @@ static void
 ide_docs_search_row_set_item (IdeDocsSearchRow *self,
                               IdeDocsItem      *item)
 {
+  g_autofree gchar *with_size = NULL;
+  GtkStyleContext *style_context;
   const gchar *icon_name;
   const gchar *title;
+  IdeDocsItemKind kind;
   gboolean use_markup;
 
   g_return_if_fail (IDE_IS_DOCS_SEARCH_ROW (self));
@@ -61,14 +66,9 @@ ide_docs_search_row_set_item (IdeDocsSearchRow *self,
   if (item == NULL)
     return;
 
-  gtk_label_set_use_markup (self->label, FALSE);
-
-  if ((title = ide_docs_item_get_display_name (self->item)))
-    use_markup = TRUE;
-  else
-    title = ide_docs_item_get_title (self->item);
+  kind = ide_docs_item_get_kind (self->item);
 
-  switch (ide_docs_item_get_kind (self->item))
+  switch (kind)
     {
     case IDE_DOCS_ITEM_KIND_FUNCTION:
       icon_name = "lang-function-symbolic";
@@ -117,6 +117,29 @@ ide_docs_search_row_set_item (IdeDocsSearchRow *self,
       break;
     }
 
+  gtk_label_set_use_markup (self->label, FALSE);
+
+  if ((title = ide_docs_item_get_display_name (self->item)))
+    use_markup = TRUE;
+  else
+    title = ide_docs_item_get_title (self->item);
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  if (kind == IDE_DOCS_ITEM_KIND_BOOK && ide_docs_item_has_child (item))
+    {
+      guint n_children = ide_docs_item_get_n_children (item);
+
+      gtk_style_context_add_class (style_context, "header");
+
+      if (n_children > DEFAULT_MAX_CHILDREN)
+        title = with_size = g_strdup_printf ("%s  +%u", title, n_children - DEFAULT_MAX_CHILDREN);
+    }
+  else
+    {
+      gtk_style_context_remove_class (style_context, "header");
+    }
+
   g_object_set (self->image, "icon-name", icon_name, NULL);
 
   gtk_label_set_label (self->label, title);
diff --git a/src/libide/docs/ide-docs-search-section.c b/src/libide/docs/ide-docs-search-section.c
index 0fbee384c..a19ede91b 100644
--- a/src/libide/docs/ide-docs-search-section.c
+++ b/src/libide/docs/ide-docs-search-section.c
@@ -25,19 +25,21 @@
 #include <dazzle.h>
 
 #include "ide-docs-search-group.h"
+#include "ide-docs-search-model.h"
+#include "ide-docs-search-row.h"
 #include "ide-docs-search-section.h"
 
 #define MAX_ALLOWED_BY_GROUP 1000
 
 struct _IdeDocsSearchSection
 {
-  GtkBin          parent_instance;
+  GtkBin              parent_instance;
 
-  DzlPriorityBox *groups;
+  DzlListBox         *groups;
 
-  gchar          *title;
+  gchar              *title;
 
-  gint            priority;
+  gint                priority;
 };
 
 G_DEFINE_TYPE (IdeDocsSearchSection, ide_docs_search_section, GTK_TYPE_BIN)
@@ -163,11 +165,13 @@ ide_docs_search_section_class_init (IdeDocsSearchSectionClass *klass)
 static void
 ide_docs_search_section_init (IdeDocsSearchSection *self)
 {
-  self->groups = g_object_new (DZL_TYPE_PRIORITY_BOX,
-                               "orientation", GTK_ORIENTATION_VERTICAL,
-                               "spacing", 14,
+  self->groups = g_object_new (DZL_TYPE_LIST_BOX,
+                               "row-type", IDE_TYPE_DOCS_SEARCH_ROW,
+                               "property-name", "item",
+                               "selection-mode", GTK_SELECTION_NONE,
                                "visible", TRUE,
                                NULL);
+  dzl_list_box_set_recycle_max (self->groups, 100);
   gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->groups));
 }
 
@@ -212,43 +216,32 @@ void
 ide_docs_search_section_add_groups (IdeDocsSearchSection *self,
                                     IdeDocsItem          *parent)
 {
-  gboolean show_more_items = FALSE;
+  g_autoptr(IdeDocsSearchModel) model = NULL;
 
   g_return_if_fail (IDE_IS_DOCS_SEARCH_SECTION (self));
   g_return_if_fail (IDE_IS_DOCS_ITEM (parent));
 
-  /* If there is a single group within the section, we want to show more
-   * items than we otherwise would.
-   */
-  if (ide_docs_item_get_n_children (parent) == 1)
-    show_more_items = TRUE;
+  dzl_list_box_set_model (self->groups, NULL);
+
+  model = ide_docs_search_model_new ();
+
+  gtk_widget_hide (GTK_WIDGET (self->groups));
 
   for (const GList *iter = ide_docs_item_get_children (parent);
        iter != NULL;
        iter = iter->next)
     {
       IdeDocsItem *child = iter->data;
-      IdeDocsSearchGroup *group;
-      const gchar *title;
-      gint priority;
 
       g_assert (IDE_IS_DOCS_ITEM (child));
 
-      title = ide_docs_item_get_title (child);
-      priority = ide_docs_item_get_priority (child);
-      group = g_object_new (IDE_TYPE_DOCS_SEARCH_GROUP,
-                            "title", title,
-                            "priority", priority,
-                            "visible", TRUE,
-                            NULL);
-
-      if (show_more_items)
-        ide_docs_search_group_set_max_items (group, 25);
-
       /* Truncate to a reasonable number to avoid very large lists */
       ide_docs_item_truncate (child, MAX_ALLOWED_BY_GROUP);
 
-      ide_docs_search_group_add_items (group, child);
-      gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (group));
+      ide_docs_search_model_add_group (model, child);
+
+      dzl_list_box_set_model (self->groups, G_LIST_MODEL (model));
     }
+
+  gtk_widget_show (GTK_WIDGET (self->groups));
 }
diff --git a/src/libide/docs/meson.build b/src/libide/docs/meson.build
index 31fe3fd9a..89f736f62 100644
--- a/src/libide/docs/meson.build
+++ b/src/libide/docs/meson.build
@@ -28,6 +28,7 @@ libide_docs_public_sources = [
   'ide-docs-provider.c',
   'ide-docs-query.c',
   'ide-docs-search-group.c',
+  'ide-docs-search-model.c',
   'ide-docs-search-row.c',
   'ide-docs-search-section.c',
   'ide-docs-search-view.c',
diff --git a/src/libide/themes/themes/shared/shared-docs.css b/src/libide/themes/themes/shared/shared-docs.css
index 8f10e42b7..d3fc226bc 100644
--- a/src/libide/themes/themes/shared/shared-docs.css
+++ b/src/libide/themes/themes/shared/shared-docs.css
@@ -1,28 +1,31 @@
-IdeDocsSearchGroup list {
+IdeDocsSearchSection list {
   background-color: transparent;
   }
-IdeDocsSearchGroup box.header {
+IdeDocsSearchSection list row.header:not(:first-child) {
+  margin-top: 24px;
+  }
+IdeDocsSearchSection list row.header box {
   padding: 7px;
   }
-IdeDocsSearchGroup box.header label {
+IdeDocsSearchSection list row.header box label {
   font-size: 0.8333em;
   font-weight: bold;
   color: @theme_selected_bg_color;
   }
+IdeDocsSearchSection list row:selected.header box label {
+  color: @theme_selected_fg_color;
+  }
 IdeDocsSearchView box.titles label {
   font-size: 1.3em;
   font-weight: 500;
   color: alpha(currentColor, 0.5);
   }
-IdeDocsSearchGroup row:first-child {
-  border-top: 1px solid alpha(@borders, 0.5);
-  }
-IdeDocsSearchGroup row {
+IdeDocsSearchSection list row {
   border-bottom: 1px solid alpha(@borders, 0.5);
   }
-IdeDocsSearchGroup row > box {
+IdeDocsSearchSection list row > box {
   padding: 7px;
   }
-IdeDocsSearchGroup row > box image:last-child {
+IdeDocsSearchSection list row > box image:last-child {
   min-width: 16px;
   }
diff --git a/src/plugins/devhelp/gbp-devhelp-docs-provider.c b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
index aae78a7bb..6d0616306 100644
--- a/src/plugins/devhelp/gbp-devhelp-docs-provider.c
+++ b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
@@ -120,6 +120,7 @@ gbp_devhelp_docs_provider_search_async (IdeDocsProvider     *provider,
 
           group = ide_docs_item_new ();
           ide_docs_item_set_title (group, parser->book.title);
+          ide_docs_item_set_kind (group, IDE_DOCS_ITEM_KIND_BOOK);
 
           for (guint j = 0; j < parser->keywords->len; j++)
             {


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