[gnome-software/wip/folders: 5/5] Add ui for folders



commit 27c20e35a429fe02aad61aa5a482c607ec1fd820
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Nov 3 22:42:49 2013 -0500

    Add ui for folders
    
    This adds most of the UI for organizing applications
    in folders. The folders are not written back to
    the menu files yet.

 src/Makefile.am                  |    3 +
 src/app-folder-dialog.ui         |   87 ++++++++++
 src/gnome-software.gresource.xml |    1 +
 src/gnome-software.ui            |   91 +++++++++--
 src/gs-app-folder-dialog.c       |  337 ++++++++++++++++++++++++++++++++++++++
 src/gs-app-folder-dialog.h       |   59 +++++++
 src/gs-app-widget.c              |    3 +
 src/gs-app.c                     |   14 ++
 src/gs-shell-installed.c         |  122 +++++++++++++-
 src/gtk-style.css                |    4 +-
 10 files changed, 702 insertions(+), 19 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index e41459f..d4c4834 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -23,6 +23,7 @@ desktop_in_files =                                    \
 desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 
 UI_FILES =                                             \
+       app-folder-dialog.ui                            \
        app-menu.ui                                     \
        app-tile.ui                                     \
        app-widget.ui                                   \
@@ -108,6 +109,8 @@ gnome_software_service_SOURCES =                    \
        gs-category-tile.h                              \
        gs-app-tile.c                                   \
        gs-app-tile.h                                   \
+       gs-app-folder-dialog.c                          \
+       gs-app-folder-dialog.h                          \
        gs-box.h                                        \
        gs-box.c                                        \
        gs-plugin.c                                     \
diff --git a/src/app-folder-dialog.ui b/src/app-folder-dialog.ui
new file mode 100644
index 0000000..8cf612b
--- /dev/null
+++ b/src/app-folder-dialog.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="GsAppFolderDialog" parent="GtkWindow">
+    <property name="title" translatable="yes">Application Folders</property>
+    <property name="type-hint">dialog</property>
+    <property name="modal">True</property>
+    <property name="resizable">False</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="header">
+        <property name="visible">True</property>
+        <child type="title">
+          <object class="GtkLabel" id="title_label">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">Application Folders</property>
+            <style>
+              <class name="title"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel_button">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="label" translatable="True">_Cancel</property>
+          </object>
+          <packing>
+            <property name="pack_type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="done_button">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="label" translatable="True">_Done</property>
+            <property name="receives_default">True</property>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox" id="content">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="margin">12</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="description_label">
+            <property name="visible">True</property>
+            <property name="wrap">True</property>
+            <property name="width_chars">50</property>
+            <property name="max_width_chars">55</property>
+            <property name="label">Choose a folder for Firefox, XChat and GIMP. Your application folder can 
be found in the Activities Overview.</property>
+          </object>
+          <packing>
+            <property name="fill">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkListBox" id="app_folder_list">
+            <property name="visible">True</property>
+          </object>
+          <packing>
+            <property name="fill">True</property>
+            <property name="expand">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="new_folder_button">
+            <property name="visible">True</property>
+            <property name="use_underline">True</property>
+            <property name="label" translatable="yes">_New Folder</property>
+          </object>
+          <packing>
+            <property name="fill">True</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index 1f0ece8..278ba0e 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -8,6 +8,7 @@
   <file preprocess="xml-stripblanks">category-tile.ui</file>
   <file preprocess="xml-stripblanks">app-tile.ui</file>
   <file preprocess="xml-stripblanks">app-widget.ui</file>
+  <file preprocess="xml-stripblanks">app-folder-dialog.ui</file>
   <file preprocess="xml-stripblanks">screenshot-image.ui</file>
   <file preprocess="xml-stripblanks">gs-star-widget.ui</file>
   <file>gtk-style.css</file>
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 48ef35e..1693daf 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -2,10 +2,20 @@
 <!-- Generated with glade 3.15.2 on Thu Aug 15 17:13:59 2013 -->
 <interface>
   <!-- interface-requires gtk+ 3.10 -->
-  <object class="GtkImage" id="button_select_image">
+  <object class="GtkMenu" id="header_selection_menu">
     <property name="visible">True</property>
-    <property name="icon_name">object-select-symbolic</property>
-    <property name="icon_size">0</property>
+    <child>
+      <object class="GtkMenuItem" id="select_all_menuitem">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Select All</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuItem" id="select_none_menuitem">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Select None</property>
+      </object>
+    </child>
   </object>
   <object class="GtkApplicationWindow" id="window_software">
     <property name="can_focus">False</property>
@@ -130,6 +140,36 @@
                     <property name="position">1</property>
                   </packing>
                 </child>
+                <child>
+                  <object class="GtkMenuButton" id="header_selection_menu_button">
+                   <property name="popup">header_selection_menu</property>
+                   <style>
+                      <class name="selection-menu"/>
+                    </style>
+                    <child>
+                      <object class="GtkBox" id="header_selection_box">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="header_selection_label">
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkArrow" id="header_selection_arrow">
+                            <property name="visible">True</property>
+                            <property name="arrow_type">down</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
               </object>
             </child>
             <child>
@@ -188,7 +228,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkToggleButton" id="button_select">
+              <object class="GtkButton" id="button_select">
                 <property name="image">button_select_image</property>
                 <property name="can_focus">True</property>
               </object>
@@ -509,20 +549,43 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkScrolledWindow" id="scrolledwindow_install">
+                  <object class="GtkBox" id="box_install">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hscrollbar_policy">never</property>
-                    <property name="vscrollbar_policy">automatic</property>
-                    <property name="shadow_type">none</property>
-                    <style>
-                      <class name="main-scrolled-software"/>
-                    </style>
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkListBox" id="list_box_install">
+                      <object class="GtkScrolledWindow" id="scrolledwindow_install">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="selection_mode">none</property>
+                        <property name="hscrollbar_policy">never</property>
+                        <property name="vscrollbar_policy">automatic</property>
+                        <property name="vexpand">True</property>
+                        <property name="shadow_type">none</property>
+                        <style>
+                          <class name="main-scrolled-software"/>
+                        </style>
+                        <child>
+                          <object class="GtkListBox" id="list_box_install">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="selection_mode">none</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkRevealer" id="bottom_install">
+                        <property name="visible">True</property>
+                        <property name="transition-type">4</property>
+                        <child>
+                          <object class="GtkButton" id="button_folder_install">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Application _Folders</property>
+                            <property name="use_underline">True</property>
+                            <property name="hexpand">True</property>
+                            <property name="halign">center</property>
+                            <property name="margin">10</property>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </object>
diff --git a/src/gs-app-folder-dialog.c b/src/gs-app-folder-dialog.c
new file mode 100644
index 0000000..4fa90e2
--- /dev/null
+++ b/src/gs-app-folder-dialog.c
@@ -0,0 +1,337 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-app-folder-dialog.h"
+
+typedef struct _GsAppFolderDialogPrivate        GsAppFolderDialogPrivate;
+struct _GsAppFolderDialogPrivate
+{
+       GList            *apps;
+       GtkWidget        *header;
+       GtkWidget        *cancel_button;
+       GtkWidget        *done_button;
+       GtkWidget        *description_label;
+       GtkWidget        *app_folder_list;
+       GtkWidget        *new_folder_button;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsAppFolderDialog, gs_app_folder_dialog, GTK_TYPE_WINDOW)
+
+#define PRIVATE(o) (gs_app_folder_dialog_get_instance_private (o))
+
+static void
+gs_app_folder_dialog_destroy (GtkWidget *widget)
+{
+       GsAppFolderDialog *dialog = GS_APP_FOLDER_DIALOG (widget);
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+
+       g_list_free (priv->apps);
+       priv->apps = NULL;
+
+       GTK_WIDGET_CLASS (gs_app_folder_dialog_parent_class)->destroy (widget);
+}
+
+static void
+cancel_cb (GsAppFolderDialog *dialog)
+{
+       gtk_window_close (GTK_WINDOW (dialog));
+}
+
+static void
+apply_changes (GsAppFolderDialog *dialog)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+       GtkListBoxRow *row;
+       const gchar *folder;
+       GList *l;
+       
+       row = gtk_list_box_get_selected_row (GTK_LIST_BOX (priv->app_folder_list));
+       if (row == NULL)
+               folder = NULL;
+       else
+               folder = (const gchar *)g_object_get_data (G_OBJECT (row), "folder");
+       for (l = priv->apps; l; l = l->next) {
+               GsApp *app = l->data;
+               gs_app_set_folder (app, folder);
+       }
+
+       /* TODO here is where we need to write out a menu file */
+}
+
+static void
+done_cb (GsAppFolderDialog *dialog)
+{
+       apply_changes (dialog); 
+       gtk_window_close (GTK_WINDOW (dialog));
+}
+
+static void
+delete_row (GtkButton *button, GtkWidget *row)
+{
+       GtkWidget *parent;
+
+       parent = gtk_widget_get_parent (row);
+       gtk_container_remove (GTK_CONTAINER (parent), row);
+}
+
+static void
+done_editing (GtkEntry *entry, GtkWidget *row)
+{
+       gchar *folder;
+       GtkWidget *label;
+       GtkWidget *parent;
+
+       if (gtk_entry_get_text_length (entry) == 0) {
+               delete_row (NULL, row);
+               return;
+       }
+       folder = g_strdup (gtk_entry_get_text (entry));
+       parent = gtk_widget_get_parent (GTK_WIDGET (entry));
+       gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (entry));
+
+       label = gtk_label_new (folder);
+       gtk_widget_show (label);
+       gtk_widget_set_margin_left (label, 10);
+       gtk_widget_set_margin_right (label, 10);
+       gtk_widget_set_halign (label, GTK_ALIGN_START);
+       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+       gtk_box_pack_start (GTK_BOX (parent), label, TRUE, TRUE, 0);
+       gtk_box_reorder_child (GTK_BOX (parent), label, 0);
+
+       g_object_set_data_full (G_OBJECT (row), "folder", folder, g_free);
+}
+
+static void
+new_folder_cb (GsAppFolderDialog *dialog)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+       GtkWidget *row;
+       GtkWidget *box;
+       GtkWidget *entry;
+       GtkWidget *button;
+
+       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+       entry = gtk_entry_new ();
+       gtk_widget_set_margin_left (entry, 10);
+       gtk_widget_set_margin_right (entry, 10);
+       gtk_widget_set_halign (entry, GTK_ALIGN_START);
+       gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0);
+
+       button = gtk_button_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+       gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+       
+       row = gtk_list_box_row_new ();
+       gtk_container_add (GTK_CONTAINER (row), box);
+
+       g_signal_connect (button, "clicked",
+                         G_CALLBACK (delete_row), row);
+       g_signal_connect (entry, "activate",
+                         G_CALLBACK (done_editing), row);
+
+       gtk_widget_show_all (row);
+
+       gtk_list_box_insert (GTK_LIST_BOX (priv->app_folder_list), row, -1);
+
+       gtk_widget_grab_focus (entry);
+}
+
+static void
+update_header_func (GtkListBoxRow  *row,
+                    GtkListBoxRow  *before,
+                    gpointer    user_data)
+{
+  GtkWidget *current;
+
+  if (before == NULL)
+    return;
+
+  current = gtk_list_box_row_get_header (row);
+  if (current == NULL)
+    {
+      current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+      gtk_widget_show (current);
+      gtk_list_box_row_set_header (row, current);
+    }
+}
+
+static void
+gs_app_folder_dialog_init (GsAppFolderDialog *dialog)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+
+       gtk_widget_init_template (GTK_WIDGET (dialog));
+
+       g_signal_connect_swapped (priv->cancel_button, "clicked",
+                                 G_CALLBACK (cancel_cb), dialog);
+       g_signal_connect_swapped (priv->done_button, "clicked",
+                                 G_CALLBACK (done_cb), dialog);
+       g_signal_connect_swapped (priv->new_folder_button, "clicked",
+                                 G_CALLBACK (new_folder_cb), dialog);
+
+       gtk_list_box_set_header_func (GTK_LIST_BOX (priv->app_folder_list),
+                                     update_header_func, NULL, NULL);
+}
+
+static void
+gs_app_folder_dialog_class_init (GsAppFolderDialogClass *klass)
+{
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       widget_class->destroy = gs_app_folder_dialog_destroy;
+
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/software/app-folder-dialog.ui");
+
+       gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, header);
+       gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, cancel_button);
+       gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, done_button);
+       gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, description_label);
+        gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, app_folder_list);
+        gtk_widget_class_bind_template_child_private (widget_class, GsAppFolderDialog, new_folder_button);
+}
+
+static GtkWidget *
+create_row (GsAppFolderDialog *dialog, const gchar *folder)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+       GtkWidget *row;
+       GtkWidget *box;
+       GtkWidget *label;
+       GtkWidget *button;
+       GString *s;
+       GList *l;
+
+       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+       label = gtk_label_new (folder);
+       gtk_widget_set_margin_left (label, 10);
+       gtk_widget_set_margin_right (label, 10);
+       gtk_widget_set_halign (label, GTK_ALIGN_START);
+       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+       gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
+
+       s = g_string_new ("");
+       for (l = priv->apps; l; l = l->next) {
+               GsApp *app = l->data;
+               if (g_strcmp0 (folder, gs_app_get_folder (app)) == 0) {
+                       if (s->len > 0)
+                               g_string_append (s, ", ");
+                       g_string_append (s, gs_app_get_name (app));
+               }
+       }
+       if (s->len > 0) {
+               label = gtk_label_new (s->str);
+               gtk_widget_set_margin_left (label, 10);
+               gtk_widget_set_margin_right (label, 10);
+               gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+               gtk_label_set_max_width_chars (GTK_LABEL (label), 30);
+               gtk_widget_set_halign (label, GTK_ALIGN_END);
+               gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+               gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
+       }
+       g_string_free (s, TRUE);
+
+       button = gtk_button_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_MENU);
+       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+       gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+       
+       row = gtk_list_box_row_new ();
+       gtk_container_add (GTK_CONTAINER (row), box);
+
+       gtk_widget_show_all (row);
+
+       g_object_set_data_full (G_OBJECT (row), "folder", g_strdup (folder), g_free);
+
+       g_signal_connect (button, "clicked",
+                         G_CALLBACK (delete_row), row);
+
+       return row;     
+}
+
+static void
+populate_list (GsAppFolderDialog *dialog)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+       gtk_list_box_insert (GTK_LIST_BOX (priv->app_folder_list), create_row (dialog, _("Games")), -1);
+       gtk_list_box_insert (GTK_LIST_BOX (priv->app_folder_list), create_row (dialog, _("Graphics")), -1);
+       gtk_list_box_insert (GTK_LIST_BOX (priv->app_folder_list), create_row (dialog, _("Office")), -1);
+       gtk_list_box_insert (GTK_LIST_BOX (priv->app_folder_list), create_row (dialog, _("Sundry")), -1);
+
+       /* TODO: here is where we need to load existing folders */
+}
+
+static void
+gs_app_folder_dialog_set_apps (GsAppFolderDialog *dialog,
+                              GList *apps)
+{
+       GsAppFolderDialogPrivate *priv = PRIVATE (dialog);
+       gchar *label;
+       const gchar *app1, *app2, *app3;
+
+       priv->apps = g_list_copy (apps);
+
+       switch (g_list_length (priv->apps)) {
+       case 0:
+               label = g_strdup (_("Add or remove folders. Your application folders can be found in the 
Activities Overview."));
+               break;
+       case 1:
+               app1 = gs_app_get_name (GS_APP (priv->apps->data));
+               label = g_strdup_printf (_("Choose a folder for %s. Your application folders can be found in 
the Activities Overview."), app1);
+               break;
+       case 2:
+               app1 = gs_app_get_name (GS_APP (priv->apps->data));
+               app2 = gs_app_get_name (GS_APP (priv->apps->next->data));
+               label = g_strdup_printf (_("Choose a folder for %s and %s. Your application folders can be 
found in the Activities Overview."), app1, app2);
+               break;
+       case 3:
+               app1 = gs_app_get_name (GS_APP (priv->apps->data));
+               app2 = gs_app_get_name (GS_APP (priv->apps->next->data));
+               app3 = gs_app_get_name (GS_APP (priv->apps->next->next->data));
+               label = g_strdup_printf (_("Choose a folder for %s, %s and %s. Your application folders can 
be found in the Activities Overview."), app1, app2, app3);
+               break;
+       default:
+               label = g_strdup (_("Choose a folder for the selected applications. Your application folders 
can be found in the Activities Overview."));
+               break;
+       }
+       gtk_label_set_label (GTK_LABEL (priv->description_label), label);
+       g_free (label);
+}
+
+GtkWidget *
+gs_app_folder_dialog_new (GtkWindow *parent, GList *apps)
+{
+       GsAppFolderDialog *dialog;
+
+       dialog = g_object_new (GS_TYPE_APP_FOLDER_DIALOG,
+                              "transient-for", parent,
+                              NULL);
+       gs_app_folder_dialog_set_apps (dialog, apps);
+       populate_list (dialog);
+
+       return GTK_WIDGET (dialog);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-app-folder-dialog.h b/src/gs-app-folder-dialog.h
new file mode 100644
index 0000000..4f6dcae
--- /dev/null
+++ b/src/gs-app-folder-dialog.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GS_APP_FOLDER_DIALOG_H
+#define GS_APP_FOLDER_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include "gs-app.h"
+
+#define GS_TYPE_APP_FOLDER_DIALOG              (gs_app_folder_dialog_get_type())
+#define GS_APP_FOLDER_DIALOG(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), GS_TYPE_APP_FOLDER_DIALOG, 
GsAppFolderDialog))
+#define GS_APP_FOLDER_DIALOG_CLASS(cls)                (G_TYPE_CHECK_CLASS_CAST((cls), 
GS_TYPE_APP_FOLDER_DIALOG, GsAppFolderDialogClass))
+#define GS_IS_APP_FOLDER_DIALOG(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), GS_TYPE_APP_FOLDER_DIALOG))
+#define GS_IS_APP_FOLDER_DIALOG_CLASS(cls)     (G_TYPE_CHECK_CLASS_TYPE((cls), GS_TYPE_APP_FOLDER_DIALOG))
+#define GS_APP_FOLDER_DIALOG_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), GS_TYPE_APP_FOLDER_DIALOG, 
GsAppFolderDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GsAppFolderDialog              GsAppFolderDialog;
+typedef struct _GsAppFolderDialogClass         GsAppFolderDialogClass;
+
+struct _GsAppFolderDialog
+{
+        GtkWindow      parent;
+};
+
+struct _GsAppFolderDialogClass
+{
+        GtkWindowClass      parent_class;
+};
+
+GType           gs_app_folder_dialog_get_type  (void);
+GtkWidget      *gs_app_folder_dialog_new       (GtkWindow      *parent,
+                                                GList          *apps);
+
+G_END_DECLS
+
+#endif /* GS_APP_FOLDER_DIALOG_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-app-widget.c b/src/gs-app-widget.c
index a6a13e8..ca60325 100644
--- a/src/gs-app-widget.c
+++ b/src/gs-app-widget.c
@@ -225,6 +225,9 @@ gs_app_widget_set_app (GsAppWidget *app_widget, GsApp *app)
        g_signal_connect_object (app_widget->priv->app, "notify::state",
                                 G_CALLBACK (gs_app_widget_notify_props_changed_cb),
                                 app_widget, 0);
+       g_signal_connect_object (app_widget->priv->app, "notify::folder",
+                                G_CALLBACK (gs_app_widget_notify_props_changed_cb),
+                                app_widget, 0);
        gs_app_widget_refresh (app_widget);
 }
 
diff --git a/src/gs-app.c b/src/gs-app.c
index 7518934..beb0022 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -102,6 +102,7 @@ enum {
        PROP_KIND,
        PROP_STATE,
        PROP_INSTALL_DATE,
+       PROP_FOLDER,
        PROP_LAST
 };
 
@@ -1396,6 +1397,8 @@ gs_app_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *
        case PROP_INSTALL_DATE:
                g_value_set_uint64 (value, priv->install_date);
                break;
+       case PROP_FOLDER:
+               g_value_set_string (value, priv->folder);
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -1438,6 +1441,9 @@ gs_app_set_property (GObject *object, guint prop_id, const GValue *value, GParam
        case PROP_INSTALL_DATE:
                gs_app_set_install_date (app, g_value_get_uint64 (value));
                break;
+       case PROP_FOLDER:
+               gs_app_set_folder (app, g_value_get_string (value));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -1527,6 +1533,11 @@ gs_app_class_init (GsAppClass *klass)
                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
        g_object_class_install_property (object_class, PROP_INSTALL_DATE, pspec);
 
+       pspec = g_param_spec_string ("folder", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_FOLDER, pspec);
+
        g_type_class_add_private (klass, sizeof (GsAppPrivate));
 }
 
@@ -1627,8 +1638,11 @@ gs_app_set_folder (GsApp *app,
 
 {
        GsAppPrivate *priv = app->priv;
+
        g_free (priv->folder);
        priv->folder = g_strdup (folder);
+
+       g_object_notify (G_OBJECT (app), "folder");
 }
 
 
diff --git a/src/gs-shell-installed.c b/src/gs-shell-installed.c
index ad272bf..0dd95ba 100644
--- a/src/gs-shell-installed.c
+++ b/src/gs-shell-installed.c
@@ -30,6 +30,7 @@
 #include "gs-app.h"
 #include "gs-utils.h"
 #include "gs-app-widget.h"
+#include "gs-app-folder-dialog.h"
 
 #define INSTALL_DATE_QUEUED     (G_MAXUINT - 1)
 #define INSTALL_DATE_INSTALLING (G_MAXUINT - 2)
@@ -46,6 +47,7 @@ struct GsShellInstalledPrivate
        GtkBuilder              *builder;
        GCancellable            *cancellable;
        GtkListBox              *list_box_installed;
+       GtkRevealer             *bottom_bar;
        GtkSizeGroup            *sizegroup_image;
        GtkSizeGroup            *sizegroup_name;
        gboolean                 cache_valid;
@@ -558,14 +560,45 @@ gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
 }
 
 static void
-selection_mode_cb (GtkToggleButton *button, GsShellInstalled *shell_installed)
+selection_mode_cb (GtkButton *button, GsShellInstalled *shell_installed)
 {
        GsShellInstalledPrivate *priv = shell_installed->priv;
        GList *children, *l;
        GtkWidget *row;
        GtkWidget *app_widget;
+       GtkWidget *header;
+       GtkWidget *widget;
+       GtkStyleContext *context;
        
-       priv->selection_mode = gtk_toggle_button_get_active (button);
+       priv->selection_mode = !priv->selection_mode;
+
+       header = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header"));
+       context = gtk_widget_get_style_context (header);
+       if (priv->selection_mode) {
+               gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), FALSE);
+               gtk_style_context_add_class (context, "selection-mode");
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
+               gtk_button_set_image (GTK_BUTTON (widget), NULL);
+               gtk_button_set_label (GTK_BUTTON (widget), _("Cancel"));
+               gtk_widget_show (widget);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
+               gtk_widget_hide (widget);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_menu_button"));
+               gtk_widget_show (widget);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_label"));
+               gtk_label_set_label (GTK_LABEL (widget), _("Click on items to select them"));
+       } else {
+               gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
+               gtk_style_context_remove_class (context, "selection-mode");
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
+               gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new_from_icon_name 
("object-select-symbolic", GTK_ICON_SIZE_MENU));
+               gtk_button_set_label (GTK_BUTTON (widget), NULL);
+               gtk_widget_show (widget);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
+               gtk_widget_show (widget);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_selection_menu_button"));
+               gtk_widget_hide (widget);
+       }
 
        children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_installed));
        for (l = children; l; l = l->next) {
@@ -575,6 +608,72 @@ selection_mode_cb (GtkToggleButton *button, GsShellInstalled *shell_installed)
                                              priv->selection_mode);
        }
        g_list_free (children);
+
+       gtk_revealer_set_reveal_child (priv->bottom_bar, priv->selection_mode);
+}
+
+static GList *
+get_selected_apps (GsShellInstalled *shell_installed)
+{
+       GsShellInstalledPrivate *priv = shell_installed->priv;
+       GList *children, *l, *list;
+
+       list = NULL;
+       children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_installed));
+       for (l = children; l; l = l->next) {
+               GtkListBoxRow *row = l->data;
+               GsAppWidget *app_widget = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (row)));
+               if (gs_app_widget_get_selected (app_widget)) {
+                       list = g_list_prepend (list, gs_app_widget_get_app (app_widget));       
+               }
+       }
+       g_list_free (children);
+
+       return list;
+}
+
+static void
+show_folder_dialog (GtkButton *button, GsShellInstalled *shell_installed)
+{
+       GtkWidget *toplevel;
+       GtkWidget *dialog;
+       GList *apps;
+
+       toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
+       apps = get_selected_apps (shell_installed);
+       dialog = gs_app_folder_dialog_new (GTK_WINDOW (toplevel), apps);
+       g_list_free (apps);
+       gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+select_all_cb (GtkMenuItem *item, GsShellInstalled *shell_installed)
+{
+       GsShellInstalledPrivate *priv = shell_installed->priv;
+       GList *children, *l;
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_installed));
+       for (l = children; l; l = l->next) {
+               GtkListBoxRow *row = l->data;
+               GsAppWidget *app_widget = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (row)));
+               gs_app_widget_set_selected (app_widget, TRUE);
+       }
+       g_list_free (children);
+}
+
+static void
+select_none_cb (GtkMenuItem *item, GsShellInstalled *shell_installed)
+{
+       GsShellInstalledPrivate *priv = shell_installed->priv;
+       GList *children, *l;
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_installed));
+       for (l = children; l; l = l->next) {
+               GtkListBoxRow *row = l->data;
+               GsAppWidget *app_widget = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (row)));
+               gs_app_widget_set_selected (app_widget, FALSE);
+       }
+       g_list_free (children);
 }
 
 /**
@@ -611,10 +710,27 @@ gs_shell_installed_setup (GsShellInstalled *shell_installed,
        gtk_list_box_set_sort_func (priv->list_box_installed,
                                    gs_shell_installed_sort_func,
                                    shell_installed, NULL);
+
+       priv->bottom_bar = GTK_REVEALER (gtk_builder_get_object (priv->builder, "bottom_install"));
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_folder_install"));
+       g_signal_connect (widget, "clicked",
+                         G_CALLBACK (show_folder_dialog), shell_installed);
        
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
-       g_signal_connect (widget, "toggled",
+       g_signal_connect (widget, "clicked",
                          G_CALLBACK (selection_mode_cb), shell_installed);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_select"));
+       gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new_from_icon_name ("object-select-symbolic", 
GTK_ICON_SIZE_MENU));
+       gtk_button_set_label (GTK_BUTTON (widget), NULL);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "select_all_menuitem"));
+       g_signal_connect (widget, "activate",
+                         G_CALLBACK (select_all_cb), shell_installed);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "select_none_menuitem"));
+       g_signal_connect (widget, "activate",
+                         G_CALLBACK (select_none_cb), shell_installed);
 }
 
 /**
diff --git a/src/gtk-style.css b/src/gtk-style.css
index aef3909..39f0ac1 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -63,8 +63,8 @@ GtkNotebook.main-notebook-software > GtkScrolledWindow {
 
 .folder-label {
        background-color: #999999;
-       border-radius: 5;
-       padding: 5;
+       border-radius: 5px;
+       padding: 5px;
        color: white;
 }
 


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