Commit 0ee7fd59 authored by Petr Štětka's avatar Petr Štětka
Browse files

Add rename, move, move to trash, delete and restore from trash actions for files in storage.

System-monitor: Fix crashing when commandline is null.
Update comment in about dialog.
parent 3f289f54
......@@ -7,8 +7,11 @@ New GNOME Usage!
- [x] Memory usage
- [x] Network usage
- [x] Search in processes
- [ ] Fix bug with refreshing ProcessListBox - 50% (focus, and click when refresh)
- [ ] Storage view - 75%
- [ ] Multiselection in storage
- [ ] UI for file operations errors (as duplicate file, not enough space, permission...)
- [ ] Notification for file operations and rollback
- [ ] Storage support for more users (multiuser system)
- [ ] Application section in storage
- [ ] Power view (Design?)
- [ ] Disk usage (What library we can use?)
- [ ] Data view - 0%
......
......@@ -14,6 +14,7 @@ src/power-view.vala
src/process-dialog.vala
src/storage-analyzer.vala
src/storage-item.vala
src/storage-row.vala
src/storage-view.vala
src/utils.vala
src/window.vala
......@@ -74,7 +74,7 @@ namespace Usage
Gtk.show_about_dialog (window,
program_name: _("Usage"),
comments: _("View current application and monitor system state"),
comments: _("A nice way to view information about use of system resources, like memory and disk space."),
authors: authors,
website: "https://wiki.gnome.org/Apps/Usage",
website_label: _("Websites"),
......
This diff is collapsed.
......@@ -20,6 +20,8 @@ namespace Usage
PICTURES,
VIDEOS,
TRASH,
TRASHFILE,
TRASHSUBFILE,
USER,
AVAILABLE,
FILE,
......@@ -31,15 +33,17 @@ namespace Usage
private string name;
private string path;
private StorageItemType type;
private StorageItemType? parent;
private uint64 size;
private double percentage;
private StorageItemPosition prefered_position = StorageItemPosition.ANYWHERE;
private int section;
private Gdk.RGBA color;
public StorageItem.item(StorageItemType type, string name, string path, uint64 size, double percentage, int section = 0, StorageItemPosition prefered_position = StorageItemPosition.ANYWHERE)
public StorageItem.item(StorageItemType type, StorageItemType? parent, string name, string path, uint64 size, double percentage, int section = 0, StorageItemPosition prefered_position = StorageItemPosition.ANYWHERE)
{
this.type = type;
this.parent = parent;
this.name = name;
this.path = path;
this.size = size;
......@@ -48,34 +52,34 @@ namespace Usage
this.prefered_position = prefered_position;
}
public StorageItem.directory(string name, string path, uint64 size, double percentage)
public StorageItem.directory(StorageItemType parent, string name, string path, uint64 size, double percentage)
{
StorageItem.item(StorageItemType.DIRECTORY, name, path, size, percentage);
StorageItem.item(StorageItemType.DIRECTORY, parent, name, path, size, percentage);
}
public StorageItem.file(string name, string path, uint64 size, double percentage)
public StorageItem.file(StorageItemType parent, string name, string path, uint64 size, double percentage)
{
StorageItem.item(StorageItemType.FILE, name, path, size, percentage);
StorageItem.item(StorageItemType.FILE, parent, name, path, size, percentage);
}
public StorageItem.trash(string path, uint64 size, double percentage, int section = 0)
{
StorageItem.item(StorageItemType.TRASH, _("Trash"), path, size, percentage, section, StorageItemPosition.PENULTIMATE);
StorageItem.item(StorageItemType.TRASH, StorageItemType.TRASH, _("Trash"), path, size, percentage, section, StorageItemPosition.PENULTIMATE);
}
public StorageItem.storage(string name, string path, uint64 size, int section = 0)
{
StorageItem.item(StorageItemType.STORAGE, name, path, size, 0, section, StorageItemPosition.FIRST);
StorageItem.item(StorageItemType.STORAGE, StorageItemType.STORAGE, name, path, size, 0, section, StorageItemPosition.FIRST);
}
public StorageItem.system(string name, uint64 size, double percentage, int section = 0)
{
StorageItem.item(StorageItemType.SYSTEM, name, "", size, percentage, section);
StorageItem.item(StorageItemType.SYSTEM, StorageItemType.SYSTEM, name, "", size, percentage, section);
}
public StorageItem.available(uint64 size, double percentage, int section = 0)
{
StorageItem.item(StorageItemType.AVAILABLE, _("Available"), "", size, percentage, section, StorageItemPosition.LAST);
StorageItem.item(StorageItemType.AVAILABLE, StorageItemType.AVAILABLE, _("Available"), "", size, percentage, section, StorageItemPosition.LAST);
}
public StorageItemPosition get_prefered_position()
......@@ -118,6 +122,11 @@ namespace Usage
return type;
}
public StorageItemType? get_parent_type()
{
return parent;
}
public string get_path()
{
return path;
......
......@@ -8,8 +8,10 @@ namespace Usage
private List<string?> path_history;
private List<string?> name_history;
private List<StorageItemType?> parent_type_history;
private string? actual_path;
private string? actual_name;
private StorageItemType? actual_parent_type;
private ListStore model;
private bool root = true;
private StorageAnalyzer storage_analyzer;
......@@ -27,8 +29,10 @@ namespace Usage
path_history = new List<string>();
name_history = new List<string>();
parent_type_history = new List<StorageItemType?>();
actual_path = null;
actual_name = null;
actual_parent_type = null;
storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
get_style_context().add_class("folders");
......@@ -51,11 +55,16 @@ namespace Usage
{
unowned List<string>? path = path_history.last();
unowned List<string>? name = name_history.last();
unowned List<StorageItemType?>? parent = parent_type_history.last();
actual_path = path.data;
actual_name = name.data;
load(path.data);
actual_parent_type = parent.data;
load(path.data, actual_parent_type);
path_history.delete_link(path);
name_history.delete_link(name);
parent_type_history.delete_link(parent);
if(root)
{
(GLib.Application.get_default() as Application).get_window().get_header_bar().show_storage_back_button(false);
......@@ -74,7 +83,7 @@ namespace Usage
this.hide();
loading();
storage_analyzer.cache_complete.connect(() => {
storage_analyzer.prepare_items.begin(actual_path, color, (obj, res) => {
storage_analyzer.prepare_items.begin(actual_path, color, actual_parent_type, (obj, res) => {
var header_bar = (GLib.Application.get_default() as Application).get_window().get_header_bar();
if(root == false)
{
......@@ -100,7 +109,12 @@ namespace Usage
});
}
private void load(string? path)
public void refresh()
{
load(actual_path, actual_parent_type);
}
private void load(string? path, StorageItemType? parent)
{
if(path == null)
{
......@@ -114,7 +128,7 @@ namespace Usage
this.hide();
loading();
storage_analyzer.prepare_items.begin(path, color, (obj, res) => {
storage_analyzer.prepare_items.begin(path, color, parent, (obj, res) => {
loaded();
this.show();
model.remove_all();
......@@ -130,15 +144,15 @@ namespace Usage
private void on_row_activated (Gtk.ListBoxRow row)
{
StorageRow storage_row = (StorageRow) row;
if(storage_row.get_item_type() == StorageItemType.FILE)
;//TODO: action on click
else if(storage_row.get_item_type() != StorageItemType.STORAGE && storage_row.get_item_type() != StorageItemType.SYSTEM
&& storage_row.get_item_type() != StorageItemType.AVAILABLE)
if(storage_row.get_item_type() != StorageItemType.STORAGE && storage_row.get_item_type() != StorageItemType.SYSTEM &&
storage_row.get_item_type() != StorageItemType.AVAILABLE && storage_row.get_item_type() != StorageItemType.FILE)
{
path_history.append(actual_path);
name_history.append(actual_name);
parent_type_history.append(actual_parent_type);
actual_path = storage_row.get_item_path();
actual_name = storage_row.get_item_name();
actual_parent_type = storage_row.get_parent_type();
(GLib.Application.get_default() as Application).get_window().get_header_bar().show_storage_back_button(true);
(GLib.Application.get_default() as Application).get_window().get_header_bar().set_title_text(actual_name);
......@@ -147,7 +161,7 @@ namespace Usage
if(root)
color = storage_row.get_color();
load(actual_path);
load(actual_path, actual_parent_type);
}
}
......
......@@ -3,10 +3,22 @@ namespace Usage
public class StorageRow : Gtk.ListBoxRow
{
private StorageItemType type;
private StorageItemType? parent_type;
private string item_path;
private string item_name;
private Gdk.RGBA color;
const GLib.ActionEntry[] action_entries = {
{ "rename", action_rename },
{ "move", action_move },
{ "move-to-trash", action_move_to_trash },
{ "delete", action_delete },
{ "wipe-trash", action_wipe_trash },
{ "wipe-folder", action_wipe_folder },
{ "trash-restore", action_trash_restore },
{ "trash-delete", action_trash_delete }
};
public StorageRow(StorageItem storage_item)
{
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
......@@ -16,6 +28,7 @@ namespace Usage
var size_label = new Gtk.Label(Utils.format_size_values(storage_item.get_size()));
item_path = storage_item.get_path();
item_name = storage_item.get_name();
parent_type = storage_item.get_parent_type();
type = storage_item.get_item_type();
var title_label = new Gtk.Label(storage_item.get_name());
title_label.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
......@@ -43,8 +56,6 @@ namespace Usage
box.pack_start(color_rectangle, false, false, 5);
break;
case StorageItemType.STORAGE:
box.margin_top = 10;
box.margin_bottom = 10;
title_label.set_markup ("<b>" + storage_item.get_name() + "</b>");
size_label.set_markup ("<b>" + Utils.format_size_values(storage_item.get_size()) + "</b>");
activatable = false;
......@@ -92,7 +103,10 @@ namespace Usage
box.pack_start(title_label, false, true, 5);
box.pack_end(size_label, false, true, 10);
add(box);
var event_box = new Gtk.EventBox();
event_box.add(box);
event_box.button_press_event.connect(row_press_event);
add(event_box);
show_all();
}
......@@ -116,5 +130,271 @@ namespace Usage
{
return type;
}
public StorageItemType? get_parent_type()
{
return parent_type;
}
private bool row_press_event (Gdk.EventButton event)
{
if(event.type == Gdk.EventType.BUTTON_PRESS)
{
switch (event.button)
{
case Gdk.BUTTON_PRIMARY:
action_primary();
return false;
case Gdk.BUTTON_SECONDARY:
action_secondary();
return true;
}
}
return true;
}
private void action_primary()
{
if(type == StorageItemType.FILE)
{
File file;
if(parent_type == StorageItemType.TRASH || parent_type == StorageItemType.TRASHFILE || parent_type == StorageItemType.TRASHSUBFILE)
file = File.new_for_uri(item_path);
else
file = File.new_for_path(item_path);
try {
AppInfo.launch_default_for_uri(file.get_uri(), null);
} catch (Error e) {
stderr.printf (e.message);
}
}
}
private void action_secondary()
{
bool show_popover = false;
var action_group = new GLib.SimpleActionGroup ();
action_group.add_action_entries (action_entries, this);
var menu = new GLib.Menu ();
var section = new GLib.Menu ();
switch(type)
{
case StorageItemType.SYSTEM:
case StorageItemType.AVAILABLE:
case StorageItemType.STORAGE:
case StorageItemType.TRASHSUBFILE:
case StorageItemType.TRASHFILE:
break;
case StorageItemType.USER:
case StorageItemType.DOCUMENTS:
case StorageItemType.DOWNLOADS:
case StorageItemType.DESKTOP:
case StorageItemType.MUSIC:
case StorageItemType.PICTURES:
case StorageItemType.VIDEOS:
section.append (_("Empty") + " " + item_name, "row.wipe-folder");
menu.append_section (null, section);
show_popover = true;
break;
case StorageItemType.TRASH:
section.append (_("Empty Trash"), "row.wipe-trash");
menu.append_section (null, section);
show_popover = true;
break;
case StorageItemType.DIRECTORY:
case StorageItemType.FILE:
show_popover = true;
switch(parent_type)
{
case StorageItemType.TRASHFILE:
section.append (_("Restore"), "row.trash-restore");
menu.append_section (null, section);
section = new GLib.Menu ();
section.append (_("Delete from Trash"), "row.trash-delete");
menu.append_section (null, section);
break;
case StorageItemType.TRASHSUBFILE:
show_popover = false;
break;
default:
section.append (_("Rename"), "row.rename");
section.append (_("Move to"), "row.move");
menu.append_section (null, section);
section = new GLib.Menu ();
section.append (_("Move to trash"), "row.move-to-trash");
section.append (_("Delete"), "row.delete");
menu.append_section (null, section);
break;
}
break;
}
if(show_popover)
{
var pop = new Gtk.Popover (this);
pop.bind_model (menu, null);
pop.insert_action_group ("row", action_group);
pop.set_position(Gtk.PositionType.BOTTOM);
pop.show_all ();
}
}
private void action_trash_restore()
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.restore_trash_file.begin(item_path, () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
private void action_trash_delete()
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.delete_trash_file.begin(item_path, () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
private void action_wipe_folder()
{
var dialog = new Gtk.MessageDialog ((GLib.Application.get_default() as Application).get_window(), Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, _("Empty all items from %s?").printf(item_name));
dialog.secondary_text = _("All items in the %s will be moved to the Trash.").printf(item_name);
if(dialog.run() == Gtk.ResponseType.OK)
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.wipe_folder.begin(item_path, () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
dialog.close();
}
private void action_wipe_trash()
{
var dialog = new Gtk.MessageDialog ((GLib.Application.get_default() as Application).get_window(), Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, _("Empty all items from Trash?"));
dialog.secondary_text = _("All items in the Trash will be permanently deleted.");
if(dialog.run() == Gtk.ResponseType.OK)
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.wipe_trash.begin(() => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
dialog.close();
}
private void action_rename()
{
var pop = new Gtk.Popover (this);
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
box.margin = 10;
var entry = new Gtk.Entry();
entry.set_text (Path.get_basename(item_path));
box.add(entry);
var button = new Gtk.Button.with_label(_("Rename"));
button.get_style_context().add_class ("suggested-action");
button.clicked.connect (() => {
Timeout.add(0, () => {
string destination = Path.get_dirname(item_path) + "/" + entry.get_text();
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.move_file.begin(File.new_for_path(item_path), File.new_for_path(destination), () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
});
entry.activate.connect(() => {
button.activate();
});
box.add(button);
pop.add(box);
pop.set_position(Gtk.PositionType.BOTTOM);
pop.show_all ();
}
private void action_move()
{
Gtk.FileChooserDialog chooser = new Gtk.FileChooserDialog (
_("Select destination folder"), (GLib.Application.get_default() as Application).get_window(), Gtk.FileChooserAction.SELECT_FOLDER,
_("Cancel"),
Gtk.ResponseType.CANCEL,
_("Select"),
Gtk.ResponseType.ACCEPT);
chooser.destroy_with_parent = true;
Gtk.FileFilter filter = new Gtk.FileFilter();
filter.add_custom(Gtk.FileFilterFlags.FILENAME, (filter_info) => {
if(filter_info.filename != item_path)
return true;
else
return false;
});
chooser.set_filter(filter);
chooser.set_filename(item_path);
chooser.show();
if(chooser.run() == Gtk.ResponseType.ACCEPT)
{
Timeout.add(0, () => {
string destination = chooser.get_file().get_parse_name() + "/" + Path.get_basename(item_path);
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.move_file.begin(File.new_for_path(item_path), File.new_for_path(destination), () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
chooser.close ();
}
private void action_move_to_trash()
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.trash_file.begin(item_path, () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
private void action_delete()
{
Timeout.add(0, () => {
var storage_analyzer = (GLib.Application.get_default() as Application).get_storage_analyzer();
storage_analyzer.delete_file.begin(item_path, () => {
((StorageView) (GLib.Application.get_default() as Application).get_window().get_views()[2]).get_storage_list_box().refresh();
});
return false;
});
}
}
}
......@@ -23,12 +23,23 @@ namespace Usage
paned.position = 300;
paned.add2(spinner);
Gtk.Label empty_label = new Gtk.Label("<span font_desc=\"17.0\">" + _("No content here") + "</span>");
var center_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
var image = new Gtk.Image.from_icon_name("folder-symbolic", Gtk.IconSize.DIALOG );
image.pixel_size = 128;
center_box.add(image);
Gtk.Label empty_label = new Gtk.Label("<span size='xx-large' font_weight='bold'>" + _("No content here") + "</span>");
empty_label.set_use_markup (true);
empty_label.margin_top = 10;
center_box.add(empty_label);
center_box.get_style_context().add_class("dim-label");
var empty_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
empty_box.set_center_widget(center_box);
empty_box.show_all();
storage_list_box.loading.connect(() =>
{
paned.remove(empty_label);
paned.remove(empty_box);
paned.remove(scrolled_window);
paned.remove(graph);
paned.add2(spinner);
......@@ -49,7 +60,7 @@ namespace Usage
paned.remove(scrolled_window);
paned.remove(graph);
paned.remove(spinner);
paned.add2(empty_label);
paned.add2(empty_box);
empty_label.show();
});
......
......@@ -2,9 +2,8 @@ namespace Usage
{
[Compact]
public class StorageResult {
internal string path;
internal uint64 size;
internal float percentage;
public string path;
public uint64 size;
}
public class StorageWorker
......@@ -13,28 +12,22 @@ namespace Usage
private File path;
private string[] exclude_paths;
private Cancellable cancellable;
private string? name;
private uint64 total_size;
public StorageWorker(File path, uint64 total_size, ref Cancellable cancellable, ref AsyncQueue<StorageResult> results_queue, string? name = null, string[]? exclude_paths = null)
public StorageWorker(File path, ref Cancellable cancellable, ref AsyncQueue<StorageResult> results_queue, string[]? exclude_paths = null)
{
this.path = path;
this.cancellable = cancellable;
this.name = name;
this.exclude_paths = exclude_paths;
this.results_queue = results_queue;
this.total_size = total_size;
}
private StorageResult get_directory(File file, out uint64 size)
private uint64 get_directory(File file)
{ </