[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