[gnome-boxes] Be more friendly to user on deletion of boxes



commit 1185a276c3c6f5e6c4a7c91fbdbc8da3a6145c24
Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
Date:   Thu Dec 15 15:08:04 2011 +0200

    Be more friendly to user on deletion of boxes
    
    On deletion of a box by the user:
    
    - Hide the box immediately but don't delete it yet.
    - Display a notification with an 'Undo' button.
    - Delete the box if user doesn't hit the 'Undo' button or ignores the
      notification for 6 seconds. Otherwise, add it back to the view.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=666334

 data/gtk-style.css       |    4 ++
 src/Makefile.am          |    1 +
 src/app.vala             |   41 ++++++++++++++---
 src/notificationbar.vala |  108 ++++++++++++++++++++++++++++++++++++++++++++++
 src/selectionbar.vala    |    3 +-
 5 files changed, 148 insertions(+), 9 deletions(-)
---
diff --git a/data/gtk-style.css b/data/gtk-style.css
index 5286f2e..ff83949 100644
--- a/data/gtk-style.css
+++ b/data/gtk-style.css
@@ -142,6 +142,10 @@
     border-width: 0;
 }
 
+GtkInfoBar {
+    -GtkInfoBar-action-area-border: 0;
+}
+
 BoxesMenuBox .menuitem {
     background-color: @boxes_bg2_color;
     border-radius: 15;
diff --git a/src/Makefile.am b/src/Makefile.am
index 4d529a5..9f203cc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,6 +63,7 @@ gnome_boxes_SOURCES =				\
 	winxp-installer.vala 			\
 	wizard-source.vala			\
 	wizard.vala				\
+	notificationbar.vala			\
 	$(NULL)
 
 gnome_boxes_LDADD = $(BOXES_LIBS)
diff --git a/src/app.vala b/src/app.vala
index 6a1e9fe..df93bd2 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -28,6 +28,7 @@ private class Boxes.App: Boxes.UI {
     public Clutter.Box box; // the whole app box
     public CollectionItem current_item; // current object/vm manipulated
     public Topbar topbar;
+    public Notificationbar notificationbar;
     public Sidebar sidebar;
     public Selectionbar selectionbar;
     public uint duration;
@@ -249,11 +250,18 @@ private class Boxes.App: Boxes.UI {
         sidebar = new Sidebar (this);
         view = new CollectionView (this, sidebar.category);
         topbar = new Topbar (this);
+        notificationbar = new Notificationbar (this);
+        notificationbar.actor.add_constraint (new Clutter.AlignConstraint (view.actor, AlignAxis.X_AXIS, 0.5f));
+        var yconstraint = new Clutter.BindConstraint (topbar.actor, BindCoordinate.Y, topbar.height);
+        notificationbar.actor.add_constraint (yconstraint);
+        topbar.actor.notify["height"].connect (() => {
+            yconstraint.set_offset (topbar.height);
+        });
 
         selectionbar = new Selectionbar (this);
         selectionbar.actor.add_constraint (new Clutter.AlignConstraint (view.actor, AlignAxis.X_AXIS, 0.5f));
-        var yconstraint = new Clutter.BindConstraint (view.actor, BindCoordinate.Y,
-                                                      view.actor.height - selectionbar.spacing);
+        yconstraint = new Clutter.BindConstraint (view.actor, BindCoordinate.Y,
+                                                  view.actor.height - selectionbar.spacing);
         selectionbar.actor.add_constraint (yconstraint);
         view.actor.notify["height"].connect (() => {
             yconstraint.set_offset (view.actor.height - selectionbar.spacing);
@@ -349,13 +357,32 @@ private class Boxes.App: Boxes.UI {
         owned get { return view.get_selected_items (); }
     }
 
-    public void remove_item (CollectionItem item) {
-        var machine = item as Machine;
+    public void remove_selected_items () {
+        var selected_items = view.get_selected_items ();
+        var num_selected = selected_items.length ();
+        if (num_selected == 0)
+            return;
+
+        var message = (num_selected == 1) ? _("Box '%s' has been deleted").printf (selected_items.data.name) :
+                                            _("%u boxes have been deleted").printf (num_selected);
+        foreach (var item in selected_items)
+            view.remove_item (item);
 
-        if (machine != null)
-            machine.delete ();
+        Notificationbar.ActionFunc undo = () => {
+            foreach (var selected in selected_items)
+                view.add_item (selected);
+        };
+
+        Notificationbar.IgnoreFunc really_remove = () => {
+            foreach (var selected in selected_items) {
+                var machine = selected as Machine;
+
+                if (machine != null)
+                    machine.delete ();
+            }
+        };
 
-        view.remove_item (item);
+        notificationbar.display (Gtk.Stock.UNDO, message, (owned) undo, (owned) really_remove);
     }
 
     private bool on_key_pressed (Widget widget, Gdk.EventKey event) {
diff --git a/src/notificationbar.vala b/src/notificationbar.vala
new file mode 100644
index 0000000..2fce919
--- /dev/null
+++ b/src/notificationbar.vala
@@ -0,0 +1,108 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+private class Boxes.Notificationbar: GLib.Object {
+    public Clutter.Actor actor { get; private set; }
+    public static const float spacing = 60.0f;
+
+    public delegate void ActionFunc ();
+    public delegate void IgnoreFunc ();
+
+    private App app;
+    private InfoBar info_bar;
+    private Label label;
+    private Button action_button;
+
+    private uint timeout_id;
+    private ulong response_id;
+
+    public Notificationbar (App app) {
+        this.app = app;
+
+        setup_action_notify ();
+    }
+
+    public void display (string            action_label,
+                         string            action_message,
+                         owned ActionFunc  action_func,
+                         owned IgnoreFunc? ignore_func = null) {
+        action_button.label = action_label;
+        label.label = action_message;
+
+        // Replace running notification, if any
+        if (timeout_id != 0) {
+            Source.remove (timeout_id);
+            info_bar.disconnect (response_id);
+        }
+
+        timeout_id = Timeout.add_seconds (6, () => {
+            info_bar.response (ResponseType.CANCEL);
+
+            return false;
+        });
+
+        response_id = info_bar.response.connect ((response) => {
+            hide ();
+
+            Source.remove (timeout_id);
+            info_bar.disconnect (response_id);
+            timeout_id = 0;
+            response_id = 0;
+
+            if (response == ResponseType.OK)
+                action_func ();
+            else {
+                if (ignore_func != null)
+                    ignore_func ();
+            }
+        });
+
+        show ();
+    }
+
+    private void setup_action_notify () {
+        info_bar = new InfoBar ();
+        info_bar.get_style_context ().add_class ("osd");
+        info_bar.spacing = 120;
+        info_bar.margin = 5;
+
+        label = new Label ("");
+        var content_area = info_bar.get_content_area () as Container;
+        content_area.add (label);
+
+        action_button = new Button ();
+        info_bar.add_action_widget (action_button, ResponseType.OK);
+        action_button.use_stock = true;
+
+        var image = new Image.from_icon_name ("window-close-symbolic", IconSize.BUTTON);
+        var close_button = new Button ();
+        close_button.image = image;
+        info_bar.add_action_widget (close_button, ResponseType.CANCEL);
+        close_button.relief = ReliefStyle.NONE;
+        close_button.halign = Align.START;
+
+        var button_box = info_bar.get_action_area () as ButtonBox;
+        button_box.orientation = Orientation.HORIZONTAL;
+        button_box.set_child_non_homogeneous (close_button, true);
+        info_bar.set_message_type (MessageType.INFO);
+
+        info_bar.show_all ();
+
+        actor = new GtkClutter.Actor.with_contents (info_bar);
+        app.stage.add (actor);
+        actor.hide ();
+        actor.scale_y = 0f;
+    }
+
+    private void show () {
+        actor.show ();
+        actor.queue_redraw ();
+        actor.animate (Clutter.AnimationMode.LINEAR, app.duration, "scale-y", 1f);
+    }
+
+    private void hide () {
+        var animation = actor.animate (Clutter.AnimationMode.LINEAR, app.duration, "scale-y", 0f);
+        animation.completed.connect (() => { actor.hide (); });
+    }
+}
+
diff --git a/src/selectionbar.vala b/src/selectionbar.vala
index 0597d5d..524f4e6 100644
--- a/src/selectionbar.vala
+++ b/src/selectionbar.vala
@@ -41,8 +41,7 @@ private class Boxes.Selectionbar: GLib.Object {
         toolbar.insert (remove_btn, 2);
         remove_btn.icon_name = "edit-delete-symbolic";
         remove_btn.clicked.connect (() => {
-            foreach (var item in app.selected_items)
-                app.remove_item (item);
+            app.remove_selected_items ();
         });
         toolbar.show_all ();
 



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