[virt-tools-list] [virt-manager][PATCH ] Add basic snapshot support.

Leonardo Garcia lagarcia at linux.vnet.ibm.com
Fri Aug 2 19:27:41 UTC 2013


From: Leonardo Garcia <lagarcia at br.ibm.com>

Signed-off-by: Leonardo Garcia <lagarcia at br.ibm.com>
Signed-off-by: Eduardo Elias Ferreira <edusf at linux.vnet.ibm.com>
---
 ui/vmm-details.ui      |  179 ++++++++++++++++++++++++++++++++++++++++++++++++
 virtManager/details.py |  150 ++++++++++++++++++++++++++++++++++-------
 virtManager/domain.py  |   34 +++++++++
 virtManager/engine.py  |   33 +++++++++
 virtManager/error.py   |   23 ++++--
 5 files changed, 388 insertions(+), 31 deletions(-)

diff --git a/ui/vmm-details.ui b/ui/vmm-details.ui
index a04be18..502e70f 100644
--- a/ui/vmm-details.ui
+++ b/ui/vmm-details.ui
@@ -110,6 +110,17 @@
     <property name="can_focus">False</property>
     <property name="stock">gtk-missing-image</property>
   </object>
+  <object class="GtkListStore" id="snapshot-liststore">
+    <columns>
+      <!-- column-name vm-snapshots -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkEntry" id="snapshot-name-entry">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="invisible_char">●</property>
+  </object>
   <object class="GtkWindow" id="vmm-details">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">Virtual Machine</property>
@@ -377,6 +388,17 @@
                       </object>
                     </child>
                     <child>
+                      <object class="GtkRadioMenuItem" id="details-menu-view-snapshots">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">_Snapshots</property>
+                        <property name="use_underline">True</property>
+                        <property name="draw_as_radio">True</property>
+                        <property name="group">details-menu-view-console</property>
+                        <signal name="toggled" handler="on_details_menu_view_snapshots_toggled" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
                       <object class="GtkSeparatorMenuItem" id="menuitem2">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
@@ -529,6 +551,23 @@
                   </packing>
                 </child>
                 <child>
+                  <object class="GtkRadioToolButton" id="control-vm-snapshots">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="has_tooltip">True</property>
+                    <property name="tooltip_markup" translatable="yes">Show virtual hardware details</property>
+                    <property name="tooltip_text" translatable="yes">Show virtual hardware details</property>
+                    <property name="label" translatable="yes">Snapshots</property>
+                    <property name="stock_id">gtk-floppy</property>
+                    <property name="group">control-vm-console</property>
+                    <signal name="toggled" handler="on_control_vm_snapshots_toggled" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="homogeneous">True</property>
+                  </packing>
+                </child>
+                <child>
                   <object class="GtkSeparatorToolItem" id="toolbutton3">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
@@ -7191,6 +7230,146 @@ I/O:</property>
                 <property name="tab_fill">False</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkBox" id="box1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">3</property>
+                <child>
+                  <object class="GtkButtonBox" id="buttonbox1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="layout_style">spread</property>
+                    <child>
+                      <object class="GtkButton" id="take-snapshot">
+                        <property name="label" translatable="yes">Take</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <signal name="clicked" handler="on_take_snapshot_clicked" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="restore-snapshot">
+                        <property name="label" translatable="yes">Restore</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <signal name="clicked" handler="on_restore_snapshot_clicked" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="delete-snapshot">
+                        <property name="label" translatable="yes">Delete</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <signal name="clicked" handler="on_delete_snapshot_clicked" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHBox" id="error-vm-snapshot-box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">3</property>
+                    <child>
+                      <object class="GtkImage" id="error-vm-snapshot-icon">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="stock">gtk-dialog-error</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="error-vm-snapshot-label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Snapshots are disabled for this VM.</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkTreeView" id="snapshot-list-treeview">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="model">snapshot-liststore</property>
+                    <property name="headers_clickable">False</property>
+                    <child internal-child="selection">
+                      <object class="GtkTreeSelection" id="snapshot-treeview-selection"/>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn" id="vm-snapshot-column">
+                        <property name="title" translatable="yes">VM Snapshots</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="vm-snapshot-cell-renderer-text"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label88">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Snapshots</property>
+              </object>
+              <packing>
+                <property name="position">2</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">True</property>
diff --git a/virtManager/details.py b/virtManager/details.py
index afb7945..c2e745d 100644
--- a/virtManager/details.py
+++ b/virtManager/details.py
@@ -21,6 +21,8 @@
 import logging
 import traceback
 
+from datetime import datetime
+
 # pylint: disable=E0611
 from gi.repository import GObject
 from gi.repository import Gtk
@@ -38,6 +40,7 @@ from virtManager.console import vmmConsolePages
 from virtManager.serialcon import vmmSerialConsole
 from virtManager.graphwidgets import Sparkline
 from virtManager import util as util
+from virtManager.error import vmmErrorDialog
 
 import virtinst
 
@@ -142,7 +145,8 @@ BOOT_ACTIVE = 3
 # Main tab pages
 PAGE_CONSOLE = 0
 PAGE_DETAILS = 1
-PAGE_DYNAMIC_OFFSET = 2
+PAGE_SNAPSHOTS = 2
+PAGE_DYNAMIC_OFFSET = 3
 
 
 def prettyify_disk_bus(bus):
@@ -335,6 +339,9 @@ class vmmDetails(vmmGObjectUI):
         "action-migrate-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
         "action-delete-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
         "action-clone-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
+        "action-take-snapshot": (GObject.SignalFlags.RUN_FIRST, None, [str, str, str]),
+        "action-restore-snapshot": (GObject.SignalFlags.RUN_FIRST, None, [str, str, str]),
+        "action-delete-snapshot": (GObject.SignalFlags.RUN_FIRST, None, [str, str, str]),
         "details-closed": (GObject.SignalFlags.RUN_FIRST, None, []),
         "details-opened": (GObject.SignalFlags.RUN_FIRST, None, []),
         "customize-finished": (GObject.SignalFlags.RUN_FIRST, None, []),
@@ -400,6 +407,7 @@ class vmmDetails(vmmGObjectUI):
 
             "on_control_vm_details_toggled": self.details_console_changed,
             "on_control_vm_console_toggled": self.details_console_changed,
+            "on_control_vm_snapshots_toggled": self.details_console_changed,
             "on_control_run_clicked": self.control_vm_run,
             "on_control_shutdown_clicked": self.control_vm_shutdown,
             "on_control_pause_toggled": self.control_vm_pause,
@@ -425,6 +433,7 @@ class vmmDetails(vmmGObjectUI):
             "on_details_menu_view_manager_activate": self.view_manager,
             "on_details_menu_view_details_toggled": self.details_console_changed,
             "on_details_menu_view_console_toggled": self.details_console_changed,
+            "on_details_menu_view_snapshots_toggled": self.details_console_changed,
 
             "on_details_pages_switch_page": self.switch_page,
 
@@ -516,6 +525,10 @@ class vmmDetails(vmmGObjectUI):
 
             "on_hw_list_button_press_event": self.popup_addhw_menu,
 
+            "on_take_snapshot_clicked": self._take_snapshot,
+            "on_restore_snapshot_clicked": self._restore_snapshot,
+            "on_delete_snapshot_clicked": self._delete_snapshot,
+
             # Listeners stored in vmmConsolePages
             "on_details_menu_view_fullscreen_activate": self.console.toggle_fullscreen,
             "on_details_menu_view_size_to_vm_activate": self.console.size_to_vm,
@@ -533,6 +546,7 @@ class vmmDetails(vmmGObjectUI):
         # Deliberately keep all this after signal connection
         self.vm.connect("status-changed", self.refresh_vm_state)
         self.vm.connect("config-changed", self.refresh_vm_state)
+        self.vm.connect("snapshot-changed", self.refresh_vm_state)
         self.vm.connect("resources-sampled", self.refresh_resources)
         self.widget("hw-list").get_selection().connect("changed",
                                                        self.hw_changed)
@@ -1364,10 +1378,11 @@ class vmmDetails(vmmGObjectUI):
         if not src.get_active():
             return
 
-        is_details = False
-        if (src == self.widget("control-vm-details") or
-            src == self.widget("details-menu-view-details")):
-            is_details = True
+        is_details = (src == self.widget("control-vm-details") or
+                      src == self.widget("details-menu-view-details"))
+
+        is_snapshots = (src == self.widget("control-vm-snapshots") or
+                        src == self.widget("details-menu-view-snapshots"))
 
         pages = self.widget("details-pages")
         if pages.get_current_page() == PAGE_DETAILS:
@@ -1378,29 +1393,38 @@ class vmmDetails(vmmGObjectUI):
 
         if is_details:
             pages.set_current_page(PAGE_DETAILS)
+        elif is_snapshots:
+            pages.set_current_page(PAGE_SNAPSHOTS)
         else:
             pages.set_current_page(self.last_console_page)
 
-    def sync_details_console_view(self, is_details):
+    def sync_details_console_view(self, page):
         details = self.widget("control-vm-details")
         details_menu = self.widget("details-menu-view-details")
+        snapshots = self.widget("control-vm-snapshots")
+        snapshots_menu = self.widget("details-menu-view-snapshots")
         console = self.widget("control-vm-console")
         console_menu = self.widget("details-menu-view-console")
 
         try:
             self.ignoreDetails = True
 
+            is_details = page == PAGE_DETAILS
+            is_snapshots = page == PAGE_SNAPSHOTS
+            is_console = not is_details and not is_snapshots
             details.set_active(is_details)
             details_menu.set_active(is_details)
-            console.set_active(not is_details)
-            console_menu.set_active(not is_details)
+            snapshots.set_active(is_snapshots)
+            snapshots_menu.set_active(is_snapshots)
+            console.set_active(is_console)
+            console_menu.set_active(is_console)
         finally:
             self.ignoreDetails = False
 
     def switch_page(self, ignore1=None, ignore2=None, newpage=None):
         self.page_refresh(newpage)
 
-        self.sync_details_console_view(newpage == PAGE_DETAILS)
+        self.sync_details_console_view(newpage)
         self.console.set_allow_fullscreen()
 
         if newpage == PAGE_CONSOLE or newpage >= PAGE_DYNAMIC_OFFSET:
@@ -1649,8 +1673,7 @@ class vmmDetails(vmmGObjectUI):
         if type(ret) is tuple and len(ret) >= 2:
             ret = ret[1]
 
-        import datetime
-        now = str(datetime.datetime.now()).split(".")[0].replace(" ", "_")
+        now = str(datetime.now()).split(".")[0].replace(" ", "_")
         default = "Screenshot_%s_%s.png" % (self.vm.get_name(), now)
 
         path = util.browse_local(
@@ -2625,6 +2648,66 @@ class vmmDetails(vmmGObjectUI):
 
         return True
 
+    ######################
+    # Snapshot listeners #
+    ######################
+
+    def _take_snapshot(self, src_ignore):
+        name_dialog = self.err
+        name_dialog_title = _("Snapshot name")
+        name_dialog_text = _("Give a unique name for this snapshot")
+        name_entry = self.widget("snapshot-name-entry")
+        name = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
+        name_entry.set_text(name)
+        def process_snapshot_name(src, response):
+            if response == Gtk.ResponseType.CANCEL:
+                src.destroy()
+                return
+
+            name = name_entry.get_text()
+            for snapshot in self.vm.get_snapshots():
+                if name == snapshot:
+                    err = vmmErrorDialog(src)
+                    err_summary = _("Snapshot name conflict!")
+                    err_details = _("You need to choose a snapshot name "
+                                    "different from the existing ones.")
+                    err.show_err(err_summary, err_details)
+                    return
+            src.destroy()
+            self.emit("action-take-snapshot", self.vm.conn.get_uri(),
+                      self.vm.get_uuid(), name)
+
+        name_entry.show()
+        name_dialog.ask_info(name_dialog_text, None, name_dialog_title,
+                             name_entry, process_snapshot_name)
+
+    def _restore_snapshot(self, src_ignore):
+        selection = self.widget("snapshot-treeview-selection")
+        model, path = selection.get_selected_rows()
+        if not path:
+            summary = _("Please, select a snapshot to be restored.")
+            self.err.show_err(summary, dialog_type=Gtk.MessageType.WARNING)
+            return
+        selected_snapshot = model[path[0][0]][0]
+        self.emit("action-restore-snapshot", self.vm.conn.get_uri(),
+                  self.vm.get_uuid(), selected_snapshot)
+
+    def _delete_snapshot(self, src_ignore):
+        selection = self.widget("snapshot-treeview-selection")
+        model, path = selection.get_selected_rows()
+        if not path:
+            summary = _("Please, select a snapshot to be deleted.")
+            self.err.show_err(summary, dialog_type=Gtk.MessageType.WARNING)
+            return
+        selected_snapshot = model[path[0][0]][0]
+        self.emit("action-delete-snapshot", self.vm.conn.get_uri(),
+                  self.vm.get_uuid(), selected_snapshot)
+
+    def _enable_snapshot_buttons(self, enable):
+        self.widget("take-snapshot").set_sensitive(enable)
+        self.widget("restore-snapshot").set_sensitive(enable)
+        self.widget("delete-snapshot").set_sensitive(enable)
+
     ########################
     # Details page refresh #
     ########################
@@ -2644,26 +2727,36 @@ class vmmDetails(vmmGObjectUI):
             self.refresh_stats_page()
 
     def page_refresh(self, page):
-        if page != PAGE_DETAILS:
-            return
-
         # This function should only be called when the VM xml actually
         # changes (not everytime it is refreshed). This saves us from blindly
         # parsing the xml every tick
 
-        # Add / remove new devices
-        self.repopulate_hw_list()
+        if page == PAGE_DETAILS:
+            # Add / remove new devices
+            self.repopulate_hw_list()
 
-        pagetype = self.get_hw_selection(HW_LIST_COL_TYPE)
-        if pagetype is None:
-            return
+            pagetype = self.get_hw_selection(HW_LIST_COL_TYPE)
+            if pagetype is None:
+                return
 
-        if self.widget("config-apply").get_sensitive():
-            # Apply button sensitive means user is making changes, don't
-            # erase them
-            return
+            if self.widget("config-apply").get_sensitive():
+                # Apply button sensitive means user is making changes, don't
+                # erase them
+                return
 
-        self.hw_selected(page=pagetype)
+            self.hw_selected(page=pagetype)
+        elif page == PAGE_SNAPSHOTS:
+            error_box = self.widget("error-vm-snapshot-box")
+            is_snapshot_supported, reason = self.vm.supports_snapshot()
+            if is_snapshot_supported:
+                error_box.hide()
+                self._enable_snapshot_buttons(True)
+                self._populate_snapshot_list()
+            else:
+                self.widget("error-vm-snapshot-label").set_label(reason)
+                error_box.show()
+                self._clear_snapshot_list()
+                self._enable_snapshot_buttons(False)
 
     def refresh_overview_page(self):
         # Basic details
@@ -3770,6 +3863,15 @@ class vmmDetails(vmmGObjectUI):
             # Set a default selection
             selection.select_path("0")
 
+    def _populate_snapshot_list(self):
+        snapshots = self.widget("snapshot-liststore")
+        snapshots.clear()
+        for snapshot in self.vm.get_snapshots():
+            snapshots.append([snapshot])
+
+    def _clear_snapshot_list(self):
+        self.widget("snapshot-liststore").clear()
+
     def show_pair(self, basename, show):
         combo = self.widget(basename)
         label = self.widget(basename + "-title")
diff --git a/virtManager/domain.py b/virtManager/domain.py
index de0df8a..81f8c97 100644
--- a/virtManager/domain.py
+++ b/virtManager/domain.py
@@ -29,6 +29,8 @@ import threading
 import libvirt
 import virtinst
 
+from virtinst import VirtualDisk
+
 from virtManager import util
 from virtManager.libvirtobject import vmmLibvirtObject
 
@@ -148,6 +150,7 @@ class vmmDomain(vmmLibvirtObject):
         "status-changed": (GObject.SignalFlags.RUN_FIRST, None, [int, int]),
         "resources-sampled": (GObject.SignalFlags.RUN_FIRST, None, []),
         "inspection-changed": (GObject.SignalFlags.RUN_FIRST, None, []),
+        "snapshot-changed": (GObject.SignalFlags.RUN_FIRST, None, []),
         "pre-startup": (GObject.SignalFlags.RUN_FIRST, None, [object]),
     }
 
@@ -338,6 +341,37 @@ class vmmDomain(vmmLibvirtObject):
             return "-"
         return str(i)
 
+    def supports_snapshot(self):
+        disks = self.get_disk_devices()
+        for disk in disks:
+            if disk.device == VirtualDisk.DEVICE_CDROM:
+                continue
+            if disk.driver_type != "qcow2":
+                return False, _("You cannot take a snapshot of a VM whose "
+                                "disks are not all in QCOW2 format.")
+
+        if self.is_active():
+            return False, _("You cannot take a snapshot of a running VM.")
+
+        return True, None
+
+    def get_snapshots(self):
+        return self._backend.snapshotListNames()
+
+    def create_snapshot(self, name):
+        snapshot_xml = "<domainsnapshot><name>%s</name></domainsnapshot>" % name
+        self._backend.snapshotCreateXML(snapshot_xml, 0)
+        self.emit("snapshot-changed")
+
+    def restore_snapshot(self, name):
+        snapshot = self._backend.snapshotLookupByName(name, 0)
+        self._backend.revertToSnapshot(snapshot, 0)
+
+    def delete_snapshot(self, name):
+        snapshot = self._backend.snapshotLookupByName(name, 0)
+        snapshot.delete(0)
+        self.emit("snapshot-changed")
+
     #############################
     # Internal XML handling API #
     #############################
diff --git a/virtManager/engine.py b/virtManager/engine.py
index 456f597..2215bd2 100644
--- a/virtManager/engine.py
+++ b/virtManager/engine.py
@@ -687,6 +687,9 @@ class vmmEngine(vmmGObject):
         obj.connect("action-migrate-domain", self._do_show_migrate)
         obj.connect("action-delete-domain", self._do_delete_domain)
         obj.connect("action-clone-domain", self._do_show_clone)
+        obj.connect("action-take-snapshot", self._do_take_snapshot)
+        obj.connect("action-restore-snapshot", self._do_restore_snapshot)
+        obj.connect("action-delete-snapshot", self._do_delete_snapshot)
         obj.connect("details-opened", self.increment_window_counter)
         obj.connect("details-closed", self.decrement_window_counter)
 
@@ -1081,3 +1084,33 @@ class vmmEngine(vmmGObject):
         if not self.delete_dialog:
             self.delete_dialog = vmmDeleteDialog()
         self.delete_dialog.show(vm, src.topwin)
+
+    def _do_take_snapshot(self, src, uri, uuid, snapshot_name):
+        conn = self._lookup_conn(uri)
+        vm = conn.get_vm(uuid)
+        def errorcb(error, details):
+            src.err.show_err(_("Error creating snapshot") + ": " + error,
+                             details=details)
+        title = _("Creating VM snapshot")
+        vmmAsyncJob.simple_async(vm.create_snapshot, [snapshot_name], title,
+                                 "", src, "", errorcb=errorcb)
+
+    def _do_restore_snapshot(self, src, uri, uuid, snapshot_name):
+        conn = self._lookup_conn(uri)
+        vm = conn.get_vm(uuid)
+        def errorcb(error, details):
+            src.err.show_err(_("Error restoring snapshot") + ": " + error,
+                             details=details)
+        title = _("Restoring VM snapshot")
+        vmmAsyncJob.simple_async(vm.restore_snapshot, [snapshot_name], title,
+                                 "", src, "", errorcb=errorcb)
+
+    def _do_delete_snapshot(self, src, uri, uuid, snapshot_name):
+        conn = self._lookup_conn(uri)
+        vm = conn.get_vm(uuid)
+        def errorcb(error, details):
+            src.err.show_err(_("Error deleting snapshot") + ": " + error,
+                             details=details)
+        title = _("Deleting VM snapshot")
+        vmmAsyncJob.simple_async(vm.delete_snapshot, [snapshot_name], title,
+                                 "", src, "", errorcb=errorcb)
diff --git a/virtManager/error.py b/virtManager/error.py
index 9cbcd1b..2af5f12 100644
--- a/virtManager/error.py
+++ b/virtManager/error.py
@@ -28,7 +28,7 @@ from virtManager.baseclass import vmmGObject
 
 
 def _launch_dialog(dialog, primary_text, secondary_text, title,
-                   widget=None, sync=True):
+                   widget=None, sync=True, response_cb=None):
     dialog.set_property("text", primary_text)
     dialog.format_secondary_text(secondary_text or None)
     dialog.set_title(title)
@@ -42,9 +42,11 @@ def _launch_dialog(dialog, primary_text, secondary_text, title,
         res = bool(res in [Gtk.ResponseType.YES, Gtk.ResponseType.OK])
         dialog.destroy()
     else:
-        def response_destroy(src, ignore):
-            src.destroy()
-        dialog.connect("response", response_destroy)
+        if not response_cb:
+            def destroy_cb(src, ignore):
+                src.destroy()
+            response_cb = destroy_cb
+        dialog.connect("response", response_cb)
         dialog.show()
 
     return res
@@ -97,8 +99,8 @@ class vmmErrorDialog(vmmGObject):
     # Simple one shot message dialogs #
     ###################################
 
-    def _simple_dialog(self, dialog_type, buttons, text1,
-                       text2, title, widget=None, async=False):
+    def _simple_dialog(self, dialog_type, buttons, text1, text2, title,
+                       widget=None, async=False, response_cb=None):
 
         dialog = Gtk.MessageDialog(self.get_parent(),
                                    flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
@@ -111,7 +113,8 @@ class vmmErrorDialog(vmmGObject):
         return _launch_dialog(self._simple,
                               text1, text2 or "", title or "",
                               widget=widget,
-                              sync=not async)
+                              sync=not async,
+                              response_cb=response_cb)
 
     def val_err(self, text1, text2=None, title=_("Input Error"), async=True):
         logtext = "Validation Error: %s" % text1
@@ -137,6 +140,12 @@ class vmmErrorDialog(vmmGObject):
         self._simple_dialog(dtype, buttons, text1, text2, title, widget, async)
         return False
 
+    def ask_info(self, text1, text2=None, title="", widget=None, response_cb=None):
+        dtype = Gtk.MessageType.INFO
+        buttons = Gtk.ButtonsType.OK_CANCEL
+        self._simple_dialog(dtype, buttons, text1, text2, title, widget,
+                            async=True, response_cb=response_cb)
+
     def yes_no(self, text1, text2=None, title=None):
         dtype = Gtk.MessageType.WARNING
         buttons = Gtk.ButtonsType.YES_NO
-- 
1.7.1




More information about the virt-tools-list mailing list