mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-26 10:03:54 +03:00
Storage aware 'Delete VM' dialog.
This commit is contained in:
parent
f9dfe4acab
commit
c83113e79b
@ -375,6 +375,19 @@ class vmmConnection(gobject.GObject):
|
||||
return pool
|
||||
return None
|
||||
|
||||
def get_pool_by_name(self, name):
|
||||
for p in self.pools.values():
|
||||
if p.get_name() == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
def get_vol_by_path(self, path):
|
||||
for pool in self.pools.values():
|
||||
for vol in pool.get_volumes().values():
|
||||
if vol.get_path() == path:
|
||||
return vol
|
||||
return None
|
||||
|
||||
def open(self):
|
||||
if self.state != self.STATE_DISCONNECTED:
|
||||
return
|
||||
|
384
src/virtManager/delete.py
Normal file
384
src/virtManager/delete.py
Normal file
@ -0,0 +1,384 @@
|
||||
#
|
||||
# Copyright (C) 2009 Red Hat, Inc.
|
||||
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
# MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
import gobject
|
||||
import gtk.glade
|
||||
|
||||
import os, stat
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
import libvirt
|
||||
import virtinst
|
||||
|
||||
from virtManager.error import vmmErrorDialog
|
||||
from virtManager.asyncjob import vmmAsyncJob
|
||||
from virtManager.createmeter import vmmCreateMeter
|
||||
|
||||
STORAGE_ROW_CONFIRM = 0
|
||||
STORAGE_ROW_CANT_DELETE = 1
|
||||
STORAGE_ROW_PATH = 2
|
||||
STORAGE_ROW_TARGET = 3
|
||||
STORAGE_ROW_ICON_SHOW = 4
|
||||
STORAGE_ROW_ICON = 5
|
||||
STORAGE_ROW_ICON_SIZE = 6
|
||||
STORAGE_ROW_TOOLTIP = 7
|
||||
|
||||
class vmmDeleteDialog(gobject.GObject):
|
||||
__gsignals__ = {
|
||||
#"vol-created": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, [])
|
||||
}
|
||||
|
||||
def __init__(self, config, vm):
|
||||
self.__gobject_init__()
|
||||
self.window = gtk.glade.XML(config.get_glade_dir() + \
|
||||
"/vmm-delete.glade",
|
||||
"vmm-delete", domain="virt-manager")
|
||||
self.config = config
|
||||
self.vm = vm
|
||||
self.conn = vm.connection
|
||||
|
||||
self.topwin = self.window.get_widget("vmm-delete")
|
||||
self.err = vmmErrorDialog(self.topwin,
|
||||
0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
|
||||
_("Unexpected Error"),
|
||||
_("An unexpected error occurred"))
|
||||
self.topwin.hide()
|
||||
|
||||
self.window.signal_autoconnect({
|
||||
"on_vmm_delete_delete_event" : self.close,
|
||||
"on_delete_cancel_clicked" : self.close,
|
||||
"on_delete_ok_clicked" : self.finish,
|
||||
"on_delete_remove_storage_toggled" : self.toggle_remove_storage,
|
||||
})
|
||||
|
||||
prepare_storage_list(self.window.get_widget("delete-storage-list"))
|
||||
|
||||
def toggle_remove_storage(self, src):
|
||||
dodel = src.get_active()
|
||||
self.window.get_widget("delete-storage-list").set_sensitive(dodel)
|
||||
|
||||
|
||||
def show(self):
|
||||
self.reset_state()
|
||||
self.topwin.show()
|
||||
self.topwin.present()
|
||||
|
||||
def close(self, ignore1=None, ignore2=None):
|
||||
self.topwin.hide()
|
||||
return 1
|
||||
|
||||
def reset_state(self):
|
||||
|
||||
# Set VM name in title'
|
||||
title_str = ("<span size='x-large'>%s '%s'</span>" %
|
||||
(_("Delete"), self.vm.get_name()))
|
||||
self.window.get_widget("delete-main-label").set_markup(title_str)
|
||||
|
||||
# Disable storage removal by default
|
||||
self.window.get_widget("delete-remove-storage").set_active(False)
|
||||
self.window.get_widget("delete-remove-storage").toggled()
|
||||
|
||||
populate_storage_list(self.window.get_widget("delete-storage-list"),
|
||||
self.vm, self.conn)
|
||||
|
||||
def set_vm(self, vm):
|
||||
self.vm = vm
|
||||
self.conn = vm.connection
|
||||
self.reset_state()
|
||||
|
||||
def get_config_format(self):
|
||||
format_combo = self.window.get_widget("vol-format")
|
||||
model = format_combo.get_model()
|
||||
if format_combo.get_active_iter() != None:
|
||||
model = format_combo.get_model()
|
||||
return model.get_value(format_combo.get_active_iter(), 0)
|
||||
return None
|
||||
|
||||
def get_paths_to_delete(self):
|
||||
del_list = self.window.get_widget("delete-storage-list")
|
||||
model = del_list.get_model()
|
||||
|
||||
paths = []
|
||||
if self.window.get_widget("delete-remove-storage").get_active():
|
||||
for row in model:
|
||||
if (not row[STORAGE_ROW_CANT_DELETE] and
|
||||
row[STORAGE_ROW_CONFIRM]):
|
||||
paths.append(row[STORAGE_ROW_PATH])
|
||||
return paths
|
||||
|
||||
def finish(self, src):
|
||||
devs = self.get_paths_to_delete()
|
||||
|
||||
self.error_msg = None
|
||||
self.error_details = None
|
||||
self.topwin.set_sensitive(False)
|
||||
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
||||
|
||||
title = _("Deleting virtual machine '%s'") % self.vm.get_name()
|
||||
text = title
|
||||
if devs:
|
||||
text = title + _(" and selected storage (this may take a while")
|
||||
|
||||
progWin = vmmAsyncJob(self.config, self._async_delete, [devs],
|
||||
title=title, text=text)
|
||||
progWin.run()
|
||||
|
||||
self.topwin.set_sensitive(True)
|
||||
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
|
||||
self.close()
|
||||
|
||||
if self.error_msg is not None:
|
||||
self.err.show_err(self.error_msg, self.error_details)
|
||||
|
||||
self.conn.tick(noStatsUpdate=True)
|
||||
|
||||
|
||||
def _async_delete(self, paths, asyncjob):
|
||||
newconn = None
|
||||
storage_errors = []
|
||||
|
||||
try:
|
||||
# Open a seperate connection to install on since this is async
|
||||
logging.debug("Threading off connection to delete vol.")
|
||||
#newconn = vmmConnection(self.config, self.conn.get_uri(),
|
||||
# self.conn.is_read_only())
|
||||
#newconn.open()
|
||||
#newconn.connectThreadEvent.wait()
|
||||
newconn = libvirt.open(self.conn.get_uri())
|
||||
meter = vmmCreateMeter(asyncjob)
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
logging.debug("Deleting path: %s" % path)
|
||||
meter.start(text = _("Deleting path '%s'") % path)
|
||||
self._async_delete_path(newconn, path, meter)
|
||||
except Exception, e:
|
||||
storage_errors.append((str(e),
|
||||
"".join(traceback.format_exc())))
|
||||
meter.end(0)
|
||||
|
||||
logging.debug("Removing VM '%s'" % self.vm.get_name())
|
||||
self.vm.delete()
|
||||
|
||||
except Exception, e:
|
||||
self.error_msg = (_("Error deleting virtual machine '%s': %s") %
|
||||
(self.vm.get_name(), str(e)))
|
||||
self.error_details = "".join(traceback.format_exc())
|
||||
logging.error(self.error_msg + "\n" + self.error_details)
|
||||
|
||||
storage_errstr = ""
|
||||
for errinfo in storage_errors:
|
||||
storage_errstr += "%s\n%s\n" % (errinfo[0], errinfo[1])
|
||||
|
||||
if not storage_errstr:
|
||||
return
|
||||
|
||||
# We had extra storage errors. If there was another error message,
|
||||
# errors to it. Otherwise, build the main error around them.
|
||||
if self.error_details:
|
||||
self.error_details += "\n\n"
|
||||
self.error_details += _("Additionally, there were errors removing"
|
||||
" certain storage devices: \n")
|
||||
self.error_details += storage_errstr
|
||||
else:
|
||||
self.error_msg = _("Errors encountered while removing certain "
|
||||
"storage devices.")
|
||||
self.error_details = storage_errstr
|
||||
|
||||
def _async_delete_path(self, conn, path, ignore):
|
||||
vol = None
|
||||
|
||||
try:
|
||||
vol = conn.storageVolLookupByPath(path)
|
||||
except:
|
||||
logging.debug("Path '%s' is not managed. Deleting locally." % path)
|
||||
|
||||
if vol:
|
||||
vol.delete(0)
|
||||
else:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def populate_storage_list(storage_list, vm, conn):
|
||||
model = storage_list.get_model()
|
||||
model.clear()
|
||||
|
||||
for disk in vm.get_disk_devices():
|
||||
vol = None
|
||||
|
||||
target = disk[1]
|
||||
path = disk[3]
|
||||
ro = disk[6]
|
||||
shared = disk[7]
|
||||
|
||||
# There are a few pieces here
|
||||
# 1) Can we even delete the storage? If not, make the checkbox
|
||||
# inconsistent. self.can_delete decides this for us, and if
|
||||
# we can't delete, gives us a nice message to show the user
|
||||
# for that row.
|
||||
#
|
||||
# 2) If we can delete, do we want to delete this storage by
|
||||
# default? Reasons not to, are if the storage is marked
|
||||
# readonly or sharable, or is in use by another VM.
|
||||
|
||||
if not path or path == "-":
|
||||
continue
|
||||
|
||||
default = False
|
||||
definfo = None
|
||||
vol = conn.get_vol_by_path(path)
|
||||
can_del, delinfo = can_delete(conn, vol, path)
|
||||
|
||||
if can_del:
|
||||
default, definfo = do_we_default(conn, vm.get_name(), vol,
|
||||
path, ro, shared)
|
||||
|
||||
info = None
|
||||
if not can_del:
|
||||
info = delinfo
|
||||
elif not default:
|
||||
info = definfo
|
||||
|
||||
icon = gtk.STOCK_DIALOG_WARNING
|
||||
icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
|
||||
|
||||
row = [default, not can_del, path, target,
|
||||
bool(info), icon, icon_size, info]
|
||||
model.append(row)
|
||||
|
||||
|
||||
def prepare_storage_list(storage_list):
|
||||
# Checkbox, deleteable?, storage path, target (hda), icon stock,
|
||||
# icon size, tooltip
|
||||
model = gtk.ListStore(bool, bool, str, str, bool, str, int, str)
|
||||
storage_list.set_model(model)
|
||||
try:
|
||||
storage_list.set_tooltip_column(STORAGE_ROW_TOOLTIP)
|
||||
except:
|
||||
# FIXME: use tooltip wrapper for this
|
||||
pass
|
||||
|
||||
confirmCol = gtk.TreeViewColumn()
|
||||
pathCol = gtk.TreeViewColumn(_("Storage Path"))
|
||||
targetCol = gtk.TreeViewColumn(_("Target"))
|
||||
infoCol = gtk.TreeViewColumn()
|
||||
|
||||
storage_list.append_column(confirmCol)
|
||||
storage_list.append_column(pathCol)
|
||||
storage_list.append_column(targetCol)
|
||||
storage_list.append_column(infoCol)
|
||||
|
||||
chkbox = gtk.CellRendererToggle()
|
||||
chkbox.connect('toggled', storage_item_toggled, storage_list)
|
||||
confirmCol.pack_start(chkbox, False)
|
||||
confirmCol.add_attribute(chkbox, 'active', STORAGE_ROW_CONFIRM)
|
||||
confirmCol.add_attribute(chkbox, 'inconsistent',
|
||||
STORAGE_ROW_CANT_DELETE)
|
||||
confirmCol.set_sort_column_id(STORAGE_ROW_CANT_DELETE)
|
||||
|
||||
path_txt = gtk.CellRendererText()
|
||||
pathCol.pack_start(path_txt, True)
|
||||
pathCol.add_attribute(path_txt, 'text', STORAGE_ROW_PATH)
|
||||
pathCol.set_sort_column_id(STORAGE_ROW_PATH)
|
||||
|
||||
target_txt = gtk.CellRendererText()
|
||||
targetCol.pack_start(target_txt, False)
|
||||
targetCol.add_attribute(target_txt, 'text', STORAGE_ROW_TARGET)
|
||||
targetCol.set_sort_column_id(STORAGE_ROW_TARGET)
|
||||
|
||||
info_img = gtk.CellRendererPixbuf()
|
||||
infoCol.pack_start(info_img, False)
|
||||
infoCol.add_attribute(info_img, 'visible', STORAGE_ROW_ICON_SHOW)
|
||||
infoCol.add_attribute(info_img, 'stock-id', STORAGE_ROW_ICON)
|
||||
infoCol.add_attribute(info_img, 'stock-size', STORAGE_ROW_ICON_SIZE)
|
||||
infoCol.set_sort_column_id(STORAGE_ROW_ICON)
|
||||
|
||||
def storage_item_toggled(src, index, storage_list):
|
||||
active = src.get_active()
|
||||
|
||||
model = storage_list.get_model()
|
||||
model[index][STORAGE_ROW_CONFIRM] = not active
|
||||
|
||||
def can_delete(conn, vol, path):
|
||||
"""Is the passed path even deleteable"""
|
||||
ret = True
|
||||
msg = None
|
||||
|
||||
if vol:
|
||||
# Managed storage
|
||||
if (vol.get_pool().get_type() ==
|
||||
virtinst.Storage.StoragePool.TYPE_ISCSI):
|
||||
msg = _("Cannot delete iscsi share.")
|
||||
else:
|
||||
if conn.is_remote():
|
||||
msg = _("Cannot delete unmanaged remote storage.")
|
||||
elif not os.path.exists(path):
|
||||
msg = _("Path does not exist.")
|
||||
elif not os.access(os.path.dirname(path), os.W_OK):
|
||||
msg = _("No write access to parent directory.")
|
||||
elif stat.S_ISBLK(os.stat(path)[stat.ST_MODE]):
|
||||
msg = _("Cannot delete unmanaged block device.")
|
||||
|
||||
if msg:
|
||||
ret = False
|
||||
|
||||
return (ret, msg)
|
||||
|
||||
def do_we_default(conn, vm_name, vol, path, ro, shared):
|
||||
""" Returns (do we delete by default?, info string if not)"""
|
||||
info = ""
|
||||
|
||||
def append_str(str1, str2, delim="\n"):
|
||||
if not str2:
|
||||
return str1
|
||||
if str1:
|
||||
str1 += delim
|
||||
str1 += str2
|
||||
return str1
|
||||
|
||||
if ro:
|
||||
info = append_str(info, _("Storage is read-only."))
|
||||
elif not vol and not os.access(path, os.W_OK):
|
||||
info = append_str(info, _("No write access to path."))
|
||||
|
||||
if shared:
|
||||
info = append_str(info, _("Storage is marked as shareable."))
|
||||
|
||||
# Check if disk is actually in use by other VMs. For useful err
|
||||
# reporting, we need more info from VirtualDisk is_conflict_disk
|
||||
try:
|
||||
d = virtinst.VirtualDisk(conn=conn.vmm, path=path,
|
||||
readOnly=ro, shareable=shared)
|
||||
names = d.is_conflict_disk(conn.vmm, return_names=True)
|
||||
|
||||
if len(names) > 1:
|
||||
namestr = ""
|
||||
names.remove(vm_name)
|
||||
for name in names:
|
||||
namestr = append_str(namestr, name, delim="\n- ")
|
||||
info = append_str(info, _("Storage is in use by the following "
|
||||
"virtual machines:\n- %s " % namestr))
|
||||
except Exception, e:
|
||||
logging.exception("Failed checking disk conflict: %s" % str(e))
|
||||
|
||||
return (not info, info)
|
||||
|
||||
gobject.type_register(vmmDeleteDialog)
|
@ -22,7 +22,6 @@ import gobject
|
||||
import gtk
|
||||
import gtk.glade
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
import sparkline
|
||||
import libvirt
|
||||
@ -30,6 +29,7 @@ import libvirt
|
||||
from virtManager.connection import vmmConnection
|
||||
from virtManager.asyncjob import vmmAsyncJob
|
||||
from virtManager.error import vmmErrorDialog
|
||||
from virtManager.delete import vmmDeleteDialog
|
||||
from virtManager import util as util
|
||||
|
||||
VMLIST_SORT_ID = 1
|
||||
@ -118,6 +118,9 @@ class vmmManager(gobject.GObject):
|
||||
_("An unexpected error occurred"))
|
||||
self.config = config
|
||||
self.engine = engine
|
||||
|
||||
self.delete_dialog = None
|
||||
|
||||
self.prepare_vmlist()
|
||||
|
||||
self.config.on_vmlist_domain_id_visible_changed(self.toggle_domain_id_visible_widget)
|
||||
@ -796,32 +799,31 @@ class vmmManager(gobject.GObject):
|
||||
conn = self.current_connection()
|
||||
vm = self.current_vm()
|
||||
if vm is None:
|
||||
# Delete the connection handle
|
||||
if conn is None:
|
||||
return
|
||||
|
||||
result = self.err.yes_no(_("Are you sure you want to permanently delete the connection %s?") % self.rows[conn.get_uri()][ROW_NAME])
|
||||
if not result:
|
||||
return
|
||||
self.engine.remove_connection(conn.get_uri())
|
||||
self._do_delete_connection(conn)
|
||||
else:
|
||||
# Delete the VM itself
|
||||
self._do_delete_vm(vm)
|
||||
|
||||
if vm.is_active():
|
||||
return
|
||||
def _do_delete_connection(self, conn):
|
||||
if conn is None:
|
||||
return
|
||||
|
||||
# are you sure you want to delete this VM?
|
||||
result = self.err.yes_no(_("Are you sure you want to permanently delete the virtual machine %s?") % vm.get_name())
|
||||
if not result:
|
||||
return
|
||||
conn = vm.get_connection()
|
||||
try:
|
||||
vm.delete()
|
||||
except Exception, e:
|
||||
self.err.show_err(_("Error deleting domain: %s" % str(e)),\
|
||||
"".join(traceback.format_exc()))
|
||||
return
|
||||
conn.tick(noStatsUpdate=True)
|
||||
result = self.err.yes_no(_("This will remove the connection \"%s\","
|
||||
"are you sure?") %
|
||||
self.rows[conn.get_uri()][ROW_NAME])
|
||||
if not result:
|
||||
return
|
||||
self.engine.remove_connection(conn.get_uri())
|
||||
|
||||
def _do_delete_vm(self, vm):
|
||||
if vm.is_active():
|
||||
return
|
||||
|
||||
if not self.delete_dialog:
|
||||
self.delete_dialog = vmmDeleteDialog(self.config, vm)
|
||||
else:
|
||||
self.delete_dialog.set_vm(vm)
|
||||
|
||||
self.delete_dialog.show()
|
||||
|
||||
def show_about(self, src):
|
||||
self.emit("action-show-about")
|
||||
|
@ -42,6 +42,10 @@ class vmmStorageVolume(gobject.GObject):
|
||||
def get_path(self):
|
||||
return self.vol.path()
|
||||
|
||||
def get_pool(self):
|
||||
pobj = self.vol.storagePoolLookupByVolume()
|
||||
return self.connection.get_pool_by_name(pobj.name())
|
||||
|
||||
def delete(self):
|
||||
self.vol.delete(0)
|
||||
del(self.vol)
|
||||
|
151
src/vmm-delete.glade
Normal file
151
src/vmm-delete.glade
Normal file
@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Wed Feb 25 17:45:14 2009 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkDialog" id="vmm-delete">
|
||||
<property name="width_request">500</property>
|
||||
<property name="height_request">300</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="title" translatable="yes">Delete Confirmation</property>
|
||||
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||
<property name="has_separator">False</property>
|
||||
<signal name="delete_event" handler="on_vmm_delete_delete_event"/>
|
||||
<child internal-child="vbox">
|
||||
<widget class="GtkVBox" id="dialog-vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">6</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">3</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox6">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">3</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0.059999998658895493</property>
|
||||
<property name="yalign">0</property>
|
||||
<property name="stock">gtk-delete</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="delete-main-label">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
<property name="label"><span size='x-large'>Delete 'foo'</span></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="delete-remove-storage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Delete associated storage files</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_delete_remove_storage_toggled"/>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="delete-storage-list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_clickable">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="action_area">
|
||||
<widget class="GtkHButtonBox" id="dialog-action_area1">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="delete-ok">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-delete</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_delete_ok_clicked"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="delete-cancel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_delete_cancel_clicked"/>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
Loading…
x
Reference in New Issue
Block a user