/* 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
{
GtkStack parent;
GtkWidget *privacy_listbox;
GtkWidget *main_listbox;
GtkWidget *search_listbox;
/* 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, GTK_TYPE_STACK)
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 (GTK_STACK (self), "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 (GTK_STACK (self),
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 (GTK_STACK (self), 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)
switch_to_view (self, self->previous_view);
}
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 = g_object_new (GTK_TYPE_GRID,
"visible", TRUE,
"hexpand", TRUE,
"border-width", 12,
"column-spacing", 12,
NULL);
/* Icon */
image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
gtk_style_context_add_class (gtk_widget_get_style_context (image), "sidebar-icon");
gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 1);
gtk_widget_show (image);
/* Name label */
label = g_object_new (GTK_TYPE_LABEL,
"label", name,
"visible", TRUE,
"xalign", 0.0,
"hexpand", TRUE,
NULL);
gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1);
/* Description label */
label = g_object_new (GTK_TYPE_LABEL,
"label", description,
"visible", FALSE,
"xalign", 0.0,
"hexpand", TRUE,
NULL);
gtk_label_set_max_width_chars (GTK_LABEL (label), 25);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
if (has_sidebar)
{
image = gtk_image_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_BUTTON);
gtk_style_context_add_class (gtk_widget_get_style_context (image), "sidebar-icon");
gtk_grid_attach (GTK_GRID (grid), image, 2, 0, 1, 1);
gtk_widget_show (image);
}
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_container_add (GTK_CONTAINER (data->row), grid);
gtk_widget_show (data->row);
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",
"mobile-broadband",
"bluetooth",
"background",
"notifications",
"search",
"applications",
"privacy",
"online-accounts",
"sharing",
/* Privacy page */
"location",
"camera",
"microphone",
"thunderbolt",
"usage",
"lock",
"diagnostics",
/* 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 (GTK_STACK (self), "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;
RowData *data;
GList *children, *l;
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 */
children = gtk_container_get_children (GTK_CONTAINER (real_listbox));
for (l = children; l != NULL; l = l->next)
{
RowData *real_row_data;
real_row_data = g_object_get_data (l->data, "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;
}
}
g_list_free (children);
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/ControlCenter/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_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_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, visibility, has_sidebar);
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);
/* 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)
{
g_return_if_fail (CC_IS_PANEL_LIST (self));
if (widget)
{
gtk_stack_add_named (GTK_STACK (self), widget, "custom-widget");
switch_to_view (self, CC_PANEL_LIST_WIDGET);
}
else
{
widget = get_widget_from_view (self, CC_PANEL_LIST_WIDGET);
if (widget)
gtk_container_remove (GTK_CONTAINER (self), 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));
}
}