[libdazzle] prefs: add preferences implementation
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libdazzle] prefs: add preferences implementation
- Date: Tue, 6 Jun 2017 01:42:24 +0000 (UTC)
commit 86f47f47e54bb5c15e9462de52bf27fe24dfaf7e
Author: Christian Hergert <chergert redhat com>
Date: Mon Jun 5 18:41:51 2017 -0700
prefs: add preferences implementation
This is based on Builder's dynamic preferences engine. What Builder
provides on top of this is the Addin/Interface control to load plugins
which create these elements automatically (as well as revoke them).
We will stick to keeping that part downstream as it is dependent on the
design of the application (even if we are opinionated about using libpeas).
src/dazzle.gresources.xml | 8 +
src/meson.build | 28 ++
src/prefs/dzl-preferences-bin-private.h | 33 ++
src/prefs/dzl-preferences-bin.c | 446 ++++++++++++++++++++++
src/prefs/dzl-preferences-bin.h | 55 +++
src/prefs/dzl-preferences-entry.c | 231 +++++++++++
src/prefs/dzl-preferences-entry.h | 42 ++
src/prefs/dzl-preferences-entry.ui | 26 ++
src/prefs/dzl-preferences-file-chooser-button.c | 245 ++++++++++++
src/prefs/dzl-preferences-file-chooser-button.h | 32 ++
src/prefs/dzl-preferences-file-chooser-button.ui | 49 +++
src/prefs/dzl-preferences-flow-box.c | 36 ++
src/prefs/dzl-preferences-flow-box.h | 34 ++
src/prefs/dzl-preferences-font-button.c | 327 ++++++++++++++++
src/prefs/dzl-preferences-font-button.h | 32 ++
src/prefs/dzl-preferences-font-button.ui | 68 ++++
src/prefs/dzl-preferences-group-private.h | 52 +++
src/prefs/dzl-preferences-group.c | 443 +++++++++++++++++++++
src/prefs/dzl-preferences-group.h | 37 ++
src/prefs/dzl-preferences-group.ui | 56 +++
src/prefs/dzl-preferences-language-row.c | 167 ++++++++
src/prefs/dzl-preferences-language-row.h | 32 ++
src/prefs/dzl-preferences-language-row.ui | 26 ++
src/prefs/dzl-preferences-page-private.h | 43 ++
src/prefs/dzl-preferences-page.c | 185 +++++++++
src/prefs/dzl-preferences-page.h | 39 ++
src/prefs/dzl-preferences-page.ui | 11 +
src/prefs/dzl-preferences-spin-button.c | 417 ++++++++++++++++++++
src/prefs/dzl-preferences-spin-button.h | 36 ++
src/prefs/dzl-preferences-spin-button.ui | 56 +++
src/prefs/dzl-preferences-switch.c | 439 +++++++++++++++++++++
src/prefs/dzl-preferences-switch.h | 32 ++
src/prefs/dzl-preferences-switch.ui | 59 +++
src/prefs/dzl-preferences.c | 260 +++++++++++++
src/prefs/dzl-preferences.h | 200 ++++++++++
35 files changed, 4282 insertions(+), 0 deletions(-)
---
diff --git a/src/dazzle.gresources.xml b/src/dazzle.gresources.xml
index 40af6cf..6a0ce0b 100644
--- a/src/dazzle.gresources.xml
+++ b/src/dazzle.gresources.xml
@@ -4,6 +4,14 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="dzl-simple-popover.ui">widgets/dzl-simple-popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="dzl-empty-state.ui">widgets/dzl-empty-state.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="dzl-pill-box.ui">widgets/dzl-pill-box.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="dzl-preferences-file-chooser-button.ui">prefs/dzl-preferences-file-chooser-button.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="dzl-preferences-entry.ui">prefs/dzl-preferences-entry.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="dzl-preferences-font-button.ui">prefs/dzl-preferences-font-button.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="dzl-preferences-group.ui">prefs/dzl-preferences-group.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="dzl-preferences-language-row.ui">prefs/dzl-preferences-language-row.ui</file>
+ <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-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/meson.build b/src/meson.build
index dbc5543..878d0a7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -73,6 +73,18 @@ libdazzle_public_headers = [
'panel/dzl-tab-strip.h',
'panel/dzl-tab.h',
+ 'prefs/dzl-preferences-bin.h',
+ 'prefs/dzl-preferences-entry.h',
+ 'prefs/dzl-preferences-file-chooser-button.h',
+ 'prefs/dzl-preferences-flow-box.h',
+ 'prefs/dzl-preferences-font-button.h',
+ 'prefs/dzl-preferences-group.h',
+ 'prefs/dzl-preferences-language-row.h',
+ 'prefs/dzl-preferences-page.h',
+ 'prefs/dzl-preferences-spin-button.h',
+ 'prefs/dzl-preferences-switch.h',
+ 'prefs/dzl-preferences.h',
+
'search/dzl-fuzzy-index-builder.h',
'search/dzl-fuzzy-index-cursor.h',
'search/dzl-fuzzy-index-match.h',
@@ -197,6 +209,18 @@ libdazzle_public_sources = [
'panel/dzl-tab-strip.c',
'panel/dzl-tab.c',
+ 'prefs/dzl-preferences-bin.c',
+ 'prefs/dzl-preferences-entry.c',
+ 'prefs/dzl-preferences-file-chooser-button.c',
+ 'prefs/dzl-preferences-flow-box.c',
+ 'prefs/dzl-preferences-font-button.c',
+ 'prefs/dzl-preferences-group.c',
+ 'prefs/dzl-preferences-language-row.c',
+ 'prefs/dzl-preferences-page.c',
+ 'prefs/dzl-preferences-spin-button.c',
+ 'prefs/dzl-preferences-switch.c',
+ 'prefs/dzl-preferences.c',
+
'search/dzl-fuzzy-index-builder.c',
'search/dzl-fuzzy-index-cursor.c',
'search/dzl-fuzzy-index-match.c',
@@ -294,6 +318,10 @@ libdazzle_sources = [
'panel/dzl-dock-paned-private.h',
'panel/dzl-tab-private.h',
+ 'prefs/dzl-preferences-bin-private.h',
+ 'prefs/dzl-preferences-group-private.h',
+ 'prefs/dzl-preferences-page-private.h',
+
'search/dzl-fuzzy-index-private.h',
'shortcuts/dzl-shortcut-closure-chain.c',
diff --git a/src/prefs/dzl-preferences-bin-private.h b/src/prefs/dzl-preferences-bin-private.h
new file mode 100644
index 0000000..fbb835c
--- /dev/null
+++ b/src/prefs/dzl-preferences-bin-private.h
@@ -0,0 +1,33 @@
+/* dzl-preferences-bin-private.h
+ *
+ * Copyright (C) 2015-2017 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_BIN_PRIVATE_H
+#define DZL_PREFERENCES_BIN_PRIVATE_H
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+void _dzl_preferences_bin_set_map (DzlPreferencesBin *self,
+ GHashTable *map);
+gboolean _dzl_preferences_bin_matches (DzlPreferencesBin *self,
+ DzlPatternSpec *spec);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_BIN_PRIVATE_H */
diff --git a/src/prefs/dzl-preferences-bin.c b/src/prefs/dzl-preferences-bin.c
new file mode 100644
index 0000000..fad3a15
--- /dev/null
+++ b/src/prefs/dzl-preferences-bin.c
@@ -0,0 +1,446 @@
+/* dzl-preferences-bin.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include <string.h>
+
+#include "prefs/dzl-preferences-bin.h"
+
+typedef struct
+{
+ GtkBin parent_instance;
+
+ gint priority;
+
+ gchar *keywords;
+ gchar *schema_id;
+ gchar *path;
+ GSettings *settings;
+ GHashTable *map;
+} DzlPreferencesBinPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (DzlPreferencesBin, dzl_preferences_bin, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_KEYWORDS,
+ PROP_PRIORITY,
+ PROP_SCHEMA_ID,
+ PROP_PATH,
+ LAST_PROP
+};
+
+enum {
+ PREFERENCE_ACTIVATED,
+ N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static GParamSpec *properties [LAST_PROP];
+static GHashTable *settings_cache;
+
+static gchar *
+dzl_preferences_bin_expand (DzlPreferencesBin *self,
+ const gchar *spec)
+{
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+ GHashTableIter iter;
+ const gchar *key;
+ const gchar *value;
+ gchar *expanded;
+
+ g_assert (DZL_IS_PREFERENCES_BIN (self));
+
+ if (spec == NULL)
+ return NULL;
+
+ expanded = g_strdup (spec);
+
+ if (priv->map == NULL)
+ goto validate;
+
+ g_hash_table_iter_init (&iter, priv->map);
+
+ while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+ {
+ gchar *tmp = expanded;
+ gchar **split;
+
+ split = g_strsplit (tmp, key, 0);
+ expanded = g_strjoinv (value, split);
+
+ g_strfreev (split);
+ g_free (tmp);
+ }
+
+validate:
+ if (strchr (expanded, '{') != NULL)
+ {
+ g_free (expanded);
+ return NULL;
+ }
+
+ return expanded;
+}
+
+static void
+dzl_preferences_bin_evict_settings (gpointer data,
+ GObject *where_object_was)
+{
+ g_assert (data != NULL);
+ g_assert (where_object_was != NULL);
+
+ g_hash_table_remove (settings_cache, (gchar *)data);
+}
+
+static void
+dzl_preferences_bin_cache_settings (const gchar *hash_key,
+ GSettings *settings)
+{
+ gchar *key;
+
+ g_assert (hash_key != NULL);
+ g_assert (G_IS_SETTINGS (settings));
+
+ key = g_strdup (hash_key);
+ g_hash_table_insert (settings_cache, key, settings);
+ g_object_weak_ref (G_OBJECT (settings), dzl_preferences_bin_evict_settings, key);
+}
+
+static GSettings *
+dzl_preferences_bin_get_settings (DzlPreferencesBin *self)
+{
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_BIN (self), NULL);
+
+ if (priv->settings == NULL)
+ {
+ g_autofree gchar *resolved_schema_id = NULL;
+ g_autofree gchar *resolved_path = NULL;
+ g_autofree gchar *hash_key = NULL;
+
+ resolved_schema_id = dzl_preferences_bin_expand (self, priv->schema_id);
+ resolved_path = dzl_preferences_bin_expand (self, priv->path);
+
+ if (resolved_schema_id == NULL)
+ return NULL;
+
+ if ((priv->path != NULL) && (resolved_path == NULL))
+ return NULL;
+
+ hash_key = g_strdup_printf ("%s|%s",
+ resolved_schema_id,
+ resolved_path ?: "");
+
+ if (!g_hash_table_contains (settings_cache, hash_key))
+ {
+ GSettingsSchemaSource *source;
+ GSettingsSchema *schema;
+
+ source = g_settings_schema_source_get_default ();
+ schema = g_settings_schema_source_lookup (source, resolved_schema_id, TRUE);
+
+ if (schema != NULL)
+ {
+ if (resolved_path)
+ priv->settings = g_settings_new_with_path (resolved_schema_id, resolved_path);
+ else
+ priv->settings = g_settings_new (resolved_schema_id);
+ dzl_preferences_bin_cache_settings (hash_key, priv->settings);
+ }
+
+ g_clear_pointer (&schema, g_settings_schema_unref);
+ }
+ else
+ {
+ priv->settings = g_object_ref (g_hash_table_lookup (settings_cache, hash_key));
+ }
+
+ g_clear_pointer (&hash_key, g_free);
+ g_clear_pointer (&resolved_schema_id, g_free);
+ g_clear_pointer (&resolved_path, g_free);
+ }
+
+ return (priv->settings != NULL) ? g_object_ref (priv->settings) : NULL;
+}
+
+
+static void
+dzl_preferences_bin_connect (DzlPreferencesBin *self,
+ GSettings *settings)
+{
+ g_assert (DZL_IS_PREFERENCES_BIN (self));
+ g_assert (G_IS_SETTINGS (settings));
+
+ if (DZL_PREFERENCES_BIN_GET_CLASS (self)->connect != NULL)
+ DZL_PREFERENCES_BIN_GET_CLASS (self)->connect (self, settings);
+}
+
+static void
+dzl_preferences_bin_disconnect (DzlPreferencesBin *self,
+ GSettings *settings)
+{
+ g_assert (DZL_IS_PREFERENCES_BIN (self));
+ g_assert (G_IS_SETTINGS (settings));
+
+ if (DZL_PREFERENCES_BIN_GET_CLASS (self)->disconnect != NULL)
+ DZL_PREFERENCES_BIN_GET_CLASS (self)->disconnect (self, settings);
+}
+
+static void
+dzl_preferences_bin_reload (DzlPreferencesBin *self)
+{
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+ GSettings *settings;
+
+ g_assert (DZL_IS_PREFERENCES_BIN (self));
+
+ if (priv->settings != NULL)
+ {
+ dzl_preferences_bin_disconnect (self, priv->settings);
+ g_clear_object (&priv->settings);
+ }
+
+ settings = dzl_preferences_bin_get_settings (self);
+
+ if (settings != NULL)
+ {
+ dzl_preferences_bin_connect (self, settings);
+ g_object_unref (settings);
+ }
+}
+
+static void
+dzl_preferences_bin_constructed (GObject *object)
+{
+ DzlPreferencesBin *self = (DzlPreferencesBin *)object;
+
+ G_OBJECT_CLASS (dzl_preferences_bin_parent_class)->constructed (object);
+
+ dzl_preferences_bin_reload (self);
+}
+
+static void
+dzl_preferences_bin_destroy (GtkWidget *widget)
+{
+ DzlPreferencesBin *self = (DzlPreferencesBin *)widget;
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ g_assert (DZL_IS_PREFERENCES_BIN (self));
+
+ if (priv->settings != NULL)
+ {
+ dzl_preferences_bin_disconnect (self, priv->settings);
+ g_clear_object (&priv->settings);
+ }
+
+ GTK_WIDGET_CLASS (dzl_preferences_bin_parent_class)->destroy (widget);
+}
+
+static void
+dzl_preferences_bin_activated (GtkWidget *widget)
+{
+ GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child)
+ gtk_widget_activate (child);
+}
+
+static void
+dzl_preferences_bin_finalize (GObject *object)
+{
+ DzlPreferencesBin *self = (DzlPreferencesBin *)object;
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ g_clear_pointer (&priv->schema_id, g_free);
+ g_clear_pointer (&priv->path, g_free);
+ g_clear_pointer (&priv->keywords, g_free);
+ g_clear_pointer (&priv->map, g_hash_table_unref);
+ g_clear_object (&priv->settings);
+
+ G_OBJECT_CLASS (dzl_preferences_bin_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_bin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesBin *self = DZL_PREFERENCES_BIN (object);
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_SCHEMA_ID:
+ g_value_set_string (value, priv->schema_id);
+ break;
+
+ case PROP_PATH:
+ g_value_set_string (value, priv->path);
+ break;
+
+ case PROP_KEYWORDS:
+ g_value_set_string (value, priv->keywords);
+ break;
+
+ case PROP_PRIORITY:
+ g_value_set_int (value, priv->priority);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_bin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesBin *self = DZL_PREFERENCES_BIN (object);
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_SCHEMA_ID:
+ priv->schema_id = g_value_dup_string (value);
+ break;
+
+ case PROP_PATH:
+ priv->path = g_value_dup_string (value);
+ break;
+
+ case PROP_KEYWORDS:
+ priv->keywords = g_value_dup_string (value);
+ break;
+
+ case PROP_PRIORITY:
+ priv->priority = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_bin_class_init (DzlPreferencesBinClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = dzl_preferences_bin_constructed;
+ object_class->finalize = dzl_preferences_bin_finalize;
+ object_class->get_property = dzl_preferences_bin_get_property;
+ object_class->set_property = dzl_preferences_bin_set_property;
+
+ widget_class->destroy = dzl_preferences_bin_destroy;
+
+ properties [PROP_KEYWORDS] =
+ g_param_spec_string ("keywords",
+ "Keywords",
+ "Search keywords for the widget.",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PATH] =
+ g_param_spec_string ("path",
+ "Path",
+ "Path",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "The widget priority within the group.",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SCHEMA_ID] =
+ g_param_spec_string ("schema-id",
+ "Schema Id",
+ "Schema Id",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [PREFERENCE_ACTIVATED] = widget_class->activate_signal =
+ g_signal_new_class_handler ("preference-activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (dzl_preferences_bin_activated),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_css_name (widget_class, "preferencesbin");
+
+ settings_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static void
+dzl_preferences_bin_init (DzlPreferencesBin *self)
+{
+}
+
+void
+_dzl_preferences_bin_set_map (DzlPreferencesBin *self,
+ GHashTable *map)
+{
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ g_return_if_fail (DZL_IS_PREFERENCES_BIN (self));
+
+ if (map != priv->map)
+ {
+ g_clear_pointer (&priv->map, g_hash_table_unref);
+ priv->map = map ? g_hash_table_ref (map) : NULL;
+ dzl_preferences_bin_reload (self);
+ }
+}
+
+gboolean
+_dzl_preferences_bin_matches (DzlPreferencesBin *self,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesBinPrivate *priv = dzl_preferences_bin_get_instance_private (self);
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_BIN (self), FALSE);
+
+ if (spec == NULL)
+ return TRUE;
+
+ if (priv->keywords && dzl_pattern_spec_match (spec, priv->keywords))
+ return TRUE;
+
+ if (priv->schema_id && dzl_pattern_spec_match (spec, priv->schema_id))
+ return TRUE;
+
+ if (priv->path && dzl_pattern_spec_match (spec, priv->path))
+ return TRUE;
+
+ if (DZL_PREFERENCES_BIN_GET_CLASS (self)->matches)
+ return DZL_PREFERENCES_BIN_GET_CLASS (self)->matches (self, spec);
+
+ return FALSE;
+}
diff --git a/src/prefs/dzl-preferences-bin.h b/src/prefs/dzl-preferences-bin.h
new file mode 100644
index 0000000..cb7d9d9
--- /dev/null
+++ b/src/prefs/dzl-preferences-bin.h
@@ -0,0 +1,55 @@
+/* dzl-preferences-bin.h
+ *
+ * Copyright (C) 2015-2017 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_BIN_H
+#define DZL_PREFERENCES_BIN_H
+
+#include <gtk/gtk.h>
+
+#include "search/dzl-pattern-spec.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_BIN (dzl_preferences_bin_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (DzlPreferencesBin, dzl_preferences_bin, DZL, PREFERENCES_BIN, GtkBin)
+
+struct _DzlPreferencesBinClass
+{
+ GtkBinClass parent_class;
+
+ void (*connect) (DzlPreferencesBin *self,
+ GSettings *settings);
+ void (*disconnect) (DzlPreferencesBin *self,
+ GSettings *settings);
+ gboolean (*matches) (DzlPreferencesBin *self,
+ DzlPatternSpec *spec);
+
+ gpointer _reserved1;
+ gpointer _reserved2;
+ gpointer _reserved3;
+ gpointer _reserved4;
+ gpointer _reserved5;
+ gpointer _reserved6;
+ gpointer _reserved7;
+ gpointer _reserved8;
+};
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_BIN_H */
diff --git a/src/prefs/dzl-preferences-entry.c b/src/prefs/dzl-preferences-entry.c
new file mode 100644
index 0000000..94b593b
--- /dev/null
+++ b/src/prefs/dzl-preferences-entry.c
@@ -0,0 +1,231 @@
+/* dzl-preferences-entry.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "prefs/dzl-preferences-entry.h"
+
+typedef struct
+{
+ GtkEntry *entry;
+ GtkLabel *title;
+} DzlPreferencesEntryPrivate;
+
+enum {
+ PROP_0,
+ PROP_TITLE,
+ PROP_TEXT,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATE,
+ CHANGED,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (DzlPreferencesEntry, dzl_preferences_entry, DZL_TYPE_PREFERENCES_BIN)
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+dzl_preferences_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesEntry *self = DZL_PREFERENCES_ENTRY (object);
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_TEXT:
+ g_value_set_string (value, gtk_entry_get_text (priv->entry));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_text (priv->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesEntry *self = DZL_PREFERENCES_ENTRY (object);
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_TEXT:
+ gtk_entry_set_text (priv->entry, g_value_get_string (value));
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (priv->title, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_entry_activate (DzlPreferencesEntry *self)
+{
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ g_assert (DZL_IS_PREFERENCES_ENTRY (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (priv->entry));
+}
+
+static void
+dzl_preferences_entry_changed (DzlPreferencesEntry *self,
+ GtkEntry *entry)
+{
+ const gchar *text;
+
+ g_assert (DZL_IS_PREFERENCES_ENTRY (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ text = gtk_entry_get_text (entry);
+ g_signal_emit (self, signals [CHANGED], 0, text);
+}
+
+static gboolean
+dzl_preferences_entry_matches (DzlPreferencesBin *bin,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesEntry *self = (DzlPreferencesEntry *)bin;
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+ const gchar *tmp;
+
+ g_assert (DZL_IS_PREFERENCES_ENTRY (self));
+ g_assert (spec != NULL);
+
+ tmp = gtk_label_get_label (priv->title);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ tmp = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+dzl_preferences_entry_class_init (DzlPreferencesEntryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ DzlPreferencesBinClass *bin_class = DZL_PREFERENCES_BIN_CLASS (klass);
+
+ object_class->get_property = dzl_preferences_entry_get_property;
+ object_class->set_property = dzl_preferences_entry_set_property;
+
+ bin_class->matches = dzl_preferences_entry_matches;
+
+ signals [ACTIVATE] =
+ g_signal_new_class_handler ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (dzl_preferences_entry_activate),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ signals [CHANGED] =
+ g_signal_new_class_handler ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ NULL, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ widget_class->activate_signal = signals [ACTIVATE];
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-entry.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, DzlPreferencesEntry, entry);
+ gtk_widget_class_bind_template_child_private (widget_class, DzlPreferencesEntry, title);
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+dzl_preferences_entry_init (DzlPreferencesEntry *self)
+{
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (priv->entry,
+ "changed",
+ G_CALLBACK (dzl_preferences_entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+/**
+ * dzl_preferences_entry_get_title_widget:
+ *
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+dzl_preferences_entry_get_title_widget (DzlPreferencesEntry *self)
+{
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_ENTRY (self), NULL);
+
+ return GTK_WIDGET (priv->title);
+}
+
+/**
+ * dzl_preferences_entry_get_entry_widget:
+ *
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+dzl_preferences_entry_get_entry_widget (DzlPreferencesEntry *self)
+{
+ DzlPreferencesEntryPrivate *priv = dzl_preferences_entry_get_instance_private (self);
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_ENTRY (self), NULL);
+
+ return GTK_WIDGET (priv->entry);
+}
diff --git a/src/prefs/dzl-preferences-entry.h b/src/prefs/dzl-preferences-entry.h
new file mode 100644
index 0000000..2203fd1
--- /dev/null
+++ b/src/prefs/dzl-preferences-entry.h
@@ -0,0 +1,42 @@
+/* dzl-preferences-entry.h
+ *
+ * Copyright (C) 2015-2017 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_ENTRY_H
+#define DZL_PREFERENCES_ENTRY_H
+
+#include <gtk/gtk.h>
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_ENTRY (dzl_preferences_entry_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (DzlPreferencesEntry, dzl_preferences_entry, DZL, PREFERENCES_ENTRY,
DzlPreferencesBin)
+
+struct _DzlPreferencesEntryClass
+{
+ DzlPreferencesBinClass parent_class;
+};
+
+GtkWidget *dzl_preferences_entry_get_entry_widget (DzlPreferencesEntry *self);
+GtkWidget *dzl_preferences_entry_get_title_widget (DzlPreferencesEntry *self);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_ENTRY_H */
diff --git a/src/prefs/dzl-preferences-entry.ui b/src/prefs/dzl-preferences-entry.ui
new file mode 100644
index 0000000..55f10ba
--- /dev/null
+++ b/src/prefs/dzl-preferences-entry.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesEntry" parent="DzlPreferencesBin">
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="has-frame">false</property>
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-file-chooser-button.c b/src/prefs/dzl-preferences-file-chooser-button.c
new file mode 100644
index 0000000..86b25f4
--- /dev/null
+++ b/src/prefs/dzl-preferences-file-chooser-button.c
@@ -0,0 +1,245 @@
+/* dzl-preferences-file-chooser-button.c
+ *
+ * Copyright (C) 2016 Akshaya Kakkilaya <akshaya kakkilaya gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "util/dzl-util-private.h"
+#include "prefs/dzl-preferences-file-chooser-button.h"
+
+struct _DzlPreferencesFileChooserButton
+{
+ DzlPreferencesBin parent_instance;
+
+ gchar *key;
+ GSettings *settings;
+
+ GtkFileChooserButton *widget;
+ GtkLabel *title;
+ GtkLabel *subtitle;
+};
+
+G_DEFINE_TYPE (DzlPreferencesFileChooserButton, dzl_preferences_file_chooser_button,
DZL_TYPE_PREFERENCES_BIN)
+
+enum {
+ PROP_0,
+ PROP_ACTION,
+ PROP_KEY,
+ PROP_SUBTITLE,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+dzl_preferences_file_chooser_button_save_file (DzlPreferencesFileChooserButton *self,
+ GtkFileChooserButton *widget)
+{
+ g_autofree gchar *path = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_FILE_CHOOSER_BUTTON (self));
+
+ path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (self->widget));
+
+ g_settings_set_string (self->settings, self->key, path);
+
+}
+
+static void
+dzl_preferences_file_chooser_button_connect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesFileChooserButton *self = (DzlPreferencesFileChooserButton *)bin;
+ g_autofree gchar *file = NULL;
+ g_autofree gchar *path = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_FILE_CHOOSER_BUTTON (self));
+ g_assert (G_IS_SETTINGS (settings));
+
+ self->settings = g_object_ref (settings);
+
+ file = g_settings_get_string (settings, self->key);
+
+ if (!dzl_str_empty0 (file))
+ {
+ if (!g_path_is_absolute (file))
+ path = g_build_filename (g_get_home_dir (), file, NULL);
+ else
+ path = g_steal_pointer (&file);
+
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (self->widget), path);
+ }
+
+ g_signal_connect_object (self->widget,
+ "file-set",
+ G_CALLBACK (dzl_preferences_file_chooser_button_save_file),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static gboolean
+dzl_preferences_file_chooser_button_matches (DzlPreferencesBin *bin,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesFileChooserButton *self = (DzlPreferencesFileChooserButton *)bin;
+ const gchar *tmp;
+
+ g_assert (DZL_IS_PREFERENCES_FILE_CHOOSER_BUTTON (self));
+ g_assert (spec != NULL);
+
+ tmp = gtk_label_get_label (self->title);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ tmp = gtk_label_get_label (self->subtitle);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ if (self->key && dzl_pattern_spec_match (spec, self->key))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+dzl_preferences_file_chooser_button_finalize (GObject *object)
+{
+ DzlPreferencesFileChooserButton *self = (DzlPreferencesFileChooserButton *)object;
+
+ g_clear_pointer (&self->key, g_free);
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (dzl_preferences_file_chooser_button_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_file_chooser_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesFileChooserButton *self = DZL_PREFERENCES_FILE_CHOOSER_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTION:
+ g_value_set_enum (value, gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self->widget)));
+ break;
+
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ case PROP_SUBTITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_file_chooser_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesFileChooserButton *self = DZL_PREFERENCES_FILE_CHOOSER_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTION:
+ gtk_file_chooser_set_action (GTK_FILE_CHOOSER (self->widget), g_value_get_enum (value));
+ break;
+
+ case PROP_KEY:
+ self->key = g_value_dup_string (value);
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ case PROP_SUBTITLE:
+ gtk_label_set_label (self->subtitle, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_file_chooser_button_class_init (DzlPreferencesFileChooserButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ DzlPreferencesBinClass *bin_class = DZL_PREFERENCES_BIN_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_file_chooser_button_finalize;
+ object_class->get_property = dzl_preferences_file_chooser_button_get_property;
+ object_class->set_property = dzl_preferences_file_chooser_button_set_property;
+
+ bin_class->connect = dzl_preferences_file_chooser_button_connect;
+ bin_class->matches = dzl_preferences_file_chooser_button_matches;
+
+ properties [PROP_ACTION] =
+ g_param_spec_enum ("action",
+ "Action",
+ "Action",
+ GTK_TYPE_FILE_CHOOSER_ACTION,
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "Key",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SUBTITLE] =
+ g_param_spec_string ("subtitle",
+ "Subtitle",
+ "Subtitle",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-file-chooser-button.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFileChooserButton, widget);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFileChooserButton, title);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFileChooserButton, subtitle);
+}
+
+static void
+dzl_preferences_file_chooser_button_init (DzlPreferencesFileChooserButton *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/prefs/dzl-preferences-file-chooser-button.h b/src/prefs/dzl-preferences-file-chooser-button.h
new file mode 100644
index 0000000..d87c3b4
--- /dev/null
+++ b/src/prefs/dzl-preferences-file-chooser-button.h
@@ -0,0 +1,32 @@
+/* dzl-preferences-file-chooser-button.h
+ *
+ * Copyright (C) 2016 Akshaya Kakkilaya <akshaya kakkilaya gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DZL_PREFERENCES_FILE_CHOOSER_BUTTON_H
+#define DZL_PREFERENCES_FILE_CHOOSER_BUTTON_H
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_FILE_CHOOSER_BUTTON (dzl_preferences_file_chooser_button_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesFileChooserButton, dzl_preferences_file_chooser_button, DZL,
PREFERENCES_FILE_CHOOSER_BUTTON, DzlPreferencesBin)
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_FILE_CHOOSER_BUTTON_H */
diff --git a/src/prefs/dzl-preferences-file-chooser-button.ui
b/src/prefs/dzl-preferences-file-chooser-button.ui
new file mode 100644
index 0000000..0ac6142
--- /dev/null
+++ b/src/prefs/dzl-preferences-file-chooser-button.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesFileChooserButton" parent="DzlPreferencesBin">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="expand">true</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="vexpand">true</property>
+ <property name="halign">start</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle">
+ <property name="vexpand">true</property>
+ <property name="halign">start</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.83333"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="widget">
+ <property name="expand">true</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-flow-box.c b/src/prefs/dzl-preferences-flow-box.c
new file mode 100644
index 0000000..31904c5
--- /dev/null
+++ b/src/prefs/dzl-preferences-flow-box.c
@@ -0,0 +1,36 @@
+/* dzl-preferences-flow-box.c
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#include "prefs/dzl-preferences-flow-box.h"
+
+struct _DzlPreferencesFlowBox
+{
+ DzlColumnLayout parent;
+};
+
+G_DEFINE_TYPE (DzlPreferencesFlowBox, dzl_preferences_flow_box, DZL_TYPE_COLUMN_LAYOUT)
+
+static void
+dzl_preferences_flow_box_class_init (DzlPreferencesFlowBoxClass *klass)
+{
+}
+
+static void
+dzl_preferences_flow_box_init (DzlPreferencesFlowBox *self)
+{
+}
diff --git a/src/prefs/dzl-preferences-flow-box.h b/src/prefs/dzl-preferences-flow-box.h
new file mode 100644
index 0000000..ca1417a
--- /dev/null
+++ b/src/prefs/dzl-preferences-flow-box.h
@@ -0,0 +1,34 @@
+/* dzl-preferences-flow-box.h
+ *
+ * Copyright (C) 2016 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_FLOW_BOX_H
+#define DZL_PREFERENCES_FLOW_BOX_H
+
+#include <dazzle.h>
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_FLOW_BOX (dzl_preferences_flow_box_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesFlowBox, dzl_preferences_flow_box, DZL, PREFERENCES_FLOW_BOX,
DzlColumnLayout)
+
+GtkWidget *dzl_preferences_flow_box_new (void);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_FLOW_BOX_H */
diff --git a/src/prefs/dzl-preferences-font-button.c b/src/prefs/dzl-preferences-font-button.c
new file mode 100644
index 0000000..3cd3dd6
--- /dev/null
+++ b/src/prefs/dzl-preferences-font-button.c
@@ -0,0 +1,327 @@
+/* dzl-preferences-font-button.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include "prefs/dzl-preferences-font-button.h"
+
+struct _DzlPreferencesFontButton
+{
+ GtkBin parent_instance;
+
+ gulong handler;
+
+ GSettings *settings;
+ gchar *key;
+
+ GtkLabel *title;
+ GtkLabel *font_family;
+ GtkLabel *font_size;
+ GtkPopover *popover;
+ GtkButton *confirm;
+ GtkFontChooserWidget *chooser;
+};
+
+G_DEFINE_TYPE (DzlPreferencesFontButton, dzl_preferences_font_button, DZL_TYPE_PREFERENCES_BIN)
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+dzl_preferences_font_button_show (DzlPreferencesFontButton *self)
+{
+ gchar *font = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+
+ font = g_settings_get_string (self->settings, self->key);
+ g_object_set (self->chooser, "font", font, NULL);
+ g_free (font);
+
+ gtk_popover_popup (self->popover);
+}
+
+static void
+dzl_preferences_font_button_activate (DzlPreferencesFontButton *self)
+{
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (self->popover)))
+ dzl_preferences_font_button_show (self);
+}
+
+static void
+dzl_preferences_font_button_changed (DzlPreferencesFontButton *self,
+ const gchar *key,
+ GSettings *settings)
+{
+ PangoFontDescription *font_desc;
+ gchar *name;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+ g_assert (key != NULL);
+ g_assert (G_IS_SETTINGS (settings));
+
+ name = g_settings_get_string (settings, key);
+ font_desc = pango_font_description_from_string (name);
+
+ if (font_desc != NULL)
+ {
+ gchar *font_size;
+
+ gtk_label_set_label (self->font_family, pango_font_description_get_family (font_desc));
+ font_size = g_strdup_printf ("%d", pango_font_description_get_size (font_desc) / PANGO_SCALE);
+ gtk_label_set_label (self->font_size, font_size);
+ g_free (font_size);
+ }
+
+ g_clear_pointer (&font_desc, pango_font_description_free);
+ g_free (name);
+}
+
+static void
+dzl_preferences_font_button_connect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesFontButton *self = (DzlPreferencesFontButton *)bin;
+ g_autofree gchar *signal_detail = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+
+ signal_detail = g_strdup_printf ("changed::%s", self->key);
+
+ self->settings = g_object_ref (settings);
+
+ self->handler =
+ g_signal_connect_object (settings,
+ signal_detail,
+ G_CALLBACK (dzl_preferences_font_button_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ dzl_preferences_font_button_changed (self, self->key, settings);
+}
+
+static void
+dzl_preferences_font_button_disconnect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesFontButton *self = (DzlPreferencesFontButton *)bin;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+
+ g_signal_handler_disconnect (settings, self->handler);
+ self->handler = 0;
+}
+
+static gboolean
+dzl_preferences_font_button_matches (DzlPreferencesBin *bin,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesFontButton *self = (DzlPreferencesFontButton *)bin;
+ const gchar *tmp;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+ g_assert (spec != NULL);
+
+ tmp = gtk_label_get_label (self->title);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ tmp = gtk_label_get_label (self->font_family);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+dzl_preferences_font_button_finalize (GObject *object)
+{
+ DzlPreferencesFontButton *self = (DzlPreferencesFontButton *)object;
+
+ g_clear_object (&self->settings);
+ g_clear_pointer (&self->key, g_free);
+
+ G_OBJECT_CLASS (dzl_preferences_font_button_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_font_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesFontButton *self = DZL_PREFERENCES_FONT_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_font_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesFontButton *self = DZL_PREFERENCES_FONT_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ self->key = g_value_dup_string (value);
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_font_button_class_init (DzlPreferencesFontButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ DzlPreferencesBinClass *bin_class = DZL_PREFERENCES_BIN_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_font_button_finalize;
+ object_class->get_property = dzl_preferences_font_button_get_property;
+ object_class->set_property = dzl_preferences_font_button_set_property;
+
+ bin_class->connect = dzl_preferences_font_button_connect;
+ bin_class->disconnect = dzl_preferences_font_button_disconnect;
+ bin_class->matches = dzl_preferences_font_button_matches;
+
+ signals [ACTIVATE] =
+ g_signal_new_class_handler ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (dzl_preferences_font_button_activate),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ widget_class->activate_signal = signals [ACTIVATE];
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "Key",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-font-button.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, chooser);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, confirm);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, font_family);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, font_size);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, popover);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesFontButton, title);
+}
+
+static gboolean
+transform_to (GBinding *binding,
+ const GValue *value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_value_set_boolean (to_value, !!g_value_get_boxed (value));
+ return TRUE;
+}
+
+static void
+dzl_preferences_font_button_clicked (DzlPreferencesFontButton *self,
+ GtkButton *button)
+{
+ g_autofree gchar *font = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ g_object_get (self->chooser, "font", &font, NULL);
+ g_settings_set_string (self->settings, self->key, font);
+ gtk_popover_popdown (self->popover);
+}
+
+static void
+dzl_preferences_font_button_font_activated (DzlPreferencesFontButton *self,
+ const gchar *font,
+ GtkFontChooser *chooser)
+{
+ g_assert (DZL_IS_PREFERENCES_FONT_BUTTON (self));
+ g_assert (GTK_IS_FONT_CHOOSER (chooser));
+
+ g_settings_set_string (self->settings, self->key, font);
+ gtk_popover_popdown (self->popover);
+}
+
+static void
+dzl_preferences_font_button_init (DzlPreferencesFontButton *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_object_bind_property_full (self->chooser, "font-desc",
+ self->confirm, "sensitive",
+ G_BINDING_SYNC_CREATE,
+ transform_to,
+ NULL, NULL, NULL);
+
+ g_signal_connect_object (self->chooser,
+ "font-activated",
+ G_CALLBACK (dzl_preferences_font_button_font_activated),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->confirm,
+ "clicked",
+ G_CALLBACK (dzl_preferences_font_button_clicked),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/src/prefs/dzl-preferences-font-button.h b/src/prefs/dzl-preferences-font-button.h
new file mode 100644
index 0000000..45c02bb
--- /dev/null
+++ b/src/prefs/dzl-preferences-font-button.h
@@ -0,0 +1,32 @@
+/* dzl-preferences-font-button.h
+ *
+ * Copyright (C) 2015-2017 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_FONT_BUTTON_H
+#define DZL_PREFERENCES_FONT_BUTTON_H
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_FONT_BUTTON (dzl_preferences_font_button_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesFontButton, dzl_preferences_font_button, DZL, PREFERENCES_FONT_BUTTON,
DzlPreferencesBin)
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_FONT_BUTTON_H */
diff --git a/src/prefs/dzl-preferences-font-button.ui b/src/prefs/dzl-preferences-font-button.ui
new file mode 100644
index 0000000..8ede7b8
--- /dev/null
+++ b/src/prefs/dzl-preferences-font-button.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesFontButton" parent="DzlPreferencesBin">
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">18</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="font_family">
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="font_size">
+ <property name="visible">true</property>
+ <property name="width-chars">2</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkPopover" id="popover">
+ <property name="relative-to">box</property>
+ <property name="position">bottom</property>
+ <property name="width-request">600</property>
+ <property name="border-width">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkFontChooserWidget" id="chooser">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="confirm">
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">_Select</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/prefs/dzl-preferences-group-private.h b/src/prefs/dzl-preferences-group-private.h
new file mode 100644
index 0000000..f86dde4
--- /dev/null
+++ b/src/prefs/dzl-preferences-group-private.h
@@ -0,0 +1,52 @@
+/* dzl-preferences-group-private.h
+ *
+ * Copyright (C) 2015-2017 Christian Hergert <christian hergert me>
+ *
+ * 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_GROUP_PRIVATE_H
+#define DZL_PREFERENCES_GROUP_PRIVATE_H
+
+#include "prefs/dzl-preferences-group.h"
+#include "search/dzl-pattern-spec.h"
+
+G_BEGIN_DECLS
+
+struct _DzlPreferencesGroup
+{
+ GtkBin parent_instance;
+
+ gint priority;
+ guint is_list : 1;
+
+ GtkLabel *title;
+ GtkBox *box;
+ GtkListBox *list_box;
+ GtkFrame *list_box_frame;
+
+ GPtrArray *widgets;
+
+ GtkListBoxRow *last_focused;
+ guint last_focused_tab_backward : 1;
+};
+
+void _dzl_preferences_group_set_map (DzlPreferencesGroup *self,
+ GHashTable *map);
+guint _dzl_preferences_group_refilter (DzlPreferencesGroup *self,
+ DzlPatternSpec *spec);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_GROUP_PRIVATE_H */
diff --git a/src/prefs/dzl-preferences-group.c b/src/prefs/dzl-preferences-group.c
new file mode 100644
index 0000000..959cc92
--- /dev/null
+++ b/src/prefs/dzl-preferences-group.c
@@ -0,0 +1,443 @@
+/* dzl-preferences-group.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include "prefs/dzl-preferences-bin.h"
+#include "prefs/dzl-preferences-bin-private.h"
+#include "prefs/dzl-preferences-entry.h"
+#include "prefs/dzl-preferences-group.h"
+#include "prefs/dzl-preferences-group-private.h"
+
+G_DEFINE_TYPE (DzlPreferencesGroup, dzl_preferences_group, GTK_TYPE_BIN)
+
+#define COLUMN_WIDTH 500
+
+enum {
+ PROP_0,
+ PROP_IS_LIST,
+ PROP_MODE,
+ PROP_PRIORITY,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+gint
+dzl_preferences_group_get_priority (DzlPreferencesGroup *self)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES_GROUP (self), 0);
+
+ return self->priority;
+}
+
+static void
+dzl_preferences_group_widget_destroy (DzlPreferencesGroup *self,
+ GtkWidget *widget)
+{
+ g_assert (DZL_IS_PREFERENCES_GROUP (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ g_ptr_array_remove (self->widgets, widget);
+}
+
+static void
+dzl_preferences_group_row_activated (DzlPreferencesGroup *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ GtkWidget *child;
+
+ g_assert (DZL_IS_PREFERENCES_GROUP (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+ child = gtk_bin_get_child (GTK_BIN (row));
+ if (child != NULL)
+ gtk_widget_activate (child);
+}
+
+static void
+dzl_preferences_group_row_selected (DzlPreferencesGroup *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ g_assert (DZL_IS_PREFERENCES_GROUP (self));
+ g_assert (!row || GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+ if (gtk_list_box_get_selection_mode (list_box) == GTK_SELECTION_SINGLE &&
+ GTK_IS_LIST_BOX_ROW (row) &&
+ gtk_list_box_row_get_activatable (row))
+ dzl_preferences_group_row_activated (self, row, list_box);
+}
+
+const gchar *
+dzl_preferences_group_get_title (DzlPreferencesGroup *self)
+{
+ const gchar *title;
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_GROUP (self), NULL);
+
+ title = gtk_label_get_label (self->title);
+
+ return (!title || !*title) ? NULL : title;
+}
+
+static void
+dzl_preferences_group_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ *min_width = *nat_width = COLUMN_WIDTH;
+}
+
+static void
+dzl_preferences_group_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *min_height,
+ gint *nat_height)
+{
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ GTK_WIDGET_CLASS (dzl_preferences_group_parent_class)->get_preferred_height_for_width (widget, width,
min_height, nat_height);
+}
+
+static GtkSizeRequestMode
+dzl_preferences_group_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+dzl_preferences_group_finalize (GObject *object)
+{
+ DzlPreferencesGroup *self = (DzlPreferencesGroup *)object;
+
+ g_clear_pointer (&self->widgets, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (dzl_preferences_group_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_group_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesGroup *self = DZL_PREFERENCES_GROUP (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODE:
+ g_value_set_enum (value, gtk_list_box_get_selection_mode (self->list_box));
+ break;
+
+ case PROP_IS_LIST:
+ g_value_set_boolean (value, self->is_list);
+ break;
+
+ case PROP_PRIORITY:
+ g_value_set_int (value, self->priority);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_group_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesGroup *self = DZL_PREFERENCES_GROUP (object);
+
+ switch (prop_id)
+ {
+ case PROP_MODE:
+ gtk_list_box_set_selection_mode (self->list_box, g_value_get_enum (value));
+ break;
+
+ case PROP_IS_LIST:
+ self->is_list = g_value_get_boolean (value);
+ gtk_widget_set_visible (GTK_WIDGET (self->box), !self->is_list);
+ gtk_widget_set_visible (GTK_WIDGET (self->list_box_frame), self->is_list);
+ break;
+
+ case PROP_PRIORITY:
+ self->priority = g_value_get_int (value);
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ gtk_widget_set_visible (GTK_WIDGET (self->title), !!g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_group_class_init (DzlPreferencesGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_group_finalize;
+ object_class->get_property = dzl_preferences_group_get_property;
+ object_class->set_property = dzl_preferences_group_set_property;
+
+ widget_class->get_preferred_width = dzl_preferences_group_get_preferred_width;
+ widget_class->get_preferred_height_for_width = dzl_preferences_group_get_preferred_height_for_width;
+ widget_class->get_request_mode = dzl_preferences_group_get_request_mode;
+
+ properties [PROP_MODE] =
+ g_param_spec_enum ("mode",
+ NULL,
+ NULL,
+ GTK_TYPE_SELECTION_MODE,
+ GTK_SELECTION_NONE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_IS_LIST] =
+ g_param_spec_boolean ("is-list",
+ "Is List",
+ "If the group should be rendered as a listbox.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "Priority",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-group.ui");
+ gtk_widget_class_set_css_name (widget_class, "preferencesgroup");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesGroup, box);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesGroup, list_box);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesGroup, list_box_frame);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesGroup, title);
+}
+
+static void
+dzl_preferences_group_init (DzlPreferencesGroup *self)
+{
+ self->widgets = g_ptr_array_new ();
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->list_box,
+ "row-activated",
+ G_CALLBACK (dzl_preferences_group_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->list_box,
+ "row-selected",
+ G_CALLBACK (dzl_preferences_group_row_selected),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static gboolean
+dzl_preferences_group_row_focus (DzlPreferencesGroup *self,
+ GtkDirectionType dir,
+ GtkListBoxRow *row)
+{
+ GtkWidget *child;
+ GtkWidget *entry;
+
+ self->last_focused_tab_backward = (dir == GTK_DIR_TAB_BACKWARD);
+
+ child = gtk_bin_get_child (GTK_BIN (row));
+
+ if (DZL_IS_PREFERENCES_ENTRY (child))
+ {
+ entry = dzl_preferences_entry_get_entry_widget ( DZL_PREFERENCES_ENTRY (child));
+ if (GTK_IS_ENTRY (entry) &&
+ gtk_widget_is_focus (entry) &&
+ dir == GTK_DIR_TAB_BACKWARD)
+ gtk_widget_grab_focus (GTK_WIDGET (row));
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+dzl_preferences_group_row_grab_focus (DzlPreferencesGroup *self,
+ GtkListBoxRow *row)
+{
+ GtkWidget *child;
+ GtkListBoxRow *last_focused;
+
+ last_focused = self->last_focused;
+ child = gtk_bin_get_child (GTK_BIN (row));
+ if (DZL_IS_PREFERENCES_ENTRY (child))
+ {
+ self->last_focused = row;
+ if (row != last_focused || !self->last_focused_tab_backward)
+ gtk_widget_activate (child);
+
+ return;
+ }
+
+ self->last_focused = NULL;
+}
+
+void
+dzl_preferences_group_add (DzlPreferencesGroup *self,
+ GtkWidget *widget)
+{
+ gint position = -1;
+
+ g_return_if_fail (DZL_IS_PREFERENCES_GROUP (self));
+ g_return_if_fail (DZL_IS_PREFERENCES_BIN (widget));
+
+ g_ptr_array_add (self->widgets, widget);
+
+ g_signal_connect_object (widget,
+ "destroy",
+ G_CALLBACK (dzl_preferences_group_widget_destroy),
+ self,
+ G_CONNECT_SWAPPED);
+
+ if (self->is_list)
+ {
+ GtkWidget *row;
+
+ if (GTK_IS_LIST_BOX_ROW (widget))
+ row = widget;
+ else
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", widget,
+ "visible", TRUE,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (self->list_box), row);
+ g_signal_connect_object (row,
+ "focus",
+ G_CALLBACK (dzl_preferences_group_row_focus),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (row,
+ "grab-focus",
+ G_CALLBACK (dzl_preferences_group_row_grab_focus),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+ }
+ else
+ {
+ gtk_container_add_with_properties (GTK_CONTAINER (self->box), widget,
+ "position", position,
+ NULL);
+ }
+}
+
+void
+_dzl_preferences_group_set_map (DzlPreferencesGroup *self,
+ GHashTable *map)
+{
+ guint i;
+
+ g_return_if_fail (DZL_IS_PREFERENCES_GROUP (self));
+
+ for (i = 0; i < self->widgets->len; i++)
+ {
+ GtkWidget *widget = g_ptr_array_index (self->widgets, i);
+
+ if (DZL_IS_PREFERENCES_BIN (widget))
+ _dzl_preferences_bin_set_map (DZL_PREFERENCES_BIN (widget), map);
+ }
+}
+
+static void
+dzl_preferences_group_refilter_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ DzlPreferencesBin *bin = NULL;
+ struct {
+ DzlPatternSpec *spec;
+ guint matches;
+ } *lookup = user_data;
+ gboolean matches;
+
+ if (DZL_IS_PREFERENCES_BIN (widget))
+ bin = DZL_PREFERENCES_BIN (widget);
+ else if (GTK_IS_BIN (widget) && DZL_IS_PREFERENCES_BIN (gtk_bin_get_child (GTK_BIN (widget))))
+ bin = DZL_PREFERENCES_BIN (gtk_bin_get_child (GTK_BIN (widget)));
+ else
+ return;
+
+ if (lookup->spec == NULL)
+ matches = TRUE;
+ else
+ matches = _dzl_preferences_bin_matches (bin, lookup->spec);
+
+ gtk_widget_set_visible (widget, matches);
+
+ lookup->matches += matches;
+}
+
+guint
+_dzl_preferences_group_refilter (DzlPreferencesGroup *self,
+ DzlPatternSpec *spec)
+{
+ struct {
+ DzlPatternSpec *spec;
+ guint matches;
+ } lookup = { spec, 0 };
+ const gchar *tmp;
+
+ g_return_val_if_fail (DZL_IS_PREFERENCES_GROUP (self), 0);
+
+ tmp = gtk_label_get_label (self->title);
+ if (spec && tmp && dzl_pattern_spec_match (spec, tmp))
+ lookup.spec = NULL;
+
+ gtk_container_foreach (GTK_CONTAINER (self->list_box),
+ dzl_preferences_group_refilter_cb,
+ &lookup);
+ gtk_container_foreach (GTK_CONTAINER (self->box),
+ dzl_preferences_group_refilter_cb,
+ &lookup);
+
+ gtk_widget_set_visible (GTK_WIDGET (self), lookup.matches > 0);
+
+ return lookup.matches;
+}
diff --git a/src/prefs/dzl-preferences-group.h b/src/prefs/dzl-preferences-group.h
new file mode 100644
index 0000000..2c0debf
--- /dev/null
+++ b/src/prefs/dzl-preferences-group.h
@@ -0,0 +1,37 @@
+/* dzl-preferences-group.h
+ *
+ * Copyright (C) 2015-2017 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_GROUP_H
+#define DZL_PREFERENCES_GROUP_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_GROUP (dzl_preferences_group_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesGroup, dzl_preferences_group, DZL, PREFERENCES_GROUP, GtkBin)
+
+void dzl_preferences_group_add (DzlPreferencesGroup *self,
+ GtkWidget *widget);
+const gchar *dzl_preferences_group_get_title (DzlPreferencesGroup *self);
+gint dzl_preferences_group_get_priority (DzlPreferencesGroup *self);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_GROUP_H */
diff --git a/src/prefs/dzl-preferences-group.ui b/src/prefs/dzl-preferences-group.ui
new file mode 100644
index 0000000..5c81526
--- /dev/null
+++ b/src/prefs/dzl-preferences-group.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesGroup" parent="GtkBin">
+ <property name="vexpand">false</property>
+ <property name="halign">fill</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">true</property>
+ <property name="hexpand">true</property>
+ <property name="xalign">0.0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame" id="list_box_frame">
+ <child>
+ <object class="DzlScrolledWindow">
+ <!-- propagate-natural-height on GtkScrolledWindow doesn't work for
+ us because it doesn't seem to take into account HfW properly. -->
+ <property name="visible">true</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkListBox" id="list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-language-row.c b/src/prefs/dzl-preferences-language-row.c
new file mode 100644
index 0000000..aede7e5
--- /dev/null
+++ b/src/prefs/dzl-preferences-language-row.c
@@ -0,0 +1,167 @@
+/* dzl-preferences-language-row.c
+ *
+ * Copyright (C) 2015-2017 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "prefs/dzl-preferences.h"
+#include "prefs/dzl-preferences-language-row.h"
+
+struct _DzlPreferencesLanguageRow
+{
+ DzlPreferencesBin parent_instance;
+ gchar *id;
+ GtkLabel *title;
+};
+
+G_DEFINE_TYPE (DzlPreferencesLanguageRow, dzl_preferences_language_row, DZL_TYPE_PREFERENCES_BIN)
+
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+dzl_preferences_language_row_activate (DzlPreferencesLanguageRow *self)
+{
+ GtkWidget *preferences;
+ GHashTable *map;
+
+ g_assert (DZL_IS_PREFERENCES_LANGUAGE_ROW (self));
+
+ if (self->id == NULL)
+ return;
+
+ preferences = gtk_widget_get_ancestor (GTK_WIDGET (self), DZL_TYPE_PREFERENCES);
+ if (preferences == NULL)
+ return;
+
+ map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+ g_hash_table_insert (map, "{id}", g_strdup (self->id));
+ dzl_preferences_set_page (DZL_PREFERENCES (preferences), "languages.id", map);
+ g_hash_table_unref (map);
+}
+
+static void
+dzl_preferences_language_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesLanguageRow *self = DZL_PREFERENCES_LANGUAGE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, self->id);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_language_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesLanguageRow *self = DZL_PREFERENCES_LANGUAGE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ self->id = g_value_dup_string (value);
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_language_row_finalize (GObject *object)
+{
+ DzlPreferencesLanguageRow *self = (DzlPreferencesLanguageRow *)object;
+
+ g_clear_pointer (&self->id, g_free);
+
+ G_OBJECT_CLASS (dzl_preferences_language_row_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_language_row_class_init (DzlPreferencesLanguageRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_language_row_finalize;
+ object_class->get_property = dzl_preferences_language_row_get_property;
+ object_class->set_property = dzl_preferences_language_row_set_property;
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "Id",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [ACTIVATE] =
+ g_signal_new_class_handler ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_CALLBACK (dzl_preferences_language_row_activate),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ widget_class->activate_signal = signals [ACTIVATE];
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-language-row.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesLanguageRow, title);
+}
+
+static void
+dzl_preferences_language_row_init (DzlPreferencesLanguageRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/prefs/dzl-preferences-language-row.h b/src/prefs/dzl-preferences-language-row.h
new file mode 100644
index 0000000..5500001
--- /dev/null
+++ b/src/prefs/dzl-preferences-language-row.h
@@ -0,0 +1,32 @@
+/* dzl-preferences-language-row.h
+ *
+ * Copyright (C) 2015-2017 Christian Hergert <christian hergert me>
+ *
+ * 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_LANGUAGE_ROW_H
+#define DZL_PREFERENCES_LANGUAGE_ROW_H
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_LANGUAGE_ROW (dzl_preferences_language_row_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesLanguageRow, dzl_preferences_language_row, DZL,
PREFERENCES_LANGUAGE_ROW, DzlPreferencesBin)
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_LANGUAGE_ROW_H */
diff --git a/src/prefs/dzl-preferences-language-row.ui b/src/prefs/dzl-preferences-language-row.ui
new file mode 100644
index 0000000..9eee6f7
--- /dev/null
+++ b/src/prefs/dzl-preferences-language-row.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesLanguageRow" parent="DzlPreferencesBin">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">pan-end-symbolic</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-page-private.h b/src/prefs/dzl-preferences-page-private.h
new file mode 100644
index 0000000..25d14db
--- /dev/null
+++ b/src/prefs/dzl-preferences-page-private.h
@@ -0,0 +1,43 @@
+/* dzl-preferences-page-private.h
+ *
+ * Copyright (C) 2015-2017 Christian Hergert <christian hergert me>
+ *
+ * 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_PAGE_PRIVATE_H
+#define DZL_PREFERENCES_PAGE_PRIVATE_H
+
+#include "prefs/dzl-preferences-page.h"
+#include "prefs/dzl-preferences-flow-box.h"
+#include "search/dzl-pattern-spec.h"
+
+G_BEGIN_DECLS
+
+struct _DzlPreferencesPage
+{
+ GtkBin parent_instance;
+ gint priority;
+ DzlPreferencesFlowBox *box;
+ GHashTable *groups_by_name;
+};
+
+void _dzl_preferences_page_set_map (DzlPreferencesPage *self,
+ GHashTable *map);
+void _dzl_preferences_page_refilter (DzlPreferencesPage *self,
+ DzlPatternSpec *spec);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_PAGE_PRIVATE_H */
diff --git a/src/prefs/dzl-preferences-page.c b/src/prefs/dzl-preferences-page.c
new file mode 100644
index 0000000..d0e15b3
--- /dev/null
+++ b/src/prefs/dzl-preferences-page.c
@@ -0,0 +1,185 @@
+/* dzl-preferences-page.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "prefs/dzl-preferences-group.h"
+#include "prefs/dzl-preferences-group-private.h"
+#include "prefs/dzl-preferences-page.h"
+#include "prefs/dzl-preferences-page-private.h"
+
+enum {
+ PROP_0,
+ PROP_PRIORITY,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (DzlPreferencesPage, dzl_preferences_page, GTK_TYPE_BIN)
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+dzl_preferences_page_finalize (GObject *object)
+{
+ DzlPreferencesPage *self = (DzlPreferencesPage *)object;
+
+ g_clear_pointer (&self->groups_by_name, g_hash_table_unref);
+
+ G_OBJECT_CLASS (dzl_preferences_page_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_page_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesPage *self = DZL_PREFERENCES_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRIORITY:
+ g_value_set_int (value, self->priority);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesPage *self = DZL_PREFERENCES_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRIORITY:
+ self->priority = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_page_class_init (DzlPreferencesPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_page_finalize;
+ object_class->get_property = dzl_preferences_page_get_property;
+ object_class->set_property = dzl_preferences_page_set_property;
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "Priority",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/dazzle/ui/dzl-preferences-page.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesPage, box);
+}
+
+static void
+dzl_preferences_page_init (DzlPreferencesPage *self)
+{
+ self->groups_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+dzl_preferences_page_add_group (DzlPreferencesPage *self,
+ DzlPreferencesGroup *group)
+{
+ gchar *name = NULL;
+
+ g_return_if_fail (DZL_IS_PREFERENCES_PAGE (self));
+ g_return_if_fail (DZL_IS_PREFERENCES_GROUP (group));
+
+ g_object_get (group, "name", &name, NULL);
+
+ if (g_hash_table_contains (self->groups_by_name, name))
+ {
+ g_free (name);
+ return;
+ }
+
+ g_hash_table_insert (self->groups_by_name, name, group);
+
+ gtk_container_add_with_properties (GTK_CONTAINER (self->box), GTK_WIDGET (group),
+ "priority", dzl_preferences_group_get_priority (group),
+ NULL);
+}
+
+/**
+ * dzl_preferences_page_get_group:
+ *
+ * Returns: (transfer none) (nullable): An #DzlPreferencesGroup or %NULL.
+ */
+DzlPreferencesGroup *
+dzl_preferences_page_get_group (DzlPreferencesPage *self,
+ const gchar *name)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES_PAGE (self), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_hash_table_lookup (self->groups_by_name, name);
+}
+
+void
+_dzl_preferences_page_set_map (DzlPreferencesPage *self,
+ GHashTable *map)
+{
+ DzlPreferencesGroup *group;
+ GHashTableIter iter;
+
+ g_return_if_fail (DZL_IS_PREFERENCES_PAGE (self));
+
+ g_hash_table_iter_init (&iter, self->groups_by_name);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&group))
+ _dzl_preferences_group_set_map (group, map);
+}
+
+void
+_dzl_preferences_page_refilter (DzlPreferencesPage *self,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesGroup *group;
+ GHashTableIter iter;
+ guint count = 0;
+
+ g_return_if_fail (DZL_IS_PREFERENCES_PAGE (self));
+
+ g_hash_table_iter_init (&iter, self->groups_by_name);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&group))
+ count += _dzl_preferences_group_refilter (group, spec);
+ gtk_widget_set_visible (GTK_WIDGET (self), count > 0);
+}
diff --git a/src/prefs/dzl-preferences-page.h b/src/prefs/dzl-preferences-page.h
new file mode 100644
index 0000000..515ddd4
--- /dev/null
+++ b/src/prefs/dzl-preferences-page.h
@@ -0,0 +1,39 @@
+/* dzl-preferences-page.h
+ *
+ * Copyright (C) 2015-2017 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_PAGE_H
+#define DZL_PREFERENCES_PAGE_H
+
+#include <gtk/gtk.h>
+
+#include "prefs/dzl-preferences-group.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_PAGE (dzl_preferences_page_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesPage, dzl_preferences_page, DZL, PREFERENCES_PAGE, GtkBin)
+
+void dzl_preferences_page_add_group (DzlPreferencesPage *self,
+ DzlPreferencesGroup *group);
+DzlPreferencesGroup *dzl_preferences_page_get_group (DzlPreferencesPage *self,
+ const gchar *group_name);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_PAGE_H */
diff --git a/src/prefs/dzl-preferences-page.ui b/src/prefs/dzl-preferences-page.ui
new file mode 100644
index 0000000..8f3f2eb
--- /dev/null
+++ b/src/prefs/dzl-preferences-page.ui
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesPage" parent="GtkBin">
+ <child>
+ <object class="DzlPreferencesFlowBox" id="box">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-spin-button.c b/src/prefs/dzl-preferences-spin-button.c
new file mode 100644
index 0000000..6f2e2ca
--- /dev/null
+++ b/src/prefs/dzl-preferences-spin-button.c
@@ -0,0 +1,417 @@
+/* dzl-preferences-spin-button.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include "util/dzl-util-private.h"
+#include "prefs/dzl-preferences-spin-button.h"
+
+struct _DzlPreferencesSpinButton
+{
+ DzlPreferencesBin parent_instance;
+
+ gulong handler;
+
+ guint updating : 1;
+
+ gchar *key;
+ GSettings *settings;
+
+ const GVariantType *type;
+
+ GtkSpinButton *spin_button;
+ GtkLabel *title;
+ GtkLabel *subtitle;
+};
+
+G_DEFINE_TYPE (DzlPreferencesSpinButton, dzl_preferences_spin_button, DZL_TYPE_PREFERENCES_BIN)
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_SUBTITLE,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+dzl_preferences_spin_button_activate (DzlPreferencesSpinButton *self)
+{
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->spin_button));
+}
+
+static void
+apply_value (GtkAdjustment *adj,
+ GVariant *value,
+ const gchar *property)
+{
+ GValue val = { 0 };
+ gdouble v = 0.0;
+
+ g_assert (GTK_IS_ADJUSTMENT (adj));
+ g_assert (value != NULL);
+ g_assert (property != NULL);
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE))
+ v = g_variant_get_double (value);
+
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT16))
+ v = g_variant_get_int16 (value);
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT16))
+ v = g_variant_get_uint16 (value);
+
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32))
+ v = g_variant_get_int32 (value);
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
+ v = g_variant_get_uint32 (value);
+
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT64))
+ v = g_variant_get_int64 (value);
+ else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64))
+ v = g_variant_get_uint64 (value);
+
+ else
+ g_warning ("Unknown variant type: %s\n", (gchar *)g_variant_get_type (value));
+
+ g_value_init (&val, G_TYPE_DOUBLE);
+ g_value_set_double (&val, v);
+ g_object_set_property (G_OBJECT (adj), property, &val);
+ g_value_unset (&val);
+}
+
+static void
+dzl_preferences_spin_button_value_changed (DzlPreferencesSpinButton *self,
+ GParamSpec *pspec,
+ GtkSpinButton *spin_button)
+{
+ GVariant *variant = NULL;
+ gdouble value;
+
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+ g_assert (pspec != NULL);
+ g_assert (GTK_IS_SPIN_BUTTON (spin_button));
+
+ value = gtk_spin_button_get_value (spin_button);
+
+ if (g_variant_type_equal (self->type, G_VARIANT_TYPE_DOUBLE))
+ variant = g_variant_new_double (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_INT16))
+ variant = g_variant_new_int16 (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_UINT16))
+ variant = g_variant_new_uint16 (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_INT32))
+ variant = g_variant_new_int32 (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_UINT32))
+ variant = g_variant_new_uint32 (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_INT64))
+ variant = g_variant_new_int64 (value);
+ else if (g_variant_type_equal (self->type, G_VARIANT_TYPE_UINT64))
+ variant = g_variant_new_uint64 (value);
+ else
+ g_return_if_reached ();
+
+ g_variant_ref_sink (variant);
+ g_settings_set_value (self->settings, self->key, variant);
+ g_clear_pointer (&variant, g_variant_unref);
+}
+
+static void
+dzl_preferences_spin_button_setting_changed (DzlPreferencesSpinButton *self,
+ const gchar *key,
+ GSettings *settings)
+{
+ GtkAdjustment *adj;
+ GVariant *value;
+
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+ g_assert (key != NULL);
+ g_assert (G_IS_SETTINGS (settings));
+
+ if (self->updating)
+ return;
+
+ self->updating = TRUE;
+
+ adj = gtk_spin_button_get_adjustment (self->spin_button);
+
+ value = g_settings_get_value (settings, key);
+ apply_value (adj, value, "value");
+ g_variant_unref (value);
+
+ self->updating = FALSE;
+}
+
+static void
+dzl_preferences_spin_button_connect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesSpinButton *self = (DzlPreferencesSpinButton *)bin;
+ GSettingsSchema *schema = NULL;
+ GSettingsSchemaKey *key = NULL;
+ GVariant *range = NULL;
+ GVariant *values = NULL;
+ GVariant *lower = NULL;
+ GVariant *upper = NULL;
+ gchar *type = NULL;
+ gchar *signal_detail = NULL;
+ GtkAdjustment *adj;
+ GVariantIter iter;
+
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+
+ self->settings = g_object_ref (settings);
+
+ g_object_get (self->settings, "settings-schema", &schema, NULL);
+
+ adj = gtk_spin_button_get_adjustment (self->spin_button);
+ key = g_settings_schema_get_key (schema, self->key);
+ range = g_settings_schema_key_get_range (key);
+
+ g_variant_get (range, "(sv)", &type, &values);
+
+ if (!dzl_str_equal0 (type, "range") || (2 != g_variant_iter_init (&iter, values)))
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
+ goto cleanup;
+ }
+
+ lower = g_variant_iter_next_value (&iter);
+ upper = g_variant_iter_next_value (&iter);
+
+ self->type = g_variant_get_type (lower);
+
+ apply_value (adj, lower, "lower");
+ apply_value (adj, upper, "upper");
+
+ signal_detail = g_strdup_printf ("changed::%s", self->key);
+
+ self->handler =
+ g_signal_connect_object (self->settings,
+ signal_detail,
+ G_CALLBACK (dzl_preferences_spin_button_setting_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ dzl_preferences_spin_button_setting_changed (self, self->key, self->settings);
+
+cleanup:
+ g_clear_pointer (&key, g_settings_schema_key_unref);
+ g_clear_pointer (&type, g_free);
+ g_clear_pointer (&signal_detail, g_free);
+ g_clear_pointer (&range, g_variant_unref);
+ g_clear_pointer (&values, g_variant_unref);
+ g_clear_pointer (&lower, g_variant_unref);
+ g_clear_pointer (&upper, g_variant_unref);
+ g_clear_pointer (&schema, g_settings_schema_unref);
+}
+
+static void
+dzl_preferences_spin_button_disconnect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesSpinButton *self = (DzlPreferencesSpinButton *)bin;
+
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+
+ g_signal_handler_disconnect (settings, self->handler);
+ self->handler = 0;
+}
+
+static gboolean
+dzl_preferences_spin_button_matches (DzlPreferencesBin *bin,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesSpinButton *self = (DzlPreferencesSpinButton *)bin;
+ const gchar *tmp;
+
+ g_assert (DZL_IS_PREFERENCES_SPIN_BUTTON (self));
+ g_assert (spec != NULL);
+
+ tmp = gtk_label_get_label (self->title);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ tmp = gtk_label_get_label (self->subtitle);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ if (self->key && dzl_pattern_spec_match (spec, self->key))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+dzl_preferences_spin_button_finalize (GObject *object)
+{
+ DzlPreferencesSpinButton *self = (DzlPreferencesSpinButton *)object;
+
+ g_clear_pointer (&self->key, g_free);
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (dzl_preferences_spin_button_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_spin_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesSpinButton *self = DZL_PREFERENCES_SPIN_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+
+ case PROP_SUBTITLE:
+ g_value_set_string (value, gtk_label_get_label (self->subtitle));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_spin_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesSpinButton *self = DZL_PREFERENCES_SPIN_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ self->key = g_value_dup_string (value);
+ break;
+
+ case PROP_SUBTITLE:
+ gtk_label_set_label (self->subtitle, g_value_get_string (value));
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_spin_button_class_init (DzlPreferencesSpinButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ DzlPreferencesBinClass *bin_class = DZL_PREFERENCES_BIN_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_spin_button_finalize;
+ object_class->get_property = dzl_preferences_spin_button_get_property;
+ object_class->set_property = dzl_preferences_spin_button_set_property;
+
+ bin_class->connect = dzl_preferences_spin_button_connect;
+ bin_class->disconnect = dzl_preferences_spin_button_disconnect;
+ bin_class->matches = dzl_preferences_spin_button_matches;
+
+ signals [ACTIVATE] =
+ g_signal_new_class_handler ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (dzl_preferences_spin_button_activate),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ widget_class->activate_signal = signals [ACTIVATE];
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-spin-button.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSpinButton, spin_button);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSpinButton, subtitle);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSpinButton, title);
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "Key",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SUBTITLE] =
+ g_param_spec_string ("subtitle",
+ "subtitle",
+ "subtitle",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "title",
+ "title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+dzl_preferences_spin_button_init (DzlPreferencesSpinButton *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_object_set (gtk_spin_button_get_adjustment (self->spin_button),
+ "value", 0.0,
+ "lower", 0.0,
+ "upper", 0.0,
+ "step-increment", 1.0,
+ "page-increment", 10.0,
+ "page-size", 10.0,
+ NULL);
+
+ g_signal_connect_object (self->spin_button,
+ "notify::value",
+ G_CALLBACK (dzl_preferences_spin_button_value_changed),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+/**
+ * dzl_preferences_spin_button_get_spin_button:
+ *
+ * Returns: (transfer none): The actual spin button widget.
+ */
+GtkWidget *
+dzl_preferences_spin_button_get_spin_button (DzlPreferencesSpinButton *self)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES_SPIN_BUTTON (self), NULL);
+
+ return GTK_WIDGET (self->spin_button);
+}
diff --git a/src/prefs/dzl-preferences-spin-button.h b/src/prefs/dzl-preferences-spin-button.h
new file mode 100644
index 0000000..715e656
--- /dev/null
+++ b/src/prefs/dzl-preferences-spin-button.h
@@ -0,0 +1,36 @@
+/* dzl-preferences-spin-button.h
+ *
+ * Copyright (C) 2015-2017 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_SPIN_BUTTON_H
+#define DZL_PREFERENCES_SPIN_BUTTON_H
+
+#include <gtk/gtk.h>
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_SPIN_BUTTON (dzl_preferences_spin_button_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesSpinButton, dzl_preferences_spin_button, DZL, PREFERENCES_SPIN_BUTTON,
DzlPreferencesBin)
+
+GtkWidget *dzl_preferences_spin_button_get_spin_button (DzlPreferencesSpinButton *self);
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_SPIN_BUTTON_H */
diff --git a/src/prefs/dzl-preferences-spin-button.ui b/src/prefs/dzl-preferences-spin-button.ui
new file mode 100644
index 0000000..8b1d92f
--- /dev/null
+++ b/src/prefs/dzl-preferences-spin-button.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesSpinButton" parent="DzlPreferencesBin">
+ <property name="vexpand">false</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">true</property>
+ <property name="vexpand">true</property>
+ <property name="xalign">0.0</property>
+ <property name="wrap">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle">
+ <property name="visible">true</property>
+ <property name="vexpand">true</property>
+ <property name="xalign">0.0</property>
+ <property name="wrap">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.83333"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_button">
+ <property name="visible">true</property>
+ <!-- width-chars does not seem to work well (3.19.5) -->
+ <property name="width-request">120</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences-switch.c b/src/prefs/dzl-preferences-switch.c
new file mode 100644
index 0000000..907f9c3
--- /dev/null
+++ b/src/prefs/dzl-preferences-switch.c
@@ -0,0 +1,439 @@
+/* dzl-preferences-switch.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include "util/dzl-util-private.h"
+#include "prefs/dzl-preferences-switch.h"
+
+struct _DzlPreferencesSwitch
+{
+ DzlPreferencesBin parent_instance;
+
+ guint is_radio : 1;
+ guint updating : 1;
+
+ gulong handler;
+
+ gchar *key;
+ GVariant *target;
+ GSettings *settings;
+
+ GtkLabel *subtitle;
+ GtkLabel *title;
+ GtkSwitch *widget;
+ GtkImage *image;
+};
+
+G_DEFINE_TYPE (DzlPreferencesSwitch, dzl_preferences_switch, DZL_TYPE_PREFERENCES_BIN)
+
+enum {
+ PROP_0,
+ PROP_IS_RADIO,
+ PROP_KEY,
+ PROP_SUBTITLE,
+ PROP_TARGET,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+dzl_preferences_switch_changed (DzlPreferencesSwitch *self,
+ const gchar *key,
+ GSettings *settings)
+{
+ GVariant *value;
+ gboolean active = FALSE;
+
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+ g_assert (key != NULL);
+ g_assert (G_IS_SETTINGS (settings));
+
+ if (self->updating == TRUE)
+ return;
+
+ value = g_settings_get_value (settings, key);
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
+ active = g_variant_get_boolean (value);
+ else if ((self->target != NULL) &&
+ g_variant_is_of_type (value, g_variant_get_type (self->target)))
+ active = g_variant_equal (value, self->target);
+ else if ((self->target != NULL) &&
+ g_variant_is_of_type (self->target, G_VARIANT_TYPE_STRING) &&
+ g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ g_autofree const gchar **strv = g_variant_get_strv (value, NULL);
+ const gchar *flag = g_variant_get_string (self->target, NULL);
+ active = g_strv_contains (strv, flag);
+ }
+
+ self->updating = TRUE;
+
+ if (self->is_radio)
+ {
+ gtk_widget_set_visible (GTK_WIDGET (self->image), active);
+ }
+ else
+ {
+ gtk_switch_set_active (self->widget, active);
+ gtk_switch_set_state (self->widget, active);
+ }
+
+ self->updating = FALSE;
+
+ g_variant_unref (value);
+}
+
+static void
+dzl_preferences_switch_connect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesSwitch *self = (DzlPreferencesSwitch *)bin;
+ g_autofree gchar *signal_detail = NULL;
+
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+
+ signal_detail = g_strdup_printf ("changed::%s", self->key);
+
+ self->settings = g_object_ref (settings);
+
+ self->handler =
+ g_signal_connect_object (settings,
+ signal_detail,
+ G_CALLBACK (dzl_preferences_switch_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ dzl_preferences_switch_changed (self, self->key, settings);
+}
+
+static void
+dzl_preferences_switch_disconnect (DzlPreferencesBin *bin,
+ GSettings *settings)
+{
+ DzlPreferencesSwitch *self = (DzlPreferencesSwitch *)bin;
+
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+
+ g_signal_handler_disconnect (settings, self->handler);
+ self->handler = 0;
+}
+
+static void
+dzl_preferences_switch_toggle (DzlPreferencesSwitch *self,
+ gboolean state)
+{
+ GVariant *value;
+
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+
+ if (self->updating)
+ return;
+
+ self->updating = TRUE;
+
+ value = g_settings_get_value (self->settings, self->key);
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
+ {
+ g_settings_set_boolean (self->settings, self->key, state);
+ }
+ else if ((self->target != NULL) &&
+ g_variant_is_of_type (self->target, G_VARIANT_TYPE_STRING) &&
+ g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ g_autofree const gchar **strv = g_variant_get_strv (value, NULL);
+ g_autoptr(GPtrArray) ar = g_ptr_array_new ();
+ const gchar *flag = g_variant_get_string (self->target, NULL);
+ gboolean found = FALSE;
+ gint i;
+
+ for (i = 0; strv [i]; i++)
+ {
+ if (!state && dzl_str_equal0 (strv [i], flag))
+ continue;
+ if (dzl_str_equal0 (strv [i], flag))
+ found = TRUE;
+ g_ptr_array_add (ar, (gchar *)strv [i]);
+ }
+
+ if (state && !found)
+ g_ptr_array_add (ar, (gchar *)flag);
+
+ g_ptr_array_add (ar, NULL);
+
+ g_settings_set_strv (self->settings, self->key, (const gchar * const *)ar->pdata);
+ }
+ else if ((self->target != NULL) &&
+ g_variant_is_of_type (value, g_variant_get_type (self->target)))
+ {
+ g_settings_set_value (self->settings, self->key, self->target);
+ }
+ else
+ {
+ g_warning ("I don't know how to set a variant of type %s to %s",
+ (const gchar *)g_variant_get_type (value),
+ self->target ? (const gchar *)g_variant_get_type (self->target) : "(nil)");
+ }
+
+
+ g_variant_unref (value);
+
+ if (self->is_radio)
+ gtk_widget_set_visible (GTK_WIDGET (self->image), state);
+ else
+ gtk_switch_set_state (self->widget, state);
+
+ self->updating = FALSE;
+
+ /* For good measure, so that we cleanup in the boolean deselection case */
+ dzl_preferences_switch_changed (self, self->key, self->settings);
+}
+
+static gboolean
+dzl_preferences_switch_state_set (DzlPreferencesSwitch *self,
+ gboolean state,
+ GtkSwitch *widget)
+{
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+ g_assert (GTK_IS_SWITCH (widget));
+
+ dzl_preferences_switch_toggle (self, state);
+
+ return TRUE;
+}
+
+static void
+dzl_preferences_switch_activate (DzlPreferencesSwitch *self)
+{
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+
+ if (!gtk_widget_get_sensitive (GTK_WIDGET (self)) || (self->settings == NULL))
+ return;
+
+ if (self->is_radio)
+ {
+ gboolean state;
+
+ state = !gtk_widget_get_visible (GTK_WIDGET (self->image));
+ dzl_preferences_switch_toggle (self, state);
+ }
+ else
+ gtk_widget_activate (GTK_WIDGET (self->widget));
+
+}
+
+static gboolean
+dzl_preferences_switch_matches (DzlPreferencesBin *bin,
+ DzlPatternSpec *spec)
+{
+ DzlPreferencesSwitch *self = (DzlPreferencesSwitch *)bin;
+ const gchar *tmp;
+
+ g_assert (DZL_IS_PREFERENCES_SWITCH (self));
+ g_assert (spec != NULL);
+
+ tmp = gtk_label_get_label (self->title);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ tmp = gtk_label_get_label (self->subtitle);
+ if (tmp && dzl_pattern_spec_match (spec, tmp))
+ return TRUE;
+
+ if (self->key && dzl_pattern_spec_match (spec, self->key))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+dzl_preferences_switch_finalize (GObject *object)
+{
+ DzlPreferencesSwitch *self = (DzlPreferencesSwitch *)object;
+
+ g_clear_pointer (&self->key, g_free);
+ g_clear_pointer (&self->target, g_variant_unref);
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (dzl_preferences_switch_parent_class)->finalize (object);
+}
+
+static void
+dzl_preferences_switch_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesSwitch *self = DZL_PREFERENCES_SWITCH (object);
+
+ switch (prop_id)
+ {
+ case PROP_IS_RADIO:
+ g_value_set_boolean (value, self->is_radio);
+ break;
+
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+
+ case PROP_TARGET:
+ g_value_set_variant (value, self->target);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ case PROP_SUBTITLE:
+ g_value_set_string (value, gtk_label_get_label (self->subtitle));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_switch_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DzlPreferencesSwitch *self = DZL_PREFERENCES_SWITCH (object);
+
+ switch (prop_id)
+ {
+ case PROP_IS_RADIO:
+ self->is_radio = g_value_get_boolean (value);
+ gtk_widget_set_visible (GTK_WIDGET (self->widget), !self->is_radio);
+ gtk_widget_set_visible (GTK_WIDGET (self->image), self->is_radio);
+ break;
+
+ case PROP_KEY:
+ self->key = g_value_dup_string (value);
+ break;
+
+ case PROP_TARGET:
+ self->target = g_value_dup_variant (value);
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ case PROP_SUBTITLE:
+ g_object_set (self->subtitle,
+ "label", g_value_get_string (value),
+ "visible", !!g_value_get_string (value),
+ NULL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dzl_preferences_switch_class_init (DzlPreferencesSwitchClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ DzlPreferencesBinClass *bin_class = DZL_PREFERENCES_BIN_CLASS (klass);
+
+ object_class->finalize = dzl_preferences_switch_finalize;
+ object_class->get_property = dzl_preferences_switch_get_property;
+ object_class->set_property = dzl_preferences_switch_set_property;
+
+ bin_class->connect = dzl_preferences_switch_connect;
+ bin_class->disconnect = dzl_preferences_switch_disconnect;
+ bin_class->matches = dzl_preferences_switch_matches;
+
+ signals [ACTIVATED] =
+ g_signal_new_class_handler ("activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (dzl_preferences_switch_activate),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ widget_class->activate_signal = signals [ACTIVATED];
+
+ properties [PROP_IS_RADIO] =
+ g_param_spec_boolean ("is-radio",
+ "Is Radio",
+ "If a radio style should be used instead of a switch.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+
+ properties [PROP_TARGET] =
+ g_param_spec_variant ("target",
+ "Target",
+ "Target",
+ G_VARIANT_TYPE_ANY,
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "Key",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SUBTITLE] =
+ g_param_spec_string ("subtitle",
+ "Subtitle",
+ "Subtitle",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/dazzle/ui/dzl-preferences-switch.ui");
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSwitch, image);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSwitch, subtitle);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSwitch, title);
+ gtk_widget_class_bind_template_child (widget_class, DzlPreferencesSwitch, widget);
+}
+
+static void
+dzl_preferences_switch_init (DzlPreferencesSwitch *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->widget,
+ "state-set",
+ G_CALLBACK (dzl_preferences_switch_state_set),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/src/prefs/dzl-preferences-switch.h b/src/prefs/dzl-preferences-switch.h
new file mode 100644
index 0000000..7f83523
--- /dev/null
+++ b/src/prefs/dzl-preferences-switch.h
@@ -0,0 +1,32 @@
+/* dzl-preferences-switch.h
+ *
+ * Copyright (C) 2015-2017 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_SWITCH_H
+#define DZL_PREFERENCES_SWITCH_H
+
+#include "prefs/dzl-preferences-bin.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES_SWITCH (dzl_preferences_switch_get_type())
+
+G_DECLARE_FINAL_TYPE (DzlPreferencesSwitch, dzl_preferences_switch, DZL, PREFERENCES_SWITCH,
DzlPreferencesBin)
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_SWITCH_H */
diff --git a/src/prefs/dzl-preferences-switch.ui b/src/prefs/dzl-preferences-switch.ui
new file mode 100644
index 0000000..e11b0b0
--- /dev/null
+++ b/src/prefs/dzl-preferences-switch.ui
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="DzlPreferencesSwitch" parent="DzlPreferencesBin">
+ <property name="vexpand">false</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">true</property>
+ <property name="vexpand">true</property>
+ <property name="xalign">0.0</property>
+ <property name="wrap">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle">
+ <property name="vexpand">true</property>
+ <property name="xalign">0.0</property>
+ <property name="wrap">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.83333"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="icon-name">object-select-symbolic</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="widget">
+ <property name="visible">true</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/prefs/dzl-preferences.c b/src/prefs/dzl-preferences.c
new file mode 100644
index 0000000..c791217
--- /dev/null
+++ b/src/prefs/dzl-preferences.c
@@ -0,0 +1,260 @@
+/* dzl-preferences.c
+ *
+ * Copyright (C) 2015-2017 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/>.
+ */
+
+#include <string.h>
+
+#include "prefs/dzl-preferences.h"
+
+G_DEFINE_INTERFACE (DzlPreferences, dzl_preferences, G_TYPE_OBJECT)
+
+static void
+dzl_preferences_default_init (DzlPreferencesInterface *iface)
+{
+}
+
+void
+dzl_preferences_add_page (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *title,
+ gint priority)
+{
+ g_return_if_fail (DZL_IS_PREFERENCES (self));
+ g_return_if_fail (page_name != NULL);
+ g_return_if_fail ((title != NULL) || (strchr (page_name, '.') != NULL));
+
+ DZL_PREFERENCES_GET_IFACE (self)->add_page (self, page_name, title, priority);
+}
+
+void
+dzl_preferences_add_group (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ gint priority)
+{
+ g_return_if_fail (DZL_IS_PREFERENCES (self));
+ g_return_if_fail (page_name != NULL);
+ g_return_if_fail (group_name != NULL);
+
+ DZL_PREFERENCES_GET_IFACE (self)->add_group (self, page_name, group_name, title, priority);
+}
+
+/**
+ * dzl_preferences_add_switch:
+ * @path: (nullable): An optional path
+ * @variant_string: (nullable): An optional gvariant string
+ * @title: (nullable): An optional title
+ * @subtitle: (nullable): An optional subtitle
+ * @keywords: (nullable): Optional keywords for search
+ *
+ */
+guint
+dzl_preferences_add_switch (DzlPreferences *self,
+ 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)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (schema_id != NULL, 0);
+ g_return_val_if_fail (key != NULL, 0);
+ g_return_val_if_fail (title != NULL, 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_switch (self, page_name, group_name, schema_id, key, path,
variant_string, title, subtitle, keywords, priority);
+}
+
+guint
+dzl_preferences_add_spin_button (DzlPreferences *self,
+ 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)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (schema_id != NULL, 0);
+ g_return_val_if_fail (key != NULL, 0);
+ g_return_val_if_fail (title != NULL, 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_spin_button (self, page_name, group_name, schema_id, key,
path, title, subtitle, keywords, priority);
+}
+
+/**
+ * dzl_preferences_add_custom:
+ * @keywords: (nullable): Optional keywords for search
+ *
+ */
+guint
+dzl_preferences_add_custom (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ GtkWidget *widget,
+ const gchar *keywords,
+ gint priority)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_custom (self, page_name, group_name, widget, keywords,
priority);
+}
+
+void
+dzl_preferences_add_list_group (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ GtkSelectionMode mode,
+ gint priority)
+{
+ g_return_if_fail (DZL_IS_PREFERENCES (self));
+ g_return_if_fail (page_name != NULL);
+ g_return_if_fail (group_name != NULL);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_list_group (self, page_name, group_name, title, mode,
priority);
+}
+
+/**
+ * dzl_preferences_add_radio:
+ * @path: (nullable): An optional path
+ * @variant_string: (nullable): An optional gvariant string
+ * @title: (nullable): An optional title
+ * @subtitle: (nullable): An optional subtitle
+ * @keywords: (nullable): Optional keywords for search
+ *
+ */
+guint
+dzl_preferences_add_radio (DzlPreferences *self,
+ 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)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (schema_id != NULL, 0);
+ g_return_val_if_fail (key != NULL, 0);
+ g_return_val_if_fail (title != NULL, 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_radio (self, page_name, group_name, schema_id, key, path,
variant_string, title, subtitle, keywords, priority);
+}
+
+guint
+dzl_preferences_add_font_button (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *schema_id,
+ const gchar *key,
+ const gchar *title,
+ const gchar *keywords,
+ gint priority)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (schema_id != NULL, 0);
+ g_return_val_if_fail (key != NULL, 0);
+ g_return_val_if_fail (title != NULL, 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_font_button (self, page_name, group_name, schema_id, key,
title, keywords, priority);
+}
+
+guint
+dzl_preferences_add_file_chooser (DzlPreferences *self,
+ 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)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), 0);
+ g_return_val_if_fail (page_name != NULL, 0);
+ g_return_val_if_fail (group_name != NULL, 0);
+ g_return_val_if_fail (schema_id != NULL, 0);
+ g_return_val_if_fail (key != NULL, 0);
+ g_return_val_if_fail (title != NULL, 0);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->add_file_chooser (self, page_name, group_name, schema_id, key,
path, title, subtitle, action, keywords, priority);
+}
+
+/**
+ * ide_preference_remove_id:
+ * @widget_id: An preferences widget id
+ *
+ */
+gboolean
+dzl_preferences_remove_id (DzlPreferences *self,
+ guint widget_id)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), FALSE);
+ g_return_val_if_fail (widget_id, FALSE);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->remove_id (self, widget_id);
+}
+
+void
+dzl_preferences_set_page (DzlPreferences *self,
+ const gchar *page_name,
+ GHashTable *map)
+{
+ g_return_if_fail (DZL_IS_PREFERENCES (self));
+ g_return_if_fail (page_name != NULL);
+
+ DZL_PREFERENCES_GET_IFACE (self)->set_page (self, page_name, map);
+}
+
+/**
+ * dzl_preferences_get_widget:
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+dzl_preferences_get_widget (DzlPreferences *self,
+ guint widget_id)
+{
+ g_return_val_if_fail (DZL_IS_PREFERENCES (self), NULL);
+
+ return DZL_PREFERENCES_GET_IFACE (self)->get_widget (self, widget_id);
+}
diff --git a/src/prefs/dzl-preferences.h b/src/prefs/dzl-preferences.h
new file mode 100644
index 0000000..31748cd
--- /dev/null
+++ b/src/prefs/dzl-preferences.h
@@ -0,0 +1,200 @@
+/* dzl-preferences.h
+ *
+ * Copyright (C) 2015-2017 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_H
+#define DZL_PREFERENCES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_PREFERENCES (dzl_preferences_get_type())
+
+G_DECLARE_INTERFACE (DzlPreferences, dzl_preferences, DZL, PREFERENCES, GObject)
+
+struct _DzlPreferencesInterface
+{
+ GTypeInterface parent_interface;
+
+ void (*set_page) (DzlPreferences *self,
+ const gchar *page_name,
+ GHashTable *map);
+ void (*add_page) (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *title,
+ gint priority);
+ void (*add_group) (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ gint priority);
+ void (*add_list_group) (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ GtkSelectionMode mode,
+ gint priority);
+ guint (*add_radio) (DzlPreferences *self,
+ 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);
+ guint (*add_font_button) (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *schema_id,
+ const gchar *key,
+ const gchar *title,
+ const gchar *keywords,
+ gint priority);
+ guint (*add_switch) (DzlPreferences *self,
+ 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);
+ guint (*add_spin_button) (DzlPreferences *self,
+ 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);
+ guint (*add_file_chooser) (DzlPreferences *self,
+ 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);
+ guint (*add_custom) (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ GtkWidget *widget,
+ const gchar *keywords,
+ gint priority);
+
+ gboolean (*remove_id) (DzlPreferences *self,
+ guint widget_id);
+
+ GtkWidget *(*get_widget) (DzlPreferences *self,
+ guint widget_id);
+};
+
+void dzl_preferences_add_page (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *title,
+ gint priority);
+void dzl_preferences_add_group (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ gint priority);
+void dzl_preferences_add_list_group (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *title,
+ GtkSelectionMode mode,
+ gint priority);
+guint dzl_preferences_add_radio (DzlPreferences *self,
+ 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);
+guint dzl_preferences_add_switch (DzlPreferences *self,
+ 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);
+guint dzl_preferences_add_spin_button (DzlPreferences *self,
+ 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);
+guint dzl_preferences_add_custom (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ GtkWidget *widget,
+ const gchar *keywords,
+ gint priority);
+guint dzl_preferences_add_font_button (DzlPreferences *self,
+ const gchar *page_name,
+ const gchar *group_name,
+ const gchar *schema_id,
+ const gchar *key,
+ const gchar *title,
+ const gchar *keywords,
+ gint priority);
+guint dzl_preferences_add_file_chooser (DzlPreferences *self,
+ 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);
+gboolean dzl_preferences_remove_id (DzlPreferences *self,
+ guint widget_id);
+void dzl_preferences_set_page (DzlPreferences *self,
+ const gchar *page_name,
+ GHashTable *map);
+GtkWidget *dzl_preferences_get_widget (DzlPreferences *self,
+ guint widget_id);
+
+
+G_END_DECLS
+
+#endif /* DZL_PREFERENCES_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]