[gnome-boxes] Add SnapshotListRow



commit bfb4c17ab46a26935d11752729dbf189393c8ff0
Author: Timm Bäder <mail baedert org>
Date:   Mon Jul 28 10:33:23 2014 +0200

    Add SnapshotListRow
    
    This will be used in the snapshots page of the VM properties and
    features a way to delete, rename or revert to the snapshot it
    represents.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=710306

 data/gnome-boxes.gresource.xml |    1 +
 data/gtk-style.css             |   13 +++
 data/ui/snapshot-list-row.ui   |  113 +++++++++++++++++++
 src/Makefile.am                |    1 +
 src/snapshot-list-row.vala     |  231 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 359 insertions(+), 0 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 14ae7ea..87b856e 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -28,5 +28,6 @@
     <file preprocess="xml-stripblanks">ui/wizard-source.ui</file>
     <file preprocess="xml-stripblanks">ui/wizard-summary.ui</file>
     <file preprocess="xml-stripblanks">ui/wizard-toolbar.ui</file>
+    <file preprocess="xml-stripblanks">ui/snapshot-list-row.ui</file>
   </gresource>
 </gresources>
diff --git a/data/gtk-style.css b/data/gtk-style.css
index be6e374..0eab77e 100644
--- a/data/gtk-style.css
+++ b/data/gtk-style.css
@@ -12,6 +12,19 @@
     -GtkWidget-separator-height: 1;
 }
 
+.boxes-snapshot-list-row {
+    border-bottom: 1px solid @borders;
+}
+
+.boxes-snapshot-list-row.indicator {
+    background-color: @insensitive_fg_color;
+    border: none;
+}
+
+.boxes-snapshot-list-row.indicator.active {
+    background-color: @theme_fg_color;
+}
+
 BoxesMiniGraph {
     background-color: @content_view_bg;
     border-color: @borders;
diff --git a/data/ui/snapshot-list-row.ui b/data/ui/snapshot-list-row.ui
new file mode 100644
index 0000000..f20ee62
--- /dev/null
+++ b/data/ui/snapshot-list-row.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="actions_menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Revert to this state</attribute>
+        <attribute name="action">snap.revert-to</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Rename</attribute>
+        <attribute name="action">snap.rename</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Delete</attribute>
+        <attribute name="action">snap.delete</attribute>
+      </item>
+    </section>
+  </menu>
+
+  <template class="BoxesSnapshotListRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <child>
+      <object class="GtkRevealer" id="delete_revealer">
+        <property name="visible">true</property>
+        <property name="can_focus">false</property>
+        <property name="reveal_child">true</property>
+
+        <child>
+          <object class="GtkStack" id="mode_stack">
+            <property name="visible">true</property>
+            <property name="can_focus">false</property>
+            <property name="transition_type">slide-up-down</property>
+            <child>
+              <object class="GtkBox" id="show_name_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkLabel" id="name_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="margin_start">45</property>
+                    <property name="ellipsize">end</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkMenuButton" id="more_button">
+                    <property name="visible">true</property>
+                    <property name="menu_model">actions_menu</property>
+                    <property name="valign">center</property>
+                    <property name="margin_top">4</property>
+                    <property name="margin_bottom">4</property>
+                    <property name="margin_end">4</property>
+                    <style>
+                      <class name="image-button" />
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">true</property>
+                        <property name="icon-name">emblem-system-symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkBox" id="edit_name_box">
+                <property name="visible">true</property>
+                <property name="orientation">horizontal</property>
+                <property name="can_focus">false</property>
+                <child>
+                  <object class="GtkEntry" id="name_entry">
+                    <property name="visible">true</property>
+                    <property name="can_focus">true</property>
+                    <property name="margin_start">45</property>
+                    <property name="hexpand">true</property>
+                    <signal name="activate" handler="on_save_name_button_clicked" />
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkButton" id="save_name_button">
+                    <property name="can_focus">true</property>
+                    <property name="visible">true</property>
+                    <property name="margin_start">10</property>
+                    <property name="valign">center</property>
+                    <property name="label" translatable="yes">Done</property>
+                    <signal name="clicked" handler="on_save_name_button_clicked" />
+                    <style>
+                      <class name="text-button" />
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 9ae8dbb..bc673bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -149,6 +149,7 @@ gnome_boxes_SOURCES =                               \
        downloader.vala                         \
        empty-boxes.vala                        \
        tracker-iso-query.vala                  \
+       snapshot-list-row.vala                  \
        $(NULL)
 
 gnome_boxes_LDADD =                            \
diff --git a/src/snapshot-list-row.vala b/src/snapshot-list-row.vala
new file mode 100644
index 0000000..bbe3011
--- /dev/null
+++ b/src/snapshot-list-row.vala
@@ -0,0 +1,231 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/snapshot-list-row.ui")]
+private class Boxes.SnapshotListRow : Gtk.ListBoxRow {
+    public GVir.DomainSnapshot snapshot;
+    public string activity_message { get; set; default = ""; }
+
+    [GtkChild]
+    private Gtk.Revealer delete_revealer;
+    [GtkChild]
+    private Gtk.Label name_label;
+    [GtkChild]
+    private Gtk.Stack mode_stack;
+    [GtkChild]
+    private Gtk.Entry name_entry;
+    [GtkChild]
+    private Gtk.Box edit_name_box;
+    [GtkChild]
+    private Gtk.Box show_name_box;
+
+    // index of the snapshot in the list
+    private int index;
+    private int parent_size;
+
+    private Boxes.LibvirtMachine machine;
+    private unowned Gtk.Container? parent_container = null;
+
+    private const GLib.ActionEntry[] action_entries = {
+        {"revert-to", revert_to_activated},
+        {"rename",    rename_activated},
+        {"delete",    delete_activated}
+    };
+
+    construct {
+        this.get_style_context ().add_class ("boxes-snapshot-list-row");
+        this.parent_set.connect (() => {
+            var parent = get_parent () as Gtk.Container;
+
+            if (parent == null)
+                return;
+
+            this.parent_container = parent;
+            update_index ();
+            parent.add.connect (update_index);
+            parent.remove.connect (update_index);
+        });
+        this.selectable = false;
+        this.activatable = false;
+    }
+
+
+    public SnapshotListRow (GVir.DomainSnapshot snapshot,
+                            LibvirtMachine      machine) {
+        this.snapshot = snapshot;
+        this.machine = machine;
+
+        try {
+            name_label.label = snapshot.get_config (0).get_description ();
+        } catch (GLib.Error e) {
+            critical (e.message);
+        }
+
+        var action_group = new GLib.SimpleActionGroup ();
+        action_group.add_action_entries (action_entries, this);
+        this.insert_action_group ("snap", action_group);
+
+        delete_revealer.notify["child-revealed"].connect (() => {
+            if (!delete_revealer.child_revealed) {
+                parent_container = (Gtk.Container) this.get_parent ();
+                if (parent_container != null)
+                    parent_container.remove (this);
+            }
+        });
+    }
+
+    // Need to override this in order to connect the indicators without any gaps.
+    public override bool draw (Cairo.Context ct) {
+        base.draw (ct);
+        var height = this.get_allocated_height ();
+        var sc = this.get_style_context ();
+
+        double indicator_size = height / 2.0;
+
+        sc.save ();
+        sc.add_class ("indicator");
+
+        var line_color = sc.get_background_color (this.get_state_flags ());
+        ct.set_source_rgba (line_color.red, line_color.green, line_color.blue, line_color.alpha);
+        ct.set_line_width (4);
+        if (index > 0) {
+            ct.move_to (height / 2.0 + 0.5, -1);
+            ct.line_to (height / 2.0 + 0.5, height / 2.0);
+            ct.stroke ();
+        }
+        if (index < parent_size - 1) {
+            ct.move_to (height / 2.0 + 0.5, height / 2.0);
+            ct.line_to (height / 2.0 + 0.5, height + 1);
+            ct.stroke ();
+        }
+
+        bool is_current = false;
+        try {
+            this.snapshot.get_is_current (0, out is_current);
+        } catch (GLib.Error e) {
+            warning (e.message);
+        }
+
+        if (is_current)
+            sc.add_class ("active");
+
+        ct.save();
+        sc.render_background (ct, height / 4.0, height / 4.0, indicator_size, indicator_size);
+        sc.render_frame (ct, height / 4.0, height / 4.0, indicator_size, indicator_size + 0.5);
+        ct.restore();
+
+        sc.restore ();
+
+        return true;
+    }
+
+    [GtkCallback]
+    private void on_save_name_button_clicked () {
+        try {
+            var config = snapshot.get_config (0);
+            config.set_description (name_entry.text);
+            snapshot.set_config (config);
+            name_label.label = name_entry.get_text ();
+            mode_stack.visible_child = show_name_box;
+        } catch (GLib.Error e) {
+            warning (e.message);
+        }
+    }
+
+
+    private void revert_to_activated (GLib.SimpleAction action, GLib.Variant? v) {
+        var snapshot_name = this.snapshot.get_name ();
+        var snapshot_state = GVirConfig.DomainSnapshotDomainState.NOSTATE;
+
+        try {
+            var snapshot_config = snapshot.get_config (0);
+            snapshot_name = snapshot_config.get_description ();
+            snapshot_state = snapshot_config.get_state ();
+        } catch (GLib.Error e) {}
+
+        var show_activity = (machine.state == Machine.MachineState.RUNNING);
+        if (show_activity)
+            activity_message = _("Reverting to %s...").printf (snapshot_name);
+
+        if (snapshot_state == GVirConfig.DomainSnapshotDomainState.SHUTOFF) {
+            ulong restart_id = 0;
+            restart_id = machine.domain.stopped.connect (() => {
+                machine.start.begin (Machine.ConnectFlags.NONE, null);
+                machine.domain.disconnect (restart_id);
+            });
+        }
+
+        snapshot.revert_to_async.begin (0, null, (obj, res) => {
+            try {
+                snapshot.revert_to_async.end (res);
+                parent_container.queue_draw ();
+            } catch (GLib.Error e) {
+                warning (e.message);
+                machine.window.notificationbar.display_error (_("Failed to apply snapshot"));
+            }
+            if (show_activity)
+                activity_message = "";
+        });
+    }
+
+
+    private void delete_activated (GLib.SimpleAction action, GLib.Variant? v) {
+        string snapshot_identifier = snapshot.get_name ();
+        try {
+            var config = snapshot.get_config (0);
+            snapshot_identifier = config.get_description ();
+        } catch (GLib.Error e) {
+            warning ("Could not get configuration of snapshot %s: %s",
+                      snapshot.get_name (),
+                      e.message);
+        }
+        var message = _("Snapshot \"%s\" deleted.").printf (snapshot_identifier);
+        delete_revealer.reveal_child = false;
+
+        Notification.OKFunc undo = () => {
+            parent_container.add (this);
+            this.visible = true;
+            delete_revealer.reveal_child = true;
+        };
+
+        Notification.CancelFunc really_remove = () => {
+            this.snapshot.delete_async.begin (0, null, (obj, res) =>{
+                try {
+                    this.snapshot.delete_async.end (res);
+                    parent_container.queue_draw ();
+                } catch (GLib.Error e) {
+                    warning ("Error while deleting snapshot %s: %s", snapshot.get_name (), e.message);
+                }
+            });
+        };
+        machine.window.notificationbar.display_for_action (message,
+                                                           _("_Undo"),
+                                                           (owned) undo,
+                                                           (owned) really_remove);
+    }
+
+    private void rename_activated (GLib.SimpleAction action, GLib.Variant? v) {
+        name_entry.text = name_label.get_text ();
+        mode_stack.visible_child = edit_name_box;
+        name_entry.grab_focus ();
+    }
+
+    public void unreveal () {
+        delete_revealer.reveal_child = false;
+    }
+
+    public void reveal () {
+        delete_revealer.reveal_child = true;
+    }
+
+
+    private void update_index () {
+        var parent = this.get_parent ();
+
+        if (parent == null || !(parent is Gtk.ListBox))
+            return;
+
+        var siblings = (parent as Gtk.Container).get_children ();
+        this.index = siblings.index (this);
+        this.parent_size = (int) siblings.length ();
+    }
+}


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