[libdazzle] prefs: add default DzlPreferences implementation



commit 498a9c1fda0206bbb88277edaea22dea9b856ead
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jun 6 02:26:07 2017 -0700

    prefs: add default DzlPreferences implementation
    
    This is based on IdePreferencesPerspective from Builder and extracted into
    a standalone widget. It still needs iteration, in particular for CSS
    styling. I also think we have some priority/positioning bugs to work
    out.

 src/dazzle.gresources.xml         |    1 +
 src/dazzle.h                      |    1 +
 src/meson.build                   |    2 +
 src/prefs/dzl-preferences-view.c  |  870 +++++++++++++++++++++++++++++++++++++
 src/prefs/dzl-preferences-view.h  |   36 ++
 src/prefs/dzl-preferences-view.ui |   75 ++++
 tests/meson.build                 |    6 +
 tests/test-preferences.c          |   77 ++++
 8 files changed, 1068 insertions(+), 0 deletions(-)
---
diff --git a/src/dazzle.gresources.xml b/src/dazzle.gresources.xml
index 3b5077b..97b74c3 100644
--- a/src/dazzle.gresources.xml
+++ b/src/dazzle.gresources.xml
@@ -11,6 +11,7 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-preferences-page.ui">prefs/dzl-preferences-page.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-preferences-spin-button.ui">prefs/dzl-preferences-spin-button.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-preferences-switch.ui">prefs/dzl-preferences-switch.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-preferences-view.ui">prefs/dzl-preferences-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-suggestion-popover.ui">suggestions/dzl-suggestion-popover.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-suggestion-row.ui">suggestions/dzl-suggestion-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="dzl-shortcut-accel-dialog.ui">shortcuts/dzl-shortcut-accel-dialog.ui</file>
diff --git a/src/dazzle.h b/src/dazzle.h
index 7659d2a..5117fb2 100644
--- a/src/dazzle.h
+++ b/src/dazzle.h
@@ -77,6 +77,7 @@ G_BEGIN_DECLS
 #include "prefs/dzl-preferences-page.h"
 #include "prefs/dzl-preferences-spin-button.h"
 #include "prefs/dzl-preferences-switch.h"
+#include "prefs/dzl-preferences-view.h"
 #include "prefs/dzl-preferences.h"
 #include "search/dzl-fuzzy-index.h"
 #include "search/dzl-fuzzy-index-builder.h"
diff --git a/src/meson.build b/src/meson.build
index 3e13244..f0264d2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -82,6 +82,7 @@ libdazzle_public_headers = [
   'prefs/dzl-preferences-page.h',
   'prefs/dzl-preferences-spin-button.h',
   'prefs/dzl-preferences-switch.h',
+  'prefs/dzl-preferences-view.h',
   'prefs/dzl-preferences.h',
 
   'search/dzl-fuzzy-index-builder.h',
@@ -217,6 +218,7 @@ libdazzle_public_sources = [
   'prefs/dzl-preferences-page.c',
   'prefs/dzl-preferences-spin-button.c',
   'prefs/dzl-preferences-switch.c',
+  'prefs/dzl-preferences-view.c',
   'prefs/dzl-preferences.c',
 
   'search/dzl-fuzzy-index-builder.c',
diff --git a/src/prefs/dzl-preferences-view.c b/src/prefs/dzl-preferences-view.c
new file mode 100644
index 0000000..ec31d36
--- /dev/null
+++ b/src/prefs/dzl-preferences-view.c
@@ -0,0 +1,870 @@
+/* dzl-preferences-view.c
+ *
+ * Copyright (C) 2015 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/>.
+ */
+
+#define G_LOG_DOMAIN "dzl-preferences-view"
+
+#include <glib/gi18n.h>
+
+#include "prefs/dzl-preferences-file-chooser-button.h"
+#include "prefs/dzl-preferences-font-button.h"
+#include "prefs/dzl-preferences-group-private.h"
+#include "prefs/dzl-preferences-page-private.h"
+#include "prefs/dzl-preferences-spin-button.h"
+#include "prefs/dzl-preferences-switch.h"
+#include "prefs/dzl-preferences-view.h"
+#include "util/dzl-util-private.h"
+
+struct _DzlPreferencesView
+{
+  GtkBin                 parent_instance;
+
+  guint                  last_widget_id;
+
+  GActionGroup          *actions;
+  GSequence             *pages;
+  GHashTable            *widgets;
+
+  GtkScrolledWindow     *scroller;
+  GtkStack              *page_stack;
+  GtkStackSwitcher      *page_stack_sidebar;
+  GtkSearchEntry        *search_entry;
+  GtkStack              *subpage_stack;
+};
+
+static void dzl_preferences_iface_init (DzlPreferencesInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (DzlPreferencesView, dzl_preferences_view, GTK_TYPE_BIN,
+                         G_IMPLEMENT_INTERFACE (DZL_TYPE_PREFERENCES, dzl_preferences_iface_init))
+
+static void
+dzl_preferences_view_refilter_cb (GtkWidget *widget,
+                                  gpointer   user_data)
+{
+  DzlPreferencesPage *page = (DzlPreferencesPage *)widget;
+  DzlPatternSpec *spec = user_data;
+
+  g_assert (DZL_IS_PREFERENCES_PAGE (page));
+
+  dzl_preferences_page_refilter (page, spec);
+}
+
+static void
+dzl_preferences_view_refilter (DzlPreferencesView *self,
+                               const gchar        *search_text)
+{
+  DzlPatternSpec *spec = NULL;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+
+  if (!dzl_str_empty0 (search_text))
+    spec = dzl_pattern_spec_new (search_text);
+
+  gtk_container_foreach (GTK_CONTAINER (self->page_stack),
+                         dzl_preferences_view_refilter_cb,
+                         spec);
+  gtk_container_foreach (GTK_CONTAINER (self->subpage_stack),
+                         dzl_preferences_view_refilter_cb,
+                         spec);
+
+  g_clear_pointer (&spec, dzl_pattern_spec_unref);
+}
+
+static gint
+sort_by_priority (gconstpointer a,
+                  gconstpointer b,
+                  gpointer      user_data)
+{
+  gint prioritya = 0;
+  gint priorityb = 0;
+
+  g_object_get ((gpointer)a, "priority", &prioritya, NULL);
+  g_object_get ((gpointer)b, "priority", &priorityb, NULL);
+
+  return prioritya - priorityb;
+}
+
+static void
+dzl_preferences_view_notify_visible_child (DzlPreferencesView *self,
+                                           GParamSpec         *pspec,
+                                           GtkStack           *stack)
+{
+  DzlPreferencesPage *page;
+  GHashTableIter iter;
+  gpointer value;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+
+  /* Short circuit if we are destroying everything */
+  if (gtk_widget_in_destruction (GTK_WIDGET (self)))
+    return;
+
+  gtk_widget_hide (GTK_WIDGET (self->subpage_stack));
+
+  /*
+   * If there are any selections in list groups, re-select it to cause
+   * the subpage to potentially reappear.
+   */
+
+  if (NULL == (page = DZL_PREFERENCES_PAGE (gtk_stack_get_visible_child (stack))))
+    return;
+
+  g_hash_table_iter_init (&iter, page->groups_by_name);
+
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    {
+      DzlPreferencesGroup *group = value;
+      GtkSelectionMode mode = GTK_SELECTION_NONE;
+
+      g_assert (DZL_IS_PREFERENCES_GROUP (group));
+
+      if (!group->is_list)
+        continue;
+
+      g_object_get (group, "mode", &mode, NULL);
+
+      if (mode == GTK_SELECTION_SINGLE)
+        {
+          GtkListBoxRow *selected;
+
+          selected = gtk_list_box_get_selected_row (group->list_box);
+
+          g_assert (!selected || GTK_IS_LIST_BOX_ROW (selected));
+
+          if (selected != NULL && gtk_widget_activate (GTK_WIDGET (selected)))
+            break;
+        }
+    }
+}
+
+static void
+dzl_preferences_view_finalize (GObject *object)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)object;
+
+  g_clear_pointer (&self->pages, g_sequence_free);
+  g_clear_pointer (&self->widgets, g_hash_table_unref);
+  g_clear_object (&self->actions);
+
+  G_OBJECT_CLASS (dzl_preferences_view_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_view_class_init (DzlPreferencesViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = dzl_preferences_view_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/dazzle/ui/dzl-preferences-view.ui");
+  gtk_widget_class_set_css_name (widget_class, "dzlpreferencesview");
+  gtk_widget_class_bind_template_child (widget_class, DzlPreferencesView, page_stack);
+  gtk_widget_class_bind_template_child (widget_class, DzlPreferencesView, page_stack_sidebar);
+  gtk_widget_class_bind_template_child (widget_class, DzlPreferencesView, scroller);
+  gtk_widget_class_bind_template_child (widget_class, DzlPreferencesView, search_entry);
+  gtk_widget_class_bind_template_child (widget_class, DzlPreferencesView, subpage_stack);
+}
+
+static void
+go_back_activate (GSimpleAction *action,
+                  GVariant      *parameter,
+                  gpointer       user_data)
+{
+  DzlPreferencesView *self = user_data;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+
+  gtk_widget_hide (GTK_WIDGET (self->subpage_stack));
+}
+
+static void
+dzl_preferences_view_search_entry_changed (DzlPreferencesView *self,
+                                           GtkSearchEntry     *search_entry)
+{
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (GTK_IS_SEARCH_ENTRY (search_entry));
+
+  dzl_preferences_view_refilter (self, gtk_entry_get_text (GTK_ENTRY (search_entry)));
+}
+
+static void
+dzl_preferences_view_notify_subpage_stack_visible (DzlPreferencesView *self,
+                                                   GParamSpec         *pspec,
+                                                   GtkStack           *subpage_stack)
+{
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (GTK_IS_STACK (subpage_stack));
+
+  /*
+   * Because the subpage stack can cause us to have a wider display than
+   * the screen has, we need to allow scrolling. This can happen because
+   * side-by-side we could be just a bit bigger than 1280px which is a
+   * fairly common laptop screen size (especially under HiDPI).
+   *
+   * https://bugzilla.gnome.org/show_bug.cgi?id=772700
+   */
+
+  if (gtk_widget_get_visible (GTK_WIDGET (subpage_stack)))
+    g_object_set (self->scroller, "hscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL);
+  else
+    g_object_set (self->scroller, "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
+}
+
+static void
+dzl_preferences_view_init (DzlPreferencesView *self)
+{
+  static const GActionEntry entries[] = {
+    { "go-back", go_back_activate },
+  };
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->search_entry,
+                           "changed",
+                           G_CALLBACK (dzl_preferences_view_search_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->page_stack,
+                           "notify::visible-child",
+                           G_CALLBACK (dzl_preferences_view_notify_visible_child),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->subpage_stack,
+                           "notify::visible",
+                           G_CALLBACK (dzl_preferences_view_notify_subpage_stack_visible),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->pages = g_sequence_new (NULL);
+  self->widgets = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  self->actions = G_ACTION_GROUP (g_simple_action_group_new ());
+  g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
+                                   entries, G_N_ELEMENTS (entries),
+                                   self);
+}
+
+static GtkWidget *
+dzl_preferences_view_get_page (DzlPreferencesView *self,
+                               const gchar        *page_name)
+{
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+
+  if (strchr (page_name, '.') != NULL)
+    return gtk_stack_get_child_by_name (self->subpage_stack, page_name);
+  else
+    return gtk_stack_get_child_by_name (self->page_stack, page_name);
+}
+
+static void
+dzl_preferences_view_add_page (DzlPreferences *preferences,
+                               const gchar    *page_name,
+                               const gchar    *title,
+                               gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesPage *page;
+  GSequenceIter *iter;
+  GtkStack *stack;
+  gint position = -1;
+
+  g_assert (DZL_IS_PREFERENCES (preferences));
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (title != NULL || strchr (page_name, '.'));
+
+  if (strchr (page_name, '.') != NULL)
+    stack = self->subpage_stack;
+  else
+    stack = self->page_stack;
+
+  if (gtk_stack_get_child_by_name (stack, page_name))
+    return;
+
+  page = g_object_new (DZL_TYPE_PREFERENCES_PAGE,
+                       "priority", priority,
+                       "visible", TRUE,
+                       NULL);
+
+  if (stack == self->page_stack)
+    {
+      iter = g_sequence_insert_sorted (self->pages, page, sort_by_priority, NULL);
+      position = g_sequence_iter_get_position (iter);
+    }
+
+  gtk_container_add_with_properties (GTK_CONTAINER (stack), GTK_WIDGET (page),
+                                     "position", position,
+                                     "name", page_name,
+                                     "title", title,
+                                     NULL);
+}
+
+static void
+dzl_preferences_view_add_group (DzlPreferences *preferences,
+                                const gchar    *page_name,
+                                const gchar    *group_name,
+                                const gchar    *title,
+                                gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return;
+    }
+
+  group = g_object_new (DZL_TYPE_PREFERENCES_GROUP,
+                        "name", group_name,
+                        "priority", priority,
+                        "title", title,
+                        "visible", TRUE,
+                        NULL);
+  dzl_preferences_page_add_group (DZL_PREFERENCES_PAGE (page), group);
+}
+
+static void
+dzl_preferences_view_add_list_group (DzlPreferences   *preferences,
+                                     const gchar      *page_name,
+                                     const gchar      *group_name,
+                                     const gchar      *title,
+                                     GtkSelectionMode  mode,
+                                     gint              priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return;
+    }
+
+  group = g_object_new (DZL_TYPE_PREFERENCES_GROUP,
+                        "is-list", TRUE,
+                        "mode", mode,
+                        "name", group_name,
+                        "priority", priority,
+                        "title", title,
+                        "visible", TRUE,
+                        NULL);
+  dzl_preferences_page_add_group (DZL_PREFERENCES_PAGE (page), group);
+}
+
+static guint
+dzl_preferences_view_add_radio (DzlPreferences *preferences,
+                                const gchar    *page_name,
+                                const gchar    *group_name,
+                                const gchar    *schema_id,
+                                const gchar    *key,
+                                const gchar    *path,
+                                const gchar    *variant_string,
+                                const gchar    *title,
+                                const gchar    *subtitle,
+                                const gchar    *keywords,
+                                gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesSwitch *widget;
+  DzlPreferencesGroup *group;
+  g_autoptr(GVariant) variant = NULL;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  if (variant_string != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      variant = g_variant_parse (NULL, variant_string, NULL, NULL, &error);
+
+      if (variant == NULL)
+        g_warning ("%s", error->message);
+      else
+        g_variant_ref_sink (variant);
+    }
+
+  widget = g_object_new (DZL_TYPE_PREFERENCES_SWITCH,
+                         "is-radio", TRUE,
+                         "key", key,
+                         "keywords", keywords,
+                         "path", path,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "target", variant,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+dzl_preferences_view_add_switch (DzlPreferences *preferences,
+                                 const gchar    *page_name,
+                                 const gchar    *group_name,
+                                 const gchar    *schema_id,
+                                 const gchar    *key,
+                                 const gchar    *path,
+                                 const gchar    *variant_string,
+                                 const gchar    *title,
+                                 const gchar    *subtitle,
+                                 const gchar    *keywords,
+                                 gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesSwitch *widget;
+  DzlPreferencesGroup *group;
+  g_autoptr(GVariant) variant = NULL;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  if (variant_string != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      variant = g_variant_parse (NULL, variant_string, NULL, NULL, &error);
+
+      if (variant == NULL)
+        g_warning ("%s", error->message);
+      else
+        g_variant_ref_sink (variant);
+    }
+
+  widget = g_object_new (DZL_TYPE_PREFERENCES_SWITCH,
+                         "key", key,
+                         "keywords", keywords,
+                         "path", path,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "target", variant,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+dzl_preferences_view_add_spin_button (DzlPreferences *preferences,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *path,
+                                      const gchar    *title,
+                                      const gchar    *subtitle,
+                                      const gchar    *keywords,
+                                      gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesSpinButton *widget;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget = g_object_new (DZL_TYPE_PREFERENCES_SPIN_BUTTON,
+                         "key", key,
+                         "keywords", keywords,
+                         "path", path,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+dzl_preferences_view_add_font_button (DzlPreferences *preferences,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *title,
+                                      const gchar    *keywords,
+                                      gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesSwitch *widget;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget = g_object_new (DZL_TYPE_PREFERENCES_FONT_BUTTON,
+                         "key", key,
+                         "keywords", keywords,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+dzl_preferences_view_add_file_chooser (DzlPreferences       *preferences,
+                                       const gchar          *page_name,
+                                       const gchar          *group_name,
+                                       const gchar          *schema_id,
+                                       const gchar          *key,
+                                       const gchar          *path,
+                                       const gchar          *title,
+                                       const gchar          *subtitle,
+                                       GtkFileChooserAction  action,
+                                       const gchar          *keywords,
+                                       gint                  priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesFileChooserButton *widget;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget = g_object_new (DZL_TYPE_PREFERENCES_FILE_CHOOSER_BUTTON,
+                         "action", action,
+                         "key", key,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "path", path,
+                         "subtitle", subtitle,
+                         "title", title,
+                         "keywords", keywords,
+                         "visible", TRUE,
+                         NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+dzl_preferences_view_add_custom (DzlPreferences *preferences,
+                                 const gchar    *page_name,
+                                 const gchar    *group_name,
+                                 GtkWidget      *widget,
+                                 const gchar    *keywords,
+                                 gint            priority)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  DzlPreferencesBin *container;
+  DzlPreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = dzl_preferences_page_get_group (DZL_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget_id = ++self->last_widget_id;
+
+  gtk_widget_show (widget);
+  gtk_widget_show (GTK_WIDGET (group));
+
+  if (DZL_IS_PREFERENCES_BIN (widget))
+    container = DZL_PREFERENCES_BIN (widget);
+  else
+    container = g_object_new (DZL_TYPE_PREFERENCES_BIN,
+                              "child", widget,
+                              "keywords", keywords,
+                              "priority", priority,
+                              "visible", TRUE,
+                              NULL);
+
+  dzl_preferences_group_add (group, GTK_WIDGET (container));
+
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static gboolean
+dzl_preferences_view_remove_id (DzlPreferences *preferences,
+                                guint           widget_id)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  GtkWidget *widget;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (widget_id);
+
+  widget = g_hash_table_lookup (self->widgets, GINT_TO_POINTER (widget_id));
+  if (widget != NULL)
+    {
+      if (g_hash_table_remove (self->widgets, GINT_TO_POINTER (widget_id)))
+        {
+          GtkWidget *parent = gtk_widget_get_ancestor (widget, GTK_TYPE_LIST_BOX_ROW);
+
+          /* in case we added our own row ancestor, destroy it */
+          if (parent != NULL)
+            gtk_widget_destroy (parent);
+          else
+            gtk_widget_destroy (widget);
+
+          return TRUE;
+        }
+    }
+
+  g_warning ("No Preferences widget with number %i could be found and thus removed.", widget_id);
+
+  return FALSE;
+}
+
+static void
+dzl_preferences_view_set_page (DzlPreferences *preferences,
+                               const gchar    *page_name,
+                               GHashTable     *map)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+  GtkWidget *page;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+  g_assert (page_name != NULL);
+
+  page = dzl_preferences_view_get_page (self, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No such page \"%s\"", page_name);
+      return;
+    }
+
+  if (strchr (page_name, '.') != NULL)
+    {
+      dzl_preferences_page_set_map (DZL_PREFERENCES_PAGE (page), map);
+      gtk_stack_set_visible_child (self->subpage_stack, page);
+      gtk_widget_show (GTK_WIDGET (self->subpage_stack));
+    }
+  else
+    {
+      gtk_stack_set_visible_child (self->page_stack, page);
+      gtk_widget_hide (GTK_WIDGET (self->subpage_stack));
+    }
+}
+
+static GtkWidget *
+dzl_preferences_view_get_widget (DzlPreferences *preferences,
+                                 guint           widget_id)
+{
+  DzlPreferencesView *self = (DzlPreferencesView *)preferences;
+
+  g_assert (DZL_IS_PREFERENCES_VIEW (self));
+
+  return g_hash_table_lookup (self->widgets, GINT_TO_POINTER (widget_id));
+}
+
+static void
+dzl_preferences_iface_init (DzlPreferencesInterface *iface)
+{
+  iface->add_page = dzl_preferences_view_add_page;
+  iface->add_group = dzl_preferences_view_add_group;
+  iface->add_list_group  = dzl_preferences_view_add_list_group;
+  iface->add_radio = dzl_preferences_view_add_radio;
+  iface->add_font_button = dzl_preferences_view_add_font_button;
+  iface->add_switch = dzl_preferences_view_add_switch;
+  iface->add_spin_button = dzl_preferences_view_add_spin_button;
+  iface->add_file_chooser = dzl_preferences_view_add_file_chooser;
+  iface->add_custom = dzl_preferences_view_add_custom;
+  iface->set_page = dzl_preferences_view_set_page;
+  iface->remove_id = dzl_preferences_view_remove_id;
+  iface->get_widget = dzl_preferences_view_get_widget;
+}
diff --git a/src/prefs/dzl-preferences-view.h b/src/prefs/dzl-preferences-view.h
new file mode 100644
index 0000000..91ca058
--- /dev/null
+++ b/src/prefs/dzl-preferences-view.h
@@ -0,0 +1,36 @@
+/* dzl-preferences-view.h
+ *
+ * Copyright (C) 2015 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/>.
+ */
+
+#ifndef DZL_PREFERENCES_VIEW_H
+#define DZL_PREFERENCES_VIEW_H
+
+#include <gtk/gtk.h>
+
+#include "prefs/dzl-preferences.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_VIEW (dzl_preferences_view_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesView, dzl_preferences_view, DZL, PREFERENCES_VIEW, GtkBin)
+
+GtkWidget *dzl_preferences_view_new (void);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_VIEW_H */
diff --git a/src/prefs/dzl-preferences-view.ui b/src/prefs/dzl-preferences-view.ui
new file mode 100644
index 0000000..25bb738
--- /dev/null
+++ b/src/prefs/dzl-preferences-view.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="DzlPreferencesView" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkSearchEntry" id="search_entry">
+                <property name="placeholder-text" translatable="yes">Search Preferences</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="preferences-search"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackSidebar" id="page_stack_sidebar">
+                <property name="stack">page_stack</property>
+                <property name="visible">true</property>
+                <property name="vexpand">true</property>
+                <property name="width-request">200</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scroller">
+            <property name="hscrollbar-policy">never</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">horizontal</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkStack" id="page_stack">
+                    <property name="margin">24</property>
+                    <property name="homogeneous">false</property>
+                    <property name="transition-duration">333</property>
+                    <property name="transition-type">crossfade</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="hexpand">true</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkStack" id="subpage_stack">
+                        <property name="margin-start">0</property>
+                        <property name="margin-end">24</property>
+                        <property name="margin-top">24</property>
+                        <property name="margin-bottom">24</property>
+                        <property name="halign">start</property>
+                        <property name="hexpand">false</property>
+                        <property name="homogeneous">false</property>
+                        <property name="transition-duration">333</property>
+                        <property name="transition-type">crossfade</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/tests/meson.build b/tests/meson.build
index 7fc8eed..923e554 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -232,3 +232,9 @@ test_pill_box = executable('test-pill-box', 'test-pill-box.c',
      link_args: test_link_args,
   dependencies: libdazzle_deps + [libdazzle_dep],
 )
+
+test_preferences = executable('test-preferences', 'test-preferences.c',
+        c_args: test_cflags,
+     link_args: test_link_args,
+  dependencies: libdazzle_deps + [libdazzle_dep],
+)
diff --git a/tests/test-preferences.c b/tests/test-preferences.c
new file mode 100644
index 0000000..e3b4291
--- /dev/null
+++ b/tests/test-preferences.c
@@ -0,0 +1,77 @@
+#include <dazzle.h>
+
+static const gchar *themes[] = {
+  "Applications",
+  "Cursor",
+  "Icons",
+  "System",
+  NULL
+};
+
+static void
+add_preferences (DzlPreferences *prefs)
+{
+  dzl_preferences_add_page (prefs, "appearance", "Appearance", 0);
+  dzl_preferences_add_page (prefs, "desktop", "Desktop", 1);
+  dzl_preferences_add_page (prefs, "extensions", "Extensions", 2);
+  dzl_preferences_add_page (prefs, "fonts", "Fonts", 3);
+  dzl_preferences_add_page (prefs, "keyboard", "Keyboard & Mouse", 4);
+  dzl_preferences_add_page (prefs, "power", "Power", 5);
+  dzl_preferences_add_page (prefs, "startup", "Startup Applications", 6);
+  dzl_preferences_add_page (prefs, "topbar", "Top Bar", 7);
+  dzl_preferences_add_page (prefs, "windows", "Windows", 7);
+  dzl_preferences_add_page (prefs, "workspaces", "Workspaces", 8);
+
+  dzl_preferences_add_group (prefs, "appearance", "basic", NULL, 0);
+  dzl_preferences_add_switch (prefs, "appearance", "basic", "com.example", "foo", NULL, NULL, "Global Dark 
Theme", "Applications need to be restarted for this change to take place", "dark theme", 0);
+  dzl_preferences_add_switch (prefs, "appearance", "basic", "com.example", "foo", NULL, NULL, "Animations", 
NULL, "animations", 1);
+
+  dzl_preferences_add_list_group (prefs, "appearance", "themes", "Themes", GTK_SELECTION_NONE, 10);
+
+  for (guint i = 0; themes[i]; i++)
+    dzl_preferences_add_custom (prefs, "appearance", "themes",
+                                g_object_new (GTK_TYPE_LABEL,
+                                              "label", themes[i],
+                                              "visible", TRUE,
+                                              "xalign", 0.0f,
+                                              NULL),
+                                themes[i], i);
+
+  dzl_preferences_add_group (prefs, "appearance", "install", NULL, 20);
+  dzl_preferences_add_custom (prefs, "appearance", "install",
+                              g_object_new (GTK_TYPE_BUTTON,
+                                            "label", "Install from Fileā€¦",
+                                            "halign", GTK_ALIGN_END,
+                                            "visible", TRUE,
+                                            NULL),
+                              NULL, 0);
+
+  dzl_preferences_set_page (prefs, "appearance", NULL);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  GtkWidget *window;
+  GtkWidget *view;
+
+  gtk_init (&argc, &argv);
+
+  window = g_object_new (GTK_TYPE_WINDOW,
+                         "title", "Preferences Test",
+                         "default-width", 800,
+                         "default-height", 600,
+                         NULL);
+
+  view = g_object_new (DZL_TYPE_PREFERENCES_VIEW,
+                       "visible", TRUE,
+                       NULL);
+  gtk_container_add (GTK_CONTAINER (window), view);
+
+  add_preferences (DZL_PREFERENCES (view));
+
+  g_signal_connect (window, "delete-event", gtk_main_quit, NULL);
+  gtk_window_present (GTK_WINDOW (window));
+  return gtk_main (), 0;
+}



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