Pull add hardware functionality out into separate popup window to simplify state management in details window. Implemenet adding of disks

This commit is contained in:
Daniel P. Berrange 2007-04-12 15:36:04 -04:00
parent b75b7eb57f
commit d263695fa4
6 changed files with 2917 additions and 1657 deletions

View File

@ -0,0 +1,570 @@
#
# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2006 Hugh O. Brock <hbrock@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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import gobject
import gtk
import gtk.gdk
import gtk.glade
import pango
import libvirt
import virtinst
import os, sys
import re
import subprocess
import urlgrabber.progress as progress
import tempfile
import logging
import dbus
import traceback
from virtManager.asyncjob import vmmAsyncJob
from virtManager.error import vmmErrorDialog
VM_STORAGE_PARTITION = 1
VM_STORAGE_FILE = 2
DEFAULT_STORAGE_FILE_SIZE = 500
PAGE_INTRO = 0
PAGE_DISK = 1
PAGE_NETWORK = 2
PAGE_SUMMARY = 3
class vmmCreateMeter(progress.BaseMeter):
def __init__(self, asyncjob):
# progress meter has to run asynchronously, so pass in the
# async job to call back to with progress info
progress.BaseMeter.__init__(self)
self.asyncjob = asyncjob
def _do_start(self, now):
if self.text is not None:
text = self.text
else:
text = self.basename
if self.size is None:
out = " %5sB" % (0)
self.asyncjob.pulse_pbar(out, text)
else:
out = "%3i%% %5sB" % (0, 0)
self.asyncjob.set_pbar_fraction(0, out, text)
def _do_update(self, amount_read, now=None):
if self.text is not None:
text = self.text
else:
text = self.basename
fread = progress.format_number(amount_read)
if self.size is None:
out = " %5sB" % (fread)
self.asyncjob.pulse_pbar(out, text)
else:
frac = self.re.fraction_read()
out = "%3i%% %5sB" % (frac*100, fread)
self.asyncjob.set_pbar_fraction(frac, out, text)
def _do_end(self, amount_read, now=None):
if self.text is not None:
text = self.text
else:
text = self.basename
fread = progress.format_number(amount_read)
if self.size is None:
out = " %5sB" % (fread)
self.asyncjob.pulse_pbar(out, text)
else:
out = "%3i%% %5sB" % (100, fread)
self.asyncjob.set_pbar_done(out, text)
class vmmAddHardware(gobject.GObject):
__gsignals__ = {
"action-show-help": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, [str]),
}
def __init__(self, config, vm):
self.__gobject_init__()
self.config = config
self.vm = vm
self.window = gtk.glade.XML(config.get_glade_dir() + "/vmm-add-hardware.glade", "vmm-add-hardware", domain="virt-manager")
self.topwin = self.window.get_widget("vmm-add-hardware")
self.topwin.hide()
self.window.signal_autoconnect({
"on_create_pages_switch_page" : self.page_changed,
"on_create_cancel_clicked" : self.close,
"on_vmm_create_delete_event" : self.close,
"on_create_back_clicked" : self.back,
"on_create_forward_clicked" : self.forward,
"on_create_finish_clicked" : self.finish,
"on_storage_partition_address_browse_clicked" : self.browse_storage_partition_address,
"on_storage_file_address_browse_clicked" : self.browse_storage_file_address,
"on_storage_file_address_changed": self.toggle_storage_size,
"on_storage_toggled" : self.change_storage_type,
"on_network_toggled" : self.change_network_type,
"on_create_help_clicked": self.show_help,
})
hw_list = self.window.get_widget("hardware-type")
model = gtk.ListStore(str, gtk.gdk.Pixbuf, int)
hw_list.set_model(model)
icon = gtk.CellRendererPixbuf()
hw_list.pack_start(icon, False)
hw_list.add_attribute(icon, 'pixbuf', 1)
text = gtk.CellRendererText()
hw_list.pack_start(text, True)
hw_list.add_attribute(text, 'text', 0)
pixbuf_disk = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_hdd.png")
pixbuf_nic = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_ethernet.png")
model.append(["Storage device", pixbuf_disk, PAGE_DISK])
model.append(["Network card", pixbuf_nic, PAGE_NETWORK])
self.set_initial_state()
def show(self):
self.reset_state()
self.topwin.show()
self.topwin.present()
def set_initial_state(self):
notebook = self.window.get_widget("create-pages")
notebook.set_show_tabs(False)
#XXX I don't think I should have to go through and set a bunch of background colors
# in code, but apparently I do...
black = gtk.gdk.color_parse("#000")
for num in range(PAGE_SUMMARY+1):
name = "page" + str(num) + "-title"
self.window.get_widget(name).modify_bg(gtk.STATE_NORMAL,black)
if os.getuid() != 0:
self.window.get_widget("storage-partition").set_sensitive(False)
# set up the lists for the networks
network_list = self.window.get_widget("net-network")
network_model = gtk.ListStore(str, str)
network_list.set_model(network_model)
text = gtk.CellRendererText()
network_list.pack_start(text, True)
network_list.add_attribute(text, 'text', 1)
device_list = self.window.get_widget("net-device")
device_model = gtk.ListStore(str)
device_list.set_model(device_model)
text = gtk.CellRendererText()
device_list.pack_start(text, True)
device_list.add_attribute(text, 'text', 0)
def reset_state(self):
notebook = self.window.get_widget("create-pages")
notebook.set_current_page(0)
# Hide the "finish" button until the appropriate time
self.window.get_widget("create-finish").hide()
self.window.get_widget("create-forward").show()
self.window.get_widget("create-back").set_sensitive(False)
self.window.get_widget("storage-file-size").set_sensitive(False)
self.change_storage_type()
self.change_network_type()
if os.getuid() == 0:
self.window.get_widget("storage-partition").set_active(True)
else:
self.window.get_widget("storage-file-backed").set_active(True)
self.window.get_widget("storage-partition-address").set_text("")
self.window.get_widget("storage-file-address").set_text("")
self.window.get_widget("storage-file-size").set_value(2000)
self.window.get_widget("non-sparse").set_active(True)
model = self.window.get_widget("net-network").get_model()
self.populate_network_model(model)
device = self.window.get_widget("net-device").get_model()
self.populate_device_model(device)
def forward(self, ignore=None):
notebook = self.window.get_widget("create-pages")
if(self.validate(notebook.get_current_page()) != True):
return
if notebook.get_current_page() == PAGE_INTRO:
notebook.set_current_page(self.get_config_hardware_type())
else:
notebook.set_current_page(PAGE_SUMMARY)
self.window.get_widget("create-finish").show()
self.window.get_widget("create-forward").hide()
self.window.get_widget("create-back").set_sensitive(True)
def back(self, ignore=None):
notebook = self.window.get_widget("create-pages")
if notebook.get_current_page() == PAGE_SUMMARY:
notebook.set_current_page(self.get_config_hardware_type())
self.window.get_widget("create-finish").hide()
else:
notebook.set_current_page(PAGE_INTRO)
self.window.get_widget("create-back").set_sensitive(False)
self.window.get_widget("create-forward").show()
def get_config_hardware_type(self):
type = self.window.get_widget("hardware-type")
if type.get_active_iter() == None:
return None
return type.get_model().get_value(type.get_active_iter(), 2)
def get_config_disk_image(self):
if self.window.get_widget("storage-partition").get_active():
return self.window.get_widget("storage-partition-address").get_text()
else:
return self.window.get_widget("storage-file-address").get_text()
def get_config_disk_size(self):
if self.window.get_widget("storage-partition").get_active():
return None
else:
return self.window.get_widget("storage-file-size").get_value()
def get_config_network(self):
if os.getuid() != 0:
return ["user"]
if self.window.get_widget("net-type-network").get_active():
net = self.window.get_widget("net-network")
model = net.get_model()
return ["network", model.get_value(net.get_active_iter(), 0)]
else:
dev = self.window.get_widget("net-device")
model = dev.get_model()
return ["bridge", model.get_value(dev.get_active_iter(), 0)]
def page_changed(self, notebook, page, page_number):
if page_number == PAGE_DISK:
pass
elif page_number == PAGE_NETWORK:
pass
elif page_number == PAGE_SUMMARY:
hwpage = self.get_config_hardware_type()
if hwpage == PAGE_DISK:
self.window.get_widget("summary-disk").show()
self.window.get_widget("summary-network").hide()
self.window.get_widget("summary-disk-image").set_text(self.get_config_disk_image())
disksize = self.get_config_disk_size()
if disksize != None:
self.window.get_widget("summary-disk-size").set_text(str(int(disksize)) + " MB")
else:
self.window.get_widget("summary-disk-size").set_text("-")
elif hwpage == PAGE_NETWORK:
self.window.get_widget("summary-disk").hide()
self.window.get_widget("summary-network").show()
net = self.get_config_network()
if net[0] == "bridge":
self.window.get_widget("summary-net-type").set_text(_("Shared physical device"))
self.window.get_widget("summary-net-target").set_text(net[1])
elif net[0] == "network":
self.window.get_widget("summary-net-type").set_text(_("Virtual network"))
self.window.get_widget("summary-net-target").set_text(net[1])
elif net[0] == "user":
self.window.get_widget("summary-net-type").set_text(_("Usermode networking"))
self.window.get_widget("summary-net-target").set_text("-")
else:
raise ValueError, "Unknown networking type " + net[0]
def close(self, ignore1=None,ignore2=None):
self.topwin.hide()
return 1
def is_visible(self):
if self.topwin.flags() & gtk.VISIBLE:
return 1
return 0
def finish(self, ignore=None):
hw = self.get_config_hardware_type()
self.install_error = None
self.topwin.set_sensitive(False)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
if hw == PAGE_NETWORK:
self.add_network()
elif hw == PAGE_DISK:
self.add_storage()
if self.install_error is not None:
dg = vmmErrorDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
self.install_error,
self.install_details)
dg.run()
dg.hide()
dg.destroy()
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
# Don't close becase we allow user to go back in wizard & correct
# their mistakes
#self.close()
return
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
self.close()
def add_network(self):
net = self.get_config_network()
vnic = None
if net[0] == "bridge":
vnic = virtinst.VirtualNetworkInterface(type=net[0], bridge=net[1])
elif net[0] == "network":
vnic = virtinst.VirtualNetworkInterface(type=net[0], network=net[1])
else:
raise ValueError, "Unsupported networking type " + net[0]
vnic.setup(self.vm.get_connection().vmm)
xml = vnic.get_xml_config()
logging.debug("Adding network " + xml)
self.vm.add_device(xml)
def add_storage(self):
filesize = None
disk = None
if self.get_config_disk_size() != None:
filesize = self.get_config_disk_size() / 1024.0
try:
disk = virtinst.VirtualDisk(self.get_config_disk_image(), filesize, sparse = self.is_sparse_file())
if disk.type == virtinst.VirtualDisk.TYPE_FILE and \
not self.vm.is_hvm() \
and virtinst.util.is_blktap_capable():
disk.driver_name = virtinst.VirtualDisk.DRIVER_TAP
except ValueError, e:
self._validation_error_box(_("Invalid storage address"), e.args[0])
return
used = {}
for d in self.vm.get_disk_devices():
dev = d[3]
used[dev] = 1
nodes = []
if self.vm.is_hvm():
for n in range(4):
nodes.append("hd%c" % (ord('a')+n))
else:
for n in range(26):
nodes.append("xvd%c" % (ord('a')+n))
node = None
for n in nodes:
if not used.has_key(n):
node = n
break
if node is None:
self._validation_error_box(_("Too many virtual disks"),
_("There are no more available virtual disk device nodes"))
return
progWin = vmmAsyncJob(self.config, self.do_file_allocate, [disk],
title=_("Creating Storage File"),
text=_("Allocation of disk storage may take a few minutes " + \
"to complete."))
progWin.run()
xml = disk.get_xml_config(node)
logging.debug("Adding disk " + xml)
self.vm.add_device(xml)
def do_file_allocate(self, disk, asyncjob):
meter = vmmCreateMeter(asyncjob)
try:
logging.debug("Starting background file allocate process")
disk.setup(meter)
logging.debug("Allocation completed")
except:
(type, value, stacktrace) = sys.exc_info ()
# Detailed error message, in English so it can be Googled.
details = \
"Unable to complete install '%s'" % \
(str(type) + " " + str(value) + "\n" + \
traceback.format_exc (stacktrace))
self.install_error = _("Unable to complete install: '%s'") % str(value)
self.install_details = details
logging.error(details)
def browse_storage_partition_address(self, src, ignore=None):
part = self._browse_file(_("Locate Storage Partition"), "/dev")
if part != None:
self.window.get_widget("storage-partition-address").set_text(part)
def browse_storage_file_address(self, src, ignore=None):
self.window.get_widget("storage-file-size").set_sensitive(True)
fcdialog = gtk.FileChooserDialog(_("Locate or Create New Storage File"),
self.window.get_widget("vmm-create"),
gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT),
None)
fcdialog.set_current_folder(self.config.get_default_image_dir(self.vm.get_connection()))
fcdialog.set_do_overwrite_confirmation(True)
fcdialog.connect("confirm-overwrite", self.confirm_overwrite_callback)
response = fcdialog.run()
fcdialog.hide()
file = None
if(response == gtk.RESPONSE_ACCEPT):
file = fcdialog.get_filename()
if file != None:
self.window.get_widget("storage-file-address").set_text(file)
def toggle_storage_size(self, ignore1=None, ignore2=None):
file = self.get_config_disk_image()
if file != None and len(file) > 0 and not(os.path.exists(file)):
self.window.get_widget("storage-file-size").set_sensitive(True)
self.window.get_widget("non-sparse").set_sensitive(True)
self.window.get_widget("storage-file-size").set_value(4000)
else:
self.window.get_widget("storage-file-size").set_sensitive(False)
self.window.get_widget("non-sparse").set_sensitive(False)
if os.path.isfile(file):
size = os.path.getsize(file)/(1024*1024)
self.window.get_widget("storage-file-size").set_value(size)
else:
self.window.get_widget("storage-file-size").set_value(0)
def confirm_overwrite_callback(self, chooser):
# Only called when the user has chosen an existing file
self.window.get_widget("storage-file-size").set_sensitive(False)
return gtk.FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME
def change_storage_type(self, ignore=None):
if self.window.get_widget("storage-partition").get_active():
self.window.get_widget("storage-partition-box").set_sensitive(True)
self.window.get_widget("storage-file-box").set_sensitive(False)
self.window.get_widget("storage-file-size").set_sensitive(False)
self.window.get_widget("non-sparse").set_sensitive(False)
else:
self.window.get_widget("storage-partition-box").set_sensitive(False)
self.window.get_widget("storage-file-box").set_sensitive(True)
self.toggle_storage_size()
def change_network_type(self, ignore=None):
if self.window.get_widget("net-type-network").get_active():
self.window.get_widget("net-network").set_sensitive(True)
self.window.get_widget("net-device").set_sensitive(False)
else:
self.window.get_widget("net-network").set_sensitive(False)
self.window.get_widget("net-device").set_sensitive(True)
def validate(self, page_num):
if page_num == PAGE_INTRO:
if self.get_config_hardware_type() == None:
self._validation_error_box(_("Hardware Type Required"), \
_("You must specify what type of hardware to add"))
return False
elif page_num == PAGE_DISK:
disk = self.get_config_disk_image()
if disk == None or len(disk) == 0:
self._validation_error_box(_("Storage Address Required"), \
_("You must specify a partition or a file for storage for the guest install"))
return False
if not self.window.get_widget("storage-partition").get_active():
if os.path.isdir(disk):
self._validation_error_box(_("Storage Address Is Directory"), \
_("You chose 'Simple File' storage for your storage method, but chose a directory instead of a file. Please enter a new filename or choose an existing file."))
return False
d = virtinst.VirtualDisk(self.get_config_disk_image(), self.get_config_disk_size(), sparse = self.is_sparse_file())
if d.is_conflict_disk(self.vm.get_connection().vmm) is True:
res = self._yes_no_box(_('Disk "%s" is already in use by another guest!' % disk), \
_("Do you really want to use the disk ?"))
return res
elif page_num == PAGE_NETWORK:
if self.window.get_widget("net-type-network").get_active():
if self.window.get_widget("net-network").get_active() == -1:
self._validation_error_box(_("Virtual Network Required"),
_("You must select one of the virtual networks"))
return False
else:
if self.window.get_widget("net-device").get_active() == -1:
self._validation_error_box(_("Physical Device Required"),
_("You must select one of the physical devices"))
return False
return True
def _validation_error_box(self, text1, text2=None):
message_box = gtk.MessageDialog(self.window.get_widget("vmm-create"), \
0, \
gtk.MESSAGE_ERROR, \
gtk.BUTTONS_OK, \
text1)
if text2 != None:
message_box.format_secondary_text(text2)
message_box.run()
message_box.destroy()
def _yes_no_box(self, text1, text2=None):
#import pdb; pdb.set_trace()
message_box = gtk.MessageDialog(self.window.get_widget("vmm-create"), \
0, \
gtk.MESSAGE_WARNING, \
gtk.BUTTONS_YES_NO, \
text1)
if text2 != None:
message_box.format_secondary_text(text2)
if message_box.run()== gtk.RESPONSE_YES:
res = True
else:
res = False
message_box.destroy()
return res
def populate_network_model(self, model):
model.clear()
for uuid in self.vm.get_connection().list_net_uuids():
net = self.vm.get_connection().get_net(uuid)
model.append([net.get_label(), net.get_name()])
def populate_device_model(self, model):
model.clear()
for name in self.vm.get_connection().list_net_device_paths():
net = self.vm.get_connection().get_net_device(name)
if net.is_shared():
model.append([net.get_bridge()])
def is_sparse_file(self):
if self.window.get_widget("non-sparse").get_active():
return False
else:
return True
def show_help(self, src):
# help to show depends on the notebook page, yahoo
page = self.window.get_widget("create-pages").get_current_page()
if page == PAGE_INTRO:
self.emit("action-show-help", "virt-manager-create-wizard")
elif page == PAGE_DISK:
self.emit("action-show-help", "virt-manager-storage-space")
elif page == PAGE_NETWORK:
self.emit("action-show-help", "virt-manager-network")
gobject.type_register(vmmAddHardware)

View File

@ -26,6 +26,7 @@ import logging
import traceback
from virtManager.error import vmmErrorDialog
from virtManager.addhardware import vmmAddHardware
import virtinst
import urlgrabber.progress as progress
@ -35,7 +36,6 @@ VMM_HW_CPU = 0
VMM_HW_MEMORY = 1
VMM_HW_DISK = 2
VMM_HW_NIC = 3
VMM_HW_DEVICES = [_("Virtual Disk"), _("Virtual NIC")]
class vmmDetails(gobject.GObject):
__gsignals__ = {
@ -68,6 +68,7 @@ class vmmDetails(gobject.GObject):
self.window.get_widget("hw-panel").set_show_tabs(False)
self.addhw = None
self.cpu_usage_graph = sparkline.Sparkline()
self.window.get_widget("graph-table").attach(self.cpu_usage_graph, 1, 2, 0, 1)
@ -75,7 +76,6 @@ class vmmDetails(gobject.GObject):
self.memory_usage_graph = sparkline.Sparkline()
self.window.get_widget("graph-table").attach(self.memory_usage_graph, 1, 2, 1, 2)
self.network_traffic_graph = sparkline.Sparkline()
self.window.get_widget("graph-table").attach(self.network_traffic_graph, 1, 2, 3, 4)
@ -111,45 +111,21 @@ class vmmDetails(gobject.GObject):
"on_storage_file_address_changed": self.toggle_storage_size,
"on_storage_toggled" : self.change_storage_type,
"on_add_hardware_button_clicked": self.add_hardware,
"on_vnic_apply_clicked": self.add_vnic,
"on_vnic_cancel_clicked": self.clean_up_add_hardware,
"on_vbd_add_apply_clicked": self.add_vbd,
"on_vbd_add_cancel_clicked": self.clean_up_add_hardware,
})
self.vm.connect("status-changed", self.update_widget_states)
self.vm.connect("resources-sampled", self.refresh_resources)
self.window.get_widget("hw-list").get_selection().connect("changed", self.hw_selected)
# set up the list for new hardware devices
hw_type_list = self.window.get_widget("add-hardware-device")
hw_type_model = hw_type_list.get_model()
for device_name in VMM_HW_DEVICES:
hw_type_model.append([device_name])
hw_type_list.set_active(0)
# list for network pulldown
network_list = self.window.get_widget("network-name-pulldown")
network_model = gtk.ListStore(str, str, str)
network_list.set_model(network_model)
text = gtk.CellRendererText()
network_list.pack_start(text, True)
network_list.add_attribute(text, 'text', 0)
#using this as the flag for whether the network page is in edit mode. Ugh.
self.adding_hardware = False
self.update_widget_states(vm, vm.status())
self.refresh_resources(vm)
self.pixbuf_processor = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_cpu.png")
self.pixbuf_memory = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_cpu.png")
self.pixbuf_disk = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_hdd.png")
self.pixbuf_network = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_ethernet.png")
self.pixbuf_nic = gtk.gdk.pixbuf_new_from_file(config.get_icon_dir() + "/icon_ethernet.png")
self.prepare_hw_list()
self.hw_selected()
def toggle_toolbar(self, src):
if src.get_active():
@ -170,8 +146,7 @@ class vmmDetails(gobject.GObject):
def show_help(self, src):
# From the Details window, show the help document from the Details page
self.emit("action-show-help", "virt-manager-details-window")
self.emit("action-show-help", "virt-manager-details-window")
def activate_performance_page(self):
self.window.get_widget("details-pages").set_current_page(0)
@ -189,14 +164,14 @@ class vmmDetails(gobject.GObject):
return 0
def hw_selected(self, src=None):
self.adding_hardware = False
vmlist = self.window.get_widget("hw-list")
selection = vmlist.get_selection()
active = selection.get_selected()
if active[1] != None:
pagetype = active[0].get_value(active[1], 3)
pagetype = active[0].get_value(active[1], 2)
self.window.get_widget("hw-panel").set_sensitive(True)
pagenum = -1
if pagetype == VMM_HW_CPU:
self.window.get_widget("config-vcpus-apply").set_sensitive(False)
self.refresh_config_cpu()
@ -209,18 +184,13 @@ class vmmDetails(gobject.GObject):
self.refresh_disk_page()
pagenum = 2
elif pagetype == VMM_HW_NIC:
self.window.get_widget("network-name-pulldown").hide()
self.window.get_widget("network-buttons").hide()
self.window.get_widget("network-name").set_editable(False)
self.window.get_widget("network-mac-address").set_editable(False)
self.window.get_widget("net-devlabel-label").show()
self.window.get_widget("network-device-name").show()
self.refresh_network_page()
pagenum = 3
self.window.get_widget("hw-panel").set_current_page(pagenum)
else:
logging.debug("In hw_selected with null tree iter")
self.window.get_widget("hw-panel").set_sensitive(True)
self.window.get_widget("hw-panel").set_sensitive(False)
selection.select_path(0)
self.window.get_widget("hw-panel").set_current_page(0)
@ -342,66 +312,34 @@ class vmmDetails(gobject.GObject):
self.window.get_widget("details-menu-serial").set_sensitive(False)
def refresh_resources(self, ignore):
self.refresh_summary()
if self.window.get_widget("details-pages").get_current_page() == 1:
details = self.window.get_widget("details-pages")
if details.get_current_page() == 0:
self.refresh_summary()
else:
#XXX for this week this only works for active domains, and it's temporary.
if self.vm.is_active():
self.window.get_widget("add-hardware-button").set_sensitive(True)
else:
self.window.get_widget("add-hardware-button").set_sensitive(False)
if self.adding_hardware:
return
# reload the hw model, go to the correct page, and refresh that page
hw_list = self.window.get_widget("hw-list")
hw_panel = self.window.get_widget("hw-panel")
selection = hw_list.get_selection()
active = selection.get_selected()
if active[1] != None:
pagetype = active[0].get_value(active[1], 3)
device_info = active[0].get_value(active[1], 4)
self.populate_hw_list()
pagetype = active[0].get_value(active[1], 2)
device_info = active[0].get_value(active[1], 3)
self.repopulate_hw_list()
hw_model = hw_list.get_model()
if pagetype == VMM_HW_CPU:
self.refresh_config_cpu()
pagenum = 0
selection.select_path(0)
elif pagetype == VMM_HW_MEMORY:
self.refresh_config_memory()
pagenum = 1
selection.select_path(1)
elif pagetype == VMM_HW_DISK:
self.refresh_disk_page()
# try to match the old source dev to one of the new source devs
selection.select_path(0)
pagenum = 0
i=0
for hw in hw_model:
if hw[3] == VMM_HW_DISK:
if device_info[1] == hw[4][1]:
selection.select_path(i)
pagenum = 2
break
i = i + 1
elif pagetype == VMM_HW_NIC:
self.refresh_network_page()
selection.select_path(0)
pagenum = 0
i=0
for hw in hw_model:
if hw[3] == VMM_HW_NIC:
if device_info[3] == hw[4][3]:
selection.select_path(i)
pagenum = 3
break
i = i + 1
hw_panel.set_current_page(pagenum)
else:
logging.debug("In hw_selected with null tree iter")
hw_panel.set_sensitive(True)
selection.select_path(0)
hw_panel.set_current_page(0)
def refresh_summary(self):
self.window.get_widget("overview-cpu-usage-text").set_text("%d %%" % self.vm.cpu_time_percentage())
@ -460,32 +398,24 @@ class vmmDetails(gobject.GObject):
selection = vmlist.get_selection()
active = selection.get_selected()
if active[1] != None:
diskinfo = active[0].get_value(active[1], 4)
# fill the fields on the screen
self.window.get_widget("disk-type").set_text(diskinfo[0])
self.window.get_widget("storage-source").set_text(diskinfo[1])
self.window.get_widget("storage-device").set_text(diskinfo[2])
self.window.get_widget("device-label").set_text(diskinfo[3])
diskinfo = active[0].get_value(active[1], 3)
self.window.get_widget("disk-source-type").set_text(diskinfo[0])
self.window.get_widget("disk-source-path").set_text(diskinfo[1])
self.window.get_widget("disk-target-type").set_text(diskinfo[2])
self.window.get_widget("disk-target-device").set_text(diskinfo[3])
def refresh_network_page(self):
# get the line what was clicked
if not self.adding_hardware:
# viewing net page, not adding a device. If adding, don't try to refresh
vmlist = self.window.get_widget("hw-list")
selection = vmlist.get_selection()
active = selection.get_selected()
if active[1] != None:
netinfo = active[0].get_value(active[1], 4)
if netinfo[1] == "-":
netname = "No network name"
else:
netname = netinfo[1]
name_widget = self.window.get_widget("network-name")
name_widget.set_text(netname)
name_widget.show()
self.window.get_widget("network-mac-address").set_text(netinfo[3])
self.window.get_widget("network-device-name").set_text(netinfo[2])
# viewing net page, not adding a device. If adding, don't try to refresh
vmlist = self.window.get_widget("hw-list")
selection = vmlist.get_selection()
active = selection.get_selected()
if active[1] != None:
netinfo = active[0].get_value(active[1], 3)
self.window.get_widget("network-source-type").set_text(netinfo[0])
self.window.get_widget("network-source-device").set_text(netinfo[1])
self.window.get_widget("network-target-device").set_text(netinfo[2])
self.window.get_widget("network-mac-address").set_text(netinfo[3])
def config_vcpus_changed(self, src):
self.window.get_widget("config-vcpus-apply").set_sensitive(True)
@ -576,171 +506,71 @@ class vmmDetails(gobject.GObject):
self.toggle_storage_size()
def prepare_hw_list(self):
hw_list_model = gtk.ListStore(int, str, gtk.gdk.Pixbuf, int, gobject.TYPE_PYOBJECT)
hw_list_model = gtk.ListStore(str, gtk.gdk.Pixbuf, int, gobject.TYPE_PYOBJECT)
self.window.get_widget("hw-list").set_model(hw_list_model)
self.populate_hw_list()
hwCol = gtk.TreeViewColumn("Hardware")
hw_txt = gtk.CellRendererText()
hw_img = gtk.CellRendererPixbuf()
hwCol.pack_start(hw_txt, True)
hwCol.pack_start(hw_img, False)
hwCol.add_attribute(hw_txt, 'text', 1)
hwCol.add_attribute(hw_img, 'pixbuf', 2)
hwCol.add_attribute(hw_txt, 'text', 0)
hwCol.add_attribute(hw_img, 'pixbuf', 1)
self.window.get_widget("hw-list").append_column(hwCol)
self.populate_hw_list()
def populate_hw_list(self):
hw_list_model = self.window.get_widget("hw-list").get_model()
hw_list_model.clear()
hw_list_model.append([0, "Processor", self.pixbuf_processor, VMM_HW_CPU, []])
hw_list_model.append([1, "Memory", self.pixbuf_memory, VMM_HW_MEMORY, []])
hw_list_model.append(["Processor", self.pixbuf_processor, VMM_HW_CPU, []])
hw_list_model.append(["Memory", self.pixbuf_memory, VMM_HW_MEMORY, []])
self.repopulate_hw_list()
def repopulate_hw_list(self):
hw_list_model = self.window.get_widget("hw-list").get_model()
# Populate list of disks
for disk in self.vm.get_disk_devices():
missing = True
insertAt = 0
for row in hw_list_model:
if row[2] == VMM_HW_DISK and row[3][3] == disk[3]:
# Update metadata
row[3] = disk
missing = False
# The insert position must be *before* any NICs
if row[2] != VMM_HW_NIC:
insertAt = insertAt + 1
# Add in row
if missing:
hw_list_model.insert(insertAt, ["Disk %s" % disk[3], self.pixbuf_disk, VMM_HW_DISK, disk])
# Populate list of NICs
for nic in self.vm.get_network_devices():
missing = True
insertAt = 0
for row in hw_list_model:
if row[2] == VMM_HW_NIC and row[3][3] == nic[3]:
# Update metadata
row[3] = nic
missing = False
# Insert poisition is at end....
# XXX until we add support for Mice, etc
insertAt = insertAt + 1
# Add in row
if missing:
hw_list_model.insert(insertAt, ["NIC %s" % nic[2], self.pixbuf_nic, VMM_HW_NIC, nic])
#all disks
disk_list = self.vm.get_disk_devices()
for i in range(len(disk_list)):
hw_list_model.append([i + 2, "Disk %d" % (i + 1), self.pixbuf_disk, VMM_HW_DISK, disk_list[i]])
#all nics
nic_list = self.vm.get_network_devices()
offset = len(disk_list) + 2
for i in range(len(nic_list)):
hw_list_model.append([offset + i, "Network %d" % (i + 1), self.pixbuf_network, VMM_HW_NIC, nic_list[i]])
def add_hardware(self, src):
self.adding_hardware = True
widget = self.window.get_widget("add-hardware-device")
iter = widget.get_active_iter()
device = widget.get_model().get_value(iter, 0)
if VMM_HW_DEVICES.index(device) == 0:
# a new virtual disk
self.window.get_widget("hw-panel").set_current_page(4)
elif VMM_HW_DEVICES.index(device) == 1:
# a new vnic
network_menu = self.window.get_widget("network-name-pulldown")
self.populate_network_model(network_menu.get_model())
network_menu.set_active(0)
network_menu.show()
self.window.get_widget("network-buttons").show()
self.window.get_widget("network-name").hide()
mac_addr = self.window.get_widget("network-mac-address")
mac_addr.set_editable(True)
mac_addr.set_text("")
self.window.get_widget("net-devlabel-label").hide()
self.window.get_widget("network-device-name").hide()
self.window.get_widget("hw-panel").set_current_page(3)
else:
pass
def populate_network_model(self, model):
model.clear()
for uuid in self.vm.get_connection().list_net_uuids():
net = self.vm.get_connection().get_net(uuid)
model.append([net.get_label(), net.get_name(), "network"])
br = virtinst.util.default_bridge()
model.append([_("default bridge"), br, "bridge"])
if self.addhw is None:
self.addhw = vmmAddHardware(self.config, self.vm)
def add_vnic(self, src):
network = None
bridge = None
net_name_widget = self.window.get_widget("network-name-pulldown")
net_name = net_name_widget.get_model().get_value(net_name_widget.get_active_iter(), 1)
net_type = net_name_widget.get_model().get_value(net_name_widget.get_active_iter(), 2)
mac_addr = self.window.get_widget("network-mac-address").get_text()
if mac_addr == "":
mac_addr = None
if net_type == "network":
network = net_name
else:
bridge = net_name
self.vm.add_network_device(mac_addr, net_type, bridge, network)
self.clean_up_add_hardware()
self.addhw.show()
def add_vbd(self, src):
# disks
# filesize = None
# if self.vm.is_hvm():
# disknode = "hd"
# else:
# disknode = "xvd"
# if self.get_config_disk_size() != None:
# filesize = self.get_config_disk_size() / 1024.0
# try:
# d = virtinst.VirtualDisk(self.get_config_disk_image(), filesize, sparse = self.is_sparse_file())
# if d.type == virtinst.VirtualDisk.TYPE_FILE and \
# self.vm.is_hvm() == False \
# and virtinst.util.is_blktap_capable():
# d.driver_name = virtinst.VirtualDisk.DRIVER_TAP
# if d.type == virtinst.VirtualDisk.TYPE_FILE and not \
# self.is_sparse_file():
# self.non_sparse = True
# else:
# self.non_sparse = False
# except ValueError, e:
# self._validation_error_box(_("Invalid storage address"), e.args[0])
# return
# #XXX add the progress bar in here...
# d.setup(progress.BaseMeter)
# xml = d.get_xml_config(disknode)
# logging.debug("Disk XML: %s" % d)
# self.vm.add_disk_device(xml)
self.clean_up_add_hardware()
def get_config_disk_image(self):
if self.window.get_widget("storage-partition").get_active():
return self.window.get_widget("storage-partition-address").get_text()
else:
return self.window.get_widget("storage-file-address").get_text()
def get_config_disk_size(self):
if self.window.get_widget("storage-partition").get_active():
return None
else:
return self.window.get_widget("storage-file-size").get_value()
def is_sparse_file(self):
if self.window.get_widget("non-sparse").get_active():
return False
else:
return True
def clean_up_add_hardware(self, src=None):
self.adding_hardware = False
self.hw_selected()
def _validation_error_box(self, text1, text2=None):
message_box = gtk.MessageDialog(self.window.get_widget("vmm-details"), \
0, \
gtk.MESSAGE_ERROR, \
gtk.BUTTONS_OK, \
text1)
if text2 != None:
message_box.format_secondary_text(text2)
message_box.run()
message_box.destroy()
def _browse_file(self, dialog_name, folder=None, type=None):
# user wants to browse for an ISO
fcdialog = gtk.FileChooserDialog(dialog_name,
self.window.get_widget("vmm-details"),
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT),
None)
if type != None:
f = gtk.FileFilter()
f.add_pattern("*." + type)
fcdialog.set_filter(f)
if folder != None:
fcdialog.set_current_folder(folder)
response = fcdialog.run()
fcdialog.hide()
if(response == gtk.RESPONSE_ACCEPT):
filename = fcdialog.get_filename()
fcdialog.destroy()
return filename
else:
fcdialog.destroy()
return None
gobject.type_register(vmmDetails)

View File

@ -23,7 +23,7 @@ import libxml2
import os
import sys
import logging
import virtinst
class vmmDomain(gobject.GObject):
__gsignals__ = {
@ -44,6 +44,12 @@ class vmmDomain(gobject.GObject):
self.lastStatus = None
self.record = []
self._update_status()
self.xml = None
def get_xml(self):
if self.xml is None:
self.xml = self.vm.XMLDesc(0)
return self.xml
def set_handle(self, vm):
self.vm = vm
@ -65,7 +71,7 @@ class vmmDomain(gobject.GObject):
if id < 0:
return "-"
return str(id)
def get_name(self):
return self.vm.name()
@ -129,6 +135,8 @@ class vmmDomain(gobject.GObject):
self.emit("status-changed", status)
def tick(self, now):
# Clear cached XML
self.xml = None
hostInfo = self.connection.get_host_info()
info = self.vm.info()
expected = self.config.get_stats_history_length()
@ -390,7 +398,7 @@ class vmmDomain(gobject.GObject):
return self.config.get_vm_status_icon(self.status())
def get_xml_string(self, path):
xml = self.vm.XMLDesc(0)
xml = self.get_xml()
doc = None
try:
doc = libxml2.parseDoc(xml)
@ -432,7 +440,7 @@ class vmmDomain(gobject.GObject):
return [type, None, None]
def get_disk_devices(self):
xml = self.vm.XMLDesc(0)
xml = self.get_xml()
doc = None
try:
doc = libxml2.parseDoc(xml)
@ -476,7 +484,7 @@ class vmmDomain(gobject.GObject):
self.vm.attachDevice(xml)
def get_network_devices(self):
xml = self.vm.XMLDesc(0)
xml = self.get_xml()
doc = None
try:
doc = libxml2.parseDoc(xml)
@ -487,31 +495,29 @@ class vmmDomain(gobject.GObject):
try:
ret = ctx.xpathEval("/domain/devices/interface")
n = 0
for node in ret:
type = node.prop("type")
devmac = None
source = None
target = "Unspecified"
target = "eth%d" % n
n = n + 1
for child in node.children:
if child.name == "source":
if type == "bridge":
if child.prop("bridge") != None:
source = child.prop("bridge")
else:
source = child.prop("dev")
source = child.prop("bridge")
elif type == "ethernet":
source = child.prop("dev")
elif type == "network":
source = child.prop("network")
elif type == "user":
source = "SLIRP network"
source = None
else:
source = None
elif child.name == "mac":
devmac = child.prop("address")
elif child.name == "target":
target = child.prop("dev")
if source == None:
source = "-"
nics.append([type, source, target, devmac])
finally:
if ctx != None:
ctx.xpathFreeContext()
@ -519,12 +525,12 @@ class vmmDomain(gobject.GObject):
doc.freeDoc()
return nics
def add_network_device(self, macaddr, type="bridge", bridge=None, network=None):
vnic = virtinst.VirtualNetworkInterface(macaddr, type, bridge, network)
vnic.setup(self.connection.vmm)
xml = vnic.get_xml_config()
def add_device(self, xml):
self.vm.attachDevice(xml)
def remove_device(self, xml):
self.vm.detachDevice(xml)
def set_vcpu_count(self, vcpus):
vcpus = int(vcpus)
self.vm.setVcpus(vcpus)

2002
src/vmm-add-hardware.glade Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,397 +0,0 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="vmm-add-virtual-nic">
<property name="visible">True</property>
<property name="title" translatable="yes">Add Virtual NIC</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">True</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox5">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area5">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="cancelbutton1">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="okbutton1">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-ok</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox19">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label115">
<property name="visible">True</property>
<property name="label" translatable="yes">Add virtual NIC with:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">5</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkRadioButton" id="mac-addr-random">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Randomly generated MAC address</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">True</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
<accessibility>
<atkproperty name="AtkObject::accessible_name" translatable="yes">mac-addr-random</atkproperty>
</accessibility>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkRadioButton" id="mac-addr-selected">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Specified MAC address:</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
<property name="group">mac-addr-random</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment41">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">11</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkHBox" id="hbox22">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label116">
<property name="visible">True</property>
<property name="label" translatable="yes">00:16:3e:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">2</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">•</property>
<property name="activates_default">False</property>
<property name="width_chars">2</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label117">
<property name="visible">True</property>
<property name="label" translatable="yes">:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">2</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="width_chars">2</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label118">
<property name="visible">True</property>
<property name="label" translatable="yes">:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">2</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">•</property>
<property name="activates_default">False</property>
<property name="width_chars">2</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox23">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkAlignment" id="alignment42">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">13</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkImage" id="image76">
<property name="visible">True</property>
<property name="stock">gtk-info</property>
<property name="icon_size">4</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment43">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">3</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkLabel" id="label119">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Tip:&lt;/b&gt; Acceptable values for hex digits are the numbers 0-9 and the letters A-F</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">True</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

File diff suppressed because it is too large Load Diff