[gnome-control-center/wip/gbsneto/sidelist-groups: 1/3] panel-list: create a custom class to handle the sidelist



commit c7360c4a6b5c14fb2896081cc4b8bb9fa9855cb1
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Mon Jun 6 14:13:54 2016 -0300

    panel-list: create a custom class to handle the sidelist
    
    As the sidelist gets more complex, managing it in CcWindow
    would make it very confusing.
    
    This patch introduces the CcPanelList, a widget that manages
    the sidelist.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=767301

 shell/Makefile.am                        |    7 +
 shell/alt/Makefile.am                    |    3 +
 shell/alt/cc-panel-list.c                |  790 ++++++++++++++++++++++++++++++
 shell/alt/cc-panel-list.h                |   67 +++
 shell/gnome-control-center.gresource.xml |    1 +
 shell/panel-list.ui                      |  209 ++++++++
 6 files changed, 1077 insertions(+), 0 deletions(-)
---
diff --git a/shell/Makefile.am b/shell/Makefile.am
index 36fb22e..44e48f7 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -60,6 +60,12 @@ gnome_control_center_alt_SOURCES =           \
 gnome_control_center_LDFLAGS = -export-dynamic
 gnome_control_center_alt_LDFLAGS = -export-dynamic
 
+# Temporarily add the CC_ENABLE_ALT_CATEGORIES compile-time
+# flag to keep the current panels working.
+gnome_control_center_alt_CPPFLAGS = \
+       $(AM_CPPFLAGS)                          \
+       -DCC_ENABLE_ALT_CATEGORIES
+
 gnome_control_center_LDADD =                                           \
        libshell.la                                                     \
        $(SHELL_LIBS)                                                   \
@@ -149,6 +155,7 @@ EXTRA_DIST =                                        \
        $(completion_in_files)                  \
        gnome-control-center.gresource.xml      \
        help-overlay.ui                         \
+       panel-list.ui                           \
        window.ui                               \
        $(resource_files)                       \
        list-panel.sh
diff --git a/shell/alt/Makefile.am b/shell/alt/Makefile.am
index 948b019..235d131 100644
--- a/shell/alt/Makefile.am
+++ b/shell/alt/Makefile.am
@@ -1,5 +1,6 @@
 AM_CPPFLAGS =                                  \
        -DGNOMELOCALEDIR="\"$(datadir)/locale\""\
+       -DCC_ENABLE_ALT_CATEGORIES              \
        -I$(top_srcdir)                         \
        $(SHELL_CFLAGS)                         \
        $(CHEESE_CFLAGS)                        \
@@ -11,6 +12,8 @@ AM_CPPFLAGS =                                 \
 noinst_LTLIBRARIES = libshell_alt.la
 
 libshell_alt_la_SOURCES =                      \
+       cc-panel-list.c                         \
+       cc-panel-list.h                         \
        cc-window.c                             \
        cc-window.h
 
diff --git a/shell/alt/cc-panel-list.c b/shell/alt/cc-panel-list.c
new file mode 100644
index 0000000..d116510
--- /dev/null
+++ b/shell/alt/cc-panel-list.c
@@ -0,0 +1,790 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Georges Basile Stavracas Neto <gbsneto gnome org>
+ */
+
+#include "cc-panel.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;
+} RowData;
+
+struct _CcPanelList
+{
+  GtkStack            parent;
+
+  GtkWidget          *details_listbox;
+  GtkWidget          *devices_listbox;
+  GtkWidget          *main_listbox;
+  GtkWidget          *search_listbox;
+
+  GtkListBoxRow      *details_row;
+  GtkListBoxRow      *devices_row;
+
+  GtkWidget          *empty_search_placeholder;
+
+  gchar              *search_query;
+
+  CcPanelListView     previous_view;
+  CcPanelListView     view;
+};
+
+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_listbox_from_view (CcPanelList     *self,
+                       CcPanelListView  view)
+{
+  switch (view)
+    {
+    case CC_PANEL_LIST_MAIN:
+      return self->main_listbox;
+
+    case CC_PANEL_LIST_DETAILS:
+      return self->details_listbox;
+
+    case CC_PANEL_LIST_DEVICES:
+      return self->devices_listbox;
+
+    case CC_PANEL_LIST_SEARCH:
+      return self->search_listbox;
+
+    default:
+      return NULL;
+    }
+}
+
+static CcPanelListView
+get_view_from_listbox (CcPanelList *self,
+                       GtkWidget   *listbox)
+{
+  if (listbox == self->main_listbox)
+    return CC_PANEL_LIST_MAIN;
+
+  if (listbox == self->details_listbox)
+    return CC_PANEL_LIST_DETAILS;
+
+  if (listbox == self->devices_listbox)
+    return CC_PANEL_LIST_DEVICES;
+
+  return CC_PANEL_LIST_SEARCH;
+}
+
+/*
+ * RowData functions
+ */
+static void
+row_data_free (RowData *data)
+{
+  g_free (data->description);
+  g_free (data->name);
+  g_free (data);
+}
+
+static RowData*
+row_data_new (CcPanelCategory  category,
+              const gchar     *id,
+              const gchar     *name,
+              const gchar     *description,
+              const gchar     *icon)
+{
+  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);
+
+  /* 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), "dim-label");
+
+  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);
+
+  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);
+
+  return data;
+}
+
+/*
+ * GtkListBox functions
+ */
+static gboolean
+filter_func (GtkListBoxRow *row,
+             gpointer       user_data)
+{
+  CcPanelList *self;
+  RowData *data;
+  gchar *search_text, *panel_text, *panel_description;
+  gboolean retval;
+
+  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);
+
+  retval = g_strstr_len (panel_text, -1, search_text) != NULL ||
+           g_strstr_len (panel_description, -1, search_text) != NULL;
+
+  g_free (panel_text);
+  g_free (search_text);
+  g_free (panel_description);
+
+  return retval;
+}
+
+static gint
+sort_function (GtkListBoxRow *a,
+               GtkListBoxRow *b,
+               gpointer       user_data)
+{
+  CcPanelList *self;
+  RowData *a_data, *b_data;
+
+  self = CC_PANEL_LIST (user_data);
+
+  /* Handle the Devices and the Details rows */
+  if (a == self->details_row && b == self->devices_row)
+    return 1;
+  if (a == self->devices_row && b == self->details_row)
+    return -1;
+  if (a == self->details_row || a == self->devices_row)
+    return 1;
+  if (b == self->details_row || b == self->devices_row)
+    return -1;
+
+  /*
+   * We can only retrieve the data after assuring that none
+   * of the rows are Devices and Details.
+   */
+  a_data = g_object_get_data (G_OBJECT (a), "data");
+  b_data = g_object_get_data (G_OBJECT (b), "data");
+
+  if (a_data->category != b_data->category)
+    return a_data->category - b_data->category;
+
+  return g_strcmp0 (a_data->name, b_data->name);
+}
+
+static gint
+search_sort_function (GtkListBoxRow *a,
+                      GtkListBoxRow *b,
+                      gpointer       user_data)
+{
+  CcPanelList *self;
+  RowData *a_data, *b_data;
+  gchar *a_name, *b_name, *search, *a_strstr, *b_strstr;
+  gint a_distance, b_distance;
+  gint retval;
+
+  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)
+    {
+      retval = g_strcmp0 (a_name, b_name);
+      goto out;
+    }
+
+  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;
+
+  retval = a_distance - b_distance;
+
+out:
+  g_free (a_name);
+  g_free (b_name);
+  g_free (search);
+
+  return retval;
+}
+
+static void
+header_func (GtkListBoxRow *row,
+             GtkListBoxRow *before,
+             gpointer       user_data)
+{
+  CcPanelList *self = CC_PANEL_LIST (user_data);
+
+  if (!before)
+    return;
+
+  /* The Details row always have the separator */
+  if (row == self->details_row)
+    {
+      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
+    {
+      RowData *row_data, *before_data;
+
+      if (row == self->devices_row ||
+          before == self->details_row ||
+          before == self->devices_row)
+        {
+          return;
+        }
+
+      /*
+       * We can only retrieve the data after assuring that none
+       * of the rows are Devices and Details.
+       */
+      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);
+        }
+    }
+}
+
+/*
+ * Callbacks
+ */
+static void
+row_activated_cb (GtkWidget     *listbox,
+                  GtkListBoxRow *row,
+                  CcPanelList   *self)
+{
+  RowData *data;
+
+  /* Details */
+  if (row == self->details_row)
+    {
+      cc_panel_list_set_view (self, CC_PANEL_LIST_DETAILS);
+      return;
+    }
+
+  /* Devices */
+  if (row == self->devices_row)
+    {
+      cc_panel_list_set_view (self, CC_PANEL_LIST_DEVICES);
+      return;
+    }
+
+  /*
+   * 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->details_listbox)
+        gtk_list_box_unselect_all (GTK_LIST_BOX (self->details_listbox));
+
+      if (listbox != self->devices_listbox)
+        gtk_list_box_unselect_all (GTK_LIST_BOX (self->devices_listbox));
+    }
+
+  /*
+   * Since we're not sure that the activated row is in the
+   * current view, set the view here.
+   */
+  cc_panel_list_set_view (self, get_view_from_listbox (self, listbox));
+
+  data = g_object_get_data (G_OBJECT (row), "data");
+
+  g_signal_emit (self, signals[SHOW_PANEL], 0, data->id);
+}
+
+static void
+search_row_activated_cb (GtkWidget     *listbox,
+                         GtkListBoxRow *row,
+                         CcPanelList   *self)
+{
+  GtkWidget *real_listbox;
+  RowData *data;
+  GList *children, *l;
+
+  data = g_object_get_data (G_OBJECT (row), "data");
+
+  if (data->category == CC_CATEGORY_DETAILS)
+    real_listbox = self->details_listbox;
+  else if (data->category == CC_CATEGORY_DEVICES)
+    real_listbox = self->devices_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);
+}
+
+static void
+cc_panel_list_finalize (GObject *object)
+{
+  CcPanelList *self = (CcPanelList *)object;
+
+  g_clear_pointer (&self->search_query, g_free);
+
+  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:
+      if (g_value_get_boolean (value))
+        cc_panel_list_set_view (self, CC_PANEL_LIST_SEARCH);
+      else
+        cc_panel_list_set_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));
+      break;
+
+    case PROP_SEARCH_QUERY:
+      cc_panel_list_set_search_query (self, g_value_get_string (value));
+      break;
+
+    case PROP_VIEW:
+      cc_panel_list_set_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:
+   *
+   * Emited 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/panel-list.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcPanelList, details_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcPanelList, details_row);
+  gtk_widget_class_bind_template_child (widget_class, CcPanelList, devices_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcPanelList, devices_row);
+  gtk_widget_class_bind_template_child (widget_class, CcPanelList, empty_search_placeholder);
+  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->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_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);
+
+  gtk_list_box_set_placeholder (GTK_LIST_BOX (self->search_listbox), self->empty_search_placeholder);
+}
+
+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;
+
+  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);
+
+  /* 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");
+    }
+
+  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);
+
+      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_set_view (CcPanelList     *self,
+                        CcPanelListView  view)
+{
+  g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+  if (self->view != view)
+    {
+      GtkWidget *visible_child;
+      gboolean should_crossfade;
+
+      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_listbox_from_view (self, view);
+
+      gtk_stack_set_visible_child (GTK_STACK (self), visible_child);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_MODE]);
+    }
+}
+
+void
+cc_panel_list_add_panel (CcPanelList     *self,
+                         CcPanelCategory  category,
+                         const gchar     *id,
+                         const gchar     *title,
+                         const gchar     *description,
+                         const gchar     *icon)
+{
+  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, 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;
+    }
+
+  gtk_container_add (GTK_CONTAINER (listbox), data->row);
+
+  /* And add to the search listbox too */
+  search_data = row_data_new (category, id, title, description, icon);
+  gtk_container_add (GTK_CONTAINER (self->search_listbox), search_data->row);
+}
diff --git a/shell/alt/cc-panel-list.h b/shell/alt/cc-panel-list.h
new file mode 100644
index 0000000..0cd1880
--- /dev/null
+++ b/shell/alt/cc-panel-list.h
@@ -0,0 +1,67 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Georges Basile Stavracas Neto <gbsneto gnome org>
+ */
+
+#ifndef CC_PANEL_LIST_H
+#define CC_PANEL_LIST_H
+
+#include <glib-object.h>
+
+#include "cc-panel.h"
+#include "cc-shell-model.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  CC_PANEL_LIST_MAIN,
+  CC_PANEL_LIST_DETAILS,
+  CC_PANEL_LIST_DEVICES,
+  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, GtkStack)
+
+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_set_view                      (CcPanelList        *self,
+                                                                  CcPanelListView     view);
+
+void                 cc_panel_list_add_panel                     (CcPanelList        *self,
+                                                                  CcPanelCategory     category,
+                                                                  const gchar        *id,
+                                                                  const gchar        *title,
+                                                                  const gchar        *description,
+                                                                  const gchar        *icon);
+
+G_END_DECLS
+
+#endif /* CC_PANEL_LIST_H */
+
diff --git a/shell/gnome-control-center.gresource.xml b/shell/gnome-control-center.gresource.xml
index d4256cb..2482393 100644
--- a/shell/gnome-control-center.gresource.xml
+++ b/shell/gnome-control-center.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/ControlCenter/gtk">
     <file preprocess="xml-stripblanks">help-overlay.ui</file>
+    <file preprocess="xml-stripblanks">panel-list.ui</file>
     <file preprocess="xml-stripblanks">window.ui</file>
   </gresource>
 </gresources>
diff --git a/shell/panel-list.ui b/shell/panel-list.ui
new file mode 100644
index 0000000..340ddcf
--- /dev/null
+++ b/shell/panel-list.ui
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="CcPanelList" parent="GtkStack">
+    <property name="width_request">200</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="vhomogeneous">False</property>
+    <property name="transition_type">slide-left-right</property>
+    <child>
+      <object class="GtkListBox" id="main_listbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <signal name="row-activated" handler="row_activated_cb" object="CcPanelList" swapped="no" />
+        <child>
+          <object class="GtkListBoxRow" id="devices_row">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="selectable">False</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">applications-system-symbolic</property>
+                    <style>
+                      <class name="dim-label" />
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="label" translatable="yes">Devices</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">go-next-symbolic</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow" id="details_row">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="selectable">False</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">applications-system-symbolic</property>
+                    <style>
+                      <class name="dim-label" />
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="label" translatable="yes">Details</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">go-next-symbolic</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="name">main</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkListBox" id="devices_listbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <signal name="row-activated" handler="row_activated_cb" object="CcPanelList" swapped="no" />
+      </object>
+      <packing>
+        <property name="name">devices</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkListBox" id="details_listbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <signal name="row-activated" handler="row_activated_cb" object="CcPanelList" swapped="no" />
+      </object>
+      <packing>
+        <property name="name">details</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkListBox" id="search_listbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <signal name="row-activated" handler="search_row_activated_cb" object="CcPanelList" swapped="no" />
+      </object>
+      <packing>
+        <property name="name">search</property>
+        <property name="position">3</property>
+      </packing>
+    </child>
+  </template>
+  <object class="GtkBox" id="empty_search_placeholder">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="halign">center</property>
+    <property name="valign">center</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="border_width">18</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkImage">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="pixel_size">72</property>
+        <property name="icon_name">edit-find-symbolic</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">No results found</property>
+        <attributes>
+          <attribute name="weight" value="bold"/>
+          <attribute name="scale" value="1.44"/>
+        </attributes>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Try a different search</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+    </child>
+  </object>
+</interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]