[gnome-control-center/wip/applications] Tweak to follow an alternative design



commit 0978dba1917799f3003e1d67ef5b32b7ef0fccb6
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Nov 28 11:05:05 2018 -0500

    Tweak to follow an alternative design

 panels/applications/applications.gresource.xml |   1 +
 panels/applications/cc-applications-panel.c    | 323 ++++++++++--------
 panels/applications/cc-applications-panel.ui   | 431 +++++++++++++++----------
 panels/applications/cc-info-row.c              | 145 +++++++++
 panels/applications/cc-info-row.h              |  32 ++
 panels/applications/cc-info-row.ui             |  48 +++
 panels/applications/cc-toggle-row.c            | 127 +++++---
 panels/applications/cc-toggle-row.h            |   6 +-
 panels/applications/cc-toggle-row.ui           |  27 +-
 panels/applications/meson.build                |   1 +
 panels/applications/utils.c                    |  26 ++
 panels/applications/utils.h                    |   2 +
 12 files changed, 827 insertions(+), 342 deletions(-)
---
diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml
index a8b996f49..2a8fd59b4 100644
--- a/panels/applications/applications.gresource.xml
+++ b/panels/applications/applications.gresource.xml
@@ -4,6 +4,7 @@
     <file preprocess="xml-stripblanks">cc-applications-panel.ui</file>
     <file preprocess="xml-stripblanks">cc-applications-row.ui</file>
     <file preprocess="xml-stripblanks">cc-toggle-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-info-row.ui</file>
     <file preprocess="xml-stripblanks">cc-action-row.ui</file>
     <file>cc-applications-panel.css</file>
   </gresource>
diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c
index f82a78fb8..5da724097 100644
--- a/panels/applications/cc-applications-panel.c
+++ b/panels/applications/cc-applications-panel.c
@@ -26,6 +26,7 @@
 #include "cc-applications-panel.h"
 #include "cc-applications-row.h"
 #include "cc-toggle-row.h"
+#include "cc-info-row.h"
 #include "cc-action-row.h"
 #include "cc-applications-resources.h"
 #include "list-box-helper.h"
@@ -59,24 +60,30 @@ struct _CcApplicationsPanel
   GDBusProxy *perm_store;
   GSettings *notification_settings;
 
+  GtkListBox *stack;
   GtkWidget *permission_section;
   GtkWidget *permission_list;
 
   GtkWidget *camera;
   GtkWidget *location;
   GtkWidget *microphone;
+
+  GtkWidget *permission_footer;
+
+  GtkWidget *information_section;
+  GtkWidget *information_list;
   GtkWidget *notification;
   GtkWidget *sound;
 
-  GtkWidget *static_permissions;
-
   GtkWidget *device_section;
   GtkWidget *device_list;
 
   GtkWidget *handler_section;
   GtkWidget *handler_list;
-  GtkWidget *cache_section;
-  GtkWidget *cache_list;
+  GtkWidget *storage_section;
+  GtkWidget *storage_list;
+  GtkWidget *app;
+  GtkWidget *data;
   GtkWidget *cache;
 };
 
@@ -198,7 +205,7 @@ get_notification_allowed (CcApplicationsPanel *self,
 {
   if (self->notification_settings)
     {
-      return g_settings_get_boolean (self->notification_settings, "enable"); 
+      return g_settings_get_boolean (self->notification_settings, "enable");
     }
   else
     {
@@ -206,21 +213,17 @@ get_notification_allowed (CcApplicationsPanel *self,
                                                      "notifications",
                                                      "notification",
                                                      app_id);
-      return perms == NULL || !g_strv_contains ((const char * const*)perms, "no");
+      return perms == NULL || strcmp (perms[0], "no") != 0;
     }
 }
 
 static void
-notification_cb (CcToggleRow *row, CcApplicationsPanel *self)
+set_notification_allowed (CcApplicationsPanel *self,
+                          gboolean allowed)
 {
-  gboolean allowed = cc_toggle_row_get_allowed (row);
-
-  if (self->current_app_id == NULL)
-    return;
-
   if (self->notification_settings)
     {
-      g_settings_set_boolean (self->notification_settings, "enable", allowed); 
+      g_settings_set_boolean (self->notification_settings, "enable", allowed);
     }
   else
     {
@@ -231,6 +234,13 @@ notification_cb (CcToggleRow *row, CcApplicationsPanel *self)
     }
 }
 
+static void
+notification_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_notification_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->notification)));
+}
+
 static char *
 munge_app_id (const char *app_id)
 {
@@ -270,7 +280,7 @@ get_device_allowed (CcApplicationsPanel *self,
 
   perms = get_flatpak_permissions (self, "devices", device, app_id);
 
-  return perms == NULL || !g_strv_contains ((const char * const*)perms, "no");
+  return perms == NULL || strcmp (perms[0], "no") != 0;
 }
 
 static void
@@ -280,9 +290,6 @@ set_device_allowed (CcApplicationsPanel *self,
 {
   const char *perms[2];
 
-  if (self->current_app_id == NULL)
-    return;
-
   perms[0] = allowed ? "yes" : "no";
   perms[1] = NULL;
 
@@ -290,21 +297,24 @@ set_device_allowed (CcApplicationsPanel *self,
 }
 
 static void
-microphone_cb (CcToggleRow *row, CcApplicationsPanel *self)
+microphone_cb (CcApplicationsPanel *self)
 {
-  set_device_allowed (self, "microphone", cc_toggle_row_get_allowed (row));
+  if (self->current_app_id)
+    set_device_allowed (self, "microphone", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->microphone)));
 }
 
 static void
-sound_cb (CcToggleRow *row, CcApplicationsPanel *self)
+sound_cb (CcApplicationsPanel *self)
 {
-  set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (row));
+  if (self->current_app_id)
+   set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->sound)));
 }
 
 static void
-camera_cb (CcToggleRow *row, CcApplicationsPanel *self)
+camera_cb (CcApplicationsPanel *self)
 {
-  set_device_allowed (self, "camera", cc_toggle_row_get_allowed (row));
+  if (self->current_app_id)
+    set_device_allowed (self, "camera", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->camera)));
 }
 
 static gboolean
@@ -315,7 +325,7 @@ get_location_allowed (CcApplicationsPanel *self,
 
   perms = get_flatpak_permissions (self, "location", "location", app_id);
 
-  return perms == NULL || !g_str_equal (perms[0], "NONE");
+  return perms == NULL || strcmp (perms[0], "NONE") != 0;
 }
 
 static void
@@ -325,9 +335,6 @@ set_location_allowed (CcApplicationsPanel *self,
   const char *perms[3];
   g_autofree char *time = NULL;
 
-  if (self->current_app_id == NULL)
-    return;
-
   // FIXME allow setting accuracy
   perms[0] = allowed ? "EXACT" : "NONE";
   perms[1] = "0";
@@ -337,52 +344,79 @@ set_location_allowed (CcApplicationsPanel *self,
 }
 
 static void
-location_cb (CcToggleRow *row, CcApplicationsPanel *self)
+location_cb (CcApplicationsPanel *self)
 {
-  set_location_allowed (self, cc_toggle_row_get_allowed (row));
+  if (self->current_app_id)
+    set_location_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->location)));
 }
 
 static GFile *
-get_cache_dir (const char *app_id)
+get_app_dir (const char *app_id,
+             const char *subdir)
 {
   g_autofree char *path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL);
   g_autoptr(GFile) appdir = g_file_new_for_path (path);
-  return g_file_get_child (appdir, "cache");
+  return g_file_get_child (appdir, subdir);
 }
 
 static guint64
-calculate_cache_size (const char *app_id)
+calculate_dir_size (const char *app_id,
+                    const char *subdir)
 {
-  g_autoptr(GFile) cachedir = get_cache_dir (app_id);
+  g_autoptr(GFile) cachedir = get_app_dir (app_id, subdir);
   return file_size_recursively (cachedir);
 }
 
 static void
-update_cache_row (CcActionRow *row,
-                  const char *app_id)
+update_app_row (CcActionRow *row,
+                const char *app_id)
+{
+  g_autofree char *formatted_size = NULL;
+
+  formatted_size = get_flatpak_app_size (app_id);
+  g_object_set (row, "subtitle", formatted_size, NULL);
+}
+
+static void
+update_dir_row (CcActionRow *row,
+                const char *app_id,
+                const char *subdir)
 {
   guint64 size;
   g_autofree char *formatted_size = NULL;
-  g_autofree char *name = NULL;
 
-  size = calculate_cache_size (app_id);
+  subdir = g_strdup (subdir);
+  g_object_set_data_full (G_OBJECT (row), "subdir", (gpointer)subdir, g_free);
+
+  size = calculate_dir_size (app_id, subdir);
   formatted_size = g_format_size (size);
-  name = g_strdup_printf (_("Cache size: %s"), formatted_size); 
-  cc_action_row_set_name (row, name);
-  cc_action_row_set_action (row, _("Clear…"), size > 0);
+  g_object_set (row,
+                "subtitle", formatted_size,
+                "enabled", size > 0,
+                NULL);
 }
 
 static void
-clear_cache_cb (CcActionRow *row, CcApplicationsPanel *self)
+clear_cb (CcActionRow *row, CcApplicationsPanel *self)
 {
-  g_autoptr(GFile) cachedir = NULL;
+  g_autoptr(GFile) dir = NULL;
+  const char *subdir;
 
   if (self->current_app_id == NULL)
     return;
 
-  cachedir = get_cache_dir (self->current_app_id);
-  file_remove_recursively (cachedir);
-  update_cache_row (CC_ACTION_ROW (self->cache), self->current_app_id);
+  subdir = (const char *) g_object_get_data (G_OBJECT (row), "subdir");
+  dir = get_app_dir (self->current_app_id, subdir);
+  file_remove_recursively (dir);
+  update_dir_row (CC_ACTION_ROW (row), self->current_app_id, subdir);
+}
+
+static void
+uninstall_cb (CcActionRow *row, CcApplicationsPanel *self)
+{
+  // FIXME: confirmation dialog ? undo notification ?
+
+  uninstall_flatpak_app (self->current_app_id);
 }
 
 static char *
@@ -421,8 +455,25 @@ find_flatpak_ref (const char *app_id)
   return NULL;
 }
 
-static char *
-get_static_permissions (const char *app_id)
+static void
+add_static_permission_row (CcApplicationsPanel *self,
+                           const char *title,
+                           const char *subtitle)
+{
+  GtkWidget *row;
+
+  row = g_object_new (CC_TYPE_INFO_ROW,
+                      "title", title,
+                      "subtitle", subtitle,
+                      "info", _("Built-in"),
+                      "visible", TRUE,
+                      NULL);
+  gtk_container_add (GTK_CONTAINER (self->permission_list), row);
+}
+
+static void
+add_static_permissions (CcApplicationsPanel *self,
+                        const char *app_id)
 {
   g_autoptr(FlatpakInstalledRef) ref = NULL;
   g_autoptr(GBytes) bytes = NULL;
@@ -430,12 +481,7 @@ get_static_permissions (const char *app_id)
   g_autoptr(GKeyFile) keyfile = NULL;
   char **strv;
   char *str;
-  GString *s;
-  int len;
   
-  s = g_string_new ("");
-  g_string_append_printf (s, "<b>%s</b> ", _("Built-in permissions:"));
-
   ref = find_flatpak_ref (app_id);
   bytes = flatpak_installed_ref_load_metadata (ref, NULL, NULL);
   keyfile = g_key_file_new ();
@@ -445,67 +491,57 @@ get_static_permissions (const char *app_id)
                                   0, &error))
     {
       g_warning ("%s", error->message);
-      g_string_append (s, _("Unknown."));
-
-      return g_string_free (s, FALSE);
+      return;
     }
 
-  len = s->len;
- 
   strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL);
   if (strv && g_strv_contains ((const char * const*)strv, "system-bus"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, _("system bus"));
-    }
+    add_static_permission_row (self, _("System Bus"), _("Has access to the system bus"));
   if (strv && g_strv_contains ((const char * const*)strv, "x11"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, "X11");
-    }
+    add_static_permission_row (self, _("X11"), _("Has access to the display server"));
   g_strfreev (strv);
 
   strv = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL);
   if (strv && g_strv_contains ((const char * const*)strv, "all"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, "/dev");
-    }
+    add_static_permission_row (self, _("Devices"), _("Has full access to /dev"));
   g_strfreev (strv);
 
   strv = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL);
   if (strv && g_strv_contains ((const char * const*)strv, "network"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, _("network"));
-    }
+    add_static_permission_row (self, _("Network"), _("Has network access"));
   g_strfreev (strv);
 
   strv = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL);
-  if (strv && g_strv_contains ((const char * const *)strv, "home"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, _("home directory"));
-    }
-  if (strv && g_strv_contains ((const char * const *)strv, "host"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, _("host filesystem"));
-    }
+  if (strv && (g_strv_contains ((const char * const *)strv, "home") ||
+               g_strv_contains ((const char * const *)strv, "home:rw")))
+    add_static_permission_row (self, _("Home"), _("Read-write access"));
+  else if (strv && g_strv_contains ((const char * const *)strv, "home:ro"))
+    add_static_permission_row (self, _("Home"), _("Readonly access"));
+  if (strv && (g_strv_contains ((const char * const *)strv, "host") ||
+               g_strv_contains ((const char * const *)strv, "host:rw")))
+    add_static_permission_row (self, _("Filesystem"), _("Full filesystem access"));
+  else if (strv && g_strv_contains ((const char * const *)strv, "host:ro"))
+    add_static_permission_row (self, _("Filesystem"), _("Readonly access"));
   g_strfreev (strv);
 
   str = g_key_file_get_string (keyfile, "Session Bus Policy", "ca.desrt.dconf", NULL);
   if (str && g_str_equal (str, "talk"))
-    {
-      if (s->len > len) g_string_append (s, ", ");
-      g_string_append (s, _("settings"));
-    }
+    add_static_permission_row (self, _("Settings"), _("Can change settings"));
   g_free (str);
+}
 
-  g_string_append (s, ". ");
-  g_string_append (s, _("These are set by the application and cannot be altered."));
+static void
+remove_static_permissions (CcApplicationsPanel *self)
+{
+  GList *children, *l;
 
-  return g_string_free (s, FALSE);
+  children = gtk_container_get_children (GTK_CONTAINER (self->permission_list));
+  for (l = children; l; l = l->next)
+    {
+      if (CC_IS_INFO_ROW (l->data))
+        gtk_widget_destroy (GTK_WIDGET (l->data));
+    }
+  g_list_free (children);
 }
 
 static void
@@ -513,41 +549,43 @@ update_permission_section (CcApplicationsPanel *self,
                            GAppInfo *info)
 {
   g_autofree char *app_id = get_app_id (info);
+  g_autofree char *permissions = NULL;
 
-  if (app_info_is_flatpak (info))
+  if (!app_info_is_flatpak (info))
     {
-      g_autofree char *permissions = NULL;
+      gtk_widget_hide (self->permission_section);
+      return;
+    }
 
-      g_clear_object (&self->notification_settings);
+  gtk_widget_show (self->permission_section);
 
-      gtk_widget_show (self->camera);
-      gtk_widget_show (self->location);
-      gtk_widget_show (self->microphone);
-      gtk_widget_show (self->notification);
-      gtk_widget_show (self->sound);
+  remove_static_permissions (self);
+  add_static_permissions (self, app_id);
+
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->camera), get_device_allowed (self, "camera", app_id));
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->location), get_location_allowed (self, app_id));
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->microphone), get_device_allowed (self, "microphone", 
app_id));
+}
 
-      permissions = get_static_permissions (app_id);
-      gtk_label_set_markup (GTK_LABEL (self->static_permissions), permissions);
+static void
+update_information_section (CcApplicationsPanel *self,
+                            GAppInfo *info)
+{
+  g_autofree char *app_id = get_app_id (info);
 
-      cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->notification), get_notification_allowed (self, 
app_id));
+  if (app_info_is_flatpak (info))
+    {
+      g_clear_object (&self->notification_settings);
 
+      gtk_widget_show (self->sound);
     }
   else
     {
       g_set_object (&self->notification_settings, get_notification_settings (app_id));
 
-      gtk_widget_hide (self->camera);
-      gtk_widget_hide (self->location);
-      gtk_widget_hide (self->microphone);
-      gtk_widget_show (self->notification);
       gtk_widget_hide (self->sound);
-      gtk_label_set_label (GTK_LABEL (self->static_permissions),
-                           _("This application is not sandboxed and permissions cannot be fully enforced."));
     }
 
-  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->camera), get_device_allowed (self, "camera", app_id));
-  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->location), get_location_allowed (self, app_id));
-  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->microphone), get_device_allowed (self, "microphone", 
app_id));
   cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->notification), get_notification_allowed (self, app_id));
   cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->sound), get_device_allowed (self, "speakers", app_id));
 }
@@ -570,7 +608,7 @@ add_handler_row (CcApplicationsPanel *self,
 
   desc = g_content_type_get_description (type);
   row = cc_action_row_new ();
-  cc_action_row_set_name (row, desc);
+  cc_action_row_set_title (row, desc);
   cc_action_row_set_action (row, _("Remove"), FALSE);
   gtk_list_box_insert (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (row), -1);
 }
@@ -610,18 +648,20 @@ update_handler_section (CcApplicationsPanel *self,
 }
 
 static void
-update_cache_section (CcApplicationsPanel *self,
-                      GAppInfo *info)
+update_storage_section (CcApplicationsPanel *self,
+                        GAppInfo *info)
 {
   if (app_info_is_flatpak (info))
     {
       g_autofree char *app_id = get_app_id (info);
-      gtk_widget_show (self->cache_section);
-      update_cache_row (CC_ACTION_ROW (self->cache), app_id);
+      gtk_widget_show (self->storage_section);
+      update_app_row (CC_ACTION_ROW (self->app), app_id);
+      update_dir_row (CC_ACTION_ROW (self->data), app_id, "data");
+      update_dir_row (CC_ACTION_ROW (self->cache), app_id, "cache");
     }
   else
     {
-      gtk_widget_hide (self->cache_section);
+      gtk_widget_hide (self->storage_section);
     }
 }
 
@@ -639,12 +679,13 @@ update_panel (CcApplicationsPanel *self)
 
   if (row == NULL)
     {
-      g_print ("nothing selected\n");
       gtk_label_set_label (GTK_LABEL (self->title_label), _("Applications"));
-      // FIXME handle no-app case
+      gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "empty");
       return;
     }
 
+  gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "settings");
+
   g_clear_pointer (&self->current_app_id, g_free);
 
   info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row));
@@ -652,9 +693,10 @@ update_panel (CcApplicationsPanel *self)
   gtk_label_set_label (GTK_LABEL (self->title_label), g_app_info_get_display_name (info));
 
   update_permission_section (self, info);
+  update_information_section (self, info);
   update_device_section (self, info);
   update_handler_section (self, info);
-  update_cache_section (self, info);
+  update_storage_section (self, info);
 
   self->current_app_id = get_app_id (info);
 }
@@ -683,6 +725,26 @@ populate_applications (CcApplicationsPanel *self)
   g_list_free_full (infos, g_object_unref);
 }
 
+static void
+prepare_content (CcApplicationsPanel *self)
+{
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->permission_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->information_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->handler_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->storage_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+}
+
 static int
 compare_rows (GtkListBoxRow *row1,
               GtkListBoxRow *row2,
@@ -765,20 +827,25 @@ cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_listbox);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, title_label);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, header_button);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, stack);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_section);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_list);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, camera);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_footer);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, information_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, information_list);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notification);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sound);
-  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, static_permissions);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, device_section);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, device_list);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_section);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_list);
-  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache_section);
-  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, data);
   gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache);
 
   gtk_widget_class_bind_template_callback (widget_class, camera_cb);
@@ -786,7 +853,8 @@ cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
   gtk_widget_class_bind_template_callback (widget_class, microphone_cb);
   gtk_widget_class_bind_template_callback (widget_class, notification_cb);
   gtk_widget_class_bind_template_callback (widget_class, sound_cb);
-  gtk_widget_class_bind_template_callback (widget_class, clear_cache_cb);
+  gtk_widget_class_bind_template_callback (widget_class, clear_cb);
+  gtk_widget_class_bind_template_callback (widget_class, uninstall_cb);
 }
 
 static void
@@ -815,16 +883,9 @@ cc_applications_panel_init (CcApplicationsPanel *self)
   populate_applications (self);
 
   self->monitor = g_app_info_monitor_get ();
-
   g_signal_connect (self->monitor, "changed", G_CALLBACK (apps_changed), self);
 
-  gtk_list_box_set_header_func (GTK_LIST_BOX (self->permission_list),
-                                cc_list_box_update_header_func,
-                                NULL, NULL);
-
-  gtk_list_box_set_header_func (GTK_LIST_BOX (self->handler_list),
-                                cc_list_box_update_header_func,
-                                NULL, NULL);
+  prepare_content (self);
 
   g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
                             G_DBUS_PROXY_FLAGS_NONE,
diff --git a/panels/applications/cc-applications-panel.ui b/panels/applications/cc-applications-panel.ui
index 042f8a80f..b6dc32df9 100644
--- a/panels/applications/cc-applications-panel.ui
+++ b/panels/applications/cc-applications-panel.ui
@@ -8,265 +8,356 @@
         <property name="visible">1</property>
         <property name="hscrollbar-policy">never</property>
         <child>
-          <object class="GtkBox">
+          <object class="GtkStack" id="stack">
             <property name="visible">1</property>
             <child>
               <object class="GtkBox">
                 <property name="visible">1</property>
-                <property name="hexpand">1</property>
               </object>
+              <packing>
+                <property name="name">empty</property>
+              </packing>
             </child>
             <child>
               <object class="GtkBox">
                 <property name="visible">1</property>
-                <property name="orientation">vertical</property>
-                <property name="margin-top">32</property>
-                <property name="margin-bottom">32</property>
-                <property name="margin-left">24</property>
-                <property name="margin-right">24</property>
-                <property name="spacing">24</property>
-                <property name="hexpand">1</property>
                 <child>
-                  <object class="GtkBox" id="permission_section">
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="hexpand">1</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
                     <property name="visible">1</property>
                     <property name="orientation">vertical</property>
-                    <property name="spacing">12</property>
-                    <style>
-                      <class name="section"/>
-                    </style>
+                    <property name="margin-top">32</property>
+                    <property name="margin-bottom">32</property>
+                    <property name="margin-left">24</property>
+                    <property name="margin-right">24</property>
+                    <property name="spacing">24</property>
+                    <property name="hexpand">1</property>
                     <child>
-                      <object class="GtkBox">
+                      <object class="GtkBox" id="permission_section">
                         <property name="visible">1</property>
                         <property name="orientation">vertical</property>
-                        <property name="spacing">6</property>
+                        <property name="spacing">12</property>
+                        <style>
+                          <class name="section"/>
+                        </style>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkBox">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Access and Permissions</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Access and Permissions</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Applications will ask for access 
and permission that they require</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkListBox" id="permission_list">
+                            <property name="visible">1</property>
+                            <property name="selection-mode">none</property>
+                            <child>
+                              <object class="CcToggleRow" id="camera">
+                                <property name="title" translatable="yes">Camera</property>
+                                <property name="on-subtitle" translatable="yes">Only when using the 
app</property>
+                                <property name="off-subtitle" translatable="yes">Off</property>
+                                <signal name="notify::allowed" handler="camera_cb" swapped="yes"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcToggleRow" id="microphone">
+                                <property name="title" translatable="yes">Microphone</property>
+                                <property name="on-subtitle" translatable="yes">Only when using the 
app</property>
+                                <property name="off-subtitle" translatable="yes">Off</property>
+                                <signal name="notify::allowed" handler="microphone_cb" swapped="yes"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcToggleRow" id="location">
+                                <property name="title" translatable="yes">Location Services</property>
+                                <property name="on-subtitle" translatable="yes">Only when using the 
app</property>
+                                <property name="off-subtitle" translatable="yes">Off</property>
+                                <signal name="notify::allowed" handler="location_cb" swapped="yes"/>
+                              </object>
+                            </child>
                             <style>
-                              <class name="section-title"/>
+                              <class name="view"/>
+                              <class name="frame"/>
                             </style>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkLabel" id="permission_footer">
                             <property name="visible">1</property>
                             <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Applications will ask for access and 
permission that they require</property>
+                            <property name="wrap">1</property>
+                            <property name="label" translatable="yes">To manage access and 
permission-related settings for all applications, go to the Privacy panel.</property>
                             <style>
-                              <class name="section-subtitle"/>
+                              <class name="dim-label"/>
                             </style>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkListBox" id="permission_list">
+                      <object class="GtkBox" id="information_section">
                         <property name="visible">1</property>
-                        <property name="selection-mode">none</property>
-                        <child>
-                          <object class="CcToggleRow" id="camera">
-                            <property name="name" translatable="yes">Camera</property>
-                            <signal name="changed" handler="camera_cb"/>
-                          </object> 
-                        </child>
-                        <child>
-                          <object class="CcToggleRow" id="location">
-                            <property name="name" translatable="yes">Location</property>
-                            <signal name="changed" handler="location_cb"/>
-                          </object> 
-                        </child>
-                        <child>
-                          <object class="CcToggleRow" id="microphone">
-                            <property name="name" translatable="yes">Microphone</property>
-                            <signal name="changed" handler="microphone_cb"/>
-                          </object> 
-                        </child>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <style>
+                          <class name="section"/>
+                        </style>
                         <child>
-                          <object class="CcToggleRow" id="notification">
-                            <property name="name" translatable="yes">Notifications</property>
-                            <signal name="changed" handler="notification_cb"/>
-                          </object> 
+                          <object class="GtkBox">
+                            <property name="visible">1</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Information</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">How the application can 
communicate with you or provide information and feedback.</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
                         </child>
                         <child>
-                          <object class="CcToggleRow" id="sound">
-                            <property name="name" translatable="yes">Sound</property>
-                            <signal name="changed" handler="sound_cb"/>
-                          </object> 
+                          <object class="GtkListBox" id="information_list">
+                            <property name="visible">1</property>
+                            <property name="selection-mode">none</property>
+                            <child>
+                              <object class="CcToggleRow" id="notification">
+                                <property name="title" translatable="yes">Notifications</property>
+                                <property name="on-subtitle" translatable="yes">Only on lock 
screen</property>
+                                <property name="off-subtitle" translatable="yes">Off</property>
+                                <signal name="notify::allowed" handler="notification_cb" swapped="yes"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcToggleRow" id="sound">
+                                <property name="title" translatable="yes">Sounds</property>
+                                <property name="on-subtitle" translatable="yes">Alerts only</property>
+                                <property name="off-subtitle" translatable="yes">Off</property>
+                                <signal name="notify::allowed" handler="sound_cb" swapped="yes"/>
+                              </object>
+                            </child>
+                            <style>
+                              <class name="view"/>
+                              <class name="frame"/>
+                            </style>
+                          </object>
                         </child>
-                        <style>
-                          <class name="view"/>
-                          <class name="frame"/>
-                        </style>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkLabel" id="static_permissions">
+                      <object class="GtkBox" id="device_section">
                         <property name="visible">1</property>
-                        <property name="xalign">0</property>
-                        <property name="wrap">1</property>
-                        <property name="wrap-mode">word</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
                         <style>
-                          <class name="dim-label"/>
+                          <class name="section"/>
                         </style>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox" id="device_section">
-                    <property name="visible">1</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">12</property>
-                    <style>
-                      <class name="section"/>
-                    </style>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">1</property>
-                        <property name="orientation">vertical</property>
-                        <property name="spacing">6</property>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkBox">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Device Access</property>
-                            <style>
-                              <class name="section-title"/>
-                            </style>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Device Access</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">USB Devices that this application 
has access to</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkListBox" id="device_list">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">USB Devices that this application has 
access to</property>
+                            <property name="selection-mode">none</property>
                             <style>
-                              <class name="section-subtitle"/>
+                              <class name="view"/>
+                              <class name="frame"/>
                             </style>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkListBox" id="device_list">
+                      <object class="GtkBox" id="handler_section">
                         <property name="visible">1</property>
-                        <property name="selection-mode">none</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
                         <style>
-                          <class name="view"/>
-                          <class name="frame"/>
+                          <class name="section"/>
                         </style>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox" id="handler_section">
-                    <property name="visible">1</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">12</property>
-                    <style>
-                      <class name="section"/>
-                    </style>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">1</property>
-                        <property name="orientation">vertical</property>
-                        <property name="spacing">6</property>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkBox">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Default Handler</property>
-                            <style>
-                              <class name="section-title"/>
-                            </style>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Default Handler</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Links and Files that are opened by 
this application</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkListBox" id="handler_list">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Links and Files that are opened by 
this application</property>
+                            <property name="selection-mode">none</property>
                             <style>
-                              <class name="section-subtitle"/>
+                              <class name="view"/>
+                              <class name="frame"/>
                             </style>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkListBox" id="handler_list">
+                      <object class="GtkBox" id="storage_section">
                         <property name="visible">1</property>
-                        <property name="selection-mode">none</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
                         <style>
-                          <class name="view"/>
-                          <class name="frame"/>
+                          <class name="section"/>
                         </style>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox" id="cache_section">
-                    <property name="visible">1</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">12</property>
-                    <style>
-                      <class name="section"/>
-                    </style>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">1</property>
-                        <property name="orientation">vertical</property>
-                        <property name="spacing">6</property>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkBox">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Cache</property>
-                            <style>
-                              <class name="section-title"/>
-                            </style>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Storage</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">How much disk space this 
application is occupying with app data and caches</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkLabel">
+                          <object class="GtkListBox" id="storage_list">
                             <property name="visible">1</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Maintained by the application and 
cannot be altered</property>
+                            <property name="selection-mode">none</property>
+                            <child>
+                              <object class="CcActionRow" id="app">
+                                <property name="title" translatable="yes">App Size</property>
+                                <property name="subtitle" translatable="yes">Unknown</property>
+                                <property name="action" translatable="yes">Uninstall App…</property>
+                                <property name="destructive">True</property>
+                                <signal name="activated" handler="uninstall_cb"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcActionRow" id="data">
+                                <property name="title" translatable="yes">Data</property>
+                                <property name="action" translatable="yes">Clear Data…</property>
+                                <signal name="activated" handler="clear_cb"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcActionRow" id="cache">
+                                <property name="title" translatable="yes">Cache</property>
+                                <property name="action" translatable="yes">Clear Cache…</property>
+                                <signal name="activated" handler="clear_cb"/>
+                              </object>
+                            </child>
                             <style>
-                              <class name="section-subtitle"/>
+                              <class name="view"/>
+                              <class name="frame"/>
                             </style>
                           </object>
                         </child>
                       </object>
                     </child>
-                    <child>
-                      <object class="GtkListBox" id="cache_list">
-                        <property name="visible">1</property>
-                        <property name="selection-mode">none</property>
-                        <child>
-                          <object class="CcActionRow" id="cache">
-                            <signal name="activated" handler="clear_cache_cb"/>
-                          </object>
-                        </child>
-                        <style>
-                          <class name="view"/>
-                          <class name="frame"/>
-                        </style>
-                      </object>
-                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="hexpand">1</property>
                   </object>
                 </child>
               </object>
-            </child>
-            <child>
-              <object class="GtkBox">
-                <property name="visible">1</property>
-                <property name="hexpand">1</property>
-              </object>
+              <packing>
+                <property name="name">settings</property>
+              </packing>
             </child>
           </object>
         </child>
diff --git a/panels/applications/cc-info-row.c b/panels/applications/cc-info-row.c
new file mode 100644
index 000000000..2f11e180c
--- /dev/null
+++ b/panels/applications/cc-info-row.c
@@ -0,0 +1,145 @@
+/* cc-info-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-info-row.h"
+#include "cc-applications-resources.h"
+
+enum {
+  PROP_ZERO,
+  PROP_TITLE,
+  PROP_SUBTITLE,
+  PROP_INFO
+};
+
+struct _CcInfoRow
+{
+  GtkListBoxRow parent;
+
+  GtkWidget *title;
+  GtkWidget *subtitle;
+  GtkWidget *info;
+};
+
+G_DEFINE_TYPE (CcInfoRow, cc_info_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_info_row_finalize (GObject *object)
+{
+  //CcInfoRow *row = CC_INFO_ROW (object);
+
+  G_OBJECT_CLASS (cc_info_row_parent_class)->finalize (object);
+}
+
+static void
+cc_info_row_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  CcInfoRow *row = CC_INFO_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+      break;
+    case PROP_SUBTITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->subtitle)));
+      break;
+    case PROP_INFO:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->info)));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_info_row_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  CcInfoRow *row = CC_INFO_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+      break;
+    case PROP_SUBTITLE:
+      gtk_label_set_label (GTK_LABEL (row->subtitle), g_value_get_string (value));
+      break;
+    case PROP_INFO:
+      gtk_label_set_label (GTK_LABEL (row->info), g_value_get_string (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_info_row_class_init (CcInfoRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = cc_info_row_finalize;
+  object_class->get_property = cc_info_row_get_property;
+  object_class->set_property = cc_info_row_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-info-row.ui");
+
+  g_object_class_install_property (object_class,
+                                   PROP_TITLE,
+                                   g_param_spec_string ("title", "title", "title",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_SUBTITLE,
+                                   g_param_spec_string ("subtitle", "subtitle", "subtitle",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_INFO,
+                                   g_param_spec_string ("info", "info", "info",
+                                                        NULL, G_PARAM_READWRITE));
+
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, title);
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, subtitle);
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, info);
+}
+
+static void
+cc_info_row_init (CcInfoRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcInfoRow *
+cc_info_row_new (void)
+{
+  return CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW, NULL));
+}
diff --git a/panels/applications/cc-info-row.h b/panels/applications/cc-info-row.h
new file mode 100644
index 000000000..583de2e7f
--- /dev/null
+++ b/panels/applications/cc-info-row.h
@@ -0,0 +1,32 @@
+/* cc-info-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * 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 3 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_INFO_ROW (cc_info_row_get_type())
+G_DECLARE_FINAL_TYPE (CcInfoRow, cc_info_row, CC, INFO_ROW, GtkListBoxRow)
+
+CcInfoRow *cc_info_row_new (void);
+
+G_END_DECLS
diff --git a/panels/applications/cc-info-row.ui b/panels/applications/cc-info-row.ui
new file mode 100644
index 000000000..cb4b1c53e
--- /dev/null
+++ b/panels/applications/cc-info-row.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcInfoRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="activatable">False</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="border-width">12</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="spacing">4</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="title">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="hexpand">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="subtitle">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="hexpand">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="info">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/applications/cc-toggle-row.c b/panels/applications/cc-toggle-row.c
index 7a4d9c53c..9060ad89a 100644
--- a/panels/applications/cc-toggle-row.c
+++ b/panels/applications/cc-toggle-row.c
@@ -26,16 +26,21 @@
 
 enum {
   PROP_ZERO,
-  PROP_NAME
+  PROP_TITLE,
+  PROP_ON_SUBTITLE,
+  PROP_OFF_SUBTITLE,
+  PROP_ALLOWED
 };
 
-static int changed_signal;
-
 struct _CcToggleRow
 {
   GtkListBoxRow parent;
 
-  GtkWidget *label;
+  char *on_subtitle;
+  char *off_subtitle;
+
+  GtkWidget *title;
+  GtkWidget *subtitle;
   GtkWidget *toggle;
 };
 
@@ -44,7 +49,10 @@ G_DEFINE_TYPE (CcToggleRow, cc_toggle_row, GTK_TYPE_LIST_BOX_ROW)
 static void
 cc_toggle_row_finalize (GObject *object)
 {
-  //CcToggleRow *row = CC_TOGGLE_ROW (object);
+  CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+  g_free (row->on_subtitle);
+  g_free (row->off_subtitle);
 
   G_OBJECT_CLASS (cc_toggle_row_parent_class)->finalize (object);
 }
@@ -59,8 +67,17 @@ cc_toggle_row_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_NAME:
-      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->label)));
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+      break;
+    case PROP_ON_SUBTITLE:
+      g_value_set_string (value, row->on_subtitle);
+      break;
+    case PROP_OFF_SUBTITLE:
+      g_value_set_string (value, row->off_subtitle);
+      break;
+    case PROP_ALLOWED:
+      g_value_set_boolean (value, cc_toggle_row_get_allowed (row));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -78,8 +95,17 @@ cc_toggle_row_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_NAME:
-      gtk_label_set_label (GTK_LABEL (row->label), g_value_get_string (value));
+    case PROP_TITLE:
+      gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+      break;
+    case PROP_ON_SUBTITLE:
+      cc_toggle_row_set_on_subtitle (row, g_value_get_string (value));
+      break;
+    case PROP_OFF_SUBTITLE:
+      cc_toggle_row_set_off_subtitle (row, g_value_get_string (value));
+      break;
+    case PROP_ALLOWED:
+      cc_toggle_row_set_allowed (row, g_value_get_boolean (value));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -100,28 +126,44 @@ cc_toggle_row_class_init (CcToggleRowClass *klass)
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-toggle-row.ui");
 
   g_object_class_install_property (object_class,
-                                   PROP_NAME,
-                                   g_param_spec_string ("name", "name", "name",
+                                   PROP_TITLE,
+                                   g_param_spec_string ("title", "title", "title",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ON_SUBTITLE,
+                                   g_param_spec_string ("on-subtitle", "on-subtitle", "on-subtitle",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_OFF_SUBTITLE,
+                                   g_param_spec_string ("off-subtitle", "off-subtitle", "off-subtitle",
                                                         NULL, G_PARAM_READWRITE));
 
-  changed_signal = g_signal_new ("changed",
-                          G_OBJECT_CLASS_TYPE (object_class),
-                          G_SIGNAL_RUN_FIRST,
-                          0,
-                          NULL, NULL,
-                          NULL,
-                          G_TYPE_NONE, 0);
+  g_object_class_install_property (object_class,
+                                   PROP_ALLOWED,
+                                   g_param_spec_boolean ("allowed", "allowed", "allowed",
+                                                         FALSE, G_PARAM_READWRITE));
 
-  gtk_widget_class_bind_template_child (widget_class, CcToggleRow, label);
+  gtk_widget_class_bind_template_child (widget_class, CcToggleRow, title);
+  gtk_widget_class_bind_template_child (widget_class, CcToggleRow, subtitle);
   gtk_widget_class_bind_template_child (widget_class, CcToggleRow, toggle);
 }
 
 static void
-changed_cb (GtkSwitch *toggle,
-            GParamSpec *pspec,
-            CcToggleRow *row)
+update_subtitle (CcToggleRow *row)
+{
+  if (gtk_switch_get_active (GTK_SWITCH (row->toggle)))
+    gtk_label_set_label (GTK_LABEL (row->subtitle), row->on_subtitle);
+  else
+    gtk_label_set_label (GTK_LABEL (row->subtitle), row->off_subtitle);
+}
+
+static void
+changed_cb (GtkSwitch *toggle, GParamSpec *pspec, CcToggleRow *row)
 {
-  g_signal_emit (row, changed_signal, 0);
+  update_subtitle (row);
+  g_object_notify (G_OBJECT (row), "allowed");
 }
 
 static void
@@ -129,31 +171,44 @@ cc_toggle_row_init (CcToggleRow *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
 
-  gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), FALSE);
   g_signal_connect (self->toggle, "notify::active", G_CALLBACK (changed_cb), self);
 }
 
 CcToggleRow *
-cc_toggle_row_new (const char *name)
+cc_toggle_row_new (void)
 {
-  CcToggleRow *row;
-
-  row = g_object_new (CC_TYPE_TOGGLE_ROW, NULL);
-
-  gtk_label_set_label (GTK_LABEL (row->label), name);
-
-  return row;
+  return CC_TOGGLE_ROW (g_object_new (CC_TYPE_TOGGLE_ROW, NULL));
 }
 
 void
-cc_toggle_row_set_allowed (CcToggleRow *row,
+cc_toggle_row_set_allowed (CcToggleRow *self,
                            gboolean     allowed)
 {
-  gtk_switch_set_active (GTK_SWITCH (row->toggle), allowed);
+  gtk_switch_set_active (GTK_SWITCH (self->toggle), allowed);
 }
 
 gboolean
-cc_toggle_row_get_allowed (CcToggleRow *row)
+cc_toggle_row_get_allowed (CcToggleRow *self)
+{
+  return gtk_switch_get_active (GTK_SWITCH (self->toggle));
+}
+
+void
+cc_toggle_row_set_on_subtitle (CcToggleRow *self,
+                               const char *subtitle)
+{
+  g_free (self->on_subtitle);
+  self->on_subtitle = g_strdup (subtitle);
+  update_subtitle (self);
+  g_object_notify (G_OBJECT (self), "on-subtitle");
+}
+
+void
+cc_toggle_row_set_off_subtitle (CcToggleRow *self,
+                                const char *subtitle)
 {
-  return gtk_switch_get_active (GTK_SWITCH (row->toggle));
+  g_free (self->off_subtitle);
+  self->off_subtitle = g_strdup (subtitle);
+  update_subtitle (self);
+  g_object_notify (G_OBJECT (self), "off-subtitle");
 }
diff --git a/panels/applications/cc-toggle-row.h b/panels/applications/cc-toggle-row.h
index 44464db7b..316e3131a 100644
--- a/panels/applications/cc-toggle-row.h
+++ b/panels/applications/cc-toggle-row.h
@@ -27,9 +27,13 @@ G_BEGIN_DECLS
 #define CC_TYPE_TOGGLE_ROW (cc_toggle_row_get_type())
 G_DECLARE_FINAL_TYPE (CcToggleRow, cc_toggle_row, CC, TOGGLE_ROW, GtkListBoxRow)
 
-CcToggleRow *cc_toggle_row_new (const char *name);
+CcToggleRow *cc_toggle_row_new (void);
 void         cc_toggle_row_set_allowed (CcToggleRow *row,
                                         gboolean     allowed);
 gboolean     cc_toggle_row_get_allowed (CcToggleRow *row);
+void         cc_toggle_row_set_on_subtitle (CcToggleRow *row,
+                                            const char *subtitle);
+void         cc_toggle_row_set_off_subtitle (CcToggleRow *row,
+                                             const char *subtitle);
 
 G_END_DECLS
diff --git a/panels/applications/cc-toggle-row.ui b/panels/applications/cc-toggle-row.ui
index 33dfe79e5..9de4a8ade 100644
--- a/panels/applications/cc-toggle-row.ui
+++ b/panels/applications/cc-toggle-row.ui
@@ -3,21 +3,40 @@
   <template class="CcToggleRow" parent="GtkListBoxRow">
     <property name="visible">True</property>
     <property name="can_focus">True</property>
+    <property name="activatable">False</property>
     <child>
-      <object class="GtkBox" id="box">
+      <object class="GtkBox">
         <property name="visible">True</property>
         <property name="border-width">12</property>
         <property name="spacing">12</property>
         <child>
-          <object class="GtkLabel" id="label">
+          <object class="GtkBox">
             <property name="visible">True</property>
-            <property name="xalign">0</property>
-            <property name="hexpand">True</property>
+            <property name="spacing">4</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="title">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="hexpand">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="subtitle">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="hexpand">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
           </object>
         </child>
         <child>
           <object class="GtkSwitch" id="toggle">
             <property name="visible">True</property>
+            <property name="valign">center</property>
           </object>
         </child>
       </object>
diff --git a/panels/applications/meson.build b/panels/applications/meson.build
index 20c6eed39..e8f11eb4b 100644
--- a/panels/applications/meson.build
+++ b/panels/applications/meson.build
@@ -20,6 +20,7 @@ i18n.merge_file(
 sources = files('cc-applications-panel.c',
                 'cc-applications-row.c',
                 'cc-toggle-row.c',
+                'cc-info-row.c',
                 'cc-action-row.c',
                 'utils.c'
 )
diff --git a/panels/applications/utils.c b/panels/applications/utils.c
index b2bc10cc6..35a37791f 100644
--- a/panels/applications/utils.c
+++ b/panels/applications/utils.c
@@ -66,3 +66,29 @@ container_remove_all (GtkContainer *container)
   g_list_free (children);
 }
 
+char *
+get_flatpak_app_size (const char *app_id)
+{
+  const char *argv[] = { "flatpak", "info", "-s", "appid", NULL };
+  char *out;
+
+  /* FIXME: use libflatpak */
+  argv[3] = app_id;
+  
+  if (!g_spawn_sync (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &out, NULL, NULL, NULL))
+    return NULL;
+
+  return g_strstrip (out);
+}
+
+void
+uninstall_flatpak_app (const char *app_id)
+{
+  const char *argv[] = { "flatpak", "uninstall", "--assumeyes", "appid", NULL };
+  g_autofree char *out = NULL;
+
+  /* FIXME: use libflatpak, error handling */
+  argv[3] = app_id;
+  
+  g_spawn_sync (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &out, NULL, NULL, NULL);
+}
diff --git a/panels/applications/utils.h b/panels/applications/utils.h
index c3e9e5425..c6dbd97eb 100644
--- a/panels/applications/utils.h
+++ b/panels/applications/utils.h
@@ -28,5 +28,7 @@ G_BEGIN_DECLS
 void    file_remove_recursively (GFile *file);
 guint64 file_size_recursively (GFile *file);
 void    container_remove_all (GtkContainer *container);
+char *get_flatpak_app_size (const char *app_id);
+void uninstall_flatpak_app (const char *app_id);
 
 G_END_DECLS


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