[gnome-usage/tracker-powered-storage-view-wip: 95/103] storage: Add actionbar with delete action



commit 4d9b4510be35243f6f57c253292aab36e327af30
Author: Petr Štětka <pstetka redhat com>
Date:   Tue Aug 14 14:22:35 2018 +0200

    storage: Add actionbar with delete action

 data/ui/storage-actionbar.ui        |  66 +++--------------------
 data/ui/storage-view.ui             |   6 +++
 src/meson.build                     |   1 +
 src/storage/storage-actionbar.vala  |  86 ++++++++++++++++++++++++++++++
 src/storage/storage-view-item.vala  |  15 +++---
 src/storage/storage-view-row.vala   |  11 +++-
 src/storage/storage-view.vala       | 102 ++++++++++++++++++++++++++++++++----
 src/storage/tracker-controller.vala |  10 ++--
 8 files changed, 217 insertions(+), 80 deletions(-)
---
diff --git a/data/ui/storage-actionbar.ui b/data/ui/storage-actionbar.ui
index 1c5fda6..e45c6a2 100644
--- a/data/ui/storage-actionbar.ui
+++ b/data/ui/storage-actionbar.ui
@@ -3,71 +3,21 @@
 <interface>
   <requires lib="gtk+" version="3.12"/>
   <template class="UsageStorageActionBar" parent="GtkActionBar">
-    <property name="visible">True</property>
     <child>
-      <object class="GtkButton" id="move_to_button">
-        <property name="label" translatable="yes">Move to</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="move_to_clicked" swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkButton" id="delete_button">
-        <property name="label" translatable="yes">Delete</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="delete_clicked" swapped="no"/>
-        <style>
-          <class name="destructive-action"/>
-        </style>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkButton" id="move_to_trash_button">
-        <property name="label" translatable="yes">Move to Trash</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="move_to_trash_clicked" swapped="no"/>
+      <object class="GtkLabel" id="size_label">
+        <property name="visible">True</property>
       </object>
       <packing>
-        <property name="pack_type">end</property>
+        <property name="pack_type">start</property>
       </packing>
     </child>
+
     <child>
-      <object class="GtkButton" id="empty_folder_button">
-        <property name="label" translatable="yes">Empty folder</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="empty_folder_clicked" swapped="no"/>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-     <child>
-      <object class="GtkButton" id="restore_button">
-        <property name="label" translatable="yes">Restore</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="restore_clicked" swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkButton" id="delete_from_trash_button">
-        <property name="label" translatable="yes">Delete from Trash</property>
-        <property name="visible">False</property>
+      <object class="GtkButton" id="delete_button">
+        <property name="label" translatable="yes">Delete...</property>
         <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <signal name="clicked" handler="delete_from_trash_clicked" swapped="no"/>
+        <property name="visible">True</property>
+        <signal name="clicked" handler="delete_clicked" swapped="no"/>
         <style>
           <class name="destructive-action"/>
         </style>
diff --git a/data/ui/storage-view.ui b/data/ui/storage-view.ui
index 5296a6a..6f8173b 100644
--- a/data/ui/storage-view.ui
+++ b/data/ui/storage-view.ui
@@ -54,6 +54,12 @@
                     </style>
                   </object>
                 </child>
+
+                <child>
+                  <object class="UsageStorageActionBar" id="actionbar">
+                  </object>
+                </child>
+
               </object>
             </child>
             <child>
diff --git a/src/meson.build b/src/meson.build
index 5f0774d..e31625f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,6 +29,7 @@ vala_sources = [
   'quit-process-dialog.vala',
   'settings.vala',
   'speedometer.vala',
+  'storage/storage-actionbar.vala',
   'storage/storage-graph.vala',
   'storage/query-builder.vala',
   'storage/storage-row-popover.vala',
diff --git a/src/storage/storage-actionbar.vala b/src/storage/storage-actionbar.vala
new file mode 100644
index 0000000..8b738eb
--- /dev/null
+++ b/src/storage/storage-actionbar.vala
@@ -0,0 +1,86 @@
+/* storage-actionbar.vala
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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/>.
+ *
+ * Authors: Petr Štětka <pstetka redhat com>
+ */
+
+namespace Usage
+{
+    [GtkTemplate (ui = "/org/gnome/Usage/ui/storage-actionbar.ui")]
+    public class StorageActionBar : Gtk.ActionBar
+    {
+        private unowned List<StorageViewItem> selected_items;
+
+        [GtkChild]
+        private Gtk.Label size_label;
+
+        public signal void refresh_listbox ();
+
+        public void update_selected_items(List<StorageViewItem> selected_items) {
+            this.selected_items = selected_items;
+
+            uint64 size = 0;
+            foreach(var item in selected_items) {
+                size += item.size;
+            }
+            size_label.label = _("%s selected").printf(Utils.format_size_values(size));
+        }
+
+        [GtkCallback]
+        private void delete_clicked() {
+            string display_message = _("Are you sure you want to permanently delete selected items?");
+
+            var dialog = new Gtk.MessageDialog ((GLib.Application.get_default() as 
Application).get_window(), Gtk.DialogFlags.MODAL,
+                Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, display_message);
+            dialog.secondary_text = _("If you delete these items, they will be permanently lost.");
+
+            if(dialog.run() == Gtk.ResponseType.OK) {
+                foreach(var item in selected_items) {
+                    if(item.type == FileType.DIRECTORY && item.custom_type == "root_item")
+                        delete_file(item.uri, false);
+                    else
+                        delete_file(item.uri, true);
+                }
+                refresh_listbox();
+            }
+            dialog.destroy();
+        }
+
+        private void delete_file(string uri, bool delete_basefile) {
+            var file = File.new_for_uri(uri);
+            var type = file.query_file_type (FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
+
+            try {
+                if(type == FileType.DIRECTORY) {
+                    FileInfo info;
+                    FileEnumerator enumerator = file.enumerate_children("standard::*", 
FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
+
+                    while((info = enumerator.next_file(null)) != null) {
+                        var child = file.get_child(info.get_name());
+                        delete_file(child.get_uri(), true);
+                    }
+                }
+
+                if(delete_basefile)
+                    file.delete();
+            }
+            catch (Error e) {
+                stderr.printf ("Error: %s\n", e.message);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/storage/storage-view-item.vala b/src/storage/storage-view-item.vala
index 8749227..9251c8c 100644
--- a/src/storage/storage-view-item.vala
+++ b/src/storage/storage-view-item.vala
@@ -51,18 +51,21 @@ public class Usage.StorageViewItem : GLib.Object {
         }
     }
 
-    public StorageViewItem.from_file (File file) {
-        uri = file.get_uri ();
+    public static StorageViewItem? from_file(File file) {
+        var item = new StorageViewItem();
+        item.uri = file.get_uri ();
 
         try {
             var info = file.query_info (FileAttribute.STANDARD_SIZE + "," + FileAttribute.STANDARD_NAME + 
"," + FileAttribute.STANDARD_TYPE + "," + FileAttribute.TRASH_ORIG_PATH, 
FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
 
-            name = info.get_name ();
-            size = info.get_size ();
-            type = info.get_file_type ();
+            item.name = info.get_name ();
+            item.size = info.get_size ();
+            item.type = info.get_file_type ();
         } catch (GLib.Error error) {
-            warning (error.message);
+            return null;
         }
+
+        return item;
     }
 
     private void setup_tag_style () {
diff --git a/src/storage/storage-view-row.vala b/src/storage/storage-view-row.vala
index 4f50a06..7c0845e 100644
--- a/src/storage/storage-view-row.vala
+++ b/src/storage/storage-view-row.vala
@@ -56,7 +56,13 @@ public class Usage.StorageViewRow : Gtk.ListBoxRow {
         }
     }
 
-    public StorageViewItem item; 
+    public bool selected {
+        get { return check_button.active; }
+    }
+
+    public signal void check_button_toggled();
+
+    public StorageViewItem item;
 
     public StorageViewRow.from_item (StorageViewItem item) {
         this.item = item;
@@ -66,6 +72,9 @@ public class Usage.StorageViewRow : Gtk.ListBoxRow {
 
         tag.get_style_context ().add_class (item.style_class);
         check_button.visible = item.show_check_button;
+        check_button.toggled.connect(() => {
+            check_button_toggled();
+        });
 
         if (item.type == FileType.DIRECTORY || item.custom_type != null)
             tag.width_request = tag.height_request = 20;
diff --git a/src/storage/storage-view.vala b/src/storage/storage-view.vala
index 0841efb..665548e 100644
--- a/src/storage/storage-view.vala
+++ b/src/storage/storage-view.vala
@@ -16,6 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  * Authors: Felipe Borges <felipeborges gnome org>
+ *          Petr Štětka <pstetka redhat com>
  */
 
 using Tracker;
@@ -25,10 +26,6 @@ using GTop;
 public class Usage.NewStorageView : Usage.View {
     public const uint MIN_PERCENTAGE_SHOWN_FILES = 2;
 
-    private Sparql.Connection connection;
-    private TrackerController controller;
-    private StorageQueryBuilder query_builder;
-
     [GtkChild]
     private Gtk.Label header_label;
 
@@ -44,10 +41,18 @@ public class Usage.NewStorageView : Usage.View {
     [GtkChild]
     private StorageGraph graph;
 
+    [GtkChild]
+    private StorageActionBar actionbar;
+
+    private Sparql.Connection connection;
+    private TrackerController controller;
+    private StorageQueryBuilder query_builder;
+
     private StorageViewItem os_item = new StorageViewItem ();
     private StorageRowPopover row_popover = new StorageRowPopover();
     private uint storage_row_i = 0;
     private uint? shown_rows_number = null;
+    private uint need_refresh_depth = 0;
 
     private uint64 total_used_size = 0;
     private uint64 total_free_size = 0;
@@ -61,6 +66,10 @@ public class Usage.NewStorageView : Usage.View {
         UserDirectory.VIDEOS,
     };
 
+    private List<StorageViewItem> selected_items = new List<StorageViewItem> ();
+    private Queue<List> selected_items_stack = new Queue<List> ();
+    private Queue<StorageViewItem> actual_item = new Queue<StorageViewItem> ();
+
     construct {
         name = "STORAGE";
         title = _("Storage");
@@ -73,6 +82,23 @@ public class Usage.NewStorageView : Usage.View {
 
         query_builder = new StorageQueryBuilder ();
         controller = new TrackerController (connection);
+
+        actionbar.refresh_listbox.connect(() => {
+            var item = actual_item.peek_head();
+
+            stack_listbox_up();
+            clear_selected_items();
+
+            if(listbox.get_depth() >= 1) {
+                selected_items_stack.push_head((owned) selected_items);
+                actual_item.push_head(item);
+                present_dir.begin (item.uri, item.dir);
+            }
+            else
+                populate_view.begin ();
+
+            need_refresh_depth = listbox.get_depth();
+        });
     }
 
     public NewStorageView () {
@@ -88,16 +114,43 @@ public class Usage.NewStorageView : Usage.View {
         var storage_row = row as StorageViewRow;
 
         if(storage_row.item.custom_type == "up-folder") {
-            shown_rows_number = null;
-            storage_row_i = 0;
-            listbox.pop();
-            graph.model = (ListStore) listbox.get_model();
+            stack_listbox_up();
         } else if (storage_row.item.type == FileType.DIRECTORY) {
+            selected_items_stack.push_head((owned) selected_items);
+            actual_item.push_head(storage_row.item);
+            clear_selected_items();
             present_dir.begin (storage_row.item.uri, storage_row.item.dir);
         } else if (storage_row.item.custom_type != null) {
             row_popover.present(storage_row);
         } else {
-            AppInfo.launch_default_for_uri(storage_row.item.uri, null);
+            try {
+                AppInfo.launch_default_for_uri(storage_row.item.uri, null);
+            } catch (GLib.Error error) {
+                warning (error.message);
+            }
+        }
+    }
+
+    private void stack_listbox_up() {
+        shown_rows_number = null;
+        storage_row_i = 0;
+        selected_items = selected_items_stack.pop_head();
+        actual_item.pop_head();
+        refresh_actionbar();
+        listbox.pop();
+        graph.model = (ListStore) listbox.get_model();
+
+        if(need_refresh_depth >= listbox.get_depth()) {
+            var item = actual_item.peek_head();
+            need_refresh_depth -= 1;
+
+            clear_selected_items();
+            listbox.pop();
+
+            if(listbox.get_depth() == 0)
+                populate_view.begin ();
+            else
+                present_dir.begin (item.uri, item.dir);
         }
     }
 
@@ -111,6 +164,18 @@ public class Usage.NewStorageView : Usage.View {
         var row = new StorageViewRow.from_item (item);
         row.visible = true;
 
+        if(selected_items.find(item) != null)
+            row.check_button.active = true;
+
+        row.check_button_toggled.connect(() => {
+            if(row.selected)
+                selected_items.append(row.item);
+            else
+                selected_items.remove(row.item);
+
+            refresh_actionbar();
+        });
+
         if(item.custom_type == "available-graph")
             return new Gtk.ListBoxRow();
 
@@ -143,7 +208,7 @@ public class Usage.NewStorageView : Usage.View {
             var model = yield controller.enumerate_children (uri, dir);
 
             var file = File.new_for_uri (uri);
-            var item = new StorageViewItem.from_file (file);
+            var item = StorageViewItem.from_file (file);
             item.custom_type = "up-folder";
             item.dir = dir;
             controller.get_file_size.begin (item.uri, (obj, res) => {
@@ -224,13 +289,14 @@ public class Usage.NewStorageView : Usage.View {
 
         foreach (var dir in xdg_folders) {
             var file = File.new_for_uri (get_user_special_dir_path (dir));
-            var item = new StorageViewItem.from_file (file);
+            var item = StorageViewItem.from_file (file);
             item.dir = dir;
 
             controller.get_file_size.begin (item.uri, (obj, res) => {
                 try {
                     item.size = controller.get_file_size.end (res);
                     item.percentage = item.size * 100 / (double) total_size;
+                    item.custom_type = "root_item";
                     model.insert (1, item);
                 } catch (GLib.Error error) {
                     warning (error.message);
@@ -247,4 +313,18 @@ public class Usage.NewStorageView : Usage.View {
         available_graph_item.percentage = available_graph_item.size * 100 / (double) total_size;
         graph.model.append(available_graph_item);
     }
+
+    private void refresh_actionbar() {
+        actionbar.update_selected_items(selected_items);
+
+        if(selected_items.length() == 0)
+            actionbar.hide();
+        else
+            actionbar.show();
+    }
+
+    private void clear_selected_items() {
+        selected_items = new List<StorageViewItem>();
+        refresh_actionbar();
+    }
 }
diff --git a/src/storage/tracker-controller.vala b/src/storage/tracker-controller.vala
index d6e2621..222fab6 100644
--- a/src/storage/tracker-controller.vala
+++ b/src/storage/tracker-controller.vala
@@ -50,7 +50,11 @@ public class Usage.TrackerController : GLib.Object {
         while (yield worker.fetch_next (out n_uri, out file_type)) {
             try {
                 var file = File.new_for_uri (n_uri);
-                var item = new StorageViewItem.from_file (file);
+                var item = StorageViewItem.from_file (file);
+
+                if(item == null)
+                    continue;
+
                 item.ontology = file_type;
                 item.dir = dir;
 
@@ -89,10 +93,8 @@ public class Usage.TrackerController : GLib.Object {
 
             return info.get_size ();
         } catch (GLib.Error error) {
-            warning (error.message);
+             return 0;
         }
-
-        return 0;
     }
 
     public async uint64 get_file_size (string uri) throws GLib.Error {


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