diff --git a/panels/network/cc-wifi-panel.c b/panels/network/cc-wifi-panel.c index d785bad0d1078cbd04862400364bcd6f426692c6..be8f787e656e47b4b74c2aa50ce3650a813b3850 100644 --- a/panels/network/cc-wifi-panel.c +++ b/panels/network/cc-wifi-panel.c @@ -23,8 +23,9 @@ #include "net-device-wifi.h" #include "network-dialogs.h" +#include "shell/cc-application.h" +#include "shell/cc-debug.h" #include "shell/cc-object-storage.h" -#include "list-box-helper.h" #include #include @@ -81,6 +82,67 @@ enum N_PROPS }; +/* Static init function */ + +static void +update_panel_visibility (NMClient *client) +{ + const GPtrArray *devices; + CcApplication *application; + gboolean visible; + guint i; + + CC_TRACE_MSG ("Updating Wi-Fi panel visibility"); + + devices = nm_client_get_devices (client); + visible = FALSE; + + for (i = 0; devices && i < devices->len; i++) + { + NMDevice *device = g_ptr_array_index (devices, i); + + visible |= NM_IS_DEVICE_WIFI (device); + + if (visible) + break; + } + + /* Set the new visibility */ + application = CC_APPLICATION (g_application_get_default ()); + cc_shell_model_set_panel_visibility (cc_application_get_model (application), + "wifi", + visible ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH); + + CC_TRACE_MSG ("Wi-Fi panel visible: %s", visible ? "yes" : "no"); +} + +void +cc_wifi_panel_static_init_func (void) +{ + NMClient *client; + + g_debug ("Monitoring NetworkManager for Wi-Fi devices"); + + /* Create and store a NMClient instance if it doesn't exist yet */ + if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) + { + client = nm_client_new (NULL, NULL); + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client); + g_object_unref (client); + } + + client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); + + /* Update the panel visibility and monitor for changes */ + + g_signal_connect (client, "device-added", G_CALLBACK (update_panel_visibility), NULL); + g_signal_connect (client, "device-removed", G_CALLBACK (update_panel_visibility), NULL); + + update_panel_visibility (client); + + g_object_unref (client); +} + /* Auxiliary methods */ static void diff --git a/panels/network/cc-wifi-panel.h b/panels/network/cc-wifi-panel.h index 43ad4bf96b45b7b2b66f4af83e84d46910f0c596..059507dc96f448bd7e20867a8b4451224606f9bd 100644 --- a/panels/network/cc-wifi-panel.h +++ b/panels/network/cc-wifi-panel.h @@ -28,6 +28,8 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (CcWifiPanel, cc_wifi_panel, CC, WIFI_PANEL, CcPanel) +void cc_wifi_panel_static_init_func (void); + G_END_DECLS #endif /* CC_WIFI_PANEL_H */ diff --git a/shell/cc-application.c b/shell/cc-application.c index 862baab9e225553173fbee1b759d0c4c87b706e8..8ffb2bdbfc4d6a6cee4014cc97dba415b430c1df 100644 --- a/shell/cc-application.c +++ b/shell/cc-application.c @@ -39,6 +39,8 @@ struct _CcApplication { GtkApplication parent; + CcShellModel *model; + CcWindow *window; }; @@ -259,7 +261,8 @@ cc_application_startup (GApplication *application) gtk_application_set_accels_for_action (GTK_APPLICATION (application), "app.help", help_accels); - self->window = cc_window_new (GTK_APPLICATION (application)); + self->model = cc_shell_model_new (); + self->window = cc_window_new (GTK_APPLICATION (application), self->model); } static void @@ -320,3 +323,11 @@ cc_application_new (void) "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 index eeda001b0e9065bf8d49f1e880353382fd93b972..e9c8b60af77d7d98aa808f452e63fe8659f6ef43 100644 --- a/shell/cc-application.h +++ b/shell/cc-application.h @@ -19,6 +19,8 @@ #pragma once +#include "cc-shell-model.h" + #include G_BEGIN_DECLS @@ -29,4 +31,6 @@ G_DECLARE_FINAL_TYPE (CcApplication, cc_application, CC, APPLICATION, GtkApplica GtkApplication *cc_application_new (void); -G_END_DECLS \ No newline at end of file +CcShellModel *cc_application_get_model (CcApplication *self); + +G_END_DECLS diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c index f5b83509d6461fe21cc5e6cc89d11e19fe328ab2..408a0de77bd128bd6a9293074888129efd3a08ab 100644 --- a/shell/cc-panel-list.c +++ b/shell/cc-panel-list.c @@ -18,20 +18,24 @@ * 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; + GtkWidget *row; + GtkWidget *description_label; + CcPanelCategory category; + gchar *id; + gchar *name; + gchar *description; + gchar **keywords; + CcPanelVisibility visibility; } RowData; struct _CcPanelList @@ -53,11 +57,13 @@ struct _CcPanelList GtkWidget *empty_search_placeholder; + 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, GTK_TYPE_STACK) @@ -106,6 +112,49 @@ get_listbox_from_view (CcPanelList *self, } } +static GtkWidget * +get_listbox_from_category (CcPanelList *self, + CcPanelCategory category) +{ + + switch (category) + { + case CC_CATEGORY_DEVICES: + return self->devices_listbox; + break; + + case CC_CATEGORY_DETAILS: + return self->details_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 == self->devices_row || next_row == self->details_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) @@ -159,12 +208,13 @@ row_data_free (RowData *data) } static RowData* -row_data_new (CcPanelCategory category, - const gchar *id, - const gchar *name, - const gchar *description, - gchar **keywords, - const gchar *icon) +row_data_new (CcPanelCategory category, + const gchar *id, + const gchar *name, + const gchar *description, + gchar **keywords, + const gchar *icon, + CcPanelVisibility visibility) { GtkWidget *label, *grid, *image; RowData *data; @@ -222,6 +272,8 @@ row_data_new (CcPanelCategory category, g_object_set_data_full (G_OBJECT (data->row), "data", data, (GDestroyNotify) row_data_free); + data->visibility = visibility; + return data; } @@ -580,7 +632,9 @@ 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); } @@ -719,6 +773,7 @@ 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), @@ -766,18 +821,21 @@ 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_listbox_from_view (self, self->view); - if (self->view == CC_PANEL_LIST_SEARCH) - row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (listbox), 0); - else - row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (listbox), 0); + /* 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) + if (row && !gtk_list_box_row_is_selected (row)) { gtk_list_box_select_row (GTK_LIST_BOX (listbox), row); gtk_widget_grab_focus (GTK_WIDGET (row)); @@ -785,7 +843,7 @@ cc_panel_list_activate (CcPanelList *self) g_signal_emit_by_name (row, "activate"); } - return row != NULL; + CC_RETURN (row != NULL); } const gchar* @@ -865,13 +923,14 @@ cc_panel_list_set_view (CcPanelList *self, } void -cc_panel_list_add_panel (CcPanelList *self, - CcPanelCategory category, - const gchar *id, - const gchar *title, - const gchar *description, - gchar **keywords, - const gchar *icon) +cc_panel_list_add_panel (CcPanelList *self, + CcPanelCategory category, + const gchar *id, + const gchar *title, + const gchar *description, + gchar **keywords, + const gchar *icon, + CcPanelVisibility visibility) { GtkWidget *listbox; RowData *data, *search_data; @@ -879,30 +938,20 @@ cc_panel_list_add_panel (CcPanelList *self, 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); - - switch (category) - { - case CC_CATEGORY_DEVICES: - listbox = self->devices_listbox; - break; - - case CC_CATEGORY_DETAILS: - listbox = self->details_listbox; - break; - - default: - listbox = self->main_listbox; - break; - } + data = row_data_new (category, id, title, description, keywords, icon, visibility); + gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); + listbox = get_listbox_from_category (self, category); gtk_container_add (GTK_CONTAINER (listbox), data->row); /* And add to the search listbox too */ - search_data = row_data_new (category, id, title, description, keywords, icon); + search_data = row_data_new (category, id, title, description, keywords, icon, visibility); + gtk_widget_set_visible (search_data->row, visibility != CC_PANEL_HIDDEN); + gtk_container_add (GTK_CONTAINER (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); } /** @@ -925,8 +974,35 @@ cc_panel_list_set_active_panel (CcPanelList *self, 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-existant 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); @@ -936,4 +1012,46 @@ cc_panel_list_set_active_panel (CcPanelList *self, self->autoselect_panel = FALSE; 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); } diff --git a/shell/cc-panel-list.h b/shell/cc-panel-list.h index 14db2fece6cadb8b1b61668490635e2f5c563d98..303ce1d9c8148c5d3088e45a4a37905b54fe77d4 100644 --- a/shell/cc-panel-list.h +++ b/shell/cc-panel-list.h @@ -60,11 +60,16 @@ void cc_panel_list_add_panel (CcPanelList const gchar *title, const gchar *description, gchar **keywords, - const gchar *icon); + const gchar *icon, + CcPanelVisibility visibility); 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); + G_END_DECLS #endif /* CC_PANEL_LIST_H */ diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c index 55492ef984d0dbfcf4104374bb448a1ede18b924..3beba0bfddfcce075cc7f4d16e864a0973b9904d 100644 --- a/shell/cc-panel-loader.c +++ b/shell/cc-panel-loader.c @@ -24,6 +24,7 @@ #include #include +#include "cc-panel.h" #include "cc-panel-loader.h" #ifndef CC_PANEL_LOADER_NO_GTYPES @@ -63,11 +64,16 @@ extern GType cc_user_panel_get_type (void); extern GType cc_wacom_panel_get_type (void); #endif /* BUILD_WACOM */ -#define PANEL_TYPE(name, get_type) { name, get_type } +/* Static init functions */ +#ifdef BUILD_NETWORK +extern void cc_wifi_panel_static_init_func (void); +#endif /* BUILD_NETWORK */ + +#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) { name } +#define PANEL_TYPE(name, get_type, init_func) { name } #endif @@ -75,40 +81,41 @@ static struct { const char *name; #ifndef CC_PANEL_LOADER_NO_GTYPES GType (*get_type)(void); + CcPanelStaticInitFunc static_init_func; #endif } all_panels[] = { - PANEL_TYPE("background", cc_background_panel_get_type ), + PANEL_TYPE("background", cc_background_panel_get_type, NULL), #ifdef BUILD_BLUETOOTH - PANEL_TYPE("bluetooth", cc_bluetooth_panel_get_type ), + PANEL_TYPE("bluetooth", cc_bluetooth_panel_get_type, NULL), #endif - PANEL_TYPE("color", cc_color_panel_get_type ), - PANEL_TYPE("datetime", cc_date_time_panel_get_type ), - PANEL_TYPE("display", cc_display_panel_get_type ), - PANEL_TYPE("info-overview", cc_info_overview_panel_get_type), - PANEL_TYPE("default-apps", cc_info_default_apps_panel_get_type), - PANEL_TYPE("removable-media", cc_info_removable_media_panel_get_type), - PANEL_TYPE("keyboard", cc_keyboard_panel_get_type ), - PANEL_TYPE("mouse", cc_mouse_panel_get_type ), + PANEL_TYPE("color", cc_color_panel_get_type, NULL), + PANEL_TYPE("datetime", cc_date_time_panel_get_type, NULL), + PANEL_TYPE("display", cc_display_panel_get_type, NULL), + PANEL_TYPE("info-overview", cc_info_overview_panel_get_type, NULL), + PANEL_TYPE("default-apps", cc_info_default_apps_panel_get_type, NULL), + PANEL_TYPE("removable-media", cc_info_removable_media_panel_get_type, NULL), + PANEL_TYPE("keyboard", cc_keyboard_panel_get_type, NULL), + PANEL_TYPE("mouse", cc_mouse_panel_get_type, NULL), #ifdef BUILD_NETWORK - PANEL_TYPE("network", cc_network_panel_get_type ), - PANEL_TYPE("wifi", cc_wifi_panel_get_type ), + 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), - PANEL_TYPE("online-accounts", cc_goa_panel_get_type ), - PANEL_TYPE("power", cc_power_panel_get_type ), - PANEL_TYPE("printers", cc_printers_panel_get_type ), - PANEL_TYPE("privacy", cc_privacy_panel_get_type ), - PANEL_TYPE("region", cc_region_panel_get_type ), - PANEL_TYPE("search", cc_search_panel_get_type ), - PANEL_TYPE("sharing", cc_sharing_panel_get_type ), - PANEL_TYPE("sound", cc_sound_panel_get_type ), + PANEL_TYPE("notifications", cc_notifications_panel_get_type, NULL), + PANEL_TYPE("online-accounts", cc_goa_panel_get_type, NULL), + PANEL_TYPE("power", cc_power_panel_get_type, NULL), + PANEL_TYPE("printers", cc_printers_panel_get_type, NULL), + PANEL_TYPE("privacy", cc_privacy_panel_get_type, NULL), + PANEL_TYPE("region", cc_region_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 ), + PANEL_TYPE("thunderbolt", cc_bolt_panel_get_type, NULL), #endif - PANEL_TYPE("universal-access", cc_ua_panel_get_type ), - PANEL_TYPE("user-accounts", cc_user_panel_get_type ), + PANEL_TYPE("universal-access", cc_ua_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 ), + PANEL_TYPE("wacom", cc_wacom_panel_get_type, NULL), #endif }; @@ -192,6 +199,18 @@ cc_panel_loader_fill_model (CcShellModel *model) cc_shell_model_add_item (model, category, G_APP_INFO (app), all_panels[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 < G_N_ELEMENTS (all_panels); i++) + { + if (all_panels[i].static_init_func) + all_panels[i].static_init_func (); + } +#endif } #ifndef CC_PANEL_LOADER_NO_GTYPES diff --git a/shell/cc-panel.c b/shell/cc-panel.c index 1042397fd0ab78f11f7fb4aeda7c671506c7b4f7..1fd13fa23aacd9170b2b917df591b5526b5afc85 100644 --- a/shell/cc-panel.c +++ b/shell/cc-panel.c @@ -42,7 +42,7 @@ #define CC_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_PANEL, CcPanelPrivate)) -struct CcPanelPrivate +typedef struct { gchar *id; gchar *display_name; @@ -51,16 +51,19 @@ struct CcPanelPrivate gboolean is_active; CcShell *shell; -}; +} CcPanelPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (CcPanel, cc_panel, GTK_TYPE_BIN) enum { - PROP_0, - PROP_SHELL, - PROP_PARAMETERS + PROP_0, + PROP_SHELL, + PROP_PARAMETERS, + N_PROPS }; -G_DEFINE_ABSTRACT_TYPE (CcPanel, cc_panel, GTK_TYPE_BIN) +static GParamSpec *properties [N_PROPS]; static void cc_panel_set_property (GObject *object, @@ -68,23 +71,23 @@ cc_panel_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - CcPanel *panel; - - panel = CC_PANEL (object); + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); switch (prop_id) { case PROP_SHELL: /* construct only property */ - panel->priv->shell = g_value_get_object (value); + priv->shell = g_value_get_object (value); break; case PROP_PARAMETERS: { - GVariant *parameters = g_value_get_variant (value); - GVariant *v; + g_autoptr (GVariant) v = NULL; + GVariant *parameters; gsize n_parameters; + parameters = g_value_get_variant (value); + if (parameters == NULL) return; @@ -100,8 +103,6 @@ cc_panel_set_property (GObject *object, else if (g_variant_n_children (v) > 0) g_warning ("Ignoring additional flags"); - g_variant_unref (v); - if (n_parameters > 1) g_warning ("Ignoring additional parameters"); @@ -119,14 +120,12 @@ cc_panel_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - CcPanel *panel; - - panel = CC_PANEL (object); + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); switch (prop_id) { case PROP_SHELL: - g_value_set_object (value, panel->priv->shell); + g_value_set_object (value, priv->shell); break; default: @@ -138,15 +137,10 @@ cc_panel_get_property (GObject *object, static void cc_panel_finalize (GObject *object) { - CcPanel *panel; - - g_return_if_fail (object != NULL); - g_return_if_fail (CC_IS_PANEL (object)); + CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object)); - panel = CC_PANEL (object); - - g_free (panel->priv->id); - g_free (panel->priv->display_name); + g_clear_pointer (&priv->id, g_free); + g_clear_pointer (&priv->display_name, g_free); G_OBJECT_CLASS (cc_panel_parent_class)->finalize (object); } @@ -206,9 +200,8 @@ cc_panel_size_allocate (GtkWidget *widget, static void cc_panel_class_init (CcPanelClass *klass) { - GParamSpec *pspec; - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (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; @@ -218,31 +211,25 @@ cc_panel_class_init (CcPanelClass *klass) widget_class->get_preferred_height = cc_panel_get_preferred_height; widget_class->size_allocate = cc_panel_size_allocate; - gtk_container_class_handle_border_width (GTK_CONTAINER_CLASS (klass)); - - g_type_class_add_private (klass, sizeof (CcPanelPrivate)); - - pspec = g_param_spec_object ("shell", - "Shell", - "Shell the Panel resides in", - CC_TYPE_SHELL, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS - | G_PARAM_CONSTRUCT_ONLY); - g_object_class_install_property (object_class, PROP_SHELL, pspec); - - pspec = g_param_spec_variant ("parameters", - "Structured parameters", - "Additional parameters passed externally (ie. command line, dbus activation)", - G_VARIANT_TYPE ("av"), - NULL, - G_PARAM_WRITABLE); - g_object_class_install_property (object_class, PROP_PARAMETERS, pspec); + 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_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); + + g_object_class_install_properties (object_class, N_PROPS, properties); } static void cc_panel_init (CcPanel *panel) { - panel->priv = CC_PANEL_GET_PRIVATE (panel); } /** @@ -256,7 +243,13 @@ cc_panel_init (CcPanel *panel) CcShell * cc_panel_get_shell (CcPanel *panel) { - return panel->priv->shell; + CcPanelPrivate *priv; + + g_return_val_if_fail (CC_IS_PANEL (panel), NULL); + + priv = cc_panel_get_instance_private (panel); + + return priv->shell; } GPermission * diff --git a/shell/cc-panel.h b/shell/cc-panel.h index c9cff10d7461ea34cf21ec37c5af5a345e5b80f7..5c705a4aa35c43b864c8f8b596b9388d5139f4a6 100644 --- a/shell/cc-panel.h +++ b/shell/cc-panel.h @@ -20,52 +20,55 @@ * Thomas Wood */ - -#ifndef __CC_PANEL_H -#define __CC_PANEL_H +#pragma once #include #include - -G_BEGIN_DECLS - -#define CC_TYPE_PANEL (cc_panel_get_type ()) -#define CC_PANEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_PANEL, CcPanel)) -#define CC_PANEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_PANEL, CcPanelClass)) -#define CC_IS_PANEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_PANEL)) -#define CC_IS_PANEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_PANEL)) -#define CC_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_PANEL, CcPanelClass)) - -/*• +/** * 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) +#define CC_PANEL_REGISTER(PluginName, plugin_name) G_DEFINE_TYPE (PluginName, plugin_name, CC_TYPE_PANEL) -typedef struct CcPanelPrivate CcPanelPrivate; +/** + * 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); -typedef struct _CcPanel CcPanel; -typedef struct _CcPanelClass CcPanelClass; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (CcPanel, g_object_unref) +#define CC_TYPE_PANEL (cc_panel_get_type()) -/* cc-shell.h requires CcPanel, so make sure it is defined first */ -#include "cc-shell.h" +G_DECLARE_DERIVABLE_TYPE (CcPanel, cc_panel, CC, PANEL, GtkBin) /** - * CcPanel: + * CcPanelVisibility: * - * The contents of this struct are private and should not be accessed directly. + * @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. */ -struct _CcPanel +typedef enum { - /*< private >*/ - GtkBin parent; - CcPanelPrivate *priv; -}; + 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: * @@ -94,4 +97,3 @@ GtkWidget *cc_panel_get_title_widget (CcPanel *panel); G_END_DECLS -#endif /* __CC_PANEL_H */ diff --git a/shell/cc-shell-model.c b/shell/cc-shell-model.c index 4dcddba45828baac479885c3e807dc89eb8fefc7..2e209a7d023187a50521db77fd01f9894d354793 100644 --- a/shell/cc-shell-model.c +++ b/shell/cc-shell-model.c @@ -254,8 +254,8 @@ cc_shell_model_class_init (CcShellModelClass *klass) 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}; + 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 }; gtk_list_store_set_column_types (GTK_LIST_STORE (self), N_COLS, types); @@ -318,6 +318,7 @@ cc_shell_model_add_item (CcShellModel *model, COL_CASEFOLDED_DESCRIPTION, casefolded_description, COL_GICON, icon, COL_KEYWORDS, keywords, + COL_VISIBILITY, CC_PANEL_VISIBLE, -1); g_free (casefolded_name); @@ -399,3 +400,43 @@ cc_shell_model_set_sort_terms (CcShellModel *self, 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-existant 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 index b0fe50ff602d1de6948b57f18fce5daf86ca604f..397fc7d221cdb9b5a7ade4d5d3097cc57c272b8e 100644 --- a/shell/cc-shell-model.h +++ b/shell/cc-shell-model.h @@ -20,6 +20,8 @@ #pragma once +#include "cc-panel.h" + #include G_BEGIN_DECLS @@ -50,6 +52,7 @@ enum COL_CASEFOLDED_DESCRIPTION, COL_GICON, COL_KEYWORDS, + COL_VISIBILITY, N_COLS }; @@ -57,19 +60,23 @@ enum CcShellModel* cc_shell_model_new (void); -void cc_shell_model_add_item (CcShellModel *model, - CcPanelCategory category, - GAppInfo *appinfo, - const char *id); +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_has_panel (CcShellModel *model, - const char *id); +gboolean cc_shell_model_iter_matches_search (CcShellModel *model, + GtkTreeIter *iter, + const char *term); -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_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-window.c b/shell/cc-window.c index 4f66f2a6d7aeaf53207eabce926fe4335516a6e8..7e1620c21be61774fee37cb4d390c3c50b1d94a6 100644 --- a/shell/cc-window.c +++ b/shell/cc-window.c @@ -36,6 +36,7 @@ #include #include +#include "cc-application.h" #include "cc-panel.h" #include "cc-shell.h" #include "cc-shell-category-view.h" @@ -89,7 +90,8 @@ G_DEFINE_TYPE_WITH_CODE (CcWindow, cc_window, GTK_TYPE_APPLICATION_WINDOW, enum { PROP_0, - PROP_ACTIVE_PANEL + PROP_ACTIVE_PANEL, + PROP_MODEL }; /* Auxiliary methods */ @@ -160,11 +162,12 @@ get_symbolic_icon_name_from_g_icon (GIcon *gicon) } static gboolean -activate_panel (CcWindow *self, - const gchar *id, - GVariant *parameters, - const gchar *name, - GIcon *gicon) +activate_panel (CcWindow *self, + const gchar *id, + GVariant *parameters, + const gchar *name, + GIcon *gicon, + CcPanelVisibility visibility) { g_autoptr (GTimer) timer = NULL; GtkWidget *box, *title_widget; @@ -173,6 +176,9 @@ activate_panel (CcWindow *self, if (!id) return FALSE; + if (visibility == CC_PANEL_HIDDEN) + return FALSE; + timer = g_timer_new (); g_settings_set_string (self->settings, "last-panel", id); @@ -281,6 +287,23 @@ update_list_title (CcWindow *self) gtk_header_bar_set_title (GTK_HEADER_BAR (self->header), title); } +static void +on_row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CcWindow *self) +{ + 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 (CC_PANEL_LIST (self->panel_list), id, visibility); +} + static void setup_model (CcWindow *shell) { @@ -288,7 +311,9 @@ setup_model (CcWindow *shell) GtkTreeIter iter; gboolean valid; - shell->store = (GtkListStore *) cc_shell_model_new (); + /* CcApplication must have a valid model at this point */ + g_assert (shell->store != NULL); + model = GTK_TREE_MODEL (shell->store); cc_panel_loader_fill_model (CC_SHELL_MODEL (shell->store)); @@ -305,6 +330,7 @@ setup_model (CcWindow *shell) g_autofree gchar *id = NULL; g_autofree gchar *icon_name = NULL; g_autofree GStrv keywords = NULL; + CcPanelVisibility visibility; gtk_tree_model_get (model, &iter, COL_CATEGORY, &category, @@ -313,6 +339,7 @@ setup_model (CcWindow *shell) COL_ID, &id, COL_NAME, &name, COL_KEYWORDS, &keywords, + COL_VISIBILITY, &visibility, -1); icon_name = get_symbolic_icon_name_from_g_icon (icon); @@ -323,10 +350,14 @@ setup_model (CcWindow *shell) name, description, keywords, - icon_name); + icon_name, + visibility); 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), shell, 0); } @@ -339,6 +370,7 @@ set_active_panel_from_id (CcShell *shell, { g_autoptr(GIcon) gicon = NULL; g_autofree gchar *name = NULL; + CcPanelVisibility visibility; GtkTreeIter iter; GtkWidget *old_panel; CcWindow *self; @@ -354,9 +386,6 @@ set_active_panel_from_id (CcShell *shell, return TRUE; } - /* clear any custom widgets */ - remove_all_custom_widgets (self); - iter_valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter); /* find the details for this item */ @@ -368,6 +397,7 @@ set_active_panel_from_id (CcShell *shell, COL_NAME, &name, COL_GICON, &gicon, COL_ID, &id, + COL_VISIBILITY, &visibility, -1); if (id && strcmp (id, start_id) == 0) @@ -388,12 +418,18 @@ set_active_panel_from_id (CcShell *shell, } /* Activate the panel */ - activated = activate_panel (CC_WINDOW (shell), start_id, parameters, name, gicon); + activated = activate_panel (CC_WINDOW (shell), start_id, parameters, name, gicon, visibility); /* Failed to activate the panel for some reason, let's keep the old * panel around instead */ if (!activated) - return TRUE; + { + g_debug ("Failed to activate panel"); + return TRUE; + } + + /* clear any custom widgets */ + remove_all_custom_widgets (self); if (add_to_history) add_current_panel_to_history (shell, start_id); @@ -700,6 +736,11 @@ cc_window_get_property (GObject *object, case PROP_ACTIVE_PANEL: g_value_set_object (value, self->active_panel); break; + + case PROP_MODEL: + g_value_set_object (value, self->store); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -718,11 +759,49 @@ cc_window_set_property (GObject *object, case PROP_ACTIVE_PANEL: set_active_panel (shell, g_value_get_object (value)); break; + + case PROP_MODEL: + g_assert (shell->store == NULL); + shell->store = g_value_dup_object (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } +static void +cc_window_constructed (GObject *object) +{ + g_autofree char *id = NULL; + GtkSettings *settings; + CcWindow *self; + + self = CC_WINDOW (object); + + /* Handle decorations for the split headers. */ + settings = gtk_settings_get_default (); + g_signal_connect (settings, + "notify::gtk-decoration-layout", + G_CALLBACK (split_decorations_cb), + self); + + split_decorations_cb (settings, NULL, 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 (CC_SHELL_MODEL (self->store), id)) + cc_panel_list_set_active_panel (CC_PANEL_LIST (self->panel_list), id); + else + cc_panel_list_activate (CC_PANEL_LIST (self->panel_list)); + + G_OBJECT_CLASS (cc_window_parent_class)->constructed (object); +} + static void cc_window_dispose (GObject *object) { @@ -760,6 +839,7 @@ cc_window_class_init (CcWindowClass *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; @@ -767,6 +847,14 @@ cc_window_class_init (CcWindowClass *klass) 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)); + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/ControlCenter/gtk/window.ui"); gtk_widget_class_bind_template_child (widget_class, CcWindow, development_warning_dialog); @@ -800,45 +888,22 @@ cc_window_class_init (CcWindowClass *klass) static void cc_window_init (CcWindow *self) { - GtkSettings *settings; - g_autofree char *id = NULL; - gtk_widget_init_template (GTK_WIDGET (self)); gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_RELEASE_MASK); self->settings = g_settings_new ("org.gnome.ControlCenter"); - - /* Handle decorations for the split headers. */ - settings = gtk_settings_get_default (); - g_signal_connect (settings, - "notify::gtk-decoration-layout", - G_CALLBACK (split_decorations_cb), - self); - - split_decorations_cb (settings, NULL, self); - - /* Add the panels */ self->custom_widgets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); self->previous_panels = g_queue_new (); - 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 (CC_SHELL_MODEL (self->store), id)) - cc_panel_list_set_active_panel (CC_PANEL_LIST (self->panel_list), id); - else - cc_panel_list_activate (CC_PANEL_LIST (self->panel_list)); - /* Add a custom CSS class on development builds */ if (in_flatpak_sandbox ()) add_development_build_css (self); } CcWindow * -cc_window_new (GtkApplication *application) +cc_window_new (GtkApplication *application, + CcShellModel *model) { g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL); @@ -849,6 +914,7 @@ cc_window_new (GtkApplication *application) "icon-name", DEFAULT_WINDOW_ICON_NAME, "window-position", GTK_WIN_POS_CENTER, "show-menubar", FALSE, + "model", model, NULL); } diff --git a/shell/cc-window.h b/shell/cc-window.h index a6ec98e46bb9042f2a0d6942f2a75e5053d140ab..a2e53eaabd7d7998f7e787d7c5129c3bdbc531bb 100644 --- a/shell/cc-window.h +++ b/shell/cc-window.h @@ -23,6 +23,7 @@ #include #include "cc-shell.h" +#include "cc-shell-model.h" G_BEGIN_DECLS @@ -30,7 +31,8 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (CcWindow, cc_window, CC, WINDOW, GtkApplicationWindow) -CcWindow *cc_window_new (GtkApplication *application); +CcWindow *cc_window_new (GtkApplication *application, + CcShellModel *model); void cc_window_set_search_item (CcWindow *center, const char *search);