From ae1c76ff830d146d41e88d6fba724c0a54bce868 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:45:20 +0200 Subject: Adding upstream version 1:43.6. Signed-off-by: Daniel Baumann --- shell/appdata/meson.build | 9 + shell/appdata/org.gnome.Settings.appdata.xml.in | 48 + shell/appdata/screenshot-appearance.png | Bin 0 -> 137096 bytes shell/appdata/screenshot-keyboard.png | Bin 0 -> 74325 bytes shell/appdata/screenshot-mouse.png | Bin 0 -> 56021 bytes shell/appdata/screenshot-sound.png | Bin 0 -> 60381 bytes shell/cc-application.c | 306 +++++++ shell/cc-application.h | 36 + shell/cc-debug.h.in | 229 +++++ shell/cc-log.c | 101 ++ shell/cc-log.h | 25 + shell/cc-object-storage.c | 444 +++++++++ shell/cc-object-storage.h | 65 ++ shell/cc-panel-list.c | 1114 +++++++++++++++++++++++ shell/cc-panel-list.h | 78 ++ shell/cc-panel-list.ui | 133 +++ shell/cc-panel-loader.c | 332 +++++++ shell/cc-panel-loader.h | 53 ++ shell/cc-panel-private.h | 28 + shell/cc-panel.c | 433 +++++++++ shell/cc-panel.h | 109 +++ shell/cc-panel.ui | 45 + shell/cc-shell-model.c | 422 +++++++++ shell/cc-shell-model.h | 84 ++ shell/cc-shell.c | 152 ++++ shell/cc-shell.h | 70 ++ shell/cc-window.c | 894 ++++++++++++++++++ shell/cc-window.h | 39 + shell/cc-window.ui | 170 ++++ shell/completions/gnome-control-center.in | 51 ++ shell/completions/meson.build | 12 + shell/gnome-control-center.gresource.xml | 16 + shell/help-overlay.ui | 70 ++ shell/icons/multitasking-symbolic.svg | 98 ++ shell/list-panel.sh | 8 + shell/main.c | 61 ++ shell/meson.build | 171 ++++ shell/org.gnome.Settings.desktop.in.in | 18 + shell/org.gnome.Settings.gschema.xml | 24 + shell/org.gnome.Settings.service.in | 3 + shell/style.css | 7 + 41 files changed, 5958 insertions(+) create mode 100644 shell/appdata/meson.build create mode 100644 shell/appdata/org.gnome.Settings.appdata.xml.in create mode 100644 shell/appdata/screenshot-appearance.png create mode 100644 shell/appdata/screenshot-keyboard.png create mode 100644 shell/appdata/screenshot-mouse.png create mode 100644 shell/appdata/screenshot-sound.png create mode 100644 shell/cc-application.c create mode 100644 shell/cc-application.h create mode 100644 shell/cc-debug.h.in create mode 100644 shell/cc-log.c create mode 100644 shell/cc-log.h create mode 100644 shell/cc-object-storage.c create mode 100644 shell/cc-object-storage.h create mode 100644 shell/cc-panel-list.c create mode 100644 shell/cc-panel-list.h create mode 100644 shell/cc-panel-list.ui create mode 100644 shell/cc-panel-loader.c create mode 100644 shell/cc-panel-loader.h create mode 100644 shell/cc-panel-private.h create mode 100644 shell/cc-panel.c create mode 100644 shell/cc-panel.h create mode 100644 shell/cc-panel.ui create mode 100644 shell/cc-shell-model.c create mode 100644 shell/cc-shell-model.h create mode 100644 shell/cc-shell.c create mode 100644 shell/cc-shell.h create mode 100644 shell/cc-window.c create mode 100644 shell/cc-window.h create mode 100644 shell/cc-window.ui create mode 100644 shell/completions/gnome-control-center.in create mode 100644 shell/completions/meson.build create mode 100644 shell/gnome-control-center.gresource.xml create mode 100644 shell/help-overlay.ui create mode 100644 shell/icons/multitasking-symbolic.svg create mode 100755 shell/list-panel.sh create mode 100644 shell/main.c create mode 100644 shell/meson.build create mode 100644 shell/org.gnome.Settings.desktop.in.in create mode 100644 shell/org.gnome.Settings.gschema.xml create mode 100644 shell/org.gnome.Settings.service.in create mode 100644 shell/style.css (limited to 'shell') diff --git a/shell/appdata/meson.build b/shell/appdata/meson.build new file mode 100644 index 0000000..526a10d --- /dev/null +++ b/shell/appdata/meson.build @@ -0,0 +1,9 @@ +appdata = 'org.gnome.Settings.appdata.xml' + +i18n.merge_file( + input: appdata + '.in', + output: appdata, + po_dir: po_dir, + install: true, + install_dir: join_paths(control_center_datadir, 'metainfo') +) diff --git a/shell/appdata/org.gnome.Settings.appdata.xml.in b/shell/appdata/org.gnome.Settings.appdata.xml.in new file mode 100644 index 0000000..99c594a --- /dev/null +++ b/shell/appdata/org.gnome.Settings.appdata.xml.in @@ -0,0 +1,48 @@ + + + + org.gnome.Settings.desktop + CC0-1.0 + GPL-2.0+ + Settings + Utility to configure the GNOME desktop + +

+ Settings is the primary interface for configuring your system. +

+
+ + + https://gitlab.gnome.org/GNOME/gnome-control-center/raw/HEAD/shell/appdata/screenshot-appearance.png + + + https://gitlab.gnome.org/GNOME/gnome-control-center/raw/HEAD/shell/appdata/screenshot-sound.png + + + https://gitlab.gnome.org/GNOME/gnome-control-center/raw/HEAD/shell/appdata/screenshot-mouse.png + + + https://gitlab.gnome.org/GNOME/gnome-control-center/raw/HEAD/shell/appdata/screenshot-keyboard.png + + + https://gitlab.gnome.org/GNOME/gnome-control-center/issues/ + https://www.gnome.org/friends/ + https://wiki.gnome.org/TranslationProject + gnomecc-list_at_gnome.org + GNOME + org.gnome.Settings.desktop + The GNOME Project + GNOME + + HiDpiIcon + HighContrast + ModernToolkit + SearchProvider + UserDocs + + gnome-control-center-2.0 + + workstation + mobile + +
diff --git a/shell/appdata/screenshot-appearance.png b/shell/appdata/screenshot-appearance.png new file mode 100644 index 0000000..7b7f5d6 Binary files /dev/null and b/shell/appdata/screenshot-appearance.png differ diff --git a/shell/appdata/screenshot-keyboard.png b/shell/appdata/screenshot-keyboard.png new file mode 100644 index 0000000..127620d Binary files /dev/null and b/shell/appdata/screenshot-keyboard.png differ diff --git a/shell/appdata/screenshot-mouse.png b/shell/appdata/screenshot-mouse.png new file mode 100644 index 0000000..c1e4a33 Binary files /dev/null and b/shell/appdata/screenshot-mouse.png differ diff --git a/shell/appdata/screenshot-sound.png b/shell/appdata/screenshot-sound.png new file mode 100644 index 0000000..9998f53 Binary files /dev/null and b/shell/appdata/screenshot-sound.png differ diff --git a/shell/cc-application.c b/shell/cc-application.c new file mode 100644 index 0000000..a51568b --- /dev/null +++ b/shell/cc-application.c @@ -0,0 +1,306 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "cc-application.h" +#include "cc-log.h" +#include "cc-object-storage.h" +#include "cc-panel-loader.h" +#include "cc-window.h" + +struct _CcApplication +{ + AdwApplication parent; + + CcShellModel *model; + + CcWindow *window; +}; + +static void cc_application_quit (GSimpleAction *simple, + GVariant *parameter, + gpointer user_data); + +static void launch_panel_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +static void help_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +G_DEFINE_TYPE (CcApplication, cc_application, ADW_TYPE_APPLICATION) + +const GOptionEntry all_options[] = { + { "version", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Display version number"), NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, NULL, N_("Enable verbose mode"), NULL }, + { "search", 's', 0, G_OPTION_ARG_STRING, NULL, N_("Search for the string"), "SEARCH" }, + { "list", 'l', 0, G_OPTION_ARG_NONE, NULL, N_("List possible panel names and exit"), NULL }, + { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, N_("Panel to display"), N_("[PANEL] [ARGUMENT…]") }, + { NULL, 0, 0, 0, NULL, NULL, NULL } /* end the list */ +}; + +static const GActionEntry cc_app_actions[] = { + { "launch-panel", launch_panel_activated, "(sav)", NULL, NULL, { 0 } }, + { "help", help_activated, NULL, NULL, NULL, { 0 } }, + { "quit", cc_application_quit, NULL, NULL, NULL, { 0 } } +}; + +static void +help_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + CcApplication *self = CC_APPLICATION (user_data); + CcPanel *panel; + GtkWidget *window; + const char *uri = NULL; + + panel = cc_shell_get_active_panel (CC_SHELL (self->window)); + if (panel) + uri = cc_panel_get_help_uri (panel); + + window = cc_shell_get_toplevel (CC_SHELL (self->window)); + gtk_show_uri (GTK_WINDOW (window), + uri ? uri : "help:gnome-help/prefs", + GDK_CURRENT_TIME); +} + +static void +launch_panel_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + CcApplication *self = CC_APPLICATION (user_data); + g_autoptr(GVariant) parameters = NULL; + g_autoptr(GError) error = NULL; + gchar *panel_id; + + g_variant_get (parameter, "(&s@av)", &panel_id, ¶meters); + + g_debug ("gnome-control-center: 'launch-panel' activated for panel '%s' with %"G_GSIZE_FORMAT" arguments", + panel_id, + g_variant_n_children (parameters)); + + if (!cc_shell_set_active_panel_from_id (CC_SHELL (self->window), panel_id, parameters, &error)) + g_warning ("Failed to activate the '%s' panel: %s", panel_id, error->message); + + /* Now present the window */ + g_application_activate (G_APPLICATION (self)); +} + +static gint +cc_application_handle_local_options (GApplication *application, + GVariantDict *options) +{ + if (g_variant_dict_contains (options, "version")) + { + g_print ("%s %s\n", PACKAGE, VERSION); + return 0; + } + + if (g_variant_dict_contains (options, "list")) + { + cc_panel_loader_list_panels (); + return 0; + } + + return -1; +} + +static int +cc_application_command_line (GApplication *application, + GApplicationCommandLine *command_line) +{ + CcApplication *self; + g_autofree GStrv start_panels = NULL; + GVariantDict *options; + int retval = 0; + char *search_str; + gboolean debug; + + self = CC_APPLICATION (application); + options = g_application_command_line_get_options_dict (command_line); + + debug = g_variant_dict_contains (options, "verbose"); + + if (debug) + cc_log_init (); + + gtk_window_present (GTK_WINDOW (self->window)); + + if (g_variant_dict_lookup (options, "search", "&s", &search_str)) + { + cc_window_set_search_item (self->window, search_str); + } + else if (g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&ay", &start_panels)) + { + const char *start_id; + GError *err = NULL; + GVariant *parameters; + GVariantBuilder builder; + int i; + + g_return_val_if_fail (start_panels[0] != NULL, 1); + start_id = start_panels[0]; + + if (start_panels[1]) + g_debug ("Extra argument: %s", start_panels[1]); + else + g_debug ("No extra argument"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); + + for (i = 1; start_panels[i] != NULL; i++) + g_variant_builder_add (&builder, "v", g_variant_new_string (start_panels[i])); + parameters = g_variant_builder_end (&builder); + if (!cc_shell_set_active_panel_from_id (CC_SHELL (self->window), start_id, parameters, &err)) + { + g_warning ("Could not load setting panel \"%s\": %s", start_id, + (err) ? err->message : "Unknown error"); + retval = 1; + + if (err) + g_clear_error (&err); + } + } + + return retval; +} + +static void +cc_application_quit (GSimpleAction *simple, + GVariant *parameter, + gpointer user_data) +{ + CcApplication *self = CC_APPLICATION (user_data); + + gtk_window_destroy (GTK_WINDOW (self->window)); +} + + +static void +cc_application_activate (GApplication *application) +{ + CcApplication *self = CC_APPLICATION (application); + + gtk_window_present (GTK_WINDOW (self->window)); +} + +static void +cc_application_startup (GApplication *application) +{ + CcApplication *self = CC_APPLICATION (application); + const gchar *help_accels[] = { "F1", NULL }; + g_autoptr(GtkCssProvider) provider = NULL; + + g_action_map_add_action_entries (G_ACTION_MAP (self), + cc_app_actions, + G_N_ELEMENTS (cc_app_actions), + self); + + G_APPLICATION_CLASS (cc_application_parent_class)->startup (application); + + gtk_application_set_accels_for_action (GTK_APPLICATION (application), + "app.help", help_accels); + + self->model = cc_shell_model_new (); + self->window = cc_window_new (GTK_APPLICATION (application), self->model); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/Settings/gtk/style.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +static void +cc_application_finalize (GObject *object) +{ + /* Destroy the object storage cache when finalizing */ + cc_object_storage_destroy (); + + G_OBJECT_CLASS (cc_application_parent_class)->finalize (object); +} + +static GObject * +cc_application_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + static GObject *self = NULL; + + if (self == NULL) + { + self = G_OBJECT_CLASS (cc_application_parent_class)->constructor (type, + n_construct_params, + construct_params); + g_object_add_weak_pointer (self, (gpointer) &self); + return self; + } + + return g_object_ref (self); +} + +static void +cc_application_class_init (CcApplicationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GApplicationClass *application_class = G_APPLICATION_CLASS (klass); + + object_class->finalize = cc_application_finalize; + object_class->constructor = cc_application_constructor; + application_class->activate = cc_application_activate; + application_class->startup = cc_application_startup; + application_class->command_line = cc_application_command_line; + application_class->handle_local_options = cc_application_handle_local_options; +} + +static void +cc_application_init (CcApplication *self) +{ + cc_object_storage_initialize (); + + g_application_add_main_option_entries (G_APPLICATION (self), all_options); +} + +GtkApplication * +cc_application_new (void) +{ + return g_object_new (CC_TYPE_APPLICATION, + "application-id", "org.gnome.Settings", + "flags", G_APPLICATION_HANDLES_COMMAND_LINE, + NULL); +} + +CcShellModel * +cc_application_get_model (CcApplication *self) +{ + g_return_val_if_fail (CC_IS_APPLICATION (self), NULL); + + return self->model; +} diff --git a/shell/cc-application.h b/shell/cc-application.h new file mode 100644 index 0000000..7b1fa3b --- /dev/null +++ b/shell/cc-application.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#pragma once + +#include "cc-shell-model.h" + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_APPLICATION (cc_application_get_type()) + +G_DECLARE_FINAL_TYPE (CcApplication, cc_application, CC, APPLICATION, AdwApplication) + +GtkApplication *cc_application_new (void); + +CcShellModel *cc_application_get_model (CcApplication *self); + +G_END_DECLS diff --git a/shell/cc-debug.h.in b/shell/cc-debug.h.in new file mode 100644 index 0000000..226f82e --- /dev/null +++ b/shell/cc-debug.h.in @@ -0,0 +1,229 @@ +/* cc-debug.h.in + * + * Copyright © 2018 Georges Basile Stavracas Neto + * + * 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 2 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 . + */ + +#pragma once + +#include + +/** + * SECTION:cc-debug + * @short_description: Debugging macros + * @title:Debugging + * @stability:stable + * + * Macros used for tracing and debugging code. These + * are only valid when Settings is compiled with tracing + * suppoer (pass `--enable-tracing` to the configure + * script to do that). + */ + +G_BEGIN_DECLS + +#ifndef CC_ENABLE_TRACE +# define CC_ENABLE_TRACE @ENABLE_TRACING@ +#endif +#if CC_ENABLE_TRACE != 1 +# undef CC_ENABLE_TRACE +#endif + +/** + * CC_LOG_LEVEL_TRACE: (skip) + */ +#ifndef CC_LOG_LEVEL_TRACE +# define CC_LOG_LEVEL_TRACE ((GLogLevelFlags)(1 << G_LOG_LEVEL_USER_SHIFT)) +#endif + +#ifdef CC_ENABLE_TRACE + +/** + * CC_TRACE_MSG: + * @fmt: printf-like format of the message + * @...: arguments for @fmt + * + * Prints a trace message. + */ +# define CC_TRACE_MSG(fmt, ...) \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " MSG: %s():%d: " fmt, \ + G_STRFUNC, __LINE__, ##__VA_ARGS__) + +/** + * CC_PROBE: + * + * Prints a probing message. Put this macro in the code when + * you want to check the program reaches a certain section + * of code. + */ +# define CC_PROBE \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, "PROBE: %s():%d", \ + G_STRFUNC, __LINE__) + +/** + * CC_TODO: + * @_msg: the message to print + * + * Prints a TODO message. + */ +# define CC_TODO(_msg) \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " TODO: %s():%d: %s", \ + G_STRFUNC, __LINE__, _msg) + +/** + * CC_ENTRY: + * + * Prints an entry message. This shouldn't be used in + * critical functions. Place this at the beggining of + * the function, before any assertion. + */ +# define CC_ENTRY \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, "ENTRY: %s():%d", \ + G_STRFUNC, __LINE__) + +/** + * CC_EXIT: + * + * Prints an exit message. This shouldn't be used in + * critical functions. Place this at the end of + * the function, after any relevant code. If the + * function returns something, use CC_RETURN() + * instead. + */ +# define CC_EXIT \ + G_STMT_START { \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " EXIT: %s():%d", \ + G_STRFUNC, __LINE__); \ + return; \ + } G_STMT_END + +/** + * CC_GOTO: + * @_l: goto tag + * + * Logs a goto jump. + */ +# define CC_GOTO(_l) \ + G_STMT_START { \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " GOTO: %s():%d ("#_l")",\ + G_STRFUNC, __LINE__); \ + goto _l; \ + } G_STMT_END + +/** + * CC_RETURN: + * @_r: the return value. + * + * Prints an exit message, and returns @_r. See #CC_EXIT. + */ +# define CC_RETURN(_r) \ + G_STMT_START { \ + g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " EXIT: %s():%d ", \ + G_STRFUNC, __LINE__); \ + return _r; \ + } G_STMT_END + +#else + +/** + * CC_TODO: + * @_msg: the message to print + * + * Prints a TODO message. + */ +# define CC_TODO(_msg) + +/** + * CC_PROBE: + * + * Prints a probing message. + */ +# define CC_PROBE + +/** + * CC_TRACE_MSG: + * @fmt: printf-like format of the message + * @...: arguments for @fmt + * + * Prints a trace message. + */ +# define CC_TRACE_MSG(fmt, ...) + +/** + * CC_ENTRY: + * + * Prints a probing message. This shouldn't be used in + * critical functions. Place this at the beggining of + * the function, before any assertion. + */ +# define CC_ENTRY + +/** + * CC_GOTO: + * @_l: goto tag + * + * Logs a goto jump. + */ +# define CC_GOTO(_l) goto _l + +/** + * CC_EXIT: + * + * Prints an exit message. This shouldn't be used in + * critical functions. Place this at the end of + * the function, after any relevant code. If the + * function returns somethin, use CC_RETURN() + * instead. + */ +# define CC_EXIT return + +/** + * CC_RETURN: + * @_r: the return value. + * + * Prints an exit message, and returns @_r. See #CC_EXIT. + */ +# define CC_RETURN(_r) return _r +#endif + +/** + * _CC_BUG: (skip) + */ +#define _CC_BUG(Component, Description, File, Line, Func, ...) \ + G_STMT_START { \ + g_printerr ("-----------------------------------------------------------------\n"); \ + g_printerr ("You've found a bug in Settings or one of its dependent libraries.\n"); \ + g_printerr ("Please help us help you by filing a bug report at:\n"); \ + g_printerr ("\n"); \ + g_printerr ("@BUGREPORT_URL@&component=%s\n", Component); \ + g_printerr ("\n"); \ + g_printerr ("%s:%d in function %s()\n", File, Line, Func); \ + g_printerr ("\n"); \ + g_printerr (Description"\n", ##__VA_ARGS__); \ + g_printerr ("-----------------------------------------------------------------\n"); \ + } G_STMT_END + +/** + * CC_BUG: + * @Component: the component + * @Description: the description + * @...: extra arguments + * + * Logs a bug-friendly message. + */ +#define CC_BUG(Component, Description, ...) \ + _CC_BUG(Component, Description, __FILE__, __LINE__, G_STRFUNC, ##__VA_ARGS__) + +G_END_DECLS diff --git a/shell/cc-log.c b/shell/cc-log.c new file mode 100644 index 0000000..dba4f5a --- /dev/null +++ b/shell/cc-log.c @@ -0,0 +1,101 @@ +/* cc-shell-log.c + * + * Copyright © 2018 Georges Basile Stavracas Neto + * + * 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 2 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 . + */ + +#include "cc-debug.h" +#include "cc-log.h" + +#include +#include + +G_LOCK_DEFINE_STATIC (channel_lock); + +GIOChannel *standard_channel = NULL; + +static const gchar* ignored_domains[] = +{ + "GdkPixbuf", + NULL +}; + +static const gchar * +log_level_str (GLogLevelFlags log_level) +{ + switch (((gulong)log_level & G_LOG_LEVEL_MASK)) + { + case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m"; + case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m"; + case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m"; + case G_LOG_LEVEL_MESSAGE: return " \033[1;34mMESSAGE\033[0m"; + case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m"; + case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m"; + case CC_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m"; + default: return " UNKNOWN"; + } +} + +static void +log_handler (const gchar *domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + g_autoptr(GDateTime) now = NULL; + const gchar *level; + g_autofree gchar *ftime = NULL; + g_autofree gchar *buffer = NULL; + + /* Skip ignored log domains */ + if (domain && g_strv_contains (ignored_domains, domain)) + return; + + level = log_level_str (log_level); + now = g_date_time_new_now_local (); + ftime = g_date_time_format (now, "%H:%M:%S"); + buffer = g_strdup_printf ("%s.%04d %24s: %s: %s\n", + ftime, + g_date_time_get_microsecond (now) / 1000, + domain, + level, + message); + + /* Safely write to the channel */ + G_LOCK (channel_lock); + + g_io_channel_write_chars (standard_channel, buffer, -1, NULL, NULL); + g_io_channel_flush (standard_channel, NULL); + + G_UNLOCK (channel_lock); +} + +void +cc_log_init (void) +{ + static gsize initialized = FALSE; + + if (g_once_init_enter (&initialized)) + { + standard_channel = g_io_channel_unix_new (STDOUT_FILENO); + + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + + g_log_set_default_handler (log_handler, NULL); + + g_once_init_leave (&initialized, TRUE); + } +} + diff --git a/shell/cc-log.h b/shell/cc-log.h new file mode 100644 index 0000000..2d04c0b --- /dev/null +++ b/shell/cc-log.h @@ -0,0 +1,25 @@ +/* cc-log.h + * + * Copyright © 2018 Georges Basile Stavracas Neto + * + * 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 2 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 . + */ + +#pragma once + +G_BEGIN_DECLS + +void cc_log_init (void); + +G_END_DECLS diff --git a/shell/cc-object-storage.c b/shell/cc-object-storage.c new file mode 100644 index 0000000..f9a08f3 --- /dev/null +++ b/shell/cc-object-storage.c @@ -0,0 +1,444 @@ +/* cc-object-storage.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 2 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 . + */ + +#define G_LOG_DOMAIN "cc-object-storage" + +#include "cc-object-storage.h" + +struct _CcObjectStorage +{ + GObject parent_instance; + + GHashTable *id_to_object; +}; + +G_DEFINE_TYPE (CcObjectStorage, cc_object_storage, G_TYPE_OBJECT) + +/* Singleton instance */ +static CcObjectStorage *_instance = NULL; + +/* GTask API to create a new D-Bus proxy */ +typedef struct +{ + GBusType bus_type; + GDBusProxyFlags flags; + gchar *name; + gchar *path; + gchar *interface; + gboolean cached; +} TaskData; + +static TaskData* +task_data_new (GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *path, + const gchar *interface) +{ + TaskData *data = g_slice_new (TaskData); + data->bus_type = bus_type; + data->flags =flags; + data->name = g_strdup (name); + data->path = g_strdup (path); + data->interface = g_strdup (interface); + data->cached = FALSE; + + return data; +} + +static void +task_data_free (TaskData *data) +{ + g_free (data->name); + g_free (data->path); + g_free (data->interface); + g_slice_free (TaskData, data); +} + +static void +create_dbus_proxy_in_thread_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GError) local_error = NULL; + TaskData *data = task_data; + + proxy = g_dbus_proxy_new_for_bus_sync (data->bus_type, + data->flags, + NULL, + data->name, + data->path, + data->interface, + cancellable, + &local_error); + + if (local_error) + { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + g_task_return_pointer (task, g_object_ref (g_steal_pointer (&proxy)), g_object_unref); +} + +static void +cc_object_storage_finalize (GObject *object) +{ + CcObjectStorage *self = (CcObjectStorage *)object; + + g_debug ("Destroying cached objects"); + + g_clear_pointer (&self->id_to_object, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_object_storage_parent_class)->finalize (object); +} + +static void +cc_object_storage_class_init (CcObjectStorageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_object_storage_finalize; +} + +static void +cc_object_storage_init (CcObjectStorage *self) +{ + self->id_to_object = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); +} + +/** + * cc_object_storage_has_object: + * @key: the unique string identifier of the object + * + * Checks whether there is an object associated with @key. + * + * Returns: %TRUE if the object is stored, %FALSE otherwise. + */ +gboolean +cc_object_storage_has_object (const gchar *key) +{ + g_assert (CC_IS_OBJECT_STORAGE (_instance)); + g_assert (key != NULL); + + return g_hash_table_contains (_instance->id_to_object, key); +} + +/** + * cc_object_storage_add_object: + * @key: the unique string identifier of the object + * @object: (type GObject): the object to be stored + * + * Adds @object to the object storage. It is a programming error to try to + * add an object that was already added. + * + * @object must be a GObject. + * + * Always check if the object is stored with cc_object_storage_has_object() + * before calling this function. + */ +void +cc_object_storage_add_object (const gchar *key, + gpointer object) +{ + /* Trying to add an object that was already added is a hard error. Each + * object must be added once, and only once, over the entire lifetime + * of the application. + */ + g_assert (CC_IS_OBJECT_STORAGE (_instance)); + g_assert (key != NULL); + g_assert (G_IS_OBJECT (object)); + g_assert (!g_hash_table_contains (_instance->id_to_object, key)); + + g_debug ("Adding object %s (%s → %p) to the storage", + g_type_name (G_OBJECT_TYPE (object)), + key, + object); + + g_hash_table_insert (_instance->id_to_object, g_strdup (key), g_object_ref (object)); +} + +/** + * cc_object_storage_get_object: + * @key: the unique string identifier of the object + * + * Retrieves the object associated with @key. It is a programming error to + * try to retrieve an object before adding it. + * + * Always check if the object is stored with cc_object_storage_has_object() + * before calling this function. + * + * Returns: (transfer full): the GObject associated with @key. + */ +gpointer +cc_object_storage_get_object (const gchar *key) +{ + /* Trying to peek an object that was not yet added is a hard error. Users + * of this API need to first check if the object is available with + * cc_object_storage_has_object(). + */ + g_assert (CC_IS_OBJECT_STORAGE (_instance)); + g_assert (key != NULL); + g_assert (g_hash_table_contains (_instance->id_to_object, key)); + + return g_object_ref (g_hash_table_lookup (_instance->id_to_object, key)); +} + +/** + * cc_object_storage_create_dbus_proxy_sync: + * @name: the D-Bus name + * @flags: the D-Bus proxy flags + * @path: the D-Bus object path + * @interface: the D-Bus interface name + * @cancellable: (nullable): #GCancellable to cancel the operation + * @error: (nullable): return location for a #GError + * + * Synchronously create a #GDBusProxy with @name, @path and @interface, + * stores it in the cache, and returns the newly created proxy. + * + * If a proxy with that signature is already created, it will be used + * instead of creating a new one. + * + * Returns: (transfer full)(nullable): the new #GDBusProxy. + */ +gpointer +cc_object_storage_create_dbus_proxy_sync (GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *path, + const gchar *interface, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GError) local_error = NULL; + g_autofree gchar *key = NULL; + + g_assert (CC_IS_OBJECT_STORAGE (_instance)); + g_assert (name && *name); + g_assert (path && *path); + g_assert (interface && *interface); + g_assert (!error || !*error); + + key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); + + g_debug ("Creating D-Bus proxy for %s", key); + + /* Check if a DBus proxy with that signature is already available; if it is, + * return that instead of a new one. + */ + if (g_hash_table_contains (_instance->id_to_object, key)) + return cc_object_storage_get_object (key); + + proxy = g_dbus_proxy_new_for_bus_sync (bus_type, + flags, + NULL, + name, + path, + interface, + cancellable, + &local_error); + + if (local_error) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + + /* Store the newly created D-Bus proxy */ + cc_object_storage_add_object (key, proxy); + + return g_steal_pointer (&proxy); +} + + +/** + * cc_object_storage_create_dbus_proxy: + * @name: the D-Bus name + * @flags: the D-Bus proxy flags + * @path: the D-Bus object path + * @interface: the D-Bus interface name + * @cancellable: (nullable): #GCancellable to cancel the operation + * @callback: callback for when the async operation is finished + * @user_data: user data for @callback + * + * Asynchronously create a #GDBusProxy with @name, @path and @interface. + * + * If a proxy with that signature is already created, it will be used instead of + * creating a new one. + * + * It is a programming error to create the an identical proxy while asynchronously + * creating one. Not cancelling this operation will result in an assertion failure + * when calling cc_object_storage_create_dbus_proxy_finish(). + */ +void +cc_object_storage_create_dbus_proxy (GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *path, + const gchar *interface, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autofree gchar *key = NULL; + TaskData *data = NULL; + + g_assert (CC_IS_OBJECT_STORAGE (_instance)); + g_assert (name && *name); + g_assert (path && *path); + g_assert (interface && *interface); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + data = task_data_new (bus_type, flags, name, path, interface); + + task = g_task_new (_instance, cancellable, callback, user_data); + g_task_set_source_tag (task, cc_object_storage_create_dbus_proxy); + g_task_set_task_data (task, data, (GDestroyNotify) task_data_free); + + /* Check if the D-Bus proxy is already created */ + key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); + + g_debug ("Asynchronously creating D-Bus proxy for %s", key); + + if (g_hash_table_contains (_instance->id_to_object, key)) + { + /* Mark this GTask as already cached, so we can call the right assertions + * on the callback + * */ + data->cached = TRUE; + + g_debug ("Found in cache the D-Bus proxy %s", key); + + g_task_return_pointer (task, cc_object_storage_get_object (key), g_object_unref); + return; + } + + g_task_run_in_thread (task, create_dbus_proxy_in_thread_cb); +} + +/** + * cc_object_storage_create_dbus_proxy_finish: + * @result: + * @error: (nullable): return location for a #GError + * + * Finishes a D-Bus proxy creation started by cc_object_storage_create_dbus_proxy(). + * + * Synchronously create a #GDBusProxy with @name, @path and @interface, + * stores it in the cache, and returns the newly created proxy. + * + * If a proxy with that signature is already created, it will be used + * instead of creating a new one. + * + * Returns: (transfer full)(nullable): the new #GDBusProxy. + */ +gpointer +cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result, + GError **error) +{ + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GError) local_error = NULL; + g_autofree gchar *key = NULL; + TaskData *task_data; + GTask *task; + + task = G_TASK (result); + + g_assert (task && G_TASK (result)); + g_assert (!error || !*error); + + task_data = g_task_get_task_data (task); + g_assert (task_data != NULL); + + key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", + task_data->name, + task_data->path, + task_data->interface); + + g_debug ("Finished creating D-Bus proxy for %s", key); + + /* Retrieve the newly created proxy */ + proxy = g_task_propagate_pointer (task, &local_error); + + /* If the proxy is not cached, do the normal caching routine */ + if (local_error) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + + /* Either we have the object stored right when trying to create it - in which case, + * task_data->cached == TRUE and cc_object_storage_has_object (key) == TRUE - or we + * didn't have a cached proxy before, and we shouldn't have it now. + * + * This is to force consumers of this code to *never* try to create the same D-Bus + * proxy asynchronously multiple times. Trying to do so is considered a programming + * error. + */ + g_assert (task_data->cached == cc_object_storage_has_object (key)); + + /* If the proxy is already cached, destroy the newly created and used the cached proxy + * instead. + */ + if (cc_object_storage_has_object (key)) + return cc_object_storage_get_object (key); + + /* Store the newly created D-Bus proxy */ + cc_object_storage_add_object (key, proxy); + + return g_steal_pointer (&proxy); +} + +/** + * cc_object_storage_initialize: + * + * Initializes the single CcObjectStorage. This must be called only once, + * and before every other method of this object. + */ +void +cc_object_storage_initialize (void) +{ + g_assert (_instance == NULL); + + if (g_once_init_enter (&_instance)) + { + CcObjectStorage *instance = g_object_new (CC_TYPE_OBJECT_STORAGE, NULL); + + g_debug ("Initializing object storage"); + + g_once_init_leave (&_instance, instance); + } +} + +/** + * cc_object_storage_destroy: + * + * Destroys the instance of #CcObjectStorage. This must be called only + * once during the application lifetime. It is a programming error to + * call this function multiple times + */ +void +cc_object_storage_destroy (void) +{ + g_assert (_instance != NULL); + + g_clear_object (&_instance); +} diff --git a/shell/cc-object-storage.h b/shell/cc-object-storage.h new file mode 100644 index 0000000..efea0e2 --- /dev/null +++ b/shell/cc-object-storage.h @@ -0,0 +1,65 @@ +/* cc-object-storage.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 2 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 . + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +/* Default storage keys */ +#define CC_OBJECT_NMCLIENT "CcObjectStorage::nm-client" + + +#define CC_TYPE_OBJECT_STORAGE (cc_object_storage_get_type()) + +G_DECLARE_FINAL_TYPE (CcObjectStorage, cc_object_storage, CC, OBJECT_STORAGE, GObject) + +gboolean cc_object_storage_has_object (const gchar *key); + +void cc_object_storage_add_object (const gchar *key, + gpointer object); + +gpointer cc_object_storage_get_object (const gchar *key); + +gpointer cc_object_storage_create_dbus_proxy_sync (GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *path, + const gchar *interface, + GCancellable *cancellable, + GError **error); + +void cc_object_storage_create_dbus_proxy (GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *path, + const gchar *interface, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gpointer cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result, + GError **error); + +void cc_object_storage_initialize (void); + +void cc_object_storage_destroy (void); + +G_END_DECLS diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c new file mode 100644 index 0000000..185b16d --- /dev/null +++ b/shell/cc-panel-list.c @@ -0,0 +1,1114 @@ +/* cc-panel-list.c + * + * Copyright (C) 2016 Endless, Inc + * + * 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 2 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 . + * + * Author: Georges Basile Stavracas Neto + */ + +#define G_LOG_DOMAIN "cc-panel-list" + +#include + +#include "cc-debug.h" +#include "cc-panel-list.h" +#include "cc-util.h" + +typedef struct +{ + GtkWidget *row; + GtkWidget *description_label; + CcPanelCategory category; + gchar *id; + gchar *name; + gchar *description; + gchar **keywords; + CcPanelVisibility visibility; +} RowData; + +struct _CcPanelList +{ + AdwBin parent; + + GtkWidget *privacy_listbox; + GtkWidget *main_listbox; + GtkWidget *search_listbox; + GtkStack *stack; + + /* When clicking on Details or Devices row, show it + * automatically select the first panel of the list. + */ + gboolean autoselect_panel : 1; + + GtkListBoxRow *privacy_row; + + gchar *current_panel_id; + gchar *search_query; + + CcPanelListView previous_view; + CcPanelListView view; + GHashTable *id_to_data; + GHashTable *id_to_search_data; +}; + +G_DEFINE_TYPE (CcPanelList, cc_panel_list, ADW_TYPE_BIN) + +enum +{ + PROP_0, + PROP_SEARCH_MODE, + PROP_SEARCH_QUERY, + PROP_VIEW, + N_PROPS +}; + +enum +{ + SHOW_PANEL, + LAST_SIGNAL +}; + +static GParamSpec *properties [N_PROPS] = { NULL, }; +static gint signals [LAST_SIGNAL] = { 0, }; + +/* + * Auxiliary methods + */ +static GtkWidget* +get_widget_from_view (CcPanelList *self, + CcPanelListView view) +{ + switch (view) + { + case CC_PANEL_LIST_MAIN: + return self->main_listbox; + + case CC_PANEL_LIST_PRIVACY: + return self->privacy_listbox; + + case CC_PANEL_LIST_SEARCH: + return self->search_listbox; + + case CC_PANEL_LIST_WIDGET: + return gtk_stack_get_child_by_name (self->stack, "custom-widget"); + + default: + return NULL; + } +} + +static GtkWidget * +get_listbox_from_category (CcPanelList *self, + CcPanelCategory category) +{ + + switch (category) + { + case CC_CATEGORY_PRIVACY: + return self->privacy_listbox; + break; + + default: + return self->main_listbox; + break; + } + + return NULL; +} + +static void +activate_row_below (CcPanelList *self, + RowData *data) +{ + GtkListBoxRow *next_row; + GtkListBox *listbox; + guint row_index; + + row_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (data->row)); + listbox = GTK_LIST_BOX (get_listbox_from_category (self, data->category)); + next_row = gtk_list_box_get_row_at_index (listbox, row_index + 1); + + /* Try the previous one if the current is invalid */ + if (!next_row) + next_row = gtk_list_box_get_row_at_index (listbox, row_index - 1); + + if (next_row) + g_signal_emit_by_name (next_row, "activate"); +} + +static CcPanelListView +get_view_from_listbox (CcPanelList *self, + GtkWidget *listbox) +{ + if (listbox == self->main_listbox) + return CC_PANEL_LIST_MAIN; + + if (listbox == self->privacy_listbox) + return CC_PANEL_LIST_PRIVACY; + + return CC_PANEL_LIST_SEARCH; +} + +static void +switch_to_view (CcPanelList *self, + CcPanelListView view) +{ + GtkWidget *visible_child; + gboolean should_crossfade; + + CC_ENTRY; + + if (self->view == view) + CC_RETURN (); + + CC_TRACE_MSG ("Switching to view: %d", view); + + self->previous_view = self->view; + self->view = view; + + /* + * When changing to or from the search view, the animation should + * be crossfade. Otherwise, it's the previous-forward movement. + */ + should_crossfade = view == CC_PANEL_LIST_SEARCH || + self->previous_view == CC_PANEL_LIST_SEARCH; + + gtk_stack_set_transition_type (self->stack, + should_crossfade ? GTK_STACK_TRANSITION_TYPE_CROSSFADE : + GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); + + visible_child = get_widget_from_view (self, view); + + gtk_stack_set_visible_child (self->stack, visible_child); + + /* For non-search views, make sure the displayed panel matches the + * newly selected row + */ + if (self->autoselect_panel && + view != CC_PANEL_LIST_SEARCH && + self->previous_view != CC_PANEL_LIST_WIDGET) + { + cc_panel_list_activate (self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_MODE]); + + CC_EXIT; +} + +static void +update_search (CcPanelList *self) +{ + /* + * Only change to the search view is there's a + * search query available. + */ + if (self->search_query && + g_utf8_strlen (self->search_query, -1) > 0) + { + if (self->view == CC_PANEL_LIST_MAIN) + switch_to_view (self, CC_PANEL_LIST_SEARCH); + } + else if (self->view == CC_PANEL_LIST_SEARCH) + { + GtkSelectionMode selection_mode; + gboolean autoselect_panel; + + /* Autoselect panel only if allowed. Autoselect of + * panel isn’t allowed if the panel is folded */ + autoselect_panel = self->autoselect_panel; + selection_mode = gtk_list_box_get_selection_mode (GTK_LIST_BOX (self->main_listbox)); + self->autoselect_panel = selection_mode != GTK_SELECTION_NONE; + + switch_to_view (self, self->previous_view); + + self->autoselect_panel = autoselect_panel; + } + + gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox)); + gtk_list_box_unselect_all (GTK_LIST_BOX (self->search_listbox)); +} + +static const gchar* +get_panel_id_from_row (CcPanelList *self, + GtkListBoxRow *row) +{ + + RowData *row_data; + + if (row == self->privacy_row) + return "privacy"; + + row_data = g_object_get_data (G_OBJECT (row), "data"); + + g_assert (row_data != NULL); + return row_data->id; +} + +/* + * RowData functions + */ +static void +row_data_free (RowData *data) +{ + g_strfreev (data->keywords); + g_free (data->description); + g_free (data->name); + g_free (data->id); + g_free (data); +} + +static RowData* +row_data_new (CcPanelCategory category, + const gchar *id, + const gchar *name, + const gchar *description, + const GStrv keywords, + const gchar *icon, + CcPanelVisibility visibility, + gboolean has_sidebar) +{ + GtkWidget *label, *grid, *image; + RowData *data; + + data = g_new0 (RowData, 1); + data->category = category; + data->row = gtk_list_box_row_new (); + data->id = g_strdup (id); + data->name = g_strdup (name); + data->description = g_strdup (description); + data->keywords = g_strdupv (keywords); + + /* Setup the row */ + grid = gtk_grid_new (); + gtk_widget_set_hexpand (grid, TRUE); + gtk_widget_set_margin_top (grid, 12); + gtk_widget_set_margin_bottom (grid, 12); + gtk_widget_set_margin_start (grid, 6); + gtk_widget_set_margin_end (grid, 6); + gtk_grid_set_column_spacing (GTK_GRID (grid), 12); + + /* Icon */ + image = gtk_image_new_from_icon_name (icon); + + gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 1); + + /* Name label */ + label = gtk_label_new (name); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_widget_set_hexpand (label, TRUE); + gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1); + + /* Description label */ + label = gtk_label_new (description); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_widget_set_hexpand (label, TRUE); + gtk_label_set_max_width_chars (GTK_LABEL (label), 25); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_widget_hide (label); + + if (has_sidebar) + { + image = gtk_image_new_from_icon_name ("go-next-symbolic"); + gtk_grid_attach (GTK_GRID (grid), image, 2, 0, 1, 1); + } + + gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); + gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1); + + data->description_label = label; + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (data->row), grid); + + g_object_set_data_full (G_OBJECT (data->row), "data", data, (GDestroyNotify) row_data_free); + + data->visibility = visibility; + + return data; +} + +/* + * GtkListBox functions + */ +static gboolean +filter_func (GtkListBoxRow *row, + gpointer user_data) +{ + CcPanelList *self; + RowData *data; + g_autofree gchar *search_text = NULL; + g_autofree gchar *panel_text = NULL; + g_autofree gchar *panel_description = NULL; + gboolean retval = FALSE; + gint i; + + self = CC_PANEL_LIST (user_data); + data = g_object_get_data (G_OBJECT (row), "data"); + + if (!self->search_query) + return TRUE; + + panel_text = cc_util_normalize_casefold_and_unaccent (data->name); + search_text = cc_util_normalize_casefold_and_unaccent (self->search_query); + panel_description = cc_util_normalize_casefold_and_unaccent (data->description); + + g_strstrip (panel_text); + g_strstrip (search_text); + g_strstrip (panel_description); + + /* + * The description label is only visible when the search is + * happening. + */ + gtk_widget_set_visible (data->description_label, self->view == CC_PANEL_LIST_SEARCH); + + for (i = 0; !retval && data->keywords[i] != NULL; i++) + retval = (strstr (data->keywords[i], search_text) == data->keywords[i]); + + retval = retval || g_strstr_len (panel_text, -1, search_text) != NULL || + g_strstr_len (panel_description, -1, search_text) != NULL; + + return retval; +} + +static const gchar * const panel_order[] = { + /* Main page */ + "wifi", + "network", + "wwan", + "mobile-broadband", + "bluetooth", + "background", + "notifications", + "search", + "multitasking", + "applications", + "privacy", + "online-accounts", + "sharing", + + /* Privacy page */ + "location", + "camera", + "microphone", + "thunderbolt", + "usage", + "lock", + "diagnostics", + "firmware-security", + + /* Devices page */ + "sound", + "power", + "display", + "mouse", + "keyboard", + "printers", + "removable-media", + "wacom", + "color", + + /* Details page */ + "region", + "universal-access", + "user-accounts", + "default-apps", + "reset-settings", + "datetime", + "info-overview", +}; + +static guint +get_panel_id_index (const gchar *panel_id) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (panel_order); i++) + { + if (g_str_equal (panel_order[i], panel_id)) + return i; + } + + return 0; +} + +static gint +sort_function (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + CcPanelList *self = CC_PANEL_LIST (user_data); + const gchar *a_id, *b_id; + + a_id = get_panel_id_from_row (self, a); + b_id = get_panel_id_from_row (self, b); + + return get_panel_id_index (a_id) - get_panel_id_index (b_id); +} + +static gint +search_sort_function (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + CcPanelList *self; + RowData *a_data, *b_data; + g_autofree gchar *a_name = NULL; + g_autofree gchar *b_name = NULL; + g_autofree gchar *search = NULL; + gchar *a_strstr, *b_strstr; + gint a_distance, b_distance; + + self = CC_PANEL_LIST (user_data); + search = NULL; + a_data = g_object_get_data (G_OBJECT (a), "data"); + b_data = g_object_get_data (G_OBJECT (b), "data"); + + a_distance = b_distance = G_MAXINT; + + a_name = cc_util_normalize_casefold_and_unaccent (a_data->name); + b_name = cc_util_normalize_casefold_and_unaccent (b_data->name); + g_strstrip (a_name); + g_strstrip (b_name); + + if (self->search_query) + { + search = cc_util_normalize_casefold_and_unaccent (self->search_query); + g_strstrip (search); + } + + /* Default result for empty search */ + if (!search || g_utf8_strlen (search, -1) == 0) + return g_strcmp0 (a_name, b_name); + + a_strstr = g_strstr_len (a_name, -1, search); + b_strstr = g_strstr_len (b_name, -1, search); + + if (a_strstr) + a_distance = g_strstr_len (a_name, -1, search) - a_name; + + if (b_strstr) + b_distance = g_strstr_len (b_name, -1, search) - b_name; + + return a_distance - b_distance; +} + +static void +header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + CcPanelList *self = CC_PANEL_LIST (user_data); + RowData *row_data, *before_data; + + if (!before) + return; + + if (row == self->privacy_row || before == self->privacy_row) + return; + + /* + * We can only retrieve the data after assuring that none + * of the rows are the Privacy row. + */ + row_data = g_object_get_data (G_OBJECT (row), "data"); + before_data = g_object_get_data (G_OBJECT (before), "data"); + + if (row_data->category != before_data->category) + { + GtkWidget *separator; + + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_hexpand (separator, TRUE); + gtk_widget_show (separator); + + gtk_list_box_row_set_header (row, separator); + } + else + { + gtk_list_box_row_set_header (row, NULL); + } +} + +/* + * Callbacks + */ +static void +row_activated_cb (GtkWidget *listbox, + GtkListBoxRow *row, + CcPanelList *self) +{ + RowData *data; + + if (row == self->privacy_row) + { + switch_to_view (self, CC_PANEL_LIST_PRIVACY); + goto out; + } + + /* + * When a panel is selected, the previous one should be + * unselected, except when it's search. + */ + if (listbox != self->search_listbox) + { + if (listbox != self->main_listbox) + gtk_list_box_unselect_all (GTK_LIST_BOX (self->main_listbox)); + + if (listbox != self->privacy_listbox) + gtk_list_box_unselect_all (GTK_LIST_BOX (self->privacy_listbox)); + } + + /* + * Since we're not sure that the activated row is in the + * current view, set the view here. + */ + switch_to_view (self, get_view_from_listbox (self, listbox)); + + data = g_object_get_data (G_OBJECT (row), "data"); + + /* If the activated row is relative to the current panel, and it has + * a custom widget, show the custom widget again. + */ + if (g_strcmp0 (data->id, self->current_panel_id) == 0 && + self->previous_view != CC_PANEL_LIST_SEARCH && + gtk_stack_get_child_by_name (self->stack, "custom-widget") != NULL) + { + CC_TRACE_MSG ("Switching to panel widget"); + + switch_to_view (self, CC_PANEL_LIST_WIDGET); + } + + g_signal_emit (self, signals[SHOW_PANEL], 0, data->id); + +out: + /* After selecting the panel and eventually changing the view, reset the + * autoselect flag. If necessary, cc_panel_list_set_active_panel() will + * set it to FALSE again. + */ + self->autoselect_panel = TRUE; +} + +static void +search_row_activated_cb (GtkWidget *listbox, + GtkListBoxRow *row, + CcPanelList *self) +{ + GtkWidget *real_listbox; + GtkWidget *child; + RowData *data; + + CC_ENTRY; + + data = g_object_get_data (G_OBJECT (row), "data"); + + if (data->category == CC_CATEGORY_PRIVACY) + real_listbox = self->privacy_listbox; + else + real_listbox = self->main_listbox; + + /* Select the correct row */ + for (child = gtk_widget_get_first_child (real_listbox); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + RowData *real_row_data; + + real_row_data = g_object_get_data (G_OBJECT (child), "data"); + + /* + * The main listbox has the Details & Devices rows, and neither + * of them contains "data", so we have to ensure we have valid + * data before going on. + */ + if (!real_row_data) + continue; + + if (g_strcmp0 (real_row_data->id, data->id) == 0) + { + GtkListBoxRow *real_row; + + real_row = GTK_LIST_BOX_ROW (real_row_data->row); + + gtk_list_box_select_row (GTK_LIST_BOX (real_listbox), real_row); + gtk_widget_grab_focus (GTK_WIDGET (real_row)); + + g_signal_emit_by_name (real_row, "activate"); + break; + } + } + + CC_EXIT; +} + +static void +cc_panel_list_finalize (GObject *object) +{ + CcPanelList *self = (CcPanelList *)object; + + g_clear_pointer (&self->search_query, g_free); + g_clear_pointer (&self->current_panel_id, g_free); + g_clear_pointer (&self->id_to_data, g_hash_table_destroy); + g_clear_pointer (&self->id_to_search_data, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_panel_list_parent_class)->finalize (object); +} + +static void +cc_panel_list_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcPanelList *self = CC_PANEL_LIST (object); + + switch (prop_id) + { + case PROP_SEARCH_MODE: + g_value_set_boolean (value, self->view == CC_PANEL_LIST_SEARCH); + break; + + case PROP_SEARCH_QUERY: + g_value_set_string (value, self->search_query); + break; + + case PROP_VIEW: + g_value_set_int (value, self->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_panel_list_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcPanelList *self = CC_PANEL_LIST (object); + + switch (prop_id) + { + case PROP_SEARCH_MODE: + update_search (self); + break; + + case PROP_SEARCH_QUERY: + cc_panel_list_set_search_query (self, g_value_get_string (value)); + break; + + case PROP_VIEW: + switch_to_view (self, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_panel_list_class_init (CcPanelListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_panel_list_finalize; + object_class->get_property = cc_panel_list_get_property; + object_class->set_property = cc_panel_list_set_property; + + /** + * CcPanelList:show-panel: + * + * Emitted when a panel is selected. + */ + signals[SHOW_PANEL] = g_signal_new ("show-panel", + CC_TYPE_PANEL_LIST, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + /** + * CcPanelList:search-mode: + * + * Whether the search is visible or not. + */ + properties[PROP_SEARCH_MODE] = g_param_spec_boolean ("search-mode", + "Search mode", + "Whether it's in search mode or not", + FALSE, + G_PARAM_READWRITE); + + /** + * CcPanelList:search-query: + * + * The search that is being applied to sidelist. + */ + properties[PROP_SEARCH_QUERY] = g_param_spec_string ("search-query", + "Search query", + "The current search query", + NULL, + G_PARAM_READWRITE); + + /** + * CcPanelList:view: + * + * The current view of the sidelist. + */ + properties[PROP_VIEW] = g_param_spec_int ("view", + "View", + "The current view of the sidelist", + CC_PANEL_LIST_MAIN, + CC_PANEL_LIST_SEARCH, + CC_PANEL_LIST_MAIN, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/gtk/cc-panel-list.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcPanelList, privacy_listbox); + gtk_widget_class_bind_template_child (widget_class, CcPanelList, privacy_row); + gtk_widget_class_bind_template_child (widget_class, CcPanelList, main_listbox); + gtk_widget_class_bind_template_child (widget_class, CcPanelList, search_listbox); + gtk_widget_class_bind_template_child (widget_class, CcPanelList, stack); + + gtk_widget_class_bind_template_callback (widget_class, row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, search_row_activated_cb); +} + +static void +cc_panel_list_init (CcPanelList *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->id_to_data = g_hash_table_new (g_str_hash, g_str_equal); + self->id_to_search_data = g_hash_table_new (g_str_hash, g_str_equal); + self->view = CC_PANEL_LIST_MAIN; + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->main_listbox), + sort_function, + self, + NULL); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->privacy_listbox), + sort_function, + self, + NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->main_listbox), + header_func, + self, + NULL); + + /* Search listbox */ + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->search_listbox), + search_sort_function, + self, + NULL); + + gtk_list_box_set_filter_func (GTK_LIST_BOX (self->search_listbox), + filter_func, + self, + NULL); +} + +GtkWidget* +cc_panel_list_new (void) +{ + return g_object_new (CC_TYPE_PANEL_LIST, NULL); +} + +gboolean +cc_panel_list_activate (CcPanelList *self) +{ + GtkListBoxRow *row; + GtkWidget *listbox; + guint i = 0; + + CC_ENTRY; + + g_return_val_if_fail (CC_IS_PANEL_LIST (self), FALSE); + + listbox = get_widget_from_view (self, self->view); + if (!GTK_IS_LIST_BOX (listbox)) + CC_RETURN (FALSE); + + /* Select the first visible row */ + do + row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (listbox), i++); + while (row && !gtk_widget_get_visible (GTK_WIDGET (row))); + + /* If the row is valid, activate it */ + if (row) + { + gtk_list_box_select_row (GTK_LIST_BOX (listbox), row); + gtk_widget_grab_focus (GTK_WIDGET (row)); + + g_signal_emit_by_name (row, "activate"); + } + + CC_RETURN (row != NULL); +} + +const gchar* +cc_panel_list_get_search_query (CcPanelList *self) +{ + g_return_val_if_fail (CC_IS_PANEL_LIST (self), NULL); + + return self->search_query; +} + +void +cc_panel_list_set_search_query (CcPanelList *self, + const gchar *search) +{ + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + if (g_strcmp0 (self->search_query, search) != 0) + { + g_clear_pointer (&self->search_query, g_free); + self->search_query = g_strdup (search); + + update_search (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_QUERY]); + + gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox)); + gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->search_listbox)); + } +} + +CcPanelListView +cc_panel_list_get_view (CcPanelList *self) +{ + g_return_val_if_fail (CC_IS_PANEL_LIST (self), -1); + + return self->view; +} + +void +cc_panel_list_go_previous (CcPanelList *self) +{ + CcPanelListView previous_view; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + previous_view = self->previous_view; + + /* The back button is only visible and clickable outside the main view. If + * the previous view is the widget view itself, it means we went from: + * + * Main → Details or Devices → Widget + * + * to + * + * Main → Details or Devices + * + * A similar situation may happen with search. + * + * To avoid a loop (Details or Devices → Widget → Details or Devices → ...), + * make sure to go back to the main view when the current view is details or + * devices. + */ + if (previous_view == CC_PANEL_LIST_WIDGET || previous_view == CC_PANEL_LIST_SEARCH) + previous_view = CC_PANEL_LIST_MAIN; + + switch_to_view (self, previous_view); +} + +void +cc_panel_list_add_panel (CcPanelList *self, + CcPanelCategory category, + const gchar *id, + const gchar *title, + const gchar *description, + const GStrv keywords, + const gchar *icon, + CcPanelVisibility visibility, + gboolean has_sidebar) +{ + GtkWidget *listbox; + RowData *data, *search_data; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + /* Add the panel to the proper listbox */ + data = row_data_new (category, id, title, description, keywords, icon, visibility, has_sidebar); + gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); + + listbox = get_listbox_from_category (self, category); + gtk_list_box_append (GTK_LIST_BOX (listbox), data->row); + + /* And add to the search listbox too */ + search_data = row_data_new (category, id, title, description, keywords, icon, visibility, has_sidebar); + gtk_widget_set_visible (search_data->row, visibility != CC_PANEL_HIDDEN); + + gtk_list_box_append (GTK_LIST_BOX (self->search_listbox), search_data->row); + + g_hash_table_insert (self->id_to_data, data->id, data); + g_hash_table_insert (self->id_to_search_data, search_data->id, search_data); + + /* Only show the Devices/Details rows when there's at least one panel */ + if (category == CC_CATEGORY_PRIVACY) + gtk_widget_show (GTK_WIDGET (self->privacy_row)); +} + +/** + * cc_panel_list_set_active_panel: + * @self: a #CcPanelList + * @id: the id of the panel to be activated + * + * Sets the current active panel. + */ +void +cc_panel_list_set_active_panel (CcPanelList *self, + const gchar *id) +{ + GtkWidget *listbox; + RowData *data; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + data = g_hash_table_lookup (self->id_to_data, id); + + g_assert (data != NULL); + + /* Stop if row is supposed to be always hidden */ + if (data->visibility == CC_PANEL_HIDDEN) + { + g_debug ("Panel '%s' is always hidden, stopping.", id); + cc_panel_list_activate (self); + return; + } + + /* If the currently selected panel is not always visible, for example when + * the panel is only visible on search and we're temporarily seeing it, make + * sure to hide it after the user moves out. + */ + if (self->current_panel_id != NULL && g_strcmp0 (self->current_panel_id, id) != 0) + { + RowData *current_row_data; + + current_row_data = g_hash_table_lookup (self->id_to_data, self->current_panel_id); + + /* We cannot be showing a non-existent panel */ + g_assert (current_row_data != NULL); + + gtk_widget_set_visible (current_row_data->row, current_row_data->visibility == CC_PANEL_VISIBLE); + } + + listbox = gtk_widget_get_parent (data->row); + + /* The row might be hidden now, so make sure it's visible */ + gtk_widget_show (data->row); + + gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row)); + gtk_widget_grab_focus (data->row); + + /* When setting the active panel programatically, prevent from + * autoselecting the first panel of the new view. + */ + self->autoselect_panel = FALSE; + + if (self->view != CC_PANEL_LIST_WIDGET) + g_signal_emit_by_name (data->row, "activate"); + + /* Store the current panel id */ + g_clear_pointer (&self->current_panel_id, g_free); + self->current_panel_id = g_strdup (id); +} + +/** + * cc_panel_list_set_panel_visibility: + * @self: a #CcPanelList + * @id: the id of the panel + * @visibility: visibility of panel with @id + * + * Sets the visibility of panel with @id. @id must be a valid + * id with a corresponding panel. + */ +void +cc_panel_list_set_panel_visibility (CcPanelList *self, + const gchar *id, + CcPanelVisibility visibility) +{ + RowData *data, *search_data; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + data = g_hash_table_lookup (self->id_to_data, id); + search_data = g_hash_table_lookup (self->id_to_search_data, id); + + g_assert (data != NULL); + g_assert (search_data != NULL); + + data->visibility = visibility; + + /* If this is the currently selected row, and the panel can't be displayed + * (i.e. visibility != VISIBLE), then select the next possible row */ + if (gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (data->row)) && + visibility != CC_PANEL_VISIBLE) + { + activate_row_below (self, data); + } + + gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); + gtk_widget_set_visible (search_data->row, visibility =! CC_PANEL_HIDDEN); +} + +void +cc_panel_list_add_sidebar_widget (CcPanelList *self, + GtkWidget *widget) +{ + GtkWidget *previous; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + previous = get_widget_from_view (self, CC_PANEL_LIST_WIDGET); + if (previous) + gtk_stack_remove (self->stack, previous); + + if (widget) + { + gtk_stack_add_named (self->stack, widget, "custom-widget"); + switch_to_view (self, CC_PANEL_LIST_WIDGET); + } +} + +void +cc_panel_list_set_selection_mode (CcPanelList *self, + GtkSelectionMode selection_mode) +{ + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->main_listbox), selection_mode); + + /* When selection mode changed, selection will be lost. So reselect */ + if (selection_mode == GTK_SELECTION_SINGLE && self->current_panel_id) + { + GtkWidget *listbox; + RowData *data; + + data = g_hash_table_lookup (self->id_to_data, self->current_panel_id); + listbox = gtk_widget_get_parent (data->row); + gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row)); + } +} + diff --git a/shell/cc-panel-list.h b/shell/cc-panel-list.h new file mode 100644 index 0000000..39becac --- /dev/null +++ b/shell/cc-panel-list.h @@ -0,0 +1,78 @@ +/* cc-panel-list.c + * + * Copyright (C) 2016 Endless, Inc + * + * 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 2 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 . + * + * Author: Georges Basile Stavracas Neto + */ + +#pragma once + +#include + +#include "cc-panel.h" +#include "cc-shell-model.h" + +G_BEGIN_DECLS + +typedef enum +{ + CC_PANEL_LIST_MAIN, + CC_PANEL_LIST_PRIVACY, + CC_PANEL_LIST_WIDGET, + CC_PANEL_LIST_SEARCH +} CcPanelListView; + +#define CC_TYPE_PANEL_LIST (cc_panel_list_get_type()) + +G_DECLARE_FINAL_TYPE (CcPanelList, cc_panel_list, CC, PANEL_LIST, AdwBin) + +GtkWidget* cc_panel_list_new (void); + +gboolean cc_panel_list_activate (CcPanelList *self); + +const gchar* cc_panel_list_get_search_query (CcPanelList *self); + +void cc_panel_list_set_search_query (CcPanelList *self, + const gchar *search); + +CcPanelListView cc_panel_list_get_view (CcPanelList *self); + +void cc_panel_list_go_previous (CcPanelList *self); + +void cc_panel_list_add_panel (CcPanelList *self, + CcPanelCategory category, + const gchar *id, + const gchar *title, + const gchar *description, + const GStrv keywords, + const gchar *icon, + CcPanelVisibility visibility, + gboolean has_sidebar); + +void cc_panel_list_set_active_panel (CcPanelList *self, + const gchar *id); + +void cc_panel_list_set_panel_visibility (CcPanelList *self, + const gchar *id, + CcPanelVisibility visibility); + +void cc_panel_list_add_sidebar_widget (CcPanelList *self, + GtkWidget *widget); + +void cc_panel_list_set_selection_mode (CcPanelList *self, + GtkSelectionMode selection_mode); + +G_END_DECLS diff --git a/shell/cc-panel-list.ui b/shell/cc-panel-list.ui new file mode 100644 index 0000000..c9e90e8 --- /dev/null +++ b/shell/cc-panel-list.ui @@ -0,0 +1,133 @@ + + + + + diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c new file mode 100644 index 0000000..17f9601 --- /dev/null +++ b/shell/cc-panel-loader.c @@ -0,0 +1,332 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2012 Giovanni Campagna + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#include + +#include +#include +#include + +#include "cc-panel.h" +#include "cc-panel-loader.h" + +#ifndef CC_PANEL_LOADER_NO_GTYPES + +/* Extension points */ +extern GType cc_applications_panel_get_type (void); +extern GType cc_background_panel_get_type (void); +#ifdef BUILD_BLUETOOTH +extern GType cc_bluetooth_panel_get_type (void); +#endif /* BUILD_BLUETOOTH */ +extern GType cc_color_panel_get_type (void); +extern GType cc_date_time_panel_get_type (void); +extern GType cc_default_apps_panel_get_type (void); +extern GType cc_display_panel_get_type (void); +extern GType cc_info_overview_panel_get_type (void); +extern GType cc_keyboard_panel_get_type (void); +extern GType cc_mouse_panel_get_type (void); +extern GType cc_multitasking_panel_get_type (void); +#ifdef BUILD_NETWORK +extern GType cc_network_panel_get_type (void); +extern GType cc_wifi_panel_get_type (void); +#endif /* BUILD_NETWORK */ +extern GType cc_notifications_panel_get_type (void); +extern GType cc_online_accounts_panel_get_type (void); +extern GType cc_power_panel_get_type (void); +extern GType cc_printers_panel_get_type (void); +extern GType cc_region_panel_get_type (void); +extern GType cc_removable_media_panel_get_type (void); +extern GType cc_search_panel_get_type (void); +extern GType cc_sharing_panel_get_type (void); +extern GType cc_sound_panel_get_type (void); +#ifdef BUILD_THUNDERBOLT +extern GType cc_bolt_panel_get_type (void); +#endif /* BUILD_THUNDERBOLT */ +extern GType cc_ua_panel_get_type (void); +extern GType cc_user_panel_get_type (void); +#ifdef BUILD_WACOM +extern GType cc_wacom_panel_get_type (void); +#endif /* BUILD_WACOM */ +#ifdef BUILD_WWAN +extern GType cc_wwan_panel_get_type (void); +#endif /* BUILD_WWAN */ +extern GType cc_location_panel_get_type (void); +extern GType cc_firmware_security_panel_get_type (void); +extern GType cc_camera_panel_get_type (void); +extern GType cc_microphone_panel_get_type (void); +extern GType cc_usage_panel_get_type (void); +extern GType cc_screen_panel_get_type (void); +extern GType cc_diagnostics_panel_get_type (void); + +/* Static init functions */ +extern void cc_diagnostics_panel_static_init_func (void); +#ifdef BUILD_NETWORK +extern void cc_wifi_panel_static_init_func (void); +#endif /* BUILD_NETWORK */ +#ifdef BUILD_WACOM +extern void cc_wacom_panel_static_init_func (void); +#endif /* BUILD_WACOM */ +#ifdef BUILD_WWAN +extern void cc_wwan_panel_static_init_func (void); +#endif /* BUILD_WWAN */ +extern void cc_firmware_security_panel_static_init_func (void); + +#define PANEL_TYPE(name, get_type, init_func) { name, get_type, init_func } + +#else /* CC_PANEL_LOADER_NO_GTYPES */ + +#define PANEL_TYPE(name, get_type, init_func) { name } + +#endif + +static CcPanelLoaderVtable default_panels[] = +{ + PANEL_TYPE("applications", cc_applications_panel_get_type, NULL), + PANEL_TYPE("background", cc_background_panel_get_type, NULL), +#ifdef BUILD_BLUETOOTH + PANEL_TYPE("bluetooth", cc_bluetooth_panel_get_type, NULL), +#endif + PANEL_TYPE("camera", cc_camera_panel_get_type, NULL), + PANEL_TYPE("color", cc_color_panel_get_type, NULL), + PANEL_TYPE("datetime", cc_date_time_panel_get_type, NULL), + PANEL_TYPE("default-apps", cc_default_apps_panel_get_type, NULL), + PANEL_TYPE("diagnostics", cc_diagnostics_panel_get_type, cc_diagnostics_panel_static_init_func), + PANEL_TYPE("display", cc_display_panel_get_type, NULL), + PANEL_TYPE("info-overview", cc_info_overview_panel_get_type, NULL), + PANEL_TYPE("keyboard", cc_keyboard_panel_get_type, NULL), + PANEL_TYPE("location", cc_location_panel_get_type, NULL), + PANEL_TYPE("firmware-security",cc_firmware_security_panel_get_type, cc_firmware_security_panel_static_init_func), + PANEL_TYPE("microphone", cc_microphone_panel_get_type, NULL), + PANEL_TYPE("mouse", cc_mouse_panel_get_type, NULL), + PANEL_TYPE("multitasking", cc_multitasking_panel_get_type, NULL), +#ifdef BUILD_NETWORK + PANEL_TYPE("network", cc_network_panel_get_type, NULL), + PANEL_TYPE("wifi", cc_wifi_panel_get_type, cc_wifi_panel_static_init_func), +#endif + PANEL_TYPE("notifications", cc_notifications_panel_get_type, NULL), + PANEL_TYPE("online-accounts", cc_online_accounts_panel_get_type, NULL), + PANEL_TYPE("power", cc_power_panel_get_type, NULL), + PANEL_TYPE("printers", cc_printers_panel_get_type, NULL), + PANEL_TYPE("region", cc_region_panel_get_type, NULL), + PANEL_TYPE("removable-media", cc_removable_media_panel_get_type, NULL), + PANEL_TYPE("screen", cc_screen_panel_get_type, NULL), + PANEL_TYPE("search", cc_search_panel_get_type, NULL), + PANEL_TYPE("sharing", cc_sharing_panel_get_type, NULL), + PANEL_TYPE("sound", cc_sound_panel_get_type, NULL), +#ifdef BUILD_THUNDERBOLT + PANEL_TYPE("thunderbolt", cc_bolt_panel_get_type, NULL), +#endif + PANEL_TYPE("universal-access", cc_ua_panel_get_type, NULL), + PANEL_TYPE("usage", cc_usage_panel_get_type, NULL), + PANEL_TYPE("user-accounts", cc_user_panel_get_type, NULL), +#ifdef BUILD_WACOM + PANEL_TYPE("wacom", cc_wacom_panel_get_type, cc_wacom_panel_static_init_func), +#endif +#ifdef BUILD_WWAN + PANEL_TYPE("wwan", cc_wwan_panel_get_type, cc_wwan_panel_static_init_func), +#endif +}; + +/* Override for the panel vtable. When NULL, the default_panels will + * be used. + */ +static CcPanelLoaderVtable *panels_vtable = default_panels; +static gsize panels_vtable_len = G_N_ELEMENTS (default_panels); + + +static int +parse_categories (GDesktopAppInfo *app) +{ + g_auto(GStrv) split = NULL; + const gchar *categories; + gint retval; + + categories = g_desktop_app_info_get_categories (app); + split = g_strsplit (categories, ";", -1); + + retval = -1; + +#define const_strv(s) ((const gchar* const*) s) + + if (g_strv_contains (const_strv (split), "X-GNOME-ConnectivitySettings")) + retval = CC_CATEGORY_CONNECTIVITY; + else if (g_strv_contains (const_strv (split), "X-GNOME-PersonalizationSettings")) + retval = CC_CATEGORY_PERSONALIZATION; + else if (g_strv_contains (const_strv (split), "X-GNOME-AccountSettings")) + retval = CC_CATEGORY_ACCOUNT; + else if (g_strv_contains (const_strv (split), "X-GNOME-DevicesSettings")) + retval = CC_CATEGORY_DEVICES; + else if (g_strv_contains (const_strv (split), "X-GNOME-DetailsSettings")) + retval = CC_CATEGORY_DETAILS; + else if (g_strv_contains (const_strv (split), "X-GNOME-PrivacySettings")) + retval = CC_CATEGORY_PRIVACY; + else if (g_strv_contains (const_strv (split), "HardwareSettings")) + retval = CC_CATEGORY_HARDWARE; + +#undef const_strv + + if (retval < 0) + { + g_warning ("Invalid categories %s for panel %s", + categories, g_app_info_get_id (G_APP_INFO (app))); + } + + return retval; +} + +#ifndef CC_PANEL_LOADER_NO_GTYPES + +static GHashTable *panel_types; + +static void +ensure_panel_types (void) +{ + int i; + + if (G_LIKELY (panel_types != NULL)) + return; + + panel_types = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < panels_vtable_len; i++) + g_hash_table_insert (panel_types, (char*)panels_vtable[i].name, panels_vtable[i].get_type); +} + +/** + * cc_panel_loader_load_by_name: + * @shell: a #CcShell implementation + * @name: name of the panel + * @parameters: parameters passed to the new panel + * + * Creates a new instance of a #CcPanel from @name, and sets the + * @shell and @parameters properties at construction time. + */ +CcPanel * +cc_panel_loader_load_by_name (CcShell *shell, + const gchar *name, + const gchar *title, + GVariant *parameters) +{ + GType (*get_type) (void); + + ensure_panel_types (); + + get_type = g_hash_table_lookup (panel_types, name); + g_assert (get_type != NULL); + + return g_object_new (get_type (), + "shell", shell, + "parameters", parameters, + "title", title, + NULL); +} + +#endif /* CC_PANEL_LOADER_NO_GTYPES */ + +/** + * cc_panel_loader_fill_model: + * @model: a #CcShellModel + * + * Fills @model with information from the available panels. It + * iterates over the panel vtable, gathering the panel names, + * build the desktop filename from it, and retrieves additional + * information from it. + */ +void +cc_panel_loader_fill_model (CcShellModel *model) +{ + guint i; + + for (i = 0; i < panels_vtable_len; i++) + { + g_autoptr(GDesktopAppInfo) app = NULL; + g_autofree gchar *desktop_name = NULL; + gint category; + + desktop_name = g_strconcat ("gnome-", panels_vtable[i].name, "-panel.desktop", NULL); + app = g_desktop_app_info_new (desktop_name); + + if (!app) + { + g_warning ("Ignoring broken panel %s (missing desktop file)", panels_vtable[i].name); + continue; + } + + category = parse_categories (app); + if (G_UNLIKELY (category < 0)) + continue; + + cc_shell_model_add_item (model, category, G_APP_INFO (app), panels_vtable[i].name); + } + + /* If there's an static init function, execute it after adding all panels to + * the model. This will allow the panels to show or hide themselves without + * having an instance running. + */ +#ifndef CC_PANEL_LOADER_NO_GTYPES + for (i = 0; i < panels_vtable_len; i++) + { + if (panels_vtable[i].static_init_func) + panels_vtable[i].static_init_func (); + } +#endif +} + +/** + * cc_panel_loader_list_panels: + * + * Prints the list of panels from the current panel vtable, + * usually as response to running GNOME Settings with the + * '--list' command line argument. + */ +void +cc_panel_loader_list_panels (void) +{ + guint i; + + g_print ("%s\n", _("Available panels:")); + + for (i = 0; i < panels_vtable_len; i++) + g_print ("\t%s\n", panels_vtable[i].name); + +} + +/** + * cc_panel_loader_override_vtable: + * @override_vtable: the new panel vtable + * @n_elements: number of items of @override_vtable + * + * Override the default panel vtable so that GNOME Settings loads + * a custom set of panels. Intended to be used by tests to inject + * panels that exercise specific interactions with CcWindow (e.g. + * header widgets, permissions, etc). + */ +void +cc_panel_loader_override_vtable (CcPanelLoaderVtable *override_vtable, + gsize n_elements) +{ + g_assert (override_vtable != NULL); + g_assert (n_elements > 0); + + g_debug ("Overriding default panel vtable"); + + panels_vtable = override_vtable; + panels_vtable_len = n_elements; +} diff --git a/shell/cc-panel-loader.h b/shell/cc-panel-loader.h new file mode 100644 index 0000000..72b3345 --- /dev/null +++ b/shell/cc-panel-loader.h @@ -0,0 +1,53 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2012 Giovanni Campagna + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include "config.h" + +#include +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct +{ + const gchar *name; + +#ifndef CC_PANEL_LOADER_NO_GTYPES + GType (*get_type)(void); + CcPanelStaticInitFunc static_init_func; +#endif +} CcPanelLoaderVtable; + +void cc_panel_loader_fill_model (CcShellModel *model); +void cc_panel_loader_list_panels (void); +CcPanel *cc_panel_loader_load_by_name (CcShell *shell, + const char *name, + const gchar *title, + GVariant *parameters); + +void cc_panel_loader_override_vtable (CcPanelLoaderVtable *override_vtable, + gsize n_elements); + +G_END_DECLS + diff --git a/shell/cc-panel-private.h b/shell/cc-panel-private.h new file mode 100644 index 0000000..5cc4ebc --- /dev/null +++ b/shell/cc-panel-private.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Endless OS Foundation, LLC + * + * 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 2 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 . + * + * Authors: Georges Basile Stavracas Neto + */ + +#pragma once + +#include "cc-panel.h" + +void cc_panel_set_folded (CcPanel *panel, + gboolean folded); + +G_END_DECLS + diff --git a/shell/cc-panel.c b/shell/cc-panel.c new file mode 100644 index 0000000..cd3e0a8 --- /dev/null +++ b/shell/cc-panel.c @@ -0,0 +1,433 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2010 Intel, Inc + * + * 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 2 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 . + * + * Authors: William Jon McCann + * Thomas Wood + * + */ + +/** + * SECTION:cc-panel + * @short_description: An abstract class for Control Center panels + * + * CcPanel is an abstract class used to implement panels for the shell. A + * panel contains a collection of related settings that are displayed within + * the shell window. + * + * # Buildable + * + * CcPanel implements the GtkBuildable interface, and allows having different + * types of children for convenience. + * + * It is possible to add widgets to the start and end of the panel titlebar + * using, respectively, the `titlebar-start` and `titlebar-end` child types. + * It is also possible to override the titlebar entirely with a custom titlebar + * using the `titlebar` child type. + * + * Most panels will use the `content` child type, which sets the panel content + * beneath the titlebar. + * + * At last, it is possible to override all custom CcPanel widgets by not setting + * any child type. + */ + +#include "config.h" + +#include "cc-panel-private.h" + +#include +#include + +#include +#include + +typedef struct +{ + AdwBin *content_bin; + GtkBox *main_box; + AdwBin *titlebar_bin; + AdwHeaderBar *titlebar; + + CcShell *shell; + GCancellable *cancellable; + gboolean folded; + gchar *title; +} CcPanelPrivate; + +static void cc_panel_buildable_init (GtkBuildableIface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (CcPanel, cc_panel, ADW_TYPE_BIN, + G_ADD_PRIVATE (CcPanel) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, cc_panel_buildable_init)) + +static GtkBuildableIface *parent_buildable_iface; + +enum +{ + SIDEBAR_ACTIVATED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_SHELL, + PROP_PARAMETERS, + PROP_FOLDED, + PROP_TITLE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static guint signals [LAST_SIGNAL] = { 0 }; + +/* GtkBuildable interface */ + +static void +cc_panel_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (buildable)); + + if (GTK_IS_WIDGET (child) && !priv->main_box) + { + adw_bin_set_child (ADW_BIN (buildable), GTK_WIDGET (child)); + return; + } + + if (g_strcmp0 (type, "content") == 0) + adw_bin_set_child (priv->content_bin, GTK_WIDGET (child)); + else if (g_strcmp0 (type, "titlebar-start") == 0) + adw_header_bar_pack_start (priv->titlebar, GTK_WIDGET (child)); + else if (g_strcmp0 (type, "titlebar-end") == 0) + adw_header_bar_pack_end (priv->titlebar, GTK_WIDGET (child)); + else if (g_strcmp0 (type, "titlebar") == 0) + adw_bin_set_child (priv->titlebar_bin, GTK_WIDGET (child)); + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +cc_panel_buildable_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->add_child = cc_panel_buildable_add_child; +} + +/* GObject overrides */ + +static void +cc_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); + + switch (prop_id) + { + case PROP_SHELL: + /* construct only property */ + priv->shell = g_value_get_object (value); + break; + + case PROP_PARAMETERS: + { + g_autoptr(GVariant) v = NULL; + GVariant *parameters; + gsize n_parameters; + + parameters = g_value_get_variant (value); + + if (parameters == NULL) + return; + + n_parameters = g_variant_n_children (parameters); + if (n_parameters == 0) + return; + + g_variant_get_child (parameters, 0, "v", &v); + + if (!g_variant_is_of_type (v, G_VARIANT_TYPE_DICTIONARY)) + g_warning ("Wrong type for the first argument GVariant, expected 'a{sv}' but got '%s'", + (gchar *)g_variant_get_type (v)); + else if (g_variant_n_children (v) > 0) + g_warning ("Ignoring additional flags"); + + if (n_parameters > 1) + g_warning ("Ignoring additional parameters"); + + break; + } + + case PROP_TITLE: + priv->title = g_value_dup_string (value); + break; + + case PROP_FOLDED: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); + + switch (prop_id) + { + case PROP_SHELL: + g_value_set_object (value, priv->shell); + break; + + case PROP_FOLDED: + g_value_set_boolean (value, priv->folded); + break; + + case PROP_TITLE: + g_value_set_string (value, priv->title); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_panel_finalize (GObject *object) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + + g_clear_pointer (&priv->title, g_free); + + G_OBJECT_CLASS (cc_panel_parent_class)->finalize (object); +} + +static void +cc_panel_class_init (CcPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_panel_get_property; + object_class->set_property = cc_panel_set_property; + object_class->finalize = cc_panel_finalize; + + signals[SIDEBAR_ACTIVATED] = g_signal_new ("sidebar-activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + properties[PROP_SHELL] = g_param_spec_object ("shell", + "Shell", + "Shell the Panel resides in", + CC_TYPE_SHELL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + properties[PROP_FOLDED] = g_param_spec_boolean ("folded", NULL, NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_PARAMETERS] = g_param_spec_variant ("parameters", + "Structured parameters", + "Additional parameters passed externally (ie. command line, D-Bus activation)", + G_VARIANT_TYPE ("av"), + NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_TITLE] = g_param_spec_string ("title", NULL, NULL, NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/gtk/cc-panel.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, CcPanel, content_bin); + gtk_widget_class_bind_template_child_private (widget_class, CcPanel, main_box); + gtk_widget_class_bind_template_child_private (widget_class, CcPanel, titlebar_bin); + gtk_widget_class_bind_template_child_private (widget_class, CcPanel, titlebar); +} + +static void +cc_panel_init (CcPanel *panel) +{ + gtk_widget_init_template (GTK_WIDGET (panel)); +} + +/** + * cc_panel_get_shell: + * @panel: A #CcPanel + * + * Get the shell that the panel resides in + * + * Returns: a #CcShell + */ +CcShell * +cc_panel_get_shell (CcPanel *panel) +{ + CcPanelPrivate *priv; + + g_return_val_if_fail (CC_IS_PANEL (panel), NULL); + + priv = cc_panel_get_instance_private (panel); + + return priv->shell; +} + +const gchar* +cc_panel_get_help_uri (CcPanel *panel) +{ + CcPanelClass *class = CC_PANEL_GET_CLASS (panel); + + if (class->get_help_uri) + return class->get_help_uri (panel); + + return NULL; +} + +GtkWidget* +cc_panel_get_sidebar_widget (CcPanel *panel) +{ + CcPanelClass *class = CC_PANEL_GET_CLASS (panel); + + if (class->get_sidebar_widget) + { + GtkWidget *sidebar_widget; + + sidebar_widget = class->get_sidebar_widget (panel); + g_assert (sidebar_widget != NULL); + + return sidebar_widget; + } + + return NULL; +} + +GCancellable * +cc_panel_get_cancellable (CcPanel *panel) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (panel); + + g_return_val_if_fail (CC_IS_PANEL (panel), NULL); + + if (priv->cancellable == NULL) + priv->cancellable = g_cancellable_new (); + + return priv->cancellable; +} + +void +cc_panel_set_folded (CcPanel *panel, + gboolean folded) +{ + CcPanelPrivate *priv; + + g_return_if_fail (CC_IS_PANEL (panel)); + + priv = cc_panel_get_instance_private (panel); + + if (priv->folded != folded) + { + g_debug ("Panel %s folded: %s", + G_OBJECT_TYPE_NAME (panel), + folded ? "yes" : "no"); + + priv->folded = folded; + g_object_notify_by_pspec (G_OBJECT (panel), properties[PROP_FOLDED]); + } +} + +gboolean +cc_panel_get_folded (CcPanel *panel) +{ + CcPanelPrivate *priv; + + g_return_val_if_fail (CC_IS_PANEL (panel), FALSE); + + priv = cc_panel_get_instance_private (panel); + return priv->folded; +} + +GtkWidget* +cc_panel_get_content (CcPanel *panel) +{ + CcPanelPrivate *priv; + + g_return_val_if_fail (CC_IS_PANEL (panel), NULL); + + priv = cc_panel_get_instance_private (panel); + return adw_bin_get_child (priv->content_bin); +} + +void +cc_panel_set_content (CcPanel *panel, + GtkWidget *content) +{ + CcPanelPrivate *priv; + + g_return_if_fail (CC_IS_PANEL (panel)); + + priv = cc_panel_get_instance_private (panel); + adw_bin_set_child (priv->content_bin, content); +} + +GtkWidget* +cc_panel_get_titlebar (CcPanel *panel) +{ + CcPanelPrivate *priv; + + g_return_val_if_fail (CC_IS_PANEL (panel), NULL); + + priv = cc_panel_get_instance_private (panel); + return adw_bin_get_child (priv->titlebar_bin); +} + +void +cc_panel_set_titlebar (CcPanel *panel, + GtkWidget *titlebar) +{ + CcPanelPrivate *priv; + + g_return_if_fail (CC_IS_PANEL (panel)); + + priv = cc_panel_get_instance_private (panel); + adw_bin_set_child (priv->titlebar_bin, titlebar); +} + +void +cc_panel_deactivate (CcPanel *panel) +{ + CcPanelPrivate *priv = cc_panel_get_instance_private (panel); + + g_cancellable_cancel (priv->cancellable); +} diff --git a/shell/cc-panel.h b/shell/cc-panel.h new file mode 100644 index 0000000..8cf09ac --- /dev/null +++ b/shell/cc-panel.h @@ -0,0 +1,109 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2010 Intel, Inc + * + * 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 2 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 . + * + * Authors: William Jon McCann + * Thomas Wood + */ + +#pragma once + +#include + +/** + * Utility macro used to register panels + * + * use: CC_PANEL_REGISTER (PluginName, plugin_name) + */ +#define CC_PANEL_REGISTER(PluginName, plugin_name) G_DEFINE_TYPE (PluginName, plugin_name, CC_TYPE_PANEL) + +/** + * CcPanelStaticInitFunc: + * + * Function that statically allocates resources and initializes + * any data that the panel will make use of during runtime. + * + * If panels represent hardware that can potentially not exist, + * e.g. the Wi-Fi panel, these panels can use this function to + * show or hide themselves without needing to have an instance + * created and running. + */ +typedef void (*CcPanelStaticInitFunc) (void); + + +#define CC_TYPE_PANEL (cc_panel_get_type()) +G_DECLARE_DERIVABLE_TYPE (CcPanel, cc_panel, CC, PANEL, AdwBin) + +/** + * CcPanelVisibility: + * + * @CC_PANEL_HIDDEN: Panel is hidden from search and sidebar, and not reachable. + * @CC_PANEL_VISIBLE_IN_SEARCH: Panel is hidden from main view, but can be accessed from search. + * @CC_PANEL_VISIBLE: Panel is visible everywhere. + */ +typedef enum +{ + CC_PANEL_HIDDEN, + CC_PANEL_VISIBLE_IN_SEARCH, + CC_PANEL_VISIBLE, +} CcPanelVisibility; + +/* cc-shell.h requires CcPanel, so make sure it is defined first */ +#include "cc-shell.h" + +G_BEGIN_DECLS + +/** + * CcPanelClass: + * + * The contents of this struct are private and should not be accessed directly. + */ +struct _CcPanelClass +{ + /*< private >*/ + AdwBinClass parent_class; + + const gchar* (*get_help_uri) (CcPanel *panel); + + GtkWidget* (*get_sidebar_widget) (CcPanel *panel); +}; + +CcShell* cc_panel_get_shell (CcPanel *panel); + +GPermission* cc_panel_get_permission (CcPanel *panel); + +const gchar* cc_panel_get_help_uri (CcPanel *panel); + +GtkWidget* cc_panel_get_sidebar_widget (CcPanel *panel); + +GCancellable *cc_panel_get_cancellable (CcPanel *panel); + +gboolean cc_panel_get_folded (CcPanel *panel); + +GtkWidget* cc_panel_get_content (CcPanel *panel); + +void cc_panel_set_content (CcPanel *panel, + GtkWidget *content); + +GtkWidget* cc_panel_get_titlebar (CcPanel *panel); + +void cc_panel_set_titlebar (CcPanel *panel, + GtkWidget *titlebar); + +void cc_panel_deactivate (CcPanel *panel); + +G_END_DECLS diff --git a/shell/cc-panel.ui b/shell/cc-panel.ui new file mode 100644 index 0000000..e7798bb --- /dev/null +++ b/shell/cc-panel.ui @@ -0,0 +1,45 @@ + + + + diff --git a/shell/cc-shell-model.c b/shell/cc-shell-model.c new file mode 100644 index 0000000..19fc9c6 --- /dev/null +++ b/shell/cc-shell-model.c @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2009, 2010 Intel, Inc. + * Copyright (c) 2010 Red Hat, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#include "cc-shell-model.h" +#include "cc-util.h" + +#include + +#include + +#define GNOME_SETTINGS_PANEL_ID_KEY "X-GNOME-Settings-Panel" +#define GNOME_SETTINGS_PANEL_CATEGORY GNOME_SETTINGS_PANEL_ID_KEY +#define GNOME_SETTINGS_PANEL_ID_KEYWORDS "Keywords" + +struct _CcShellModel +{ + GtkListStore parent; + + GStrv sort_terms; +}; + +G_DEFINE_TYPE (CcShellModel, cc_shell_model, GTK_TYPE_LIST_STORE) + +static gint +sort_by_name (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b) +{ + g_autofree gchar *a_name = NULL; + g_autofree gchar *b_name = NULL; + + gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1); + gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1); + + return g_strcmp0 (a_name, b_name); +} + +static gint +sort_by_name_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gboolean a_match, b_match; + g_autofree gchar *a_name = NULL; + g_autofree gchar *b_name = NULL; + gint i; + + gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1); + gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1); + + for (i = 0; terms[i]; ++i) + { + a_match = strstr (a_name, terms[i]) != NULL; + b_match = strstr (b_name, terms[i]) != NULL; + + if (a_match && !b_match) + return -1; + else if (!a_match && b_match) + return 1; + } + + return 0; +} + +static gint +count_matches (gchar **keywords, + gchar **terms) +{ + gint i, j, c; + + if (!keywords || !terms) + return 0; + + c = 0; + + for (i = 0; terms[i]; ++i) + for (j = 0; keywords[j]; ++j) + if (strstr (keywords[j], terms[i])) + c += 1; + + return c; +} + +static gint +sort_by_keywords_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint a_matches, b_matches; + g_auto(GStrv) a_keywords = NULL; + g_auto(GStrv) b_keywords = NULL; + + gtk_tree_model_get (model, a, COL_KEYWORDS, &a_keywords, -1); + gtk_tree_model_get (model, b, COL_KEYWORDS, &b_keywords, -1); + + a_matches = count_matches (a_keywords, terms); + b_matches = count_matches (b_keywords, terms); + + if (a_matches > b_matches) + return -1; + else if (a_matches < b_matches) + return 1; + + return 0; +} + +static gint +sort_by_description_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint a_matches, b_matches; + g_autofree gchar *a_description = NULL; + g_autofree gchar *b_description = NULL; + g_auto(GStrv) a_description_split = NULL; + g_auto(GStrv) b_description_split = NULL; + + gtk_tree_model_get (model, a, COL_DESCRIPTION, &a_description, -1); + gtk_tree_model_get (model, b, COL_DESCRIPTION, &b_description, -1); + + if (a_description && !b_description) + return -1; + else if (!a_description && b_description) + return 1; + else if (!a_description && !b_description) + return 0; + + a_description_split = g_strsplit (a_description, " ", -1); + b_description_split = g_strsplit (b_description, " ", -1); + + a_matches = count_matches (a_description_split, terms); + b_matches = count_matches (b_description_split, terms); + + if (a_matches > b_matches) + return -1; + else if (a_matches < b_matches) + return 1; + + return 0; +} + +static gint +sort_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint rval; + + rval = sort_by_name_with_terms (model, a, b, terms); + if (rval) + return rval; + + rval = sort_by_keywords_with_terms (model, a, b, terms); + if (rval) + return rval; + + rval = sort_by_description_with_terms (model, a, b, terms); + if (rval) + return rval; + + return sort_by_name (model, a, b); +} + +static gint +cc_shell_model_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer data) +{ + CcShellModel *self = data; + + if (!self->sort_terms || !self->sort_terms[0]) + return sort_by_name (model, a, b); + else + return sort_with_terms (model, a, b, self->sort_terms); +} + +static void +cc_shell_model_finalize (GObject *object) +{ + CcShellModel *self = CC_SHELL_MODEL (object); + + g_clear_pointer (&self->sort_terms, g_strfreev); + + G_OBJECT_CLASS (cc_shell_model_parent_class)->finalize (object); +} + +static void +cc_shell_model_class_init (CcShellModelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_shell_model_finalize; +} + +static void +cc_shell_model_init (CcShellModel *self) +{ + GType types[] = {G_TYPE_STRING, G_TYPE_STRING, G_TYPE_APP_INFO, G_TYPE_STRING, G_TYPE_UINT, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_ICON, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_BOOLEAN }; + + gtk_list_store_set_column_types (GTK_LIST_STORE (self), + N_COLS, types); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self), + cc_shell_model_sort_func, + self, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); +} + +CcShellModel * +cc_shell_model_new (void) +{ + return g_object_new (CC_TYPE_SHELL_MODEL, NULL); +} + +static char ** +get_casefolded_keywords (GAppInfo *appinfo) +{ + const char * const * keywords; + char **casefolded_keywords; + int i, n; + + keywords = g_desktop_app_info_get_keywords (G_DESKTOP_APP_INFO (appinfo)); + n = keywords ? g_strv_length ((char**) keywords) : 0; + casefolded_keywords = g_new (char*, n+1); + + for (i = 0; i < n; i++) + casefolded_keywords[i] = cc_util_normalize_casefold_and_unaccent (keywords[i]); + casefolded_keywords[n] = NULL; + + return casefolded_keywords; +} + +static GIcon * +symbolicize_g_icon (GIcon *gicon) +{ + const gchar * const *names; + g_autofree gchar *new_name = NULL; + + if (!G_IS_THEMED_ICON (gicon)) + return g_object_ref (gicon); + + names = g_themed_icon_get_names (G_THEMED_ICON (gicon)); + + if (g_str_has_suffix (names[0], "-symbolic")) + return g_object_ref (gicon); + + new_name = g_strdup_printf ("%s-symbolic", names[0]); + return g_themed_icon_new_with_default_fallbacks (new_name); +} + +void +cc_shell_model_add_item (CcShellModel *model, + CcPanelCategory category, + GAppInfo *appinfo, + const char *id) +{ + g_autoptr(GIcon) icon = NULL; + const gchar *name = g_app_info_get_name (appinfo); + const gchar *comment = g_app_info_get_description (appinfo); + g_auto(GStrv) keywords = NULL; + g_autofree gchar *casefolded_name = NULL; + g_autofree gchar *casefolded_description = NULL; + gboolean has_sidebar; + + casefolded_name = cc_util_normalize_casefold_and_unaccent (name); + casefolded_description = cc_util_normalize_casefold_and_unaccent (comment); + keywords = get_casefolded_keywords (appinfo); + icon = symbolicize_g_icon (g_app_info_get_icon (appinfo)); + has_sidebar = g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (appinfo), "X-GNOME-ControlCenter-HasSidebar"); + + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, 0, + COL_NAME, name, + COL_CASEFOLDED_NAME, casefolded_name, + COL_APP, appinfo, + COL_ID, id, + COL_CATEGORY, category, + COL_DESCRIPTION, comment, + COL_CASEFOLDED_DESCRIPTION, casefolded_description, + COL_GICON, icon, + COL_KEYWORDS, keywords, + COL_VISIBILITY, CC_PANEL_VISIBLE, + COL_HAS_SIDEBAR, has_sidebar, + -1); +} + +gboolean +cc_shell_model_has_panel (CcShellModel *model, + const char *id) +{ + GtkTreeIter iter; + gboolean valid; + + g_assert (id); + + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter); + while (valid) + { + g_autofree gchar *panel_id = NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, COL_ID, &panel_id, -1); + if (g_str_equal (id, panel_id)) + return TRUE; + + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter); + } + + return FALSE; +} + +gboolean +cc_shell_model_iter_matches_search (CcShellModel *model, + GtkTreeIter *iter, + const char *term) +{ + g_autofree gchar *name = NULL; + g_autofree gchar *description = NULL; + gboolean result; + g_auto(GStrv) keywords = NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, + COL_CASEFOLDED_NAME, &name, + COL_CASEFOLDED_DESCRIPTION, &description, + COL_KEYWORDS, &keywords, + -1); + + result = (strstr (name, term) != NULL); + + if (!result && description) + result = (strstr (description, term) != NULL); + + if (!result && keywords) + { + gint i; + + for (i = 0; !result && keywords[i]; i++) + result = (strstr (keywords[i], term) == keywords[i]); + } + + return result; +} + +void +cc_shell_model_set_sort_terms (CcShellModel *self, + gchar **terms) +{ + g_return_if_fail (CC_IS_SHELL_MODEL (self)); + + g_clear_pointer (&self->sort_terms, g_strfreev); + self->sort_terms = g_strdupv (terms); + + /* trigger a re-sort */ + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self), + cc_shell_model_sort_func, + self, + NULL); +} + +void +cc_shell_model_set_panel_visibility (CcShellModel *self, + const gchar *id, + CcPanelVisibility visibility) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean valid; + + g_return_if_fail (CC_IS_SHELL_MODEL (self)); + + model = GTK_TREE_MODEL (self); + + /* Find the iter for the panel with the given id */ + valid = gtk_tree_model_get_iter_first (model, &iter); + + while (valid) + { + g_autofree gchar *item_id = NULL; + + gtk_tree_model_get (model, &iter, COL_ID, &item_id, -1); + + /* Found the iter */ + if (g_str_equal (id, item_id)) + break; + + /* If not found, continue */ + valid = gtk_tree_model_iter_next (model, &iter); + } + + /* If we don't find any panel with the given id, we'll iterate until + * valid == FALSE, so we can use this variable to determine if the + * panel was found or not. It is a programming error to try to set + * the visibility of a non-existent panel. + */ + g_assert (valid); + + gtk_list_store_set (GTK_LIST_STORE (self), &iter, COL_VISIBILITY, visibility, -1); +} diff --git a/shell/cc-shell-model.h b/shell/cc-shell-model.h new file mode 100644 index 0000000..763803e --- /dev/null +++ b/shell/cc-shell-model.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010 Intel, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#pragma once + +#include "cc-panel.h" + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_SHELL_MODEL cc_shell_model_get_type() + +G_DECLARE_FINAL_TYPE (CcShellModel, cc_shell_model, CC, SHELL_MODEL, GtkListStore) + +typedef enum +{ + CC_CATEGORY_CONNECTIVITY, + CC_CATEGORY_PERSONALIZATION, + CC_CATEGORY_ACCOUNT, + CC_CATEGORY_HARDWARE, + CC_CATEGORY_PRIVACY, + CC_CATEGORY_DEVICES, + CC_CATEGORY_DETAILS, + CC_CATEGORY_LAST +} CcPanelCategory; + +enum +{ + COL_NAME, + COL_CASEFOLDED_NAME, + COL_APP, + COL_ID, + COL_CATEGORY, + COL_DESCRIPTION, + COL_CASEFOLDED_DESCRIPTION, + COL_GICON, + COL_KEYWORDS, + COL_VISIBILITY, + COL_HAS_SIDEBAR, + + N_COLS +}; + + +CcShellModel* cc_shell_model_new (void); + +void cc_shell_model_add_item (CcShellModel *model, + CcPanelCategory category, + GAppInfo *appinfo, + const char *id); + +gboolean cc_shell_model_has_panel (CcShellModel *model, + const char *id); + +gboolean cc_shell_model_iter_matches_search (CcShellModel *model, + GtkTreeIter *iter, + const char *term); + +void cc_shell_model_set_sort_terms (CcShellModel *model, + GStrv terms); + +void cc_shell_model_set_panel_visibility (CcShellModel *self, + const gchar *id, + CcPanelVisibility visible); + +G_END_DECLS diff --git a/shell/cc-shell.c b/shell/cc-shell.c new file mode 100644 index 0000000..a8a3dad --- /dev/null +++ b/shell/cc-shell.c @@ -0,0 +1,152 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (c) 2010 Intel, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +/** + * SECTION:cc-shell + * @short_description: Interface representing the Control Center shell + * + * CcShell is an interface that represents an instance of a control + * center shell. It provides access to some of the properties of the shell + * that panels will need to read or change. When a panel is created it has an + * instance of CcShell available that represents the current shell. + */ + + +#include "cc-shell.h" +#include "cc-panel.h" + +G_DEFINE_INTERFACE (CcShell, cc_shell, GTK_TYPE_WIDGET) + +static void +cc_shell_default_init (CcShellInterface *iface) +{ + g_object_interface_install_property (iface, + g_param_spec_object ("active-panel", + "active panel", + "The currently active Panel", + CC_TYPE_PANEL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +/** + * cc_shell_get_active_panel: + * @shell: A #CcShell + * + * Get the current active panel + * + * Returns: a #CcPanel or NULL if no panel is active + */ +CcPanel * +cc_shell_get_active_panel (CcShell *shell) +{ + CcPanel *panel = NULL; + + g_return_val_if_fail (CC_IS_SHELL (shell), NULL); + + g_object_get (shell, "active-panel", &panel, NULL); + + return panel; +} + +/** + * cc_shell_set_active_panel: + * @shell: A #CcShell + * @panel: A #CcPanel + * + * Set the current active panel. If @panel is NULL, then the shell is returned + * to a state where no panel is being displayed (for example, the list of panels + * may be shown instead). + * + */ +void +cc_shell_set_active_panel (CcShell *shell, + CcPanel *panel) +{ + g_return_if_fail (CC_IS_SHELL (shell)); + g_return_if_fail (panel == NULL || CC_IS_PANEL (panel)); + + g_object_set (shell, "active-panel", panel, NULL); +} + +/** + * cc_shell_set_active_panel_from_id: + * @shell: A #CcShell + * @id: The ID of the panel to set as active + * @parameters: A #GVariant with additional parameters + * @error: A #GError + * + * Find a panel corresponding to the specified id and set it as active. + * + * Returns: #TRUE if the panel was found and set as the active panel + */ +gboolean +cc_shell_set_active_panel_from_id (CcShell *shell, + const gchar *id, + GVariant *parameters, + GError **error) +{ + CcShellInterface *iface; + + g_return_val_if_fail (CC_IS_SHELL (shell), FALSE); + + iface = CC_SHELL_GET_IFACE (shell); + + if (!iface->set_active_panel_from_id) + { + g_warning ("Object of type \"%s\" does not implement required interface" + " method \"set_active_panel_from_id\",", + G_OBJECT_TYPE_NAME (shell)); + return FALSE; + } + else + { + return iface->set_active_panel_from_id (shell, id, parameters, error); + } +} + +/** + * cc_shell_get_toplevel: + * @shell: A #CcShell + * + * Gets the toplevel window of the shell. + * + * Returns: The #GtkWidget of the shell window, or #NULL on error. + */ +GtkWidget * +cc_shell_get_toplevel (CcShell *shell) +{ + CcShellInterface *iface; + + g_return_val_if_fail (CC_IS_SHELL (shell), NULL); + + iface = CC_SHELL_GET_IFACE (shell); + + if (iface->get_toplevel) + { + return iface->get_toplevel (shell); + } + + g_warning ("Object of type \"%s\" does not implement required interface" + " method \"get_toplevel\",", + G_OBJECT_TYPE_NAME (shell)); + + return NULL; +} diff --git a/shell/cc-shell.h b/shell/cc-shell.h new file mode 100644 index 0000000..0c2bfd2 --- /dev/null +++ b/shell/cc-shell.h @@ -0,0 +1,70 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (c) 2010 Intel, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_SHELL (cc_shell_get_type()) +#define CC_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_SHELL, CcShell)) +#define CC_IS_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_SHELL)) +#define CC_SHELL_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CC_TYPE_SHELL, CcShellInterface)) + +#define CC_SHELL_PANEL_EXTENSION_POINT "control-center-1" + +typedef struct _CcShell CcShell; +typedef struct _CcShellInterface CcShellInterface; + +/* cc-panel.h requires CcShell, so make sure they are defined first */ +#include "cc-panel.h" + +/** + * CcShellInterface: + * @set_active_panel_from_id: virtual function to set the active panel from an + * id string + * + */ +struct _CcShellInterface +{ + GTypeInterface g_iface; + + /* methods */ + gboolean (*set_active_panel_from_id) (CcShell *shell, + const gchar *id, + GVariant *parameters, + GError **error); + GtkWidget * (*get_toplevel) (CcShell *shell); +}; + +GType cc_shell_get_type (void) G_GNUC_CONST; + +CcPanel * cc_shell_get_active_panel (CcShell *shell); +void cc_shell_set_active_panel (CcShell *shell, + CcPanel *panel); +gboolean cc_shell_set_active_panel_from_id (CcShell *shell, + const gchar *id, + GVariant *parameters, + GError **error); +GtkWidget * cc_shell_get_toplevel (CcShell *shell); + +G_END_DECLS diff --git a/shell/cc-window.c b/shell/cc-window.c new file mode 100644 index 0000000..15d7124 --- /dev/null +++ b/shell/cc-window.c @@ -0,0 +1,894 @@ +/* + * Copyright (c) 2009, 2010 Intel, Inc. + * Copyright (c) 2010 Red Hat, Inc. + * Copyright (c) 2016 Endless, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#define G_LOG_DOMAIN "cc-window" + +#include + +#include "cc-debug.h" +#include "cc-window.h" + +#include +#include +#include +#include +#include +#include + +#include "cc-application.h" +#include "cc-panel-private.h" +#include "cc-shell.h" +#include "cc-shell-model.h" +#include "cc-panel-list.h" +#include "cc-panel-loader.h" +#include "cc-util.h" + +#define MOUSE_BACK_BUTTON 8 + +#define DEFAULT_WINDOW_ICON_NAME "gnome-control-center" + +struct _CcWindow +{ + AdwApplicationWindow parent; + + GtkMessageDialog *development_warning_dialog; + AdwHeaderBar *header; + AdwLeaflet *main_leaflet; + CcPanelList *panel_list; + GtkButton *previous_button; + GtkSearchBar *search_bar; + GtkToggleButton *search_button; + GtkSearchEntry *search_entry; + GtkBox *sidebar_box; + AdwWindowTitle *sidebar_title_widget; + GtkStack *stack; + + GtkWidget *old_panel; + GtkWidget *current_panel; + char *current_panel_id; + GQueue *previous_panels; + + GtkWidget *custom_titlebar; + + CcShellModel *store; + + CcPanel *active_panel; + GSettings *settings; + + gboolean folded; + + CcPanelListView previous_list_view; +}; + +static void cc_shell_iface_init (CcShellInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CcWindow, cc_window, ADW_TYPE_APPLICATION_WINDOW, + G_IMPLEMENT_INTERFACE (CC_TYPE_SHELL, cc_shell_iface_init)) + +enum +{ + PROP_0, + PROP_ACTIVE_PANEL, + PROP_MODEL, + PROP_FOLDED, +}; + +/* Auxiliary methods */ +static void +load_window_state (CcWindow *self) +{ + gint current_width = -1; + gint current_height = -1; + gboolean maximized = FALSE; + + g_settings_get (self->settings, + "window-state", + "(iib)", + ¤t_width, + ¤t_height, + &maximized); + + if (current_width != -1 && current_height != -1) + gtk_window_set_default_size (GTK_WINDOW (self), current_width, current_height); + if (maximized) + gtk_window_maximize (GTK_WINDOW (self)); +} + +static gboolean +in_flatpak_sandbox (void) +{ + return g_strcmp0 (PROFILE, "development") == 0; +} + +static void +on_sidebar_activated_cb (CcWindow *self) +{ + adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD); +} + +static gboolean +activate_panel (CcWindow *self, + const gchar *id, + GVariant *parameters, + const gchar *name, + GIcon *gicon, + CcPanelVisibility visibility) +{ + g_autoptr(GTimer) timer = NULL; + GtkWidget *sidebar_widget; + gdouble ellapsed_time; + + CC_ENTRY; + + if (!id) + CC_RETURN (FALSE); + + if (visibility == CC_PANEL_HIDDEN) + CC_RETURN (FALSE); + + timer = g_timer_new (); + + g_settings_set_string (self->settings, "last-panel", id); + + /* Begin the profile */ + g_timer_start (timer); + + if (self->current_panel) + g_signal_handlers_disconnect_by_data (self->current_panel, self); + self->current_panel = GTK_WIDGET (cc_panel_loader_load_by_name (CC_SHELL (self), id, name, parameters)); + cc_panel_set_folded (CC_PANEL (self->current_panel), adw_leaflet_get_folded (self->main_leaflet)); + cc_shell_set_active_panel (CC_SHELL (self), CC_PANEL (self->current_panel)); + + gtk_stack_add_named (self->stack, self->current_panel, id); + + /* switch to the new panel */ + gtk_stack_set_visible_child_name (self->stack, id); + + sidebar_widget = cc_panel_get_sidebar_widget (CC_PANEL (self->current_panel)); + cc_panel_list_add_sidebar_widget (self->panel_list, sidebar_widget); + /* Ensure we show the panel when the leaflet is folded and a sidebar widget's + * row is activated. + */ + g_signal_connect_object (self->current_panel, "sidebar-activated", G_CALLBACK (on_sidebar_activated_cb), self, G_CONNECT_SWAPPED); + + /* Finish profiling */ + g_timer_stop (timer); + + ellapsed_time = g_timer_elapsed (timer, NULL); + + g_debug ("Time to open panel '%s': %lfs", name, ellapsed_time); + + CC_RETURN (TRUE); +} + +static void +add_current_panel_to_history (CcWindow *self, + const char *start_id) +{ + g_return_if_fail (start_id != NULL); + + if (!self->current_panel_id || g_strcmp0 (self->current_panel_id, start_id) == 0) + return; + + g_queue_push_head (self->previous_panels, g_strdup (self->current_panel_id)); + g_debug ("Added '%s' to the previous panels", self->current_panel_id); +} + +static gboolean +find_iter_for_panel_id (CcWindow *self, + const gchar *panel_id, + GtkTreeIter *out_iter) +{ + GtkTreeIter iter; + gboolean valid; + + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter); + + while (valid) + { + g_autofree gchar *id = NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (self->store), + &iter, + COL_ID, &id, + -1); + + if (g_strcmp0 (id, panel_id) == 0) + break; + + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter); + } + + g_assert (out_iter != NULL); + *out_iter = iter; + + return valid; +} + +static void +update_list_title (CcWindow *self) +{ + CcPanelListView view; + GtkTreeIter iter; + g_autofree gchar *title = NULL; + + CC_ENTRY; + + view = cc_panel_list_get_view (self->panel_list); + title = NULL; + + switch (view) + { + case CC_PANEL_LIST_PRIVACY: + title = g_strdup (_("Privacy")); + break; + + case CC_PANEL_LIST_MAIN: + title = g_strdup (_("Settings")); + break; + + case CC_PANEL_LIST_WIDGET: + find_iter_for_panel_id (self, self->current_panel_id, &iter); + gtk_tree_model_get (GTK_TREE_MODEL (self->store), + &iter, + COL_NAME, &title, + -1); + break; + + case CC_PANEL_LIST_SEARCH: + title = NULL; + break; + } + + if (title) + adw_window_title_set_title (self->sidebar_title_widget, title); + + CC_EXIT; +} + +static void +on_row_changed_cb (CcWindow *self, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreeModel *model) +{ + g_autofree gchar *id = NULL; + CcPanelVisibility visibility; + + gtk_tree_model_get (model, iter, + COL_ID, &id, + COL_VISIBILITY, &visibility, + -1); + + cc_panel_list_set_panel_visibility (self->panel_list, id, visibility); +} + +static void +setup_model (CcWindow *self) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean valid; + + /* CcApplication must have a valid model at this point */ + g_assert (self->store != NULL); + + model = GTK_TREE_MODEL (self->store); + + cc_panel_loader_fill_model (self->store); + + /* Create a row for each panel */ + valid = gtk_tree_model_get_iter_first (model, &iter); + + while (valid) + { + CcPanelCategory category; + g_autoptr(GIcon) icon = NULL; + g_autofree gchar *name = NULL; + g_autofree gchar *description = NULL; + g_autofree gchar *id = NULL; + g_auto(GStrv) keywords = NULL; + CcPanelVisibility visibility; + gboolean has_sidebar; + const gchar *icon_name = NULL; + + gtk_tree_model_get (model, &iter, + COL_CATEGORY, &category, + COL_DESCRIPTION, &description, + COL_GICON, &icon, + COL_ID, &id, + COL_NAME, &name, + COL_KEYWORDS, &keywords, + COL_VISIBILITY, &visibility, + COL_HAS_SIDEBAR, &has_sidebar, + -1); + + if (G_IS_THEMED_ICON (icon)) + icon_name = g_themed_icon_get_names (G_THEMED_ICON (icon))[0]; + + cc_panel_list_add_panel (self->panel_list, + category, + id, + name, + description, + keywords, + icon_name, + visibility, + has_sidebar); + + valid = gtk_tree_model_iter_next (model, &iter); + } + + /* React to visibility changes */ + g_signal_connect_object (model, "row-changed", G_CALLBACK (on_row_changed_cb), self, G_CONNECT_SWAPPED); +} + +static void +update_headerbar_buttons (CcWindow *self) +{ + gboolean is_main_view; + + CC_ENTRY; + + is_main_view = cc_panel_list_get_view (self->panel_list) == CC_PANEL_LIST_MAIN; + + gtk_widget_set_visible (GTK_WIDGET (self->previous_button), !is_main_view); + gtk_widget_set_visible (GTK_WIDGET (self->search_button), is_main_view); + gtk_search_bar_set_key_capture_widget (self->search_bar, is_main_view ? GTK_WIDGET (self) : NULL); + + update_list_title (self); + + CC_EXIT; +} + +static gboolean +set_active_panel_from_id (CcWindow *self, + const gchar *start_id, + GVariant *parameters, + gboolean add_to_history, + gboolean force_moving_to_the_panel, + GError **error) +{ + g_autoptr(GIcon) gicon = NULL; + g_autofree gchar *name = NULL; + CcPanelVisibility visibility; + GtkTreeIter iter; + CcPanelListView view; + gboolean activated; + gboolean found; + + CC_ENTRY; + + view = cc_panel_list_get_view (self->panel_list); + + /* When loading the same panel again, just set its parameters */ + if (g_strcmp0 (self->current_panel_id, start_id) == 0) + { + g_object_set (G_OBJECT (self->current_panel), "parameters", parameters, NULL); + if (force_moving_to_the_panel || self->previous_list_view == view) + adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD); + self->previous_list_view = view; + CC_RETURN (TRUE); + } + + found = find_iter_for_panel_id (self, start_id, &iter); + if (!found) + { + g_warning ("Could not find settings panel \"%s\"", start_id); + CC_RETURN (TRUE); + } + + if (self->old_panel) + gtk_stack_remove (self->stack, g_steal_pointer (&self->old_panel)); + + /* old_panel will be removed by the on_stack_transition_running_changed_cb + * callback - or, if panels changed before the transition ended, by the code + * just above. + */ + self->old_panel = self->current_panel; + if (self->old_panel) + cc_panel_deactivate (CC_PANEL (self->old_panel)); + + gtk_tree_model_get (GTK_TREE_MODEL (self->store), + &iter, + COL_NAME, &name, + COL_GICON, &gicon, + COL_VISIBILITY, &visibility, + -1); + + /* Activate the panel */ + activated = activate_panel (self, start_id, parameters, name, gicon, visibility); + + /* Failed to activate the panel for some reason, let's keep the old + * panel around instead */ + if (!activated) + { + g_debug ("Failed to activate panel"); + CC_RETURN (TRUE); + } + + if (add_to_history) + add_current_panel_to_history (self, start_id); + + if (force_moving_to_the_panel) + adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD); + + g_free (self->current_panel_id); + self->current_panel_id = g_strdup (start_id); + + CC_TRACE_MSG ("Current panel id: %s", start_id); + + cc_panel_list_set_active_panel (self->panel_list, start_id); + + update_headerbar_buttons (self); + + CC_RETURN (TRUE); +} + +static void +set_active_panel (CcWindow *self, + CcPanel *panel) +{ + g_return_if_fail (CC_IS_SHELL (self)); + g_return_if_fail (panel == NULL || CC_IS_PANEL (panel)); + + if (panel != self->active_panel) + { + /* remove the old panel */ + g_clear_object (&self->active_panel); + + /* set the new panel */ + if (panel) + self->active_panel = g_object_ref (panel); + + g_object_notify (G_OBJECT (self), "active-panel"); + } +} + +static void +switch_to_previous_panel (CcWindow *self) +{ + g_autofree gchar *previous_panel_id = NULL; + + CC_ENTRY; + + if (g_queue_get_length (self->previous_panels) == 0) + CC_RETURN (); + + previous_panel_id = g_queue_pop_head (self->previous_panels); + + g_debug ("Going to previous panel (%s)", previous_panel_id); + + set_active_panel_from_id (self, previous_panel_id, NULL, FALSE, FALSE, NULL); + + CC_EXIT; +} + +/* Callbacks */ + +static void +navigate_action_cb (GtkWidget *widget, + const gchar *action_name, + GVariant *parameter) +{ + CcWindow *self = CC_WINDOW (widget); + + adw_leaflet_navigate (self->main_leaflet, g_variant_get_int32 (parameter)); +} + +static void +on_main_leaflet_folded_changed_cb (CcWindow *self) +{ + GtkSelectionMode selection_mode; + gboolean folded; + + g_assert (CC_IS_WINDOW (self)); + + folded = adw_leaflet_get_folded (self->main_leaflet); + + selection_mode = folded ? GTK_SELECTION_NONE : GTK_SELECTION_SINGLE; + cc_panel_list_set_selection_mode (self->panel_list, selection_mode); + + cc_panel_set_folded (CC_PANEL (self->current_panel), folded); +} + +static void +show_panel_cb (CcWindow *self, + const gchar *panel_id) +{ + if (!panel_id) + return; + + set_active_panel_from_id (self, panel_id, NULL, TRUE, FALSE, NULL); +} + +static void +search_entry_activate_cb (CcWindow *self) +{ + gboolean changed; + + changed = cc_panel_list_activate (self->panel_list); + + gtk_search_bar_set_search_mode (self->search_bar, !changed); +} + +static void +previous_button_clicked_cb (CcWindow *self) +{ + g_debug ("Num previous panels? %d", g_queue_get_length (self->previous_panels)); + + /* When in search, simply unset the search mode */ + if (gtk_search_bar_get_search_mode (self->search_bar)) + gtk_search_bar_set_search_mode (self->search_bar, FALSE); + else + cc_panel_list_go_previous (self->panel_list); + + update_headerbar_buttons (self); +} + +static gboolean +go_back_shortcut_cb (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + g_debug ("Going to previous panel"); + switch_to_previous_panel (CC_WINDOW (widget)); + + return GDK_EVENT_STOP; +} + +static gboolean +search_shortcut_cb (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + CcPanelListView view; + CcWindow *self; + gboolean search; + + self = CC_WINDOW (widget); + view = cc_panel_list_get_view (self->panel_list); + + /* The search only happens when we're in the MAIN view */ + if (view != CC_PANEL_LIST_MAIN && view != CC_PANEL_LIST_SEARCH) + return GDK_EVENT_PROPAGATE; + + search = !gtk_search_bar_get_search_mode (self->search_bar); + gtk_search_bar_set_search_mode (self->search_bar, search); + if (search) + gtk_widget_grab_focus (GTK_WIDGET (self->search_entry)); + + return GDK_EVENT_STOP; +} + +static void +on_development_warning_dialog_responded_cb (CcWindow *self) +{ + g_debug ("Disabling development build warning dialog"); + g_settings_set_boolean (self->settings, "show-development-warning", FALSE); + + gtk_widget_hide (GTK_WIDGET (self->development_warning_dialog)); +} + +static void +on_stack_transition_running_changed_cb (GtkStack *stack, + GParamSpec *pspec, + CcWindow *self) +{ + gboolean transition_running; + + CC_ENTRY; + + transition_running = gtk_stack_get_transition_running (stack); + + if (!transition_running && self->old_panel) + gtk_stack_remove (self->stack, g_steal_pointer (&self->old_panel)); + + CC_EXIT; +} + +/* CcShell implementation */ +static gboolean +cc_window_set_active_panel_from_id (CcShell *shell, + const gchar *start_id, + GVariant *parameters, + GError **error) +{ + return set_active_panel_from_id (CC_WINDOW (shell), start_id, parameters, TRUE, TRUE, error); +} + +static GtkWidget * +cc_window_get_toplevel (CcShell *self) +{ + return GTK_WIDGET (self); +} + +static void +cc_shell_iface_init (CcShellInterface *iface) +{ + iface->set_active_panel_from_id = cc_window_set_active_panel_from_id; + iface->get_toplevel = cc_window_get_toplevel; +} + +/* GtkWidget overrides */ +static void +cc_window_map (GtkWidget *widget) +{ + CcWindow *self = (CcWindow *) widget; + + GTK_WIDGET_CLASS (cc_window_parent_class)->map (widget); + + /* Show a warning for Flatpak builds */ + if (in_flatpak_sandbox () && g_settings_get_boolean (self->settings, "show-development-warning")) + gtk_window_present (GTK_WINDOW (self->development_warning_dialog)); +} + +static void +cc_window_unmap (GtkWidget *widget) +{ + CcWindow *self = CC_WINDOW (widget); + gboolean maximized; + gint height; + gint width; + + maximized = gtk_window_is_maximized (GTK_WINDOW (self)); + gtk_window_get_default_size (GTK_WINDOW (self), &width, &height); + + g_settings_set (self->settings, + "window-state", + "(iib)", + width, + height, + maximized); + + GTK_WIDGET_CLASS (cc_window_parent_class)->unmap (widget); +} + +/* GObject Implementation */ +static void +cc_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CcWindow *self = CC_WINDOW (object); + + switch (property_id) + { + case PROP_ACTIVE_PANEL: + g_value_set_object (value, self->active_panel); + break; + + case PROP_MODEL: + g_value_set_object (value, self->store); + break; + + case PROP_FOLDED: + g_value_set_boolean (value, self->folded); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWindow *self = CC_WINDOW (object); + + switch (property_id) + { + case PROP_ACTIVE_PANEL: + set_active_panel (self, g_value_get_object (value)); + break; + + case PROP_MODEL: + g_assert (self->store == NULL); + self->store = g_value_dup_object (value); + break; + + case PROP_FOLDED: + self->folded = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_window_constructed (GObject *object) +{ + CcWindow *self = CC_WINDOW (object); + g_autofree char *id = NULL; + + load_window_state (self); + + /* Add the panels */ + setup_model (self); + + /* After everything is loaded, select the last used panel, if any, + * or the first visible panel */ + id = g_settings_get_string (self->settings, "last-panel"); + if (id != NULL && cc_shell_model_has_panel (self->store, id)) + cc_panel_list_set_active_panel (self->panel_list, id); + else + cc_panel_list_activate (self->panel_list); + + g_signal_connect_swapped (self->panel_list, + "notify::view", + G_CALLBACK (update_headerbar_buttons), + self); + + update_headerbar_buttons (self); + adw_leaflet_set_visible_child (self->main_leaflet, + GTK_WIDGET (self->sidebar_box)); + + G_OBJECT_CLASS (cc_window_parent_class)->constructed (object); +} + +static void +cc_window_dispose (GObject *object) +{ + CcWindow *self = CC_WINDOW (object); + + g_clear_pointer (&self->current_panel_id, g_free); + g_clear_object (&self->store); + g_clear_object (&self->active_panel); + + G_OBJECT_CLASS (cc_window_parent_class)->dispose (object); +} + +static void +cc_window_finalize (GObject *object) +{ + CcWindow *self = CC_WINDOW (object); + + if (self->previous_panels) + { + g_queue_free_full (self->previous_panels, g_free); + self->previous_panels = NULL; + } + + g_clear_object (&self->settings); + + G_OBJECT_CLASS (cc_window_parent_class)->finalize (object); +} + +static void +cc_window_class_init (CcWindowClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_window_get_property; + object_class->set_property = cc_window_set_property; + object_class->constructed = cc_window_constructed; + object_class->dispose = cc_window_dispose; + object_class->finalize = cc_window_finalize; + + widget_class->map = cc_window_map; + widget_class->unmap = cc_window_unmap; + + g_object_class_override_property (object_class, PROP_ACTIVE_PANEL, "active-panel"); + + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + "Model", + "The CcShellModel of this application", + CC_TYPE_SHELL_MODEL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_FOLDED, + g_param_spec_boolean ("folded", + "Folded", + "Whether the window is foled", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/gtk/cc-window.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWindow, development_warning_dialog); + gtk_widget_class_bind_template_child (widget_class, CcWindow, header); + gtk_widget_class_bind_template_child (widget_class, CcWindow, main_leaflet); + gtk_widget_class_bind_template_child (widget_class, CcWindow, panel_list); + gtk_widget_class_bind_template_child (widget_class, CcWindow, previous_button); + gtk_widget_class_bind_template_child (widget_class, CcWindow, search_bar); + gtk_widget_class_bind_template_child (widget_class, CcWindow, search_button); + gtk_widget_class_bind_template_child (widget_class, CcWindow, search_entry); + gtk_widget_class_bind_template_child (widget_class, CcWindow, sidebar_box); + gtk_widget_class_bind_template_child (widget_class, CcWindow, sidebar_title_widget); + gtk_widget_class_bind_template_child (widget_class, CcWindow, stack); + + gtk_widget_class_bind_template_callback (widget_class, on_main_leaflet_folded_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_development_warning_dialog_responded_cb); + gtk_widget_class_bind_template_callback (widget_class, on_stack_transition_running_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, previous_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, search_entry_activate_cb); + gtk_widget_class_bind_template_callback (widget_class, show_panel_cb); + gtk_widget_class_bind_template_callback (widget_class, update_list_title); + + gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, GDK_ALT_MASK, go_back_shortcut_cb, NULL); + gtk_widget_class_add_binding (widget_class, GDK_KEY_s, GDK_CONTROL_MASK, search_shortcut_cb, NULL); + gtk_widget_class_add_binding (widget_class, GDK_KEY_S, GDK_CONTROL_MASK, search_shortcut_cb, NULL); + gtk_widget_class_add_binding (widget_class, GDK_KEY_f, GDK_CONTROL_MASK, search_shortcut_cb, NULL); + gtk_widget_class_add_binding (widget_class, GDK_KEY_F, GDK_CONTROL_MASK, search_shortcut_cb, NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_q, GDK_CONTROL_MASK, "window.close", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Q, GDK_CONTROL_MASK, "window.close", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_w, GDK_CONTROL_MASK, "window.close", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_W, GDK_CONTROL_MASK, "window.close", NULL); + + gtk_widget_class_install_action (widget_class, "window.navigate", "i", navigate_action_cb); + + g_type_ensure (CC_TYPE_PANEL_LIST); +} + +static void +cc_window_init (CcWindow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->settings = g_settings_new ("org.gnome.Settings"); + self->previous_panels = g_queue_new (); + self->previous_list_view = cc_panel_list_get_view (self->panel_list); + + g_object_bind_property (self->main_leaflet, + "folded", + self, + "folded", + G_BINDING_SYNC_CREATE); + + /* Add a custom CSS class on development builds */ + if (in_flatpak_sandbox ()) + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "devel"); +} + +CcWindow * +cc_window_new (GtkApplication *application, + CcShellModel *model) +{ + g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL); + + return g_object_new (CC_TYPE_WINDOW, + "application", application, + "resizable", TRUE, + "title", _("Settings"), + "icon-name", DEFAULT_WINDOW_ICON_NAME, + "show-menubar", FALSE, + "model", model, + NULL); +} + +void +cc_window_set_search_item (CcWindow *center, + const char *search) +{ + gtk_search_bar_set_search_mode (center->search_bar, TRUE); + gtk_editable_set_text (GTK_EDITABLE (center->search_entry), search); + gtk_editable_set_position (GTK_EDITABLE (center->search_entry), -1); +} diff --git a/shell/cc-window.h b/shell/cc-window.h new file mode 100644 index 0000000..35d6ebc --- /dev/null +++ b/shell/cc-window.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010 Intel, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#pragma once + +#include +#include "cc-shell.h" +#include "cc-shell-model.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WINDOW (cc_window_get_type ()) + +G_DECLARE_FINAL_TYPE (CcWindow, cc_window, CC, WINDOW, AdwApplicationWindow) + +CcWindow *cc_window_new (GtkApplication *application, + CcShellModel *model); + +void cc_window_set_search_item (CcWindow *center, + const char *search); + +G_END_DECLS diff --git a/shell/cc-window.ui b/shell/cc-window.ui new file mode 100644 index 0000000..07e1848 --- /dev/null +++ b/shell/cc-window.ui @@ -0,0 +1,170 @@ + + + + + + vertical + + + + + + + + + warning + CcWindow + false + true + ok + Warning: Development Version + This version of Settings should only be used for development purposes. You may experience incorrect system behavior, data loss, and other unexpected issues. + + + + +
+ + Keyboard Shortcuts + win.show-help-overlay + + + Help + app.help + +
+
+ +
diff --git a/shell/completions/gnome-control-center.in b/shell/completions/gnome-control-center.in new file mode 100644 index 0000000..51ce4fd --- /dev/null +++ b/shell/completions/gnome-control-center.in @@ -0,0 +1,51 @@ +# gnome-control-center tab completion for bash. + +_gnome_control_center() +{ + local cur prev command_list i v + + cur=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + + case "$prev" in + *) + if [ $prev = "gnome-control-center" ] ; then + command_list="--verbose --version" + command_list="$command_list @PANELS@" + elif [ $prev = "--verbose" ]; then + command_list="@PANELS@" + fi + + for i in --version @PANELS@; do + if [ $i = $prev ]; then + case $i in + keyboard) + command_list="shortcuts typing" + ;; + network) + # FIXME + # The first 3 commands need an object path like + # /org/freedesktop/NetworkManager/Devices/1 + command_list="connect-3g connect-8021x-wifi show-device connect-hidden-wifi create-wifi" + ;; + sound) + command_list="applications effects hardware input outputs" + ;; + *) + command_list="" + ;; + esac + fi + done + ;; + esac + + for i in $command_list; do + if [ -z "${i/$cur*}" ]; then + COMPREPLY=( ${COMPREPLY[@]} $i ) + fi + done +} + +# load the completion +complete -F _gnome_control_center gnome-control-center diff --git a/shell/completions/meson.build b/shell/completions/meson.build new file mode 100644 index 0000000..951d060 --- /dev/null +++ b/shell/completions/meson.build @@ -0,0 +1,12 @@ +completion_conf = configuration_data() +completion_conf.set('PANELS', ' '.join(panels_list)) + +desktop = 'gnome-control-center' + +desktop_in = configure_file( + input: desktop + '.in', + output: desktop, + configuration: completion_conf, + install: true, + install_dir: join_paths(control_center_datadir, 'bash-completion', 'completions') +) diff --git a/shell/gnome-control-center.gresource.xml b/shell/gnome-control-center.gresource.xml new file mode 100644 index 0000000..ec1feae --- /dev/null +++ b/shell/gnome-control-center.gresource.xml @@ -0,0 +1,16 @@ + + + + cc-panel.ui + cc-panel-list.ui + cc-window.ui + help-overlay.ui + style.css + + + + + icons/multitasking-symbolic.svg + style.css + + diff --git a/shell/help-overlay.ui b/shell/help-overlay.ui new file mode 100644 index 0000000..14bbbe9 --- /dev/null +++ b/shell/help-overlay.ui @@ -0,0 +1,70 @@ + + + + + + 1 + + + 1 + shortcuts + 10 + + + 1 + General + + + 1 + <ctrl>Q + Quit + + + + + 1 + <ctrl>S + Search + + + + + + + 1 + Panels + + + 1 + <Alt>Left + GTK_TEXT_DIR_LTR + Go back to previous panel + + + + + 1 + <Alt>Right + GTK_TEXT_DIR_RTL + Go back to previous panel + + + + + + + 1 + Search + + + 1 + Escape + Cancel search + + + + + + + + diff --git a/shell/icons/multitasking-symbolic.svg b/shell/icons/multitasking-symbolic.svg new file mode 100644 index 0000000..7959ddb --- /dev/null +++ b/shell/icons/multitasking-symbolic.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell/list-panel.sh b/shell/list-panel.sh new file mode 100755 index 0000000..cf31eae --- /dev/null +++ b/shell/list-panel.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +LIST="" +for i in $1/panels/*/gnome-*panel.desktop.in.in $1/panels/*/data/gnome-*panel.desktop.in.in; do + basename=`basename $i` + LIST="$LIST `echo $basename | sed 's/gnome-//' | sed 's/-panel.desktop.in.in/ /'`" +done +echo -n $LIST | tr " " "\n" | sort | tr "\n" " " diff --git a/shell/main.c b/shell/main.c new file mode 100644 index 0000000..4a6125e --- /dev/null +++ b/shell/main.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009, 2010 Intel, Inc. + * Copyright (c) 2010 Red Hat, Inc. + * + * The Control Center 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 2 of the License, or (at your + * option) any later version. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Thomas Wood + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "cc-application.h" + +static void +initialize_dependencies (gint *argc, + gchar ***argv) +{ + #ifdef GDK_WINDOWING_X11 + XInitThreads (); + #endif +} + +int +main (gint argc, + gchar **argv) +{ + g_autoptr(GtkApplication) application = NULL; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + initialize_dependencies (&argc, &argv); + + application = cc_application_new (); + + return g_application_run (G_APPLICATION (application), argc, argv); +} diff --git a/shell/meson.build b/shell/meson.build new file mode 100644 index 0000000..9553c35 --- /dev/null +++ b/shell/meson.build @@ -0,0 +1,171 @@ +subdir('appdata') +subdir('completions') + +service_conf = configuration_data() +service_conf.set('bindir', control_center_bindir) + +service = 'org.gnome.Settings.service' + +configure_file( + input : service + '.in', + output : service, + install : true, + install_dir : join_paths(control_center_datadir, 'dbus-1', 'services'), + configuration : service_conf +) + +desktop = 'org.gnome.Settings.desktop' + +desktop_in = configure_file( + input : desktop + '.in.in', + output : desktop + '.in', + configuration : desktop_conf +) + +i18n.merge_file( + type : 'desktop', + input : desktop_in, + output : desktop, + po_dir : po_dir, + install : true, + install_dir : control_center_desktopdir +) + +cflags = ['-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)] + + +# Common sources between gnome-control-center and +# libtestshell. +common_sources = files( + 'cc-application.c', + 'cc-log.c', + 'cc-object-storage.c', + 'cc-panel-loader.c', + 'cc-panel.c', + 'cc-shell.c', + 'cc-panel-list.c', + 'cc-window.c', +) + + +################### +# Generated files # +################### + +generated_sources = files() + +# Debug +debug_conf = configuration_data() +debug_conf.set('BUGREPORT_URL', 'https://gitlab.gnome.org/GNOME/' + meson.project_name() + '/issues/new') +debug_conf.set10('ENABLE_TRACING', enable_tracing) + +generated_sources += configure_file( + input : 'cc-debug.h.in', + output : 'cc-debug.h', + configuration : debug_conf +) + +#Resources +resource_data = files( + 'cc-panel-list.ui', + 'cc-window.ui', + 'help-overlay.ui', +) + +generated_sources += gnome.compile_resources( + 'resources', + meson.project_name() + '.gresource.xml', + dependencies : resource_data, + export : true +) + +common_sources += generated_sources + +############ +# libshell # +############ + +libshell = static_library( + 'shell', + sources : 'cc-shell-model.c', + include_directories : [top_inc, common_inc], + dependencies : common_deps, + c_args : cflags +) + +libshell_dep = declare_dependency( + sources : generated_sources, + include_directories : top_inc, + link_with : libshell +) + + +######################## +# gnome-control-center # +######################## + +shell_sources = common_sources + files('main.c') + +shell_deps = common_deps + [ + libdevice_dep, + liblanguage_dep, + libwidgets_dep, + x11_dep, + libshell_dep, +] + +if host_is_linux_not_s390 + shell_deps += wacom_deps +endif + +executable( + meson.project_name(), + shell_sources, + include_directories : top_inc, + dependencies : shell_deps, + c_args : cflags, + link_with : panels_libs, + install : true +) + + +################## +# lipanel_loader # +################## + +# Because it is confusing and somewhat problematic to directly add and compile +# cc-panel-loader.o by another directory (i.e. the shell search provider), we +# have to create a library and link it there, just like libshell.la. +libpanel_loader = static_library( + 'panel_loader', + sources : 'cc-panel-loader.c', + include_directories : top_inc, + dependencies : common_deps, + c_args : cflags + ['-DCC_PANEL_LOADER_NO_GTYPES'] +) + + +################ +# libtestshell # +################ + +libtestshell = static_library( + 'testshell', + common_sources, + include_directories : top_inc, + dependencies : shell_deps, + c_args : cflags, + link_with : panels_libs, +) +libtestshell_dep = declare_dependency( + sources : generated_sources, + include_directories : top_inc, + link_with : libtestshell +) +libtestshell_deps = common_deps + [ libwidgets_dep, libtestshell_dep ] + + +install_data ( + 'org.gnome.Settings.gschema.xml', + install_dir : control_center_schemadir +) diff --git a/shell/org.gnome.Settings.desktop.in.in b/shell/org.gnome.Settings.desktop.in.in new file mode 100644 index 0000000..1cb049c --- /dev/null +++ b/shell/org.gnome.Settings.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Settings +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=org.gnome.Settings +Exec=gnome-control-center +Terminal=false +Type=Application +StartupNotify=true +Categories=GNOME;GTK;Settings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=shell +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Preferences;Settings; +# Translators: Do NOT translate or transliterate this text (these are enum types)! +X-Purism-FormFactor=Workstation;Mobile; diff --git a/shell/org.gnome.Settings.gschema.xml b/shell/org.gnome.Settings.gschema.xml new file mode 100644 index 0000000..bf4d78d --- /dev/null +++ b/shell/org.gnome.Settings.gschema.xml @@ -0,0 +1,24 @@ + + + + '' + The identifier for the last Settings panel to be opened + + The identifier for the last Settings panel to be opened. Unrecognised values + will be ignored and the first panel in the list selected. + + + + true + Show warning when running a development build of Settings + + Whether Settings should show a warning when running a development build. + + + + (-1, -1, false) + Initial state of the window + A tuple containing the initial width, height and maximized state of the application window. + + + diff --git a/shell/org.gnome.Settings.service.in b/shell/org.gnome.Settings.service.in new file mode 100644 index 0000000..dbd6f82 --- /dev/null +++ b/shell/org.gnome.Settings.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.gnome.Settings +Exec=@bindir@/gnome-control-center diff --git a/shell/style.css b/shell/style.css new file mode 100644 index 0000000..7ffc6ab --- /dev/null +++ b/shell/style.css @@ -0,0 +1,7 @@ +.drag-handle { + color: alpha(@theme_fg_color, 0.4); +} + +.drag-handle:backdrop { + color: alpha(@theme_unfocused_fg_color, 0.4); +} -- cgit v1.2.3