def _populate_snapshot_list(self, select_name=None): cursnaps = [] for i in self._get_selected_snapshots(): cursnaps.append(i.get_name()) model = self.widget("snapshot-list").get_model() model.clear() try: snapshots = self.vm.list_snapshots() except Exception as e: logging.exception(e) self._set_error_page( _("Error refreshing snapshot list: %s") % str(e)) return has_external = False has_internal = False for snap in snapshots: desc = snap.get_xmlobj().description name = snap.get_name() state = snap.run_status() if snap.is_external(): has_external = True sortname = "3%s" % name external = " (%s)" % _("External") else: has_internal = True external = "" sortname = "1%s" % name label = "%s\n<span size='small'>%s: %s%s</span>" % ( (util.xml_escape(name), _("VM State"), util.xml_escape(state), external)) model.append([ name, label, desc, snap.run_status_icon_name(), sortname, snap.is_current() ]) if has_internal and has_external: model.append([None, None, None, None, "2", False]) def check_selection(treemodel, path, it, snaps): if select_name: if treemodel[it][0] == select_name: selection.select_path(path) elif treemodel[it][0] in snaps: selection.select_path(path) selection = self.widget("snapshot-list").get_selection() model = self.widget("snapshot-list").get_model() selection.unselect_all() model.foreach(check_selection, cursnaps) self._initial_populate = True
def _populate_snapshot_list(self, select_name=None): cursnaps = [] for i in self._get_selected_snapshots(): cursnaps.append(i.get_name()) model = self.widget("snapshot-list").get_model() model.clear() try: snapshots = self.vm.list_snapshots() except Exception as e: logging.exception(e) self._set_error_page(_("Error refreshing snapshot list: %s") % str(e)) return has_external = False has_internal = False for snap in snapshots: desc = snap.get_xmlobj().description name = snap.get_name() state = snap.run_status() if snap.is_external(): has_external = True sortname = "3%s" % name external = " (%s)" % _("External") else: has_internal = True external = "" sortname = "1%s" % name label = "%s\n<span size='small'>%s: %s%s</span>" % ( (util.xml_escape(name), _("VM State"), util.xml_escape(state), external)) model.append([name, label, desc, snap.run_status_icon_name(), sortname, snap.is_current()]) if has_internal and has_external: model.append([None, None, None, None, "2", False]) def check_selection(treemodel, path, it, snaps): if select_name: if treemodel[it][0] == select_name: selection.select_path(path) elif treemodel[it][0] in snaps: selection.select_path(path) selection = self.widget("snapshot-list").get_selection() model = self.widget("snapshot-list").get_model() selection.unselect_all() model.foreach(check_selection, cursnaps) self._initial_populate = True
def _get_osblob_helper(self, guest, isinstall, bootconfig): conn = guest.conn arch = self.arch machine = self.machine hvtype = self.type loader = self.loader os_type = self.os_type init = self.init or self._get_default_init(guest) hvxen = (hvtype == "xen") if not loader and self.is_hvm() and hvxen: loader = "/usr/lib/xen/boot/hvmloader" # Use older libvirt 'linux' value for back compat if os_type == "xen" and hvxen: os_type = "linux" if (not isinstall and self.is_xenpv() and not self.bootconfig.kernel): return "<bootloader>%s</bootloader>" % _pygrub_path(conn) osblob = "<os>" typexml = " <type" if arch: typexml += " arch='%s'" % arch if machine: typexml += " machine='%s'" % machine typexml += ">%s</type>" % os_type osblob = util.xml_append(osblob, typexml) if init: osblob = util.xml_append(osblob, " <init>%s</init>" % util.xml_escape(init)) if loader: osblob = util.xml_append(osblob, " <loader>%s</loader>" % util.xml_escape(loader)) if not self.is_container(): osblob = util.xml_append(osblob, bootconfig.get_xml_config()) osblob = util.xml_append(osblob, " </os>") return osblob
def reset_state(self): title_str = ("<span size='large' color='white'>%s '%s'</span>" % (_("Migrate"), util.xml_escape(self.vm.get_name()))) self.widget("header-label").set_markup(title_str) self.widget("migrate-cancel").grab_focus() name = self.vm.get_name() srchost = self.conn.get_hostname() self.widget("migrate-label-name").set_text(name) self.widget("migrate-label-src").set_text(srchost) self.widget("migrate-set-interface").set_active(False) self.widget("migrate-set-rate").set_active(False) self.widget("migrate-set-port").set_active(False) self.widget("migrate-set-maxdowntime").set_active(False) self.widget("migrate-max-downtime").set_value(30) self.widget("migrate-rate").set_value(0) self.widget("migrate-secure").set_active(False) self.widget("migrate-unsafe").set_active(False) downtime_box = self.widget("migrate-maxdowntime-box") support_downtime = self.vm.support_downtime() downtime_tooltip = "" if not support_downtime: downtime_tooltip = _("Libvirt version does not support setting " "downtime.") downtime_box.set_sensitive(support_downtime) downtime_box.set_tooltip_text(downtime_tooltip) if self.conn.is_xen(): # Default xen port is 8002 self.widget("migrate-port").set_value(8002) else: # QEMU migrate port range is 49152+64 self.widget("migrate-port").set_value(49152) secure_box = self.widget("migrate-secure-box") support_secure = hasattr(libvirt, "VIR_MIGRATE_TUNNELLED") secure_tooltip = "" if not support_secure: secure_tooltip = _("Libvirt version does not support tunnelled " "migration.") secure_box.set_sensitive(support_secure) secure_box.set_tooltip_text(secure_tooltip) unsafe_box = self.widget("migrate-unsafe-box") support_unsafe = hasattr(libvirt, "VIR_MIGRATE_UNSAFE") unsafe_tooltip = "" if not support_unsafe: unsafe_tooltip = _("Libvirt version does not support unsafe " "migration.") unsafe_box.set_sensitive(support_unsafe) unsafe_box.set_tooltip_text(unsafe_tooltip) self.rebuild_dest_rows()
def _build_row(self, conn, vm): if conn: name = conn.get_pretty_desc() markup = self._build_conn_markup(conn, name) status = ("<span size='smaller'>%s</span>" % conn.get_state_text()) status_icon = None hint = self._build_conn_hint(conn) color = self._build_conn_color(conn) os_icon = None else: name = vm.get_name_or_title() status = vm.run_status() markup = self._build_vm_markup(name, status) status_icon = vm.run_status_icon_name() hint = vm.get_description() color = None os_icon = _get_inspection_icon_pixbuf(vm, 16, 16) row = [] row.insert(ROW_HANDLE, conn or vm) row.insert(ROW_SORT_KEY, name) row.insert(ROW_MARKUP, markup) row.insert(ROW_STATUS_ICON, status_icon) row.insert(ROW_HINT, util.xml_escape(hint)) row.insert(ROW_IS_CONN, bool(conn)) row.insert(ROW_IS_CONN_CONNECTED, bool(conn) and not conn.is_disconnected()) row.insert(ROW_IS_VM, bool(vm)) row.insert(ROW_IS_VM_RUNNING, bool(vm) and vm.is_active()) row.insert(ROW_COLOR, color) row.insert(ROW_INSPECTION_OS_ICON, os_icon) return row
def vm_changed(self, vm): row = self.rows.get(self.vm_row_key(vm), None) if row is None: return try: if vm == self.current_vm(): self.update_current_selection() name = vm.get_name_or_title() status = vm.run_status() row[ROW_SORT_KEY] = name row[ROW_STATUS_ICON] = vm.run_status_icon_name() row[ROW_IS_VM_RUNNING] = vm.is_active() row[ROW_MARKUP] = self._build_vm_markup(name, status) desc = vm.get_description() if not uiutil.can_set_row_none: desc = desc or "" row[ROW_HINT] = util.xml_escape(desc) except libvirt.libvirtError, e: if util.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): return raise
def vm_changed(self, vm): row = self.rows.get(self.vm_row_key(vm), None) if row is None: return try: if vm == self.current_vm(): self.update_current_selection() name = vm.get_name_or_title() status = vm.run_status() row[ROW_SORT_KEY] = name row[ROW_STATUS_ICON] = vm.run_status_icon_name() row[ROW_IS_VM_RUNNING] = vm.is_active() row[ROW_MARKUP] = self._build_vm_markup(name, status) desc = vm.get_description() row[ROW_HINT] = util.xml_escape(desc) except libvirt.libvirtError as e: if util.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): return raise self.vm_row_updated(vm)
def _build_conn_menuitem(self, conn): menu_item = Gtk.MenuItem.new_with_label(conn.get_pretty_desc()) if conn.is_active(): label = menu_item.get_child() markup = "<b>%s</b>" % util.xml_escape(conn.get_pretty_desc()) label.set_markup(markup) menu = Gtk.Menu() vms = conn.list_vms() vms.sort(key=lambda v: v.get_name_or_title()) for vm in vms: menu.add(self._build_vm_menuitem(vm)) if not vms: vmitem = Gtk.MenuItem.new_with_label(_("No virtual machines")) vmitem.set_sensitive(False) menu.add(vmitem) menu.add(Gtk.SeparatorMenuItem()) if conn.is_active(): citem = Gtk.ImageMenuItem.new_from_stock( Gtk.STOCK_DISCONNECT, None) citem.connect("activate", _conn_disconnect_cb, conn.get_uri()) else: citem = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_CONNECT, None) citem.connect("activate", _conn_connect_cb, conn.get_uri()) menu.add(citem) menu_item.set_submenu(menu) return menu_item
def _set_xml(self, xmlbuilder, setval, root_node=None): """ Actually set the passed value in the XML document """ if root_node is None: root_node = xmlbuilder._xmlstate.xml_node ctx = xmlbuilder._xmlstate.xml_ctx else: ctx = _make_xml_context(root_node) xpath = self._make_xpath(xmlbuilder) node = _get_xpath_node(xmlbuilder._xmlstate.xml_ctx, xpath) clearlist = self._build_clear_list(xmlbuilder, node) node_map = [] if clearlist: node_map += _tuplify_lists(clearlist, None, [n.nodePath() for n in clearlist]) node_map += [(node, setval, xpath)] for node, val, use_xpath in node_map: if val is None or val is False: _remove_xpath_node(ctx, use_xpath) continue if not node: node = _build_xpath_node(root_node, use_xpath) if val is True: # Boolean property, creating the node is enough continue node.setContent(util.xml_escape(str(val)))
def _reset_state(self): title_str = ("<span size='large' color='white'>%s '%s'</span>" % (_("Migrate"), util.xml_escape(self.vm.get_name()))) self.widget("header-label").set_markup(title_str) self.widget("migrate-advanced-expander").set_expanded(False) self.widget("migrate-cancel").grab_focus() self.widget("config-box").set_visible(True) hostname = self.conn.libvirt_gethostname() srctext = "%s (%s)" % (hostname, self.conn.get_pretty_desc()) self.widget("migrate-label-name").set_text(self.vm.get_name_or_title()) self.widget("migrate-label-src").set_text(srctext) self.widget("migrate-label-src").set_tooltip_text(self.conn.get_uri()) self.widget("migrate-set-address").set_active(True) self.widget("migrate-set-address").emit("toggled") self.widget("migrate-set-port").set_active(True) self.widget("migrate-mode").set_active(0) self.widget("migrate-unsafe").set_active(False) self.widget("migrate-temporary").set_active(False) if self.conn.is_xen(): # Default xen port is 8002 self.widget("migrate-port").set_value(8002) else: # QEMU migrate port range is 49152+64 self.widget("migrate-port").set_value(49152) self._populate_destconn()
def _set_snapshot_state(self, snap=None): self.widget("snapshot-notebook").set_current_page(0) name = snap and snap.get_name() or "" desc = snap and snap.xml.description or "" state = snap and snap.xml.state or "shutoff" timestamp = "" if snap: timestamp = str(datetime.datetime.fromtimestamp( snap.xml.creationTime)) current = "" if snap and snap.is_current(): current = " (current)" title = "" if name: title = "<b>Snapshot '%s'%s:</b>" % (util.xml_escape(name), current) self.widget("snapshot-title").set_markup(title) self.widget("snapshot-timestamp").set_text(timestamp) self.widget("snapshot-description").get_buffer().set_text(desc) self.widget("snapshot-status-text").set_text(state) self.widget("snapshot-status-icon").set_from_icon_name( _snapshot_state_icon_name(state), Gtk.IconSize.MENU) self.widget("snapshot-add").set_sensitive(True) self.widget("snapshot-delete").set_sensitive(bool(snap)) self.widget("snapshot-start").set_sensitive(bool(snap)) self.widget("snapshot-apply").set_sensitive(False)
def _char_file_xml(self): """ Provide source xml for devs that require only a path (dev, pipe) """ file_xml = "" mode_xml = "" if self.source_path: file_xml = " path='%s'" % xml_escape(self.source_path) else: raise ValueError(_("A source path is required for character " "device type '%s'" % self.char_type)) if self.supports_property("source_mode") and self.source_mode: mode_xml = " mode='%s'" % xml_escape(self.source_mode) xml = " <source%s%s/>\n" % (mode_xml, file_xml) return xml
def _get_osblob_helper(self, guest, isinstall, bootconfig): conn = guest.conn arch = self.arch machine = self.machine hvtype = self.type loader = self.loader os_type = self.os_type init = self.init or self._get_default_init(guest) hvxen = (hvtype == "xen") if not loader and self.is_hvm() and hvxen: loader = "/usr/lib/xen/boot/hvmloader" # Use older libvirt 'linux' value for back compat if os_type == "xen" and hvxen: os_type = "linux" if (not isinstall and self.is_xenpv() and not self.bootconfig.kernel): return "<bootloader>%s</bootloader>" % _pygrub_path(conn) osblob = "<os>" typexml = " <type" if arch: typexml += " arch='%s'" % arch if machine: typexml += " machine='%s'" % machine typexml += ">%s</type>" % os_type osblob = util.xml_append(osblob, typexml) if init: osblob = util.xml_append( osblob, " <init>%s</init>" % util.xml_escape(init)) if loader: osblob = util.xml_append( osblob, " <loader>%s</loader>" % util.xml_escape(loader)) if not self.is_container(): osblob = util.xml_append(osblob, bootconfig.get_xml_config()) osblob = util.xml_append(osblob, " </os>") return osblob
def _build_conn_markup(self, conn, name): name = util.xml_escape(name) text = name if conn.state == conn.STATE_DISCONNECTED: text += " - " + _("Not Connected") elif conn.state == conn.STATE_CONNECTING: text += " - " + _("Connecting...") markup = "<span size='smaller'>%s</span>" % text return markup
def _build_conn_markup(self, conn, name): name = util.xml_escape(name) text = name if conn.is_disconnected(): text += " - " + _("Not Connected") elif conn.is_connecting(): text += " - " + _("Connecting...") markup = "<span size='smaller'>%s</span>" % text return markup
def _set_snapshot_state(self, snap=None): self.widget("snapshot-notebook").set_current_page(0) xmlobj = snap and snap.get_xmlobj() or None name = snap and xmlobj.name or "" desc = snap and xmlobj.description or "" state = snap and snap.run_status() or "" icon = snap and snap.run_status_icon_name() or None is_external = snap and snap.is_external() or False is_current = snap and snap.is_current() or False timestamp = "" if snap: timestamp = str(datetime.datetime.fromtimestamp( xmlobj.creationTime)) title = "" if name: title = "<b>Snapshot '%s':</b>" % util.xml_escape(name) uiutil.set_grid_row_visible( self.widget("snapshot-is-current"), is_current) self.widget("snapshot-title").set_markup(title) self.widget("snapshot-timestamp").set_text(timestamp) self.widget("snapshot-description").get_buffer().set_text(desc) self.widget("snapshot-status-text").set_text(state) if icon: self.widget("snapshot-status-icon").set_from_icon_name( icon, Gtk.IconSize.BUTTON) uiutil.set_grid_row_visible(self.widget("snapshot-mode"), is_external) if is_external: is_mem = xmlobj.memory_type == "external" is_disk = [d.snapshot == "external" for d in xmlobj.disks] if is_mem and is_disk: mode = _("External disk and memory") elif is_mem: mode = _("External memory only") else: mode = _("External disk only") self.widget("snapshot-mode").set_text(mode) sn = self._read_screenshot_file(name) self.widget("snapshot-screenshot").set_visible(bool(sn)) self.widget("snapshot-screenshot-label").set_visible(not bool(sn)) if sn: self.widget("snapshot-screenshot").set_from_pixbuf(sn) self.widget("snapshot-add").set_sensitive(True) self.widget("snapshot-delete").set_sensitive(bool(snap)) self.widget("snapshot-start").set_sensitive(bool(snap)) self.widget("snapshot-apply").set_sensitive(False) self._unapplied_changes = False
def _get_xml_config(self): xml = "" if self.kernel: xml = util.xml_append(xml, " <kernel>%s</kernel>" % util.xml_escape(self.kernel)) if self.initrd: xml = util.xml_append(xml, " <initrd>%s</initrd>" % util.xml_escape(self.initrd)) if self.kernel_args: xml = util.xml_append(xml, " <cmdline>%s</cmdline>" % util.xml_escape(self.kernel_args)) else: for dev in self.bootorder: xml = util.xml_append(xml, " <boot dev='%s'/>" % dev) if self.enable_bootmenu in [True, False]: val = self.enable_bootmenu and "yes" or "no" xml = util.xml_append(xml, " <bootmenu enable='%s'/>" % val) return xml
def _get_xml_config(self): xml = "" if self.kernel: xml = util.xml_append( xml, " <kernel>%s</kernel>" % util.xml_escape(self.kernel)) if self.initrd: xml = util.xml_append( xml, " <initrd>%s</initrd>" % util.xml_escape(self.initrd)) if self.kernel_args: xml = util.xml_append( xml, " <cmdline>%s</cmdline>" % util.xml_escape(self.kernel_args)) else: for dev in self.bootorder: xml = util.xml_append(xml, " <boot dev='%s'/>" % dev) if self.enable_bootmenu in [True, False]: val = self.enable_bootmenu and "yes" or "no" xml = util.xml_append(xml, " <bootmenu enable='%s'/>" % val) return xml
def new_setter(self, val, *args, **kwargs): # Do this regardless, for validation purposes fset(self, val, *args, **kwargs) if not self._xml_node: return # Convert from API value to XML value val = fget(self) if set_converter: val = set_converter(self, val) elif default_converter and val == "default": val = default_converter(self) nodexpath = xpath if xml_set_xpath: nodexpath = xml_set_xpath(self) if nodexpath is None: return nodes = util.listify(_get_xpath_node(self._xml_ctx, nodexpath, is_multi)) xpath_list = nodexpath if xml_set_list: xpath_list = xml_set_list(self) node_map = _tuplify_lists(nodes, val, xpath_list) for node, val, usexpath in node_map: if node: usexpath = node.nodePath() if val not in [None, False]: if not node: node = _build_xpath_node(self._xml_node, usexpath) if val is True: # Boolean property, creating the node is enough pass else: node.setContent(util.xml_escape(str(val))) else: _remove_xpath_node(self._xml_node, usexpath)
def new_setter(self, val, *args, **kwargs): # Do this regardless, for validation purposes fset(self, val, *args, **kwargs) if not self._xml_node: return # Convert from API value to XML value val = fget(self) if set_converter: val = set_converter(self, val) elif default_converter and val == "default": val = default_converter(self) nodexpath = xpath if xml_set_xpath: nodexpath = xml_set_xpath(self) if nodexpath is None: return nodes = util.listify( _get_xpath_node(self._xml_ctx, nodexpath, is_multi)) xpath_list = nodexpath if xml_set_list: xpath_list = xml_set_list(self) node_map = _tuplify_lists(nodes, val, xpath_list) for node, val, usexpath in node_map: if node: usexpath = node.nodePath() if val not in [None, False]: if not node: node = _build_xpath_node(self._xml_node, usexpath) if val is True: # Boolean property, creating the node is enough pass else: node.setContent(util.xml_escape(str(val))) else: _remove_xpath_node(self._xml_node, usexpath)
def reset_state(self): # Set VM name in title' title_str = ("<span size='large' color='white'>%s '%s'</span>" % (_("Delete"), util.xml_escape(self.vm.get_name()))) self.widget("header-label").set_markup(title_str) self.widget("delete-cancel").grab_focus() # Show warning message if VM is running vm_active = self.vm.is_active() uiutil.set_grid_row_visible( self.widget("delete-warn-running-vm-box"), vm_active) # Disable storage removal by default self.widget("delete-remove-storage").set_active(True) self.widget("delete-remove-storage").toggled() populate_storage_list(self.widget("delete-storage-list"), self.vm, self.conn)
def reset_state(self): # Set VM name in title' title_str = ("<span size='large' color='white'>%s '%s'</span>" % (_("Delete"), util.xml_escape(self.vm.get_name()))) self.widget("header-label").set_markup(title_str) self.widget("delete-cancel").grab_focus() # Show warning message if VM is running vm_active = self.vm.is_active() uiutil.set_grid_row_visible( self.widget("delete-warn-running-vm-box"), vm_active) # Enable storage removal by default self.widget("delete-remove-storage").set_active(True) self.widget("delete-remove-storage").toggled() populate_storage_list(self.widget("delete-storage-list"), self.vm, self.conn)
def vm_config_changed(self, vm): row = self.rows.get(self.vm_row_key(vm), None) if row is None: return try: name = vm.get_name_or_title() status = vm.run_status() row[ROW_SORT_KEY] = name row[ROW_STATUS_ICON] = vm.run_status_icon_name() row[ROW_IS_VM_RUNNING] = vm.is_active() row[ROW_MARKUP] = self._build_vm_markup(name, status) desc = vm.get_description() if not uiutil.can_set_row_none: desc = desc or "" row[ROW_HINT] = util.xml_escape(desc) except libvirt.libvirtError, e: if util.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): return raise
def _build_vm_markup(self, name, status): domtext = ("<span size='smaller' weight='bold'>%s</span>" % util.xml_escape(name)) statetext = "<span size='smaller'>%s</span>" % status return domtext + "\n" + statetext
class vmmSnapshotPage(vmmGObjectUI): def __init__(self, vm, builder, topwin): vmmGObjectUI.__init__(self, "snapshots.ui", None, builder=builder, topwin=topwin) self.vm = vm self._initial_populate = False self._unapplied_changes = False self._snapmenu = None self._init_ui() self._snapshot_new = self.widget("snapshot-new") self._snapshot_new.set_transient_for(self.topwin) self.bind_escape_key_close_helper(self._snapshot_new, self._snapshot_new_close) self.builder.connect_signals({ "on_snapshot_add_clicked": self._on_add_clicked, "on_snapshot_delete_clicked": self._on_delete_clicked, "on_snapshot_start_clicked": self._on_start_clicked, "on_snapshot_apply_clicked": self._on_apply_clicked, "on_snapshot_list_changed": self._snapshot_selected, "on_snapshot_list_button_press_event": self._popup_snapshot_menu, "on_snapshot_refresh_clicked": self._refresh_snapshots, "on_snapshot_list_row_activated": self._on_start_clicked, # 'Create' dialog "on_snapshot_new_delete_event": self._snapshot_new_close, "on_snapshot_new_ok_clicked": self._on_new_ok_clicked, "on_snapshot_new_cancel_clicked": self._snapshot_new_close, "on_snapshot_new_name_changed": self._snapshot_new_name_changed, "on_snapshot_new_name_activate": self._on_new_ok_clicked, }) self.top_box = self.widget("snapshot-top-box") self.widget("snapshot-top-window").remove(self.top_box) selection = self.widget("snapshot-list").get_selection() selection.emit("changed") selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.set_select_function(self._confirm_changes, None) ############## # Init stuff # ############## def _cleanup(self): self.vm = None self._snapshot_new.destroy() self._snapshot_new = None def _init_ui(self): # pylint: disable=redefined-variable-type blue = Gdk.color_parse("#0072A8") self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue) self.widget("snapshot-notebook").set_show_tabs(False) buf = Gtk.TextBuffer() buf.connect("changed", self._description_changed) self.widget("snapshot-description").set_buffer(buf) buf = Gtk.TextBuffer() self.widget("snapshot-new-description").set_buffer(buf) # [name, row label, tooltip, icon name, sortname, current] model = Gtk.ListStore(str, str, str, str, str, bool) model.set_sort_column_id(4, Gtk.SortType.ASCENDING) col = Gtk.TreeViewColumn("") col.set_min_width(150) col.set_spacing(6) img = Gtk.CellRendererPixbuf() img.set_property("stock-size", Gtk.IconSize.LARGE_TOOLBAR) col.pack_start(img, False) col.add_attribute(img, 'icon-name', 3) txt = Gtk.CellRendererText() txt.set_property("ellipsize", Pango.EllipsizeMode.END) col.pack_start(txt, False) col.add_attribute(txt, 'markup', 1) img = Gtk.CellRendererPixbuf() img.set_property("stock-size", Gtk.IconSize.MENU) img.set_property("icon-name", Gtk.STOCK_APPLY) img.set_property("xalign", 0.0) col.pack_start(img, False) col.add_attribute(img, "visible", 5) def _sep_cb(_model, _iter, ignore): return not bool(_model[_iter][0]) slist = self.widget("snapshot-list") slist.set_model(model) slist.set_tooltip_column(2) slist.append_column(col) slist.set_row_separator_func(_sep_cb, None) # Snapshot popup menu menu = Gtk.Menu() item = Gtk.ImageMenuItem.new_with_label(_("_Start snapshot")) item.set_use_underline(True) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_MEDIA_PLAY, Gtk.IconSize.MENU) item.set_image(img) item.show() item.connect("activate", self._on_start_clicked) menu.add(item) item = Gtk.ImageMenuItem.new_with_label(_("_Delete snapshot")) item.set_use_underline(True) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.MENU) item.set_image(img) item.show() item.connect("activate", self._on_delete_clicked) menu.add(item) self._snapmenu = menu ################### # Functional bits # ################### def _get_selected_snapshots(self): selection = self.widget("snapshot-list").get_selection() def add_snap(treemodel, path, it, snaps): ignore = path try: name = treemodel[it][0] for snap in self.vm.list_snapshots(): if name == snap.get_name(): snaps.append(snap) except: pass snaps = [] selection.selected_foreach(add_snap, snaps) return snaps def _refresh_snapshots(self, select_name=None): self.vm.refresh_snapshots() self._populate_snapshot_list(select_name) def show_page(self): if not self._initial_populate: self._populate_snapshot_list() def _set_error_page(self, msg): self._set_snapshot_state(None) self.widget("snapshot-notebook").set_current_page(1) self.widget("snapshot-error-label").set_text(msg) def _populate_snapshot_list(self, select_name=None): cursnaps = [] for i in self._get_selected_snapshots(): cursnaps.append(i.get_name()) model = self.widget("snapshot-list").get_model() model.clear() try: snapshots = self.vm.list_snapshots() except Exception, e: logging.exception(e) self._set_error_page( _("Error refreshing snapshot list: %s") % str(e)) return has_external = False has_internal = False for snap in snapshots: desc = snap.get_xmlobj().description name = snap.get_name() state = util.xml_escape(snap.run_status()) if snap.is_external(): has_external = True sortname = "3%s" % name external = " (%s)" % _("External") else: has_internal = True external = "" sortname = "1%s" % name label = "%s\n<span size='small'>%s: %s%s</span>" % ( (name, _("VM State"), state, external)) model.append([ name, label, desc, snap.run_status_icon_name(), sortname, snap.is_current() ]) if has_internal and has_external: model.append([None, None, None, None, "2", False]) def check_selection(treemodel, path, it, snaps): if select_name: if treemodel[it][0] == select_name: selection.select_path(path) elif treemodel[it][0] in snaps: selection.select_path(path) selection = self.widget("snapshot-list").get_selection() model = self.widget("snapshot-list").get_model() selection.unselect_all() model.foreach(check_selection, cursnaps) self._initial_populate = True