From a78cbe39634192e6e050c9bd2e6518aabf462a18 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Sun, 8 Apr 2018 19:10:25 -0300 Subject: [PATCH] model: Add visibility field This field can be used to communicate the visibility of the panel from a static context (i.e. without any instances of a CcPanel nor any access to CcShell). --- shell/cc-panel-list.c | 173 +++++++++++++++++++++++++++++++---------- shell/cc-panel-list.h | 7 +- shell/cc-panel.h | 14 ++++ shell/cc-shell-model.c | 45 ++++++++++- shell/cc-shell-model.h | 29 ++++--- shell/cc-window.c | 41 ++++++++-- 6 files changed, 246 insertions(+), 63 deletions(-) diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c index f5b83509d..f752c231b 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 @@ -106,6 +110,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 +206,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 +270,9 @@ row_data_new (CcPanelCategory category, g_object_set_data_full (G_OBJECT (data->row), "data", data, (GDestroyNotify) row_data_free); + data->visibility = visibility; + gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); + return data; } @@ -766,18 +817,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 +839,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 +919,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,27 +934,13 @@ 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); + 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_container_add (GTK_CONTAINER (self->search_listbox), search_data->row); g_hash_table_insert (self->id_to_data, data->id, data); @@ -925,8 +966,19 @@ 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; + } + 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); @@ -937,3 +989,38 @@ cc_panel_list_set_active_panel (CcPanelList *self, g_signal_emit_by_name (data->row, "activate"); } + +/** + * 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; + + g_return_if_fail (CC_IS_PANEL_LIST (self)); + + data = g_hash_table_lookup (self->id_to_data, id); + + g_assert (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); +} diff --git a/shell/cc-panel-list.h b/shell/cc-panel-list.h index 14db2fece..303ce1d9c 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.h b/shell/cc-panel.h index c517164bf..c14bad893 100644 --- a/shell/cc-panel.h +++ b/shell/cc-panel.h @@ -36,6 +36,20 @@ G_DECLARE_DERIVABLE_TYPE (CcPanel, cc_panel, CC, PANEL, GtkBin) +/** + * 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" diff --git a/shell/cc-shell-model.c b/shell/cc-shell-model.c index 4dcddba45..2e209a7d0 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 b0fe50ff6..397fc7d22 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 b041f1d76..1d417a4ea 100644 --- a/shell/cc-window.c +++ b/shell/cc-window.c @@ -162,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; @@ -175,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); @@ -283,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) { @@ -309,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, @@ -317,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); @@ -327,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); } @@ -343,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; @@ -372,6 +400,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)