[gnome-builder/wip/chergert/docs: 105/105] wip



commit 733cb57bcc17bd5835b84132217a72b1fb1c10e9
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jul 23 12:50:04 2019 -0700

    wip

 src/libide/docs/ide-docs-item.c                 |  42 +++-
 src/libide/docs/ide-docs-library.c              | 109 +++++++++-
 src/libide/docs/ide-docs-library.h              |  30 ++-
 src/libide/docs/ide-docs-pane-row.c             | 161 ++++++++++++++
 src/libide/docs/ide-docs-pane-row.h             |  36 ++++
 src/libide/docs/ide-docs-pane-row.ui            |  26 +++
 src/libide/docs/ide-docs-pane.c                 | 153 ++++++++++++-
 src/libide/docs/ide-docs-pane.h                 |   4 +-
 src/libide/docs/ide-docs-workspace.c            |  26 ++-
 src/libide/docs/ide-docs-workspace.ui           |   2 +-
 src/libide/docs/libide-docs.gresource.xml       |   1 +
 src/libide/docs/meson.build                     |  10 +-
 src/libide/gui/ide-path-bar.c                   | 275 ++++++++++++++++++++++++
 src/libide/gui/ide-path-bar.h                   |  53 +++++
 src/libide/gui/ide-path-element.c               | 181 ++++++++++++++++
 src/libide/gui/ide-path-element.h               |  51 +++++
 src/libide/gui/ide-path.c                       | 171 +++++++++++++++
 src/libide/gui/ide-path.h                       |  49 +++++
 src/libide/gui/meson.build                      |   6 +
 src/plugins/devhelp/gbp-devhelp-docs-provider.c |  12 ++
 20 files changed, 1378 insertions(+), 20 deletions(-)
---
diff --git a/src/libide/docs/ide-docs-item.c b/src/libide/docs/ide-docs-item.c
index d0d670a1e..6c7f687f3 100644
--- a/src/libide/docs/ide-docs-item.c
+++ b/src/libide/docs/ide-docs-item.c
@@ -41,7 +41,11 @@ typedef struct
   guint            deprecated : 1;
 } IdeDocsItemPrivate;
 
-G_DEFINE_TYPE_WITH_PRIVATE (IdeDocsItem, ide_docs_item, G_TYPE_OBJECT)
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeDocsItem, ide_docs_item, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (IdeDocsItem)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
 
 enum {
   PROP_0,
@@ -739,3 +743,39 @@ ide_docs_item_get_nth_child (IdeDocsItem *self,
 
   return g_list_nth_data (priv->children.head, nth);
 }
+
+static guint
+ide_docs_item_get_n_items (GListModel *model)
+{
+  IdeDocsItem *self = IDE_DOCS_ITEM (model);
+  IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+  return priv->children.length;
+}
+
+static GType
+ide_docs_item_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_DOCS_ITEM;
+}
+
+static gpointer
+ide_docs_item_get_item (GListModel *model,
+                        guint       position)
+{
+  IdeDocsItem *self = (IdeDocsItem *)model;
+  IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+  g_return_val_if_fail (position < priv->children.length, NULL);
+
+  return g_object_ref (g_queue_peek_nth (&priv->children, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = ide_docs_item_get_n_items;
+  iface->get_item_type = ide_docs_item_get_item_type;
+  iface->get_item = ide_docs_item_get_item;
+}
diff --git a/src/libide/docs/ide-docs-library.c b/src/libide/docs/ide-docs-library.c
index 7afd39598..b665e5c7b 100644
--- a/src/libide/docs/ide-docs-library.c
+++ b/src/libide/docs/ide-docs-library.c
@@ -43,8 +43,21 @@ typedef struct
   guint         n_active;
 } Search;
 
+typedef struct
+{
+  IdeDocsItem *item;
+  guint        n_active;
+} Populate;
+
 G_DEFINE_TYPE (IdeDocsLibrary, ide_docs_library, IDE_TYPE_OBJECT)
 
+static void
+populate_free (Populate *populate)
+{
+  g_clear_object (&populate->item);
+  g_slice_free (Populate, populate);
+}
+
 static void
 search_free (Search *search)
 {
@@ -172,7 +185,6 @@ ide_docs_library_search_cb (GObject      *object,
                             gpointer      user_data)
 {
   IdeDocsProvider *provider = (IdeDocsProvider *)object;
-  g_autoptr(GListModel) model = NULL;
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
   Search *search;
@@ -317,3 +329,98 @@ ide_docs_library_search_finish (IdeDocsLibrary  *self,
 
   return ide_task_propagate_boolean (IDE_TASK (result), error);
 }
+
+static void
+ide_docs_library_populate_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  IdeDocsProvider *provider = (IdeDocsProvider *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  Populate *populate;
+
+  g_assert (G_IS_OBJECT (object));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  populate = ide_task_get_task_data (task);
+
+  if (!ide_docs_provider_populate_finish (provider, result, &error))
+    {
+      if (!ide_error_ignore (error))
+        g_warning ("%s failed to populate docs: %s",
+                   G_OBJECT_TYPE_NAME (provider),
+                   error->message);
+    }
+
+  populate->n_active--;
+
+  if (populate->n_active == 0)
+    ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_docs_library_populate_foreach_cb (IdeExtensionSetAdapter *adapter,
+                                      PeasPluginInfo         *plugin,
+                                      PeasExtension          *exten,
+                                      gpointer                user_data)
+{
+  IdeDocsProvider *provider = (IdeDocsProvider *)exten;
+  IdeTask *task = user_data;
+  Populate *populate;
+
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
+  g_assert (plugin != NULL);
+  g_assert (IDE_IS_DOCS_PROVIDER (provider));
+  g_assert (IDE_IS_TASK (task));
+
+  populate = ide_task_get_task_data (task);
+  populate->n_active++;
+
+  ide_docs_provider_populate_async (provider,
+                                    populate->item,
+                                    ide_task_get_cancellable (task),
+                                    ide_docs_library_populate_cb,
+                                    g_object_ref (task));
+}
+
+void
+ide_docs_library_populate_async (IdeDocsLibrary       *self,
+                                 IdeDocsItem          *item,
+                                 GCancellable         *cancellable,
+                                 GAsyncReadyCallback   callback,
+                                 gpointer              user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  Populate *state;
+
+  g_return_if_fail (IDE_IS_DOCS_LIBRARY (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (item));
+
+  state = g_slice_new0 (Populate);
+  state->item = g_object_ref (item);
+  state->n_active = 0;
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_docs_library_populate_async);
+  ide_task_set_task_data (task, state, populate_free);
+
+  ide_extension_set_adapter_foreach (self->providers,
+                                     ide_docs_library_populate_foreach_cb,
+                                     task);
+
+  if (state->n_active == 0)
+    ide_task_return_boolean (task, TRUE);
+}
+
+gboolean
+ide_docs_library_populate_finish (IdeDocsLibrary  *self,
+                                  GAsyncResult    *result,
+                                  GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_DOCS_LIBRARY (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
diff --git a/src/libide/docs/ide-docs-library.h b/src/libide/docs/ide-docs-library.h
index 8942f920b..f9492281b 100644
--- a/src/libide/docs/ide-docs-library.h
+++ b/src/libide/docs/ide-docs-library.h
@@ -33,17 +33,27 @@ IDE_AVAILABLE_IN_3_34
 G_DECLARE_FINAL_TYPE (IdeDocsLibrary, ide_docs_library, IDE, DOCS_LIBRARY, IdeObject)
 
 IDE_AVAILABLE_IN_3_34
-IdeDocsLibrary *ide_docs_library_from_context  (IdeContext           *context);
+IdeDocsLibrary *ide_docs_library_from_context    (IdeContext           *context);
 IDE_AVAILABLE_IN_3_34
-void            ide_docs_library_search_async  (IdeDocsLibrary       *self,
-                                                IdeDocsQuery         *query,
-                                                IdeDocsItem          *results,
-                                                GCancellable         *cancellable,
-                                                GAsyncReadyCallback   callback,
-                                                gpointer              user_data);
+void            ide_docs_library_search_async    (IdeDocsLibrary       *self,
+                                                  IdeDocsQuery         *query,
+                                                  IdeDocsItem          *results,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
 IDE_AVAILABLE_IN_3_34
-gboolean        ide_docs_library_search_finish (IdeDocsLibrary       *self,
-                                                GAsyncResult         *result,
-                                                GError              **error);
+gboolean        ide_docs_library_search_finish   (IdeDocsLibrary       *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+IDE_AVAILABLE_IN_3_34
+void            ide_docs_library_populate_async  (IdeDocsLibrary       *self,
+                                                  IdeDocsItem          *item,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+IDE_AVAILABLE_IN_3_34
+gboolean        ide_docs_library_populate_finish (IdeDocsLibrary       *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
 
 G_END_DECLS
diff --git a/src/libide/docs/ide-docs-pane-row.c b/src/libide/docs/ide-docs-pane-row.c
new file mode 100644
index 000000000..c3369681a
--- /dev/null
+++ b/src/libide/docs/ide-docs-pane-row.c
@@ -0,0 +1,161 @@
+/* ide-docs-pane-row.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-pane-row"
+
+#include "ide-docs-pane-row.h"
+
+struct _IdeDocsPaneRow
+{
+  GtkListBoxRow parent_instance;
+
+  IdeDocsItem *item;
+
+  /* Template Widgets */
+  GtkLabel *title;
+};
+
+G_DEFINE_TYPE (IdeDocsPaneRow, ide_docs_pane_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+  PROP_0,
+  PROP_ITEM,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_docs_pane_row_new:
+ *
+ * Create a new #IdeDocsPaneRow.
+ *
+ * Returns: (transfer full): a newly created #IdeDocsPaneRow
+ */
+GtkWidget *
+ide_docs_pane_row_new (IdeDocsItem *item)
+{
+  g_return_val_if_fail (IDE_IS_DOCS_ITEM (item), NULL);
+
+  return g_object_new (IDE_TYPE_DOCS_PANE_ROW,
+                       "item", item,
+                       NULL);
+}
+
+static void
+ide_docs_pane_row_set_item (IdeDocsPaneRow *self,
+                            IdeDocsItem    *item)
+{
+  const gchar *title;
+
+  g_return_if_fail (IDE_IS_DOCS_PANE_ROW (self));
+  g_return_if_fail (IDE_IS_DOCS_ITEM (item));
+
+  if (!g_set_object (&self->item, item))
+    return;
+
+  title = ide_docs_item_get_title (item);
+  gtk_label_set_label (self->title, title);
+}
+
+IdeDocsItem *
+ide_docs_pane_row_get_item (IdeDocsPaneRow *self)
+{
+  g_return_val_if_fail (IDE_IS_DOCS_PANE_ROW (self), NULL);
+
+  return self->item;
+}
+
+static void
+ide_docs_pane_row_finalize (GObject *object)
+{
+  IdeDocsPaneRow *self = (IdeDocsPaneRow *)object;
+
+  g_clear_object (&self->item);
+
+  G_OBJECT_CLASS (ide_docs_pane_row_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_pane_row_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeDocsPaneRow *self = IDE_DOCS_PANE_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ITEM:
+      g_value_set_object (value, ide_docs_pane_row_get_item (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_docs_pane_row_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeDocsPaneRow *self = IDE_DOCS_PANE_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ITEM:
+      ide_docs_pane_row_set_item (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_docs_pane_row_class_init (IdeDocsPaneRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_docs_pane_row_finalize;
+  object_class->get_property = ide_docs_pane_row_get_property;
+  object_class->set_property = ide_docs_pane_row_set_property;
+
+  properties [PROP_ITEM] =
+    g_param_spec_object ("item",
+                         "Item",
+                         "The item to be displayed",
+                         IDE_TYPE_DOCS_ITEM,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-docs/ui/ide-docs-pane-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDocsPaneRow, title);
+}
+
+static void
+ide_docs_pane_row_init (IdeDocsPaneRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/libide/docs/ide-docs-pane-row.h b/src/libide/docs/ide-docs-pane-row.h
new file mode 100644
index 000000000..a5211b893
--- /dev/null
+++ b/src/libide/docs/ide-docs-pane-row.h
@@ -0,0 +1,36 @@
+/* ide-docs-pane-row.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 <gtk/gtk.h>
+
+#include "ide-docs-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_PANE_ROW (ide_docs_pane_row_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsPaneRow, ide_docs_pane_row, IDE, DOCS_PANE_ROW, GtkListBoxRow)
+
+GtkWidget   *ide_docs_pane_row_new      (IdeDocsItem    *item);
+IdeDocsItem *ide_docs_pane_row_get_item (IdeDocsPaneRow *self);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-pane-row.ui b/src/libide/docs/ide-docs-pane-row.ui
new file mode 100644
index 000000000..70a7c30d8
--- /dev/null
+++ b/src/libide/docs/ide-docs-pane-row.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="IdeDocsPaneRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="icon-name">help-contents-symbolic</property>
+            <property name="hexpand">false</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="ellipsize">end</property>
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="xalign">0</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/libide/docs/ide-docs-pane.c b/src/libide/docs/ide-docs-pane.c
index a2ac3bbb2..1d284796b 100644
--- a/src/libide/docs/ide-docs-pane.c
+++ b/src/libide/docs/ide-docs-pane.c
@@ -22,10 +22,12 @@
 
 #include "config.h"
 
+#include <glib/gi18n.h>
 #include <dazzle.h>
 
 #include "ide-docs-library.h"
 #include "ide-docs-pane.h"
+#include "ide-docs-pane-row.h"
 
 struct _IdeDocsPane
 {
@@ -87,7 +89,7 @@ ide_docs_pane_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_LIBRARY:
-      self->library = g_value_dup_object (value);
+      ide_docs_pane_set_library (self, g_value_get_object (value));
       break;
 
     default:
@@ -110,7 +112,7 @@ ide_docs_pane_class_init (IdeDocsPaneClass *klass)
                          "Library",
                          "The library for the documentation pane",
                          IDE_TYPE_DOCS_LIBRARY,
-                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
@@ -120,10 +122,122 @@ ide_docs_pane_class_init (IdeDocsPaneClass *klass)
   g_type_ensure (DZL_TYPE_STACK_LIST);
 }
 
+static GtkWidget *
+create_pane_row_cb (gpointer item_,
+                    gpointer user_data)
+{
+  IdeDocsItem *item = item_;
+
+  g_assert (IDE_IS_DOCS_ITEM (item));
+
+  return g_object_new (IDE_TYPE_DOCS_PANE_ROW,
+                       "item", item,
+                       "visible", TRUE,
+                       NULL);
+}
+
+static void
+ide_docs_pane_activate_populate_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  g_autoptr(IdeTask) task = user_data;
+  IdeDocsPane *self;
+  IdeDocsItem *item;
+
+  g_assert (IDE_IS_DOCS_LIBRARY (object));
+  g_assert (IDE_IS_TASK (task));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  self = ide_task_get_source_object (task);
+  item = ide_task_get_task_data (task);
+
+  dzl_stack_list_push (self->stack_list,
+                       create_pane_row_cb (item, self),
+                       G_LIST_MODEL (item),
+                       create_pane_row_cb,
+                       self, NULL);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_docs_pane_row_activated_cb (IdeDocsPane    *self,
+                                IdeDocsPaneRow *row,
+                                DzlStackList   *stack_list)
+{
+  g_autoptr(IdeTask) task = NULL;
+  IdeDocsLibrary *library;
+  IdeDocsItem *item;
+  IdeContext *context;
+
+  g_assert (IDE_IS_DOCS_PANE (self));
+  g_assert (IDE_IS_DOCS_PANE_ROW (row));
+  g_assert (DZL_IS_STACK_LIST (stack_list));
+
+  item = ide_docs_pane_row_get_item (row);
+  context = ide_widget_get_context (GTK_WIDGET (self));
+  library = ide_docs_library_from_context (context);
+
+  task = ide_task_new (self, NULL, NULL, NULL);
+  ide_task_set_source_tag (task, ide_docs_pane_row_activated_cb);
+  ide_task_set_task_data (task, g_object_ref (item), g_object_unref);
+
+  ide_docs_library_populate_async (library,
+                                   item,
+                                   NULL,
+                                   ide_docs_pane_activate_populate_cb,
+                                   g_steal_pointer (&task));
+}
+
 static void
 ide_docs_pane_init (IdeDocsPane *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->stack_list,
+                           "row-activated",
+                           G_CALLBACK (ide_docs_pane_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_docs_pane_populate_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+  IdeDocsLibrary *library = (IdeDocsLibrary *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeDocsPane *self;
+  IdeDocsItem *item;
+
+  g_assert (G_IS_OBJECT (object));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  item = ide_task_get_task_data (task);
+
+  g_assert (IDE_IS_DOCS_PANE (self));
+  g_assert (IDE_IS_DOCS_ITEM (item));
+
+  if (!ide_docs_library_populate_finish (library, result, &error))
+    {
+      g_warning ("Failed to populate documentation: %s",
+                 error->message);
+      return;
+    }
+
+  dzl_stack_list_clear (self->stack_list);
+  dzl_stack_list_push (self->stack_list,
+                       create_pane_row_cb (item, self),
+                       G_LIST_MODEL (item),
+                       create_pane_row_cb,
+                       self, NULL);
+
+  ide_task_return_boolean (task, TRUE);
 }
 
 /**
@@ -143,3 +257,38 @@ ide_docs_pane_get_library (IdeDocsPane *self)
 
   return self->library;
 }
+
+void
+ide_docs_pane_set_library (IdeDocsPane    *self,
+                           IdeDocsLibrary *library)
+{
+  g_return_if_fail (IDE_IS_DOCS_PANE (self));
+  g_return_if_fail (!library || IDE_IS_DOCS_LIBRARY (library));
+
+  if (g_set_object (&self->library, library))
+    {
+      g_autoptr(IdeDocsItem) root = NULL;
+      g_autoptr(IdeTask) task = NULL;
+
+      if (library == NULL)
+        {
+          dzl_stack_list_clear (self->stack_list);
+          return;
+        }
+
+      root = ide_docs_item_new ();
+      ide_docs_item_set_title (root, _("Library"));
+      ide_docs_item_set_kind (root, IDE_DOCS_ITEM_KIND_COLLECTION);
+
+      task = ide_task_new (self, NULL, NULL, NULL);
+      ide_task_set_task_data (task, g_object_ref (root), g_object_unref);
+
+      ide_docs_library_populate_async (library,
+                                       root,
+                                       NULL,
+                                       ide_docs_pane_populate_cb,
+                                       g_steal_pointer (&task));
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LIBRARY]);
+    }
+}
diff --git a/src/libide/docs/ide-docs-pane.h b/src/libide/docs/ide-docs-pane.h
index 0205f6be2..088ac6896 100644
--- a/src/libide/docs/ide-docs-pane.h
+++ b/src/libide/docs/ide-docs-pane.h
@@ -30,6 +30,8 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeDocsPane, ide_docs_pane, IDE, DOCS_PANE, IdePane)
 
-IdeDocsLibrary *ide_docs_pane_get_library (IdeDocsPane *self);
+IdeDocsLibrary *ide_docs_pane_get_library (IdeDocsPane    *self);
+void            ide_docs_pane_set_library (IdeDocsPane    *self,
+                                           IdeDocsLibrary *library);
 
 G_END_DECLS
diff --git a/src/libide/docs/ide-docs-workspace.c b/src/libide/docs/ide-docs-workspace.c
index 4c21ee709..78fc1c7ab 100644
--- a/src/libide/docs/ide-docs-workspace.c
+++ b/src/libide/docs/ide-docs-workspace.c
@@ -46,6 +46,7 @@ struct _IdeDocsWorkspace
   IdeDocsSearchView      *search_view;
   IdeDocsView            *view;
   GtkEntry               *entry;
+  IdeDocsPane            *pane;
 };
 
 G_DEFINE_TYPE (IdeDocsWorkspace, ide_docs_workspace, IDE_TYPE_WORKSPACE)
@@ -124,7 +125,7 @@ on_search_view_item_activated_cb (IdeDocsWorkspace  *self,
   g_assert (IDE_IS_DOCS_ITEM (item));
   g_assert (IDE_IS_DOCS_SEARCH_VIEW (view));
 
-  g_print ("Activate view for %s at %s\n",
+  g_debug ("Activate view for %s at %s",
            ide_docs_item_get_title (item),
            ide_docs_item_get_url (item));
 
@@ -144,6 +145,26 @@ ide_docs_workspace_focus_search_cb (GtkWidget *widget,
   gtk_widget_grab_focus (GTK_WIDGET (self->entry));
 }
 
+static void
+ide_docs_workspace_context_set (IdeWorkspace *workspace,
+                                IdeContext   *context)
+{
+  IdeDocsWorkspace *self = (IdeDocsWorkspace *)workspace;
+
+  g_assert (IDE_IS_DOCS_WORKSPACE (self));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  IDE_WORKSPACE_CLASS (ide_docs_workspace_parent_class)->context_set (workspace, context);
+
+  if (context != NULL)
+    {
+      IdeDocsLibrary *library;
+
+      library = ide_docs_library_from_context (context);
+      ide_docs_pane_set_library (self->pane, library);
+    }
+}
+
 static void
 ide_docs_workspace_destroy (GtkWidget *widget)
 {
@@ -164,8 +185,11 @@ ide_docs_workspace_class_init (IdeDocsWorkspaceClass *klass)
 
   widget_class->destroy = ide_docs_workspace_destroy;
 
+  workspace_class->context_set = ide_docs_workspace_context_set;
+
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-docs/ui/ide-docs-workspace.ui");
   gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, pane);
   gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, search_view);
   gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, stack);
   gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, view);
diff --git a/src/libide/docs/ide-docs-workspace.ui b/src/libide/docs/ide-docs-workspace.ui
index e796a8629..a30e28d47 100644
--- a/src/libide/docs/ide-docs-workspace.ui
+++ b/src/libide/docs/ide-docs-workspace.ui
@@ -20,7 +20,7 @@
           <object class="IdeSurface" id="docs_surface">
             <property name="visible">true</property>
             <child type="left">
-              <object class="IdeDocsPane">
+              <object class="IdeDocsPane" id="pane">
                 <property name="width-request">200</property>
                 <property name="visible">true</property>
               </object>
diff --git a/src/libide/docs/libide-docs.gresource.xml b/src/libide/docs/libide-docs.gresource.xml
index d17c06357..9a864efee 100644
--- a/src/libide/docs/libide-docs.gresource.xml
+++ b/src/libide/docs/libide-docs.gresource.xml
@@ -5,6 +5,7 @@
   </gresource>
   <gresource prefix="/org/gnome/libide-docs/ui">
     <file preprocess="xml-stripblanks">ide-docs-pane.ui</file>
+    <file preprocess="xml-stripblanks">ide-docs-pane-row.ui</file>
     <file preprocess="xml-stripblanks">ide-docs-search-row.ui</file>
     <file preprocess="xml-stripblanks">ide-docs-search-view.ui</file>
     <file preprocess="xml-stripblanks">ide-docs-workspace.ui</file>
diff --git a/src/libide/docs/meson.build b/src/libide/docs/meson.build
index c6186e33f..fbefb87a6 100644
--- a/src/libide/docs/meson.build
+++ b/src/libide/docs/meson.build
@@ -25,18 +25,22 @@ libide_docs_public_sources = [
   'libide-docs.c',
   'ide-docs-item.c',
   'ide-docs-library.c',
-  'ide-docs-pane.c',
   'ide-docs-provider.c',
   'ide-docs-query.c',
+  'ide-docs-workspace.c',
+]
+
+libide_docs_private_sources = [
+  'ide-docs-pane.c',
+  'ide-docs-pane-row.c',
   'ide-docs-search-model.c',
   'ide-docs-search-row.c',
   'ide-docs-search-section.c',
   'ide-docs-search-view.c',
   'ide-docs-view.c',
-  'ide-docs-workspace.c',
 ]
 
-libide_docs_sources = libide_docs_public_sources
+libide_docs_sources = libide_docs_public_sources + libide_docs_private_sources
 
 #
 # Enum generation
diff --git a/src/libide/gui/ide-path-bar.c b/src/libide/gui/ide-path-bar.c
new file mode 100644
index 000000000..311d235d5
--- /dev/null
+++ b/src/libide/gui/ide-path-bar.c
@@ -0,0 +1,275 @@
+/* ide-path-bar.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-path-bar"
+
+#include "config.h"
+
+#include "ide-path-bar.h"
+
+typedef struct
+{
+  IdePath *path;
+  IdePath *selection;
+} IdePathBarPrivate;
+
+enum {
+  PROP_0,
+  PROP_PATH,
+  PROP_SELECTION,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdePathBar, ide_path_bar, GTK_TYPE_CONTAINER)
+
+static GParamSpec *properties [N_PROPS];
+
+static GtkWidget *
+ide_path_bar_create_button (const gchar *title,
+                            gboolean     with_arrow)
+{
+  GtkButton *button;
+  GtkLabel *label;
+  GtkBox *box;
+
+  box = g_object_new (GTK_TYPE_BOX,
+                      "spacing", 3,
+                      "visible", TRUE,
+                      NULL);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (label));
+
+  if (with_arrow)
+    gtk_container_add (GTK_CONTAINER (box),
+                       g_object_new (GTK_TYPE_IMAGE,
+                                     "visible", TRUE,
+                                     NULL));
+
+  button = g_object_new (GTK_TYPE_BUTTON,
+                         "focus-on-click", FALSE,
+                         "child", box,
+                         "visible", TRUE,
+                         NULL);
+
+  return GTK_WIDGET (button);
+}
+
+static void
+ide_path_bar_update_buttons (IdePathBar *self)
+{
+  IdePathBarPrivate *priv = ide_path_bar_get_instance_private (self);
+  guint n_elements;
+
+  g_assert (IDE_IS_PATH_BAR (self));
+
+  gtk_container_foreach (GTK_CONTAINER (self),
+                         (GtkCallback) gtk_widget_destroy,
+                         NULL);
+
+  if (priv->path == NULL)
+    return;
+
+  n_elements = ide_path_get_n_elements (priv->path);
+
+  for (guint i = 0; i < n_elements; i++)
+    {
+      IdePathElement *element = ide_path_get_element (priv->path, i);
+      const gchar *title;
+      GtkWidget *button;
+      gboolean has_arrow;
+
+      g_assert (IDE_IS_PATH_ELEMENT (element));
+
+      title = ide_path_element_get_title (element);
+      has_arrow = i + 1 == n_elements;
+      button = ide_path_bar_create_button (title, has_arrow);
+
+      gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (button));
+    }
+
+}
+
+static void
+ide_path_bar_add (GtkContainer *container,
+                  GtkWidget    *widget)
+{
+  g_assert (IDE_IS_PATH_BAR (container));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  gtk_widget_set_parent (widget, GTK_WIDGET (container));
+}
+
+static void
+ide_path_bar_finalize (GObject *object)
+{
+  IdePathBar *self = (IdePathBar *)object;
+  IdePathBarPrivate *priv = ide_path_bar_get_instance_private (self);
+
+  g_clear_object (&priv->path);
+  g_clear_object (&priv->selection);
+
+  G_OBJECT_CLASS (ide_path_bar_parent_class)->finalize (object);
+}
+
+static void
+ide_path_bar_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  IdePathBar *self = IDE_PATH_BAR (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_object (value, ide_path_bar_get_path (self));
+      break;
+
+    case PROP_SELECTION:
+      g_value_set_object (value, ide_path_bar_get_selection (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_path_bar_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  IdePathBar *self = IDE_PATH_BAR (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      ide_path_bar_set_path (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_path_bar_class_init (IdePathBarClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->finalize = ide_path_bar_finalize;
+  object_class->get_property = ide_path_bar_get_property;
+  object_class->set_property = ide_path_bar_set_property;
+
+  container_class->add = ide_path_bar_add;
+
+  properties [PROP_PATH] =
+    g_param_spec_object ("path",
+                         "Path",
+                         "The path that is displayed",
+                         IDE_TYPE_PATH,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SELECTION] =
+    g_param_spec_object ("selection",
+                         "Selection",
+                         "The selected portion of the path",
+                         IDE_TYPE_PATH,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_path_bar_init (IdePathBar *self)
+{
+  GtkStyleContext *style_context;
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+  gtk_widget_set_redraw_on_allocate (GTK_WIDGET (self), FALSE);
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+  gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_LINKED);
+}
+
+/**
+ * ide_path_bar_get_selection:
+ * @self: an #IdePathBar
+ *
+ * Get the path up to the selected element.
+ *
+ * Returns: (transfer none) (nullable): an #IdePathBar or %NULL
+ *
+ * Since: 3.34
+ */
+IdePath *
+ide_path_bar_get_selection (IdePathBar *self)
+{
+  IdePathBarPrivate *priv = ide_path_bar_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_PATH_BAR (self), NULL);
+
+  return priv->selection;
+}
+
+/**
+ * ide_path_bar_get_path:
+ * @self: an #IdePathBar
+ *
+ * Gets the path for the whole path bar. This may include elements
+ * after the selected element if the selected element is before
+ * the end of the path.
+ *
+ * Returns: (transfer none) (nullable): an #IdePath or %NULL
+ *
+ * Since: 3.34
+ */
+IdePath *
+ide_path_bar_get_path (IdePathBar *self)
+{
+  IdePathBarPrivate *priv = ide_path_bar_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_PATH_BAR (self), NULL);
+
+  return priv->selection;
+}
+
+void
+ide_path_bar_set_path (IdePathBar *self,
+                       IdePath    *path)
+{
+  IdePathBarPrivate *priv = ide_path_bar_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_PATH_BAR (self));
+  g_return_if_fail (!path || IDE_IS_PATH (path));
+
+  if (g_set_object (&priv->path, path))
+    {
+      g_set_object (&priv->selection, path);
+      ide_path_bar_update_buttons (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PATH]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTION]);
+    }
+}
diff --git a/src/libide/gui/ide-path-bar.h b/src/libide/gui/ide-path-bar.h
new file mode 100644
index 000000000..f588762a4
--- /dev/null
+++ b/src/libide/gui/ide-path-bar.h
@@ -0,0 +1,53 @@
+/* ide-path-bar.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 <gtk/gtk.h>
+#include <libide-core.h>
+
+#include "ide-path.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PATH_BAR (ide_path_bar_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_DERIVABLE_TYPE (IdePathBar, ide_path_bar, IDE, PATH_BAR, GtkContainer)
+
+struct _IdePathBarClass
+{
+  GtkContainerClass parent_instance;
+
+  /*< private >*/
+  gpointer _reserved[8];
+};
+
+IDE_AVAILABLE_IN_3_34
+GtkWidget *ide_path_bar_new           (void);
+IDE_AVAILABLE_IN_3_34
+IdePath   *ide_path_bar_get_path      (IdePathBar *self);
+IDE_AVAILABLE_IN_3_34
+void       ide_path_bar_set_path      (IdePathBar *path_bar,
+                                       IdePath    *path);
+IDE_AVAILABLE_IN_3_34
+IdePath   *ide_path_bar_get_selection (IdePathBar *self);
+
+G_END_DECLS
diff --git a/src/libide/gui/ide-path-element.c b/src/libide/gui/ide-path-element.c
new file mode 100644
index 000000000..f4deabb17
--- /dev/null
+++ b/src/libide/gui/ide-path-element.c
@@ -0,0 +1,181 @@
+/* ide-path-element.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-path-element"
+
+#include "config.h"
+
+#include "ide-path-element.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *title;
+} IdePathElementPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdePathElement, ide_path_element, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_TITLE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_path_element_new:
+ * @id: the id for the path element
+ * @title: the display name for the path element
+ *
+ * Create a new #IdePathElement.
+ *
+ * Returns: (transfer full): a newly created #IdePathElement
+ *
+ * Since: 3.34
+ */
+IdePathElement *
+ide_path_element_new (const gchar *id,
+                      const gchar *title)
+{
+  return g_object_new (IDE_TYPE_PATH_ELEMENT,
+                       "id", id,
+                       "title", title,
+                       NULL);
+}
+
+static void
+ide_path_element_finalize (GObject *object)
+{
+  IdePathElement *self = (IdePathElement *)object;
+  IdePathElementPrivate *priv = ide_path_element_get_instance_private (self);
+
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->title, g_free);
+
+  G_OBJECT_CLASS (ide_path_element_parent_class)->finalize (object);
+}
+
+static void
+ide_path_element_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  IdePathElement *self = IDE_PATH_ELEMENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_path_element_get_id (self));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, ide_path_element_get_title (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_path_element_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  IdePathElement *self = IDE_PATH_ELEMENT (object);
+  IdePathElementPrivate *priv = ide_path_element_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_TITLE:
+      priv->title = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_path_element_class_init (IdePathElementClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_path_element_finalize;
+  object_class->get_property = ide_path_element_get_property;
+  object_class->set_property = ide_path_element_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The identifier for the element",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "The display title for the element",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_path_element_init (IdePathElement *self)
+{
+}
+
+const gchar *
+ide_path_element_get_id (IdePathElement *self)
+{
+  IdePathElementPrivate *priv = ide_path_element_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_PATH_ELEMENT (self), NULL);
+
+  return priv->id;
+}
+
+const gchar *
+ide_path_element_get_title (IdePathElement *self)
+{
+  IdePathElementPrivate *priv = ide_path_element_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_PATH_ELEMENT (self), NULL);
+
+  return priv->title;
+}
+
+gboolean
+ide_path_element_equal (IdePathElement *self,
+                        IdePathElement *other)
+{
+  return 0 == g_strcmp0 (ide_path_element_get_id (self),
+                         ide_path_element_get_id (other));
+}
diff --git a/src/libide/gui/ide-path-element.h b/src/libide/gui/ide-path-element.h
new file mode 100644
index 000000000..4186f01be
--- /dev/null
+++ b/src/libide/gui/ide-path-element.h
@@ -0,0 +1,51 @@
+/* ide-path-element.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 <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PATH_ELEMENT (ide_path_element_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_DERIVABLE_TYPE (IdePathElement, ide_path_element, IDE, PATH_ELEMENT, GObject)
+
+struct _IdePathElementClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[8];
+};
+
+IDE_AVAILABLE_IN_3_34
+IdePathElement *ide_path_element_new       (const gchar    *id,
+                                            const gchar    *title);
+IDE_AVAILABLE_IN_3_34
+const gchar    *ide_path_element_get_id    (IdePathElement *self);
+IDE_AVAILABLE_IN_3_34
+const gchar    *ide_path_element_get_title (IdePathElement *self);
+IDE_AVAILABLE_IN_3_34
+gboolean        ide_path_element_equal     (IdePathElement *self,
+                                            IdePathElement *other);
+
+G_END_DECLS
diff --git a/src/libide/gui/ide-path.c b/src/libide/gui/ide-path.c
new file mode 100644
index 000000000..a2d8d430c
--- /dev/null
+++ b/src/libide/gui/ide-path.c
@@ -0,0 +1,171 @@
+/* ide-path.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-path"
+
+#include "config.h"
+
+#include "ide-path.h"
+
+struct _IdePath
+{
+  GObject    parent_instance;
+  GPtrArray *elements;
+};
+
+G_DEFINE_TYPE (IdePath, ide_path, G_TYPE_OBJECT)
+
+/**
+ * ide_path_new:
+ * @elements: (transfer none) (element-type IdePathElement): an array of
+ *   elements for the path.
+ *
+ * Create a new #IdePath using the elements provided.
+ *
+ * Returns: (transfer full): a newly created #IdePath
+ *
+ * Since: 3.34
+ */
+IdePath *
+ide_path_new (GPtrArray *elements)
+{
+  IdePath *self;
+
+  g_return_val_if_fail (elements != NULL, NULL);
+
+  self = g_object_new (IDE_TYPE_PATH, NULL);
+  self->elements = g_ptr_array_new_full (elements->len, g_object_unref);
+  for (guint i = 0; i < elements->len; i++)
+    g_ptr_array_add (self->elements, g_object_ref (g_ptr_array_index (elements, i)));
+
+  return g_steal_pointer (&self);
+}
+
+static void
+ide_path_finalize (GObject *object)
+{
+  IdePath *self = (IdePath *)object;
+
+  g_clear_pointer (&self->elements, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (ide_path_parent_class)->finalize (object);
+}
+
+static void
+ide_path_class_init (IdePathClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_path_finalize;
+}
+
+static void
+ide_path_init (IdePath *self)
+{
+}
+
+guint
+ide_path_get_n_elements (IdePath *self)
+{
+  g_return_val_if_fail (IDE_IS_PATH (self), 0);
+
+  return self->elements->len;
+}
+
+/**
+ * ide_path_get_element:
+ * @self: an #IdePath
+ * @position: the position of the element
+ *
+ * Gets the element at the position starting from 0.
+ *
+ * Returns: (transfer none): an #IdePathElement
+ *
+ * Since: 3.34
+ */
+IdePathElement *
+ide_path_get_element (IdePath *self,
+                      guint    position)
+{
+  g_return_val_if_fail (IDE_IS_PATH (self), NULL);
+  g_return_val_if_fail (position < self->elements->len, NULL);
+
+  return g_ptr_array_index (self->elements, position);
+}
+
+gboolean
+ide_path_has_prefix (IdePath *self,
+                     IdePath *prefix)
+{
+  g_return_val_if_fail (IDE_IS_PATH (self), FALSE);
+  g_return_val_if_fail (IDE_IS_PATH (prefix), FALSE);
+
+  if (prefix->elements->len > self->elements->len)
+    return FALSE;
+
+  for (guint i = 0; i < prefix->elements->len; i++)
+    {
+      IdePathElement *eself = g_ptr_array_index (self->elements, i);
+      IdePathElement *pself = g_ptr_array_index (prefix->elements, i);
+
+      if (!ide_path_element_equal (eself, pself))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * ide_path_get_parent:
+ * @self: an #IdePath
+ *
+ * Gets a new path for the parent of @self.
+ *
+ * Returns: (transfer full) (nullable): an #IdePath or %NULL if the
+ *   path is the root path.
+ *
+ * Since: 3.34
+ */
+IdePath *
+ide_path_get_parent (IdePath *self)
+{
+  IdePath *ret;
+
+  g_return_val_if_fail (IDE_IS_PATH (self), NULL);
+
+  if (self->elements->len == 0)
+    return NULL;
+
+  ret = g_object_new (IDE_TYPE_PATH, NULL);
+  ret->elements = g_ptr_array_new_with_free_func (g_object_unref);
+
+  for (guint i = 0; i < self->elements->len - 1; i++)
+    g_ptr_array_add (ret->elements, g_object_ref (g_ptr_array_index (self->elements, i)));
+
+  return g_steal_pointer (&ret);
+}
+
+gboolean
+ide_path_is_root (IdePath *self)
+{
+  g_return_val_if_fail (IDE_IS_PATH (self), FALSE);
+
+  return self->elements->len == 0;
+}
diff --git a/src/libide/gui/ide-path.h b/src/libide/gui/ide-path.h
new file mode 100644
index 000000000..585cf1995
--- /dev/null
+++ b/src/libide/gui/ide-path.h
@@ -0,0 +1,49 @@
+/* ide-path.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 <libide-core.h>
+
+#include "ide-path-element.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PATH (ide_path_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_FINAL_TYPE (IdePath, ide_path, IDE, PATH, GObject)
+
+IDE_AVAILABLE_IN_3_34
+IdePath        *ide_path_new            (GPtrArray *elements);
+IDE_AVAILABLE_IN_3_34
+IdePath        *ide_path_get_parent     (IdePath   *self);
+IDE_AVAILABLE_IN_3_34
+guint           ide_path_get_n_elements (IdePath   *self);
+IDE_AVAILABLE_IN_3_34
+IdePathElement *ide_path_get_element    (IdePath   *self,
+                                         guint      position);
+IDE_AVAILABLE_IN_3_34
+gboolean        ide_path_has_prefix     (IdePath   *self,
+                                         IdePath   *prefix);
+IDE_AVAILABLE_IN_3_34
+gboolean        ide_path_is_root        (IdePath   *self);
+
+G_END_DECLS
diff --git a/src/libide/gui/meson.build b/src/libide/gui/meson.build
index 9f469d2fa..768614503 100644
--- a/src/libide/gui/meson.build
+++ b/src/libide/gui/meson.build
@@ -32,6 +32,9 @@ libide_gui_public_headers = [
   'ide-page.h',
   'ide-pane.h',
   'ide-panel.h',
+  'ide-path.h',
+  'ide-path-bar.h',
+  'ide-path-element.h',
   'ide-preferences-addin.h',
   'ide-preferences-surface.h',
   'ide-preferences-window.h',
@@ -136,6 +139,9 @@ libide_gui_public_sources = [
   'ide-page.c',
   'ide-pane.c',
   'ide-panel.c',
+  'ide-path.c',
+  'ide-path-bar.c',
+  'ide-path-element.c',
   'ide-primary-workspace.c',
   'ide-preferences-addin.c',
   'ide-preferences-surface.c',
diff --git a/src/plugins/devhelp/gbp-devhelp-docs-provider.c b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
index 0d4285df4..2e6892c49 100644
--- a/src/plugins/devhelp/gbp-devhelp-docs-provider.c
+++ b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
@@ -77,6 +77,18 @@ gbp_devhelp_docs_provider_populate_async (IdeDocsProvider     *provider,
       g_autoptr(IdeDocsItem) child = NULL;
 
       child = ide_docs_item_new ();
+      ide_docs_item_set_id (child, "devhelp:sdk");
+      ide_docs_item_set_title (child, "SDKs");
+      ide_docs_item_set_kind (child, IDE_DOCS_ITEM_KIND_COLLECTION);
+
+      ide_docs_item_append (item, child);
+    }
+  else if (ide_str_equal0 (ide_docs_item_get_id (item), "devhelp:sdks"))
+    {
+      g_autoptr(IdeDocsItem) child = NULL;
+
+      child = ide_docs_item_new ();
+      ide_docs_item_set_id (child, "devhelp:books");
       ide_docs_item_set_title (child, "Books");
       ide_docs_item_set_kind (child, IDE_DOCS_ITEM_KIND_COLLECTION);
 


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