#!/usr/bin/python

# VBoxGtk: A VirtualBox GTK+ GUI
# Copyright (C) 2008 Francisco J. Vazquez-Araujo, Spain
# franjva at gmail dot com

# This file is part of VBoxGtk.

# VBoxGtk 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 3 of the License, or
# (at your option) any later version.

# VBoxGtk 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 VBoxGtk.  If not, see <http://www.gnu.org/licenses/>.



# The GTK Interface. Interacts with the VBoxMgr.

import gtk
import gtk.glade
import datetime
import os
import sys

import util
import vboxmgr
import vboxiface
import vboxrunner_sdl_cs
import vboxrunner_sdl_thr


class VBoxGtk():
	def __init__(self, vboxmgr):
		vboxmgr.vboxiface = self
		vboxmgr.vboxrunner.vboxiface = self
		self.vboxmgr = vboxmgr
		self.vms = self.vboxmgr.vms
		self.vdis = self.vboxmgr.vdis
		self.selected_vm = -1		
		#self.base_path = os.path.expanduser('~') + '/python/'
		self.base_path = os.getcwd() + '/'
		
		self.wTree = gtk.glade.XML(self.base_path + 'vboxgtk.glade')
		dic = { 'on_window_main_destroy' : self.on_window_main_destroy,
				'on_menuitem_about_activate' : self.on_menuitem_about_activate,		
				'on_toolbutton_newvm_clicked' : self.on_toolbutton_newvm_clicked,
				'on_toolbutton_deletevm_clicked' : self.on_toolbutton_deletevm_clicked,
				'on_toolbutton_run_clicked' : self.on_toolbutton_run_clicked,
				'on_toolbutton_sleep_clicked' : self.on_toolbutton_sleep_clicked,
				'on_toolbutton_stop_clicked' : self.on_toolbutton_stop_clicked,
				'on_toolbutton_managevdis_clicked' : self.on_toolbutton_managevdis_clicked,
				'on_button_managevdis_close_clicked': self.on_button_managevdis_close_clicked,
				'on_entries_newvm_changed' : self.on_entries_newvm_changed,
				'on_entry_shared_name_changed' : self.on_entry_shared_name_changed,
				'on_button_newvm_browse_clicked' : self.on_button_newvm_browse_clicked,
				'on_button_generatemac_clicked': self.on_button_generatemac_clicked,
				'on_button_tracefile_browse_clicked' : self.on_button_tracefile_browse_clicked,
				'on_button_shared_add_clicked' : self.on_button_shared_add_clicked,
				'on_button_shared_remove_clicked' : self.on_button_shared_remove_clicked,
				'on_button_snapshots_take_clicked' : self.on_button_snapshots_take_clicked,
				'on_button_snapshots_discard_clicked' : self.on_button_snapshots_discard_clicked,
				'on_button_snapshots_revert_clicked' : self.on_button_snapshots_revert_clicked,
				'on_button_managevdis_new_clicked' : self.on_button_managevdis_new_clicked,
				'on_button_managevdis_register_clicked' : self.on_button_managevdis_register_clicked,	
				'on_button_managevdis_unregister_clicked' : self.on_button_managevdis_unregister_clicked,
				'on_combobox_newvm_ostype_changed' : self.on_combobox_newvm_ostype_changed,
				'on_combobox_newvm_vdi_changed' : self.on_combobox_newvm_vdi_changed,
				'on_combobox_disk_changed' : self.on_combobox_disk_changed,
				'on_combobox_dvd_changed' : self.on_combobox_dvd_changed,
				'on_settings_changed' : self.on_settings_changed,
				'on_devices_changed' : self.on_devices_changed,
				'on_devices_trace_changed' : self.on_devices_trace_changed,
				'on_devices_nic_changed' : self.on_devices_nic_changed}
		self.wTree.signal_autoconnect(dic)
	
		self.resetting_vdis = False
		self.resetting_devices = False
		self.entries_newvm_changed = False
		self.resetting_settings = True
		self.wTree.get_widget('hscale_ram').set_range(self.vboxmgr.min_ram, self.vboxmgr.max_ram)
		self.wTree.get_widget('hscale_vram').set_range(1, self.vboxmgr.max_vram)
		self.resetting_settings = False

		self.state_for_widgets = [] # Widgets whose state depends _only_ on the VM state
		non_sleep_widgets = [ 'label_settings_images', 'label_settings_4', 'combobox_dvd', 'button_shared_add' ]
		non_running_widgets = [ 'toolbutton_run', 'menuitem_run' ]
		only_running_widgets = [ 'toolbutton_sleep', 'toolbutton_stop', 'menuitem_sleep', 'menuitem_stop' ]
		only_stopped_widgets = [ 'toolbutton_deletevm', 'menuitem_deletevm', 'label_settings_general', 'label_settings_0', 
								 'combobox_ostype', 'checkbutton_hwvirtex', 'label_settings_memory', 'label_settings_1',
								 'hscale_ram', 'label_settings_2','hscale_vram', 'label_settings_3', 'combobox_disk', 
								 'label_settings_5', 'combobox_floppy', 'label_devices_network', 'label_devices_0',
								 'combobox_nic', 'label_devices_sound', 'label_devices_6', 'combobox_audiodev' ]
		for w in non_sleep_widgets:
			self.state_for_widgets.append((w,['powered off','aborted','running']))
		for w in non_running_widgets:
			self.state_for_widgets.append((w,['saved','aborted','powered off']))
		for w in only_stopped_widgets:
			self.state_for_widgets.append((w,['powered off','aborted']))
		for w in only_running_widgets:
			self.state_for_widgets.append((w,['running']))
		self.create_tree_and_combobox_models()
		self.update_vdi_list('disk')
		self.update_vdi_list('dvd')
		
		sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
		for i in range(6): sizegroup.add_widget(self.wTree.get_widget('label_settings_'+str(i)))
		sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
		for i in range(7): sizegroup.add_widget(self.wTree.get_widget('label_devices_'+str(i)))
		sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
		sizegroup.add_widget(self.wTree.get_widget('label_newvm_vmname'))
		sizegroup.add_widget(self.wTree.get_widget('label_newvm_filename'))
		
		icon16 = gtk.gdk.pixbuf_new_from_file(self.base_path + 'pixmaps/16x16.png')
		icon32 = gtk.gdk.pixbuf_new_from_file(self.base_path + 'pixmaps/32x32.png')
		icon48 = gtk.gdk.pixbuf_new_from_file(self.base_path + 'pixmaps/48x48.png')
		self.wTree.get_widget('window_main').set_icon_list(icon16, icon32, icon48)
		self.wTree.get_widget('about_dialog').set_logo(icon48)
		
		self.wTree.get_widget('button_snapshots_take').get_children()[0].get_children()[0].get_children()[1].set_label('Take')
		self.wTree.get_widget('button_snapshots_discard').get_children()[0].get_children()[0].get_children()[1].set_label('Discard')
		
	######## Callbacks

	## Exit

	def on_window_main_destroy(self, obj):
		self.vboxmgr.exit()
		gtk.main_quit()

	## Menu

	def on_menuitem_about_activate(self, obj):
		about_dialog = self.wTree.get_widget('about_dialog')
		about_dialog.run()
		about_dialog.hide()
	
	## VM Management
	
	def on_vmlist_row_activated(self, treeview, path, view_column):
		self.on_toolbutton_run_clicked(None)

	def on_vm_selection_changed(self, obj):
		self.selected_vm_cursor = obj.get_cursor()[0]
		if self.selected_vm_cursor == None:
			for w in self.state_for_widgets:
				self.wTree.get_widget(w[0]).set_sensitive(False)
			self.wTree.get_widget('notebook_settings').set_sensitive(False)
			self.selected_vm = -1
			return
		self.selected_vm = self.selected_vm_cursor[0]
		self.wTree.get_widget('notebook_settings').set_sensitive(True)
		for w in self.state_for_widgets:
			self.wTree.get_widget(w[0]).set_sensitive(self.vms[self.selected_vm].state in w[1])
		self.reset_everything()
		
	def reset_everything(self):
		self.update_vdi_combobox('combobox_disk', 'disk')
		self.update_vdi_combobox('combobox_dvd', 'dvd')
		self.reset_settings()
		self.reset_devices()
		self.reset_shared()
		self.reset_snapshots()

	def on_toolbutton_newvm_clicked(self, obj):
		self.wTree.get_widget('table_vmname').show()	
		self.wTree.get_widget('combobox_newvm_ostype').set_active(0)
		self.wTree.get_widget('entry_newvm_vmname').set_text('')
		self.wTree.get_widget('entry_newvm_vdiname').set_text('')
		self.update_vdi_combobox('combobox_newvm_vdi', 'disk', False)
		combobox_vdi = self.wTree.get_widget('combobox_newvm_vdi')
		combobox_model = combobox_vdi.get_model()
		combobox_model.append(['--',-100,''])
		combobox_model.append(['New VDI',-2,''])
		combobox_vdi.set_active(0)
		self.entries_newvm_changed = False
		dialog = self.wTree.get_widget('dialog_newvm')
		finished = False
		while not finished:
			if dialog.run() != 0:
				dialog.hide()
				return
			vmname = util.clean_string(self.wTree.get_widget('entry_newvm_vmname').get_text())
			vdinum = combobox_model[combobox_vdi.get_active()][1]
			if vdinum == -2: # New vdi
				vdi_path = self.wTree.get_widget('entry_newvm_vdiname').get_text()
				vdi_size = self.wTree.get_widget('hscale_newvm_vdisize').get_value()
				vdi_dynamic = self.wTree.get_widget('checkbutton_newvm_vdidynamic').get_active()
				if self.vboxmgr.create_hd(vdi_path, vdi_size * 1024, vdi_dynamic): continue
				vdi = self.vdis[-1]
			elif vdinum == -1: vdi = None
			else: vdi = self.vdis[vdinum]
			if self.vboxmgr.create_vm(vmname, vdi, self.get_value_from_combobox('combobox_newvm_ostype',0)): 
				if vdinum == -2: self.vboxmgr.remove_vdi(-1, True) # Remove created vdi
				continue
			finished = True
		self.update_vdi_list('disk') # No need to update combobox, the new VM will be selected
		self.wTree.get_widget('treeview_vmlist').get_model().append([self.vms[-1].name, 'powered off'])
		dialog.hide()
		
	def on_button_newvm_browse_clicked(self, obj):	
		dialog = gtk.FileChooserDialog('Save..', None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
		dialog.set_do_overwrite_confirmation(True)
		dialog.set_current_folder(self.vboxmgr.default_vdi_folder)
		dialog.set_default_response(gtk.RESPONSE_OK)
		if dialog.run() == gtk.RESPONSE_OK: self.wTree.get_widget('entry_newvm_vdiname').set_text(dialog.get_filename())
		dialog.destroy()

	def on_combobox_newvm_ostype_changed(self, obj):
		if self.entries_newvm_changed: return # Do nothing if the user edited the dialog entries
		if obj.get_active() < 1: return 
		ostype = self.get_value_from_combobox(obj.get_name(),0)
		entry_vmname = self.wTree.get_widget('entry_newvm_vmname')
		entry_vdiname = self.wTree.get_widget('entry_newvm_vdiname')
		entry_vmname.set_text(ostype)
		entry_vdiname.set_text(ostype + '.img')
		self.entries_newvm_changed = False
		
	def on_combobox_newvm_vdi_changed(self, obj):
		if self.resetting_vdis: return
		vdi_num = self.get_value_from_combobox('combobox_newvm_vdi',1)
		if vdi_num == -2: new_vdi = True
		else: new_vdi = False
		wlist = [ 'label_newvm_filename', 'label_newvm_size', 'entry_newvm_vdiname', 
				  'button_newvm_vdiname_browse', 'hscale_newvm_vdisize', 'checkbutton_newvm_vdidynamic' ]
		for w in wlist:
			self.wTree.get_widget(w).set_sensitive(new_vdi)
		self.on_entries_newvm_changed(None)

	def on_entries_newvm_changed(self, obj):
		self.entries_newvm_changed = True
		vmname = self.wTree.get_widget('entry_newvm_vmname').get_text()
		vdi_num = self.get_value_from_combobox('combobox_newvm_vdi',1)
		if vdi_num == -2: vdiname = self.wTree.get_widget('entry_newvm_vdiname').get_text()
		else: vdiname = 'doesntmatter'
		self.wTree.get_widget('button_newvm_add').set_sensitive(len(vmname) != 0 and len(vdiname) != 0) # FIXME: check validity of filenames

	def on_toolbutton_deletevm_clicked(self, obj):
		if self.confirm_msg('Are you sure you want to delete the virtual\nmachine "' + self.vms[self.selected_vm].name + '"?', 
			'The machine settings will be lost, but the disks attached\nto the machine will not be deleted.') == gtk.RESPONSE_CANCEL: return
		if self.vboxmgr.delete_vm(self.selected_vm): return
		del self.wTree.get_widget('treeview_vmlist').get_model()[self.selected_vm]
		self.selected_vm = -1
		self.update_vdi_list('disk')
		self.update_vdi_list('dvd')
		self.on_vm_selection_changed(self.wTree.get_widget('treeview_vmlist'))

	def on_toolbutton_run_clicked(self, obj):
		self.vboxmgr.execute_vm(self.selected_vm)
		self.update_state(self.vms[self.selected_vm])

	def on_toolbutton_sleep_clicked(self, obj):
		self.vboxmgr.sleep_vm(self.selected_vm)
		self.update_state(self.vms[self.selected_vm])

	def on_toolbutton_stop_clicked(self, obj):
		self.vboxmgr.stop_vm(self.selected_vm)
		self.update_state(self.vms[self.selected_vm])
	
	## VDI Management
	
	def on_toolbutton_managevdis_clicked(self, obj):
		self.wTree.get_widget('dialog_managevdis').show()

	def on_button_managevdis_close_clicked(self, obj):
		self.wTree.get_widget('dialog_managevdis').hide()

	def on_vdilist_selection_changed(self, obj):
		if obj == self.wTree.get_widget('treeview_manage_disk'): vdi_type = 'disk'
		else: vdi_type = 'dvd'
		sensitive = False
		selected_vdi_cursor = obj.get_cursor()[0]
		if selected_vdi_cursor:
			selected_vdi = obj.get_model()[selected_vdi_cursor[0]][3]
			sensitive = len(self.vdis[selected_vdi].attached_to) == 0
		self.wTree.get_widget('button_managevdis_unregister' + vdi_type).set_sensitive(sensitive)
		if vdi_type == 'disk': self.wTree.get_widget('button_managevdis_delete').set_sensitive(sensitive)
	
	def on_button_managevdis_new_clicked(self, obj):
		dialog = self.wTree.get_widget('dialog_newvm')
		self.select_value_in_combobox('combobox_newvm_vdi',1,-2) # Activate entry for new image name
		self.wTree.get_widget('entry_newvm_vmname').set_text('doesntmatter') # Unlock add button
		self.wTree.get_widget('entry_newvm_vdiname').set_text('')
		self.wTree.get_widget('table_vmname').hide()
		finished = False
		while not finished:
			if dialog.run() != 0:
				dialog.hide()
				return
			vdi_path = self.wTree.get_widget('entry_newvm_vdiname').get_text()
			vdi_size = self.wTree.get_widget('hscale_newvm_vdisize').get_value()
			vdi_dynamic = self.wTree.get_widget('checkbutton_newvm_vdidynamic').get_active()
			if self.vboxmgr.create_hd(vdi_path, vdi_size * 1024, vdi_dynamic): continue
			finished = True
		model_vdilist = self.wTree.get_widget('treeview_manage_disk').get_model()
		model_vdilist.append([util.basename(self.vdis[-1].path), util.location(self.vdis[-1].path), '', len(self.vdis) - 1])
		if self.selected_vm != -1: self.update_vdi_combobox('combobox_disk', 'disk')
		dialog.hide()
	
	def on_button_managevdis_register_clicked(self, obj):	
		dialog = gtk.FileChooserDialog('Open..', None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
		dialog.set_default_response(gtk.RESPONSE_OK)
		if obj == self.wTree.get_widget('button_managevdis_registerdvd'): vdi_type = 'dvd'
		else: 
			vdi_type = 'disk'
			dialog.set_current_folder(self.vboxmgr.default_vdi_folder)
		if dialog.run() == gtk.RESPONSE_CANCEL:
			dialog.destroy()
			return
		self.vboxmgr.add_vdi(dialog.get_filename(), vdi_type)
		model_vdilist = self.wTree.get_widget('treeview_manage_' + vdi_type).get_model()
		model_vdilist.append([util.basename(self.vdis[-1].path), util.location(self.vdis[-1].path), '', len(self.vdis) - 1])
		if self.selected_vm != -1: self.update_vdi_combobox('combobox_' + vdi_type, vdi_type)
		dialog.destroy()

	def on_button_managevdis_unregister_clicked(self, obj):
		remove = False
		if obj == self.wTree.get_widget('button_managevdis_unregisterdvd'): vdi_type = 'dvd'
		else: 
			vdi_type = 'disk'
			if obj == self.wTree.get_widget('button_managevdis_delete'): remove = True
		treeview_vdilist = self.wTree.get_widget('treeview_manage_' + vdi_type)
		selected_vdi_cursor = treeview_vdilist.get_cursor()[0]
		model_vdilist = treeview_vdilist.get_model()
		selected_vdi = model_vdilist[selected_vdi_cursor[0]][3]
		if remove and self.confirm_msg('Are you sure you want to delete the\ndisk image "' + util.basename(self.vdis[selected_vdi].path + '"?'),
									   'The file will be permantently lost.') == gtk.RESPONSE_CANCEL: return
		if self.vboxmgr.remove_vdi(selected_vdi, remove): return
		self.update_vdi_list('disk') # (must update ALL vdi numbers)
		self.update_vdi_list('dvd')  # Not enough to remove row 
		self.on_vdilist_selection_changed(treeview_vdilist)
		if self.selected_vm != -1: self.update_vdi_combobox('combobox_' + vdi_type, vdi_type)

	def update_vdi_list(self, vdi_type): # FIXME: keep selection
		list_model = self.wTree.get_widget('treeview_manage_' + vdi_type).get_model()
		list_model.clear()
		for i, vdi in enumerate(self.vdis):
			if vdi.type != vdi_type: continue
			vdi_owner_names = ''
			if len(vdi.attached_to) != 0:
				for vm in self.vms:
					if vm.hw.hd == vdi or vm.hw.dvd == vdi: 
						if len(vdi_owner_names) > 0: vdi_owner_names += ', '
						vdi_owner_names += vm.name
			list_model.append([util.basename(vdi.path), util.location(vdi.path), vdi_owner_names, i])
	
	def update_vdi_combobox(self, combobox_name, vdi_type, include_owned_by_selected_vm = True):
		self.resetting_vdis = True
		combobox = self.wTree.get_widget(combobox_name)
		combobox_model = combobox.get_model()
		old_active = combobox.get_active()
		if old_active == -1: old_vdi = None
		else: old_vdi = combobox_model[old_active][2] # Use path instead of number, because the numbers will change
		combobox_model.clear()
		combobox_model.append(['(none)',-1,None])
		combobox_model.append(['--',-100,''])
		if self.selected_vm == -1 or not include_owned_by_selected_vm: owned_by_selected_vm = None
		elif vdi_type == 'disk': owned_by_selected_vm = self.vms[self.selected_vm].hw.hd
		else: owned_by_selected_vm = self.vms[self.selected_vm].hw.dvd
		for i, vdi in enumerate(self.vdis):
			if vdi.type != vdi_type: continue
			if vdi_type == 'dvd' or len(vdi.attached_to) == 0 or vdi == owned_by_selected_vm or (vdi != owned_by_selected_vm and len(vdi.uuid) > 1): 
				name = util.basename(vdi.path)
				if len(vdi.uuid) > 1: name += ' (diff)'
				combobox_model.append([name, i, vdi.path])
		if vdi_type == 'dvd': 
			combobox_model.append(['--',-100,''])
			for i, vdi in enumerate(self.vdis):
				if vdi.type == 'hostdvd': combobox_model.append([vdi.path, i, vdi.path])
		self.select_value_in_combobox(combobox_name,2,old_vdi)
		self.resetting_vdis = False

	## Settings tab
	
	def reset_settings(self):
		self.resetting_settings = True
		self.select_value_in_combobox('combobox_ostype',0,self.vms[self.selected_vm].ostype)
		self.wTree.get_widget('checkbutton_hwvirtex').set_active(self.vms[self.selected_vm].hw.hwvirtex)
		self.wTree.get_widget('hscale_ram').set_value(self.vms[self.selected_vm].hw.mem)
		self.wTree.get_widget('hscale_vram').set_value(self.vms[self.selected_vm].hw.vmem)
		if self.vms[self.selected_vm].hw.hd == None: hdpath = None
		else: hdpath = self.vms[self.selected_vm].hw.hd.path
		self.select_value_in_combobox('combobox_disk',2,hdpath)
		if self.vms[self.selected_vm].state == 'running': dvd = self.vms[self.selected_vm].hw.transient_dvd
		else: dvd = self.vms[self.selected_vm].hw.dvd
		if dvd == None: dvdpath = None
		else: dvdpath = dvd.path
		self.select_value_in_combobox('combobox_dvd',2,dvdpath)
		self.wTree.get_widget('checkbutton_dvdpassthrough').set_active(self.vms[self.selected_vm].hw.dvdpassthrough)
		self.resetting_settings = False
		self.update_dvdpassthrough_state()

	def on_settings_changed(self, obj):
		if self.resetting_settings: return
		ostype = self.get_value_from_combobox('combobox_ostype',0)
		hwvirtex = self.wTree.get_widget('checkbutton_hwvirtex').get_active()
		mem = self.wTree.get_widget('hscale_ram').get_value()
		vmem = self.wTree.get_widget('hscale_vram').get_value()
		dvdpassthrough = self.wTree.get_widget('checkbutton_dvdpassthrough').get_active()
		self.vms[self.selected_vm].update_settings(ostype, hwvirtex, mem, vmem, dvdpassthrough)

	def on_combobox_disk_changed(self, obj):
		if self.resetting_vdis or self.resetting_settings: return
		vdi_num = self.get_value_from_combobox('combobox_disk', 1)
		if vdi_num == -1: vdi = None
		else: vdi = self.vdis[vdi_num]
		self.vboxmgr.update_hd(self.selected_vm, vdi)
		self.update_vdi_list('disk')
		
	def on_combobox_dvd_changed(self, obj):
		if self.resetting_vdis or self.resetting_settings: return
		vdi_num = self.get_value_from_combobox('combobox_dvd', 1)
		if vdi_num == -1: vdi = None
		else: vdi = self.vdis[vdi_num]
		self.vboxmgr.update_dvd(self.selected_vm, vdi)
		self.update_vdi_list('dvd')
		self.update_dvdpassthrough_state()
				
	def update_dvdpassthrough_state(self):
		sensitive = False
		combo = self.wTree.get_widget('combobox_dvd')
		if combo.state != gtk.STATE_INSENSITIVE: 
			vdi_num = self.get_value_from_combobox('combobox_dvd',1)
			sensitive = self.vms[self.selected_vm].state == 'powered off' and vdi_num != -1 and self.vdis[vdi_num].type == 'hostdvd'
		self.wTree.get_widget('checkbutton_dvdpassthrough').set_sensitive(sensitive)

	## Devices tab

	def reset_devices(self):
		self.resetting_devices = True
		self.wTree.get_widget('combobox_nicdevice').set_active(0) # Always have a value != -1 in the combobox
		self.select_value_in_combobox('combobox_audiodev',0,self.vms[self.selected_vm].hw.audiodev)
		if self.wTree.get_widget('entry_mac').get_text() == '': self.on_button_generatemac_clicked(None)
		if self.vms[self.selected_vm].hw.nics[0] == None: self.select_value_in_combobox('combobox_nic',0,'none')
		else:
			self.select_value_in_combobox('combobox_nic',0,self.vms[self.selected_vm].hw.nics[0].attachment)
			self.select_value_in_combobox('combobox_nicdevice',0,self.vms[self.selected_vm].hw.nics[0].device)
			self.wTree.get_widget('checkbutton_cableconnected').set_active(self.vms[self.selected_vm].hw.nics[0].cable)
			self.wTree.get_widget('entry_intnet').set_text(self.vms[self.selected_vm].hw.nics[0].intnet)
			self.wTree.get_widget('entry_mac').set_text(self.vms[self.selected_vm].hw.nics[0].mac)
			if self.vms[self.selected_vm].hw.nics[0].tracefile != '<NULL>': 
				self.wTree.get_widget('entry_tracefile').set_text(self.vms[self.selected_vm].hw.nics[0].tracefile)
			self.wTree.get_widget('checkbutton_trace').set_active(self.vms[self.selected_vm].hw.nics[0].trace)
		self.resetting_devices = False
		self.update_nic_config_state()
	
	def on_button_generatemac_clicked(self, obj):
		self.wTree.get_widget('entry_mac').set_text(util.random_mac())

	def on_button_tracefile_browse_clicked(self, obj):
		dialog = gtk.FileChooserDialog('Save..', None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
		dialog.set_do_overwrite_confirmation(True)
		dialog.set_default_response(gtk.RESPONSE_OK)
		if dialog.run() == gtk.RESPONSE_OK: self.wTree.get_widget('entry_newvm_vdiname').set_text(dialog.get_filename())
		dialog.destroy()
	
	def on_devices_changed(self, obj):
		if self.resetting_devices: return
		audiodev = self.get_value_from_combobox('combobox_audiodev',0)
		mac = self.wTree.get_widget('entry_mac').get_text()
		attachment = self.get_value_from_combobox('combobox_nic',0)
		device = self.get_value_from_combobox('combobox_nicdevice',0)
		cable = self.wTree.get_widget('checkbutton_cableconnected').get_active()		
		tracefile = self.wTree.get_widget('entry_tracefile').get_text()
		trace = self.wTree.get_widget('checkbutton_trace').get_active() and tracefile != '' # FIXME: check validity
		intnet = self.wTree.get_widget('entry_intnet').get_text()
		hostif = self.wTree.get_widget('entry_hostif').get_text() # FIXME: use it
		self.vms[self.selected_vm].update_devices(audiodev, 0, mac, attachment, device, cable, trace, tracefile, intnet)
	
	def on_devices_nic_changed(self, obj):
		if self.resetting_devices: return
		self.update_nic_config_state()
		self.on_devices_changed(obj)

	def on_devices_trace_changed(self, obj):
		if self.resetting_devices: return
		self.update_tracefile_state()
		self.on_devices_changed(obj)

	def update_nic_config_state(self):
		nic_active = self.wTree.get_widget('combobox_nic').state != gtk.STATE_INSENSITIVE
		net = self.get_value_from_combobox('combobox_nic',0)
		net_widg = ['label_devices_1','combobox_nicdevice','label_devices_6','entry_mac','button_generatemac',
					'checkbutton_cableconnected','checkbutton_trace']
		for w in net_widg:
			self.wTree.get_widget(w).set_sensitive(net != 'none' and nic_active)	
		self.wTree.get_widget('label_devices_2').set_sensitive(net == 'hostif' and nic_active)
		self.wTree.get_widget('entry_hostif').set_sensitive(net == 'hostif' and nic_active)
		self.wTree.get_widget('label_devices_3').set_sensitive(net == 'intnet' and nic_active)
		self.wTree.get_widget('entry_intnet').set_sensitive(net == 'intnet' and nic_active)
		trace = self.wTree.get_widget('checkbutton_trace')
		self.wTree.get_widget('entry_tracefile').set_sensitive(trace.get_active() and net != 'none' and nic_active)
		self.wTree.get_widget('button_tracefile_browse').set_sensitive(trace.get_active() and net != 'none' and nic_active)
		self.wTree.get_widget('label_devices_4').set_sensitive(trace.get_active() and net != 'none' and nic_active)
		self.update_tracefile_state() 
		
	def update_tracefile_state(self):
		trace = self.wTree.get_widget('checkbutton_trace')
		sensitive = trace.state != gtk.STATE_INSENSITIVE and trace.get_active()
		self.wTree.get_widget('label_devices_5').set_sensitive(sensitive)
		self.wTree.get_widget('entry_tracefile').set_sensitive(sensitive)
		self.wTree.get_widget('button_tracefile_browse').set_sensitive(sensitive)
		self.wTree.get_widget('label_devices_4').set_sensitive(sensitive)
		
	## Shared folders tab & dialog

	def reset_shared(self):
		sharedlist = self.wTree.get_widget('treeview_shared')
		model_sharedlist = sharedlist.get_model()
		model_sharedlist.clear()
		added_separator = False
		for i, shared in enumerate(self.vms[self.selected_vm].hw.shared_folders):
			if shared.transient and not added_separator: 
				model_sharedlist.append(['/////', '/////', False, True, -1])
				added_separator = True
			model_sharedlist.append([shared.name, shared.path, shared.writable, shared.transient, i])
		self.update_shared_remove_state()

	def on_entry_shared_name_changed(self, obj):
		sharedname = self.wTree.get_widget('entry_shared_name').get_text()
		self.wTree.get_widget('button_addshared_add').set_sensitive(len(sharedname) != 0) # FIXME: check validity

	def on_sharedlist_selection_changed(self, obj):
		self.update_shared_remove_state()
			
	def update_shared_remove_state(self):
		treeview_sharedlist = self.wTree.get_widget('treeview_shared')
		model_sharedlist = treeview_sharedlist.get_model()
		selected_shared_cursor = treeview_sharedlist.get_cursor()[0]
		sensitive = False
		if selected_shared_cursor != None:
			if self.vms[self.selected_vm].state == 'running':
				shared_num = model_sharedlist[selected_shared_cursor[0]][4]
				sensitive = self.vms[self.selected_vm].hw.shared_folders[shared_num].transient
			else: sensitive = self.vms[self.selected_vm].state != 'saved'
		self.wTree.get_widget('button_shared_remove').set_sensitive(sensitive)		
	
	def on_button_shared_add_clicked(self, obj):
		dialog = self.wTree.get_widget('dialog_addshared')
		finished = False
		while not finished:
			if dialog.run() != 0:
				dialog.hide()
				return
			name = self.wTree.get_widget('entry_shared_name').get_text();
			path = self.wTree.get_widget('filechooserbutton_shared_path').get_filename();
			writable = self.wTree.get_widget('checkbutton_shared_writable').get_active();
			if self.vboxmgr.add_shared(self.selected_vm, name, path, writable) == 1: continue
			finished = True
		shared = self.vms[self.selected_vm].hw.shared_folders[-1]
		model_sharedlist = self.wTree.get_widget('treeview_shared').get_model()
		if shared.transient and (len(model_sharedlist) == 0 or not model_sharedlist[-1][3]):
			model_sharedlist.append(['/////', '/////', False, True, -1])
		total_shared = len(self.vms[self.selected_vm].hw.shared_folders)
		model_sharedlist.append([shared.name, shared.path, shared.writable, shared.transient, total_shared - 1])
		dialog.hide()

	def on_button_shared_remove_clicked(self, obj):
		treeview_sharedlist = self.wTree.get_widget('treeview_shared')
		model_sharedlist = treeview_sharedlist.get_model()
		shared_num = model_sharedlist[treeview_sharedlist.get_cursor()[0][0]][4]
		if self.vboxmgr.del_shared(self.selected_vm, shared_num) == 1: return
		self.reset_shared() # Not enough to remove row; must recalculate the shared number

	## Snapshots tab

	def reset_snapshots(self):
		snapshotlist = self.wTree.get_widget('treeview_snapshots')
		model_snapshotlist = snapshotlist.get_model()
		model_snapshotlist.clear()
		for snapshot in self.vms[self.selected_vm].snapshots:
			model_snapshotlist.append([snapshot.name])
		self.on_snapshotlist_selection_changed(snapshotlist)
		self.update_snapshot_revert_state()
		
	def on_snapshotlist_selection_changed(self, obj):
		self.update_snapshot_discard_state()

	def update_snapshot_discard_state(self):
		selected_snapshot_cursor = self.wTree.get_widget('treeview_snapshots').get_cursor()[0]
		sensitive = selected_snapshot_cursor != None and self.vms[self.selected_vm].state != 'running'
		self.wTree.get_widget('button_snapshots_discard').set_sensitive(sensitive)
		
	def update_snapshot_revert_state(self):
		treeview_snapshots = self.wTree.get_widget('treeview_snapshots')
		model_snapshotlist = treeview_snapshots.get_model()
		sensitive = len(model_snapshotlist) > 0 and self.vms[self.selected_vm].state != 'running'
		self.wTree.get_widget('button_snapshots_revert').set_sensitive(sensitive)

	def on_snapshotname_edited(self, cell, path, new_name):
		if len(new_name) == 0: return
		if self.vboxmgr.edit_snapshot(self.selected_vm, int(path), new_name): return
		model = self.wTree.get_widget('treeview_snapshots').get_model()
		model[path][0] = new_name
		
	def on_button_snapshots_take_clicked(self, obj):
		treeview = self.wTree.get_widget('treeview_snapshots')
		model_snapshotlist = treeview.get_model()
		new_name = util.clean_string('Snapshot ' + datetime.datetime.now().ctime())
		if len(model_snapshotlist) > 0 and new_name == model_snapshotlist[-1][0]: new_name += '0' # FIXME, stupid
		if self.vboxmgr.take_snapshot(self.selected_vm, new_name): return
		model_snapshotlist.append([new_name])
		self.update_snapshot_revert_state()
		self.update_vdi_combobox('combobox_disk','disk')

	def on_button_snapshots_discard_clicked(self, obj):
		treeview_snapshots = self.wTree.get_widget('treeview_snapshots')
		selected_snapshot_cursor = treeview_snapshots.get_cursor()[0]
		selected_snapshot = selected_snapshot_cursor[0]
		if self.vboxmgr.discard_snapshot(self.selected_vm, selected_snapshot): return 1
		model_snapshotlist = treeview_snapshots.get_model()
		del model_snapshotlist[selected_snapshot_cursor]
		self.update_snapshot_discard_state()
		self.update_snapshot_revert_state()
		self.update_vdi_combobox('combobox_disk','disk')
		
	def on_button_snapshots_revert_clicked(self, obj):
		self.vboxmgr.discard_current_state(self.selected_vm)
		self.reset_everything()
		
	## Aux functions

	def update_state(self, vm):
		model_vmlist = self.wTree.get_widget('treeview_vmlist').get_model()
		model_vmlist.set_value(model_vmlist.get_iter(self.vms.index(vm)), 1, vm.state);
		if self.vms[self.selected_vm] == vm:
			for w in self.state_for_widgets:
				self.wTree.get_widget(w[0]).set_sensitive(vm.state in w[1]) # Widgets that only depend on VM state
			self.update_dvdpassthrough_state()
			self.update_nic_config_state()
			self.update_shared_remove_state()
			self.update_snapshot_discard_state()
			self.update_snapshot_revert_state()
		if vm.state == 'powered off': # Revert transient changes
			self.reset_settings() # FIXME: only revert dvd transient changes, not everything
			self.reset_shared()

	def show_msg(self, msg, from_thread, is_error):
		if from_thread: gtk.gdk.threads_enter()
		if is_error: msg_type = gtk.MESSAGE_ERROR
		else: msg_type = gtk.MESSAGE_INFO
		dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, msg_type, gtk.BUTTONS_CLOSE, msg)
		dialog.run()
		dialog.destroy()
		if from_thread: gtk.gdk.threads_leave()

	def show_msg_module_error(self, from_thread):
		self.show_msg('Module not loaded', from_thread, True) # FIXME: gksu load module
		return 1

	def confirm_msg(self, msg1, msg2):
		dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, msg1)
		dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_DELETE, gtk.RESPONSE_OK)
		dialog.format_secondary_text(msg2)
		decision = dialog.run()
		dialog.destroy()
		return decision
			
	def get_value_from_combobox(self, combobox_name, column):
		combobox = self.wTree.get_widget(combobox_name)
		model = combobox.get_model()
		if combobox.get_active() == -1: return None
		return model.get_value(model.get_iter(combobox.get_active()), column)

	def select_value_in_combobox(self, combobox_name, column, value):
		combobox = self.wTree.get_widget(combobox_name)
		model = combobox.get_model()
		for i, row in enumerate(model):
			if row[column] == value:
				combobox.set_active(i)
				break

	######## List and combobox models

	def combobox_separator_func(self, model, row_iter):
		if model.get_value(row_iter, 1) == -100: return True
		return False

	def treeview_separator_func(self, model, row_iter):
		if model.get_value(row_iter, 1) == '/////': return True
		return False

	def create_tree_and_combobox_models(self):
		treeview = self.wTree.get_widget('treeview_vmlist')
		treeview.insert_column_with_attributes(0, 'VM Name', gtk.CellRendererText(), text = 0)
		treeview.insert_column_with_attributes(1, 'State', gtk.CellRendererText(), text = 1)
		treeview.connect('cursor_changed', self.on_vm_selection_changed)
		treeview.connect('row_activated', self.on_vmlist_row_activated)
		treemodel = gtk.ListStore(str,str)
		treeview.set_model(treemodel)
		for vm_elem in self.vms:
			treemodel.append([vm_elem.name, vm_elem.state])

		treeview = self.wTree.get_widget('treeview_shared')
		treeview.insert_column_with_attributes(0, 'Name', gtk.CellRendererText(), text = 0)
		treeview.insert_column_with_attributes(1, 'Folder', gtk.CellRendererText(), text = 1)
		treeview.insert_column_with_attributes(2, 'Writable', gtk.CellRendererToggle(), active = 2)
		treeview.connect('cursor_changed', self.on_sharedlist_selection_changed)
		treemodel = gtk.ListStore(str,str,bool,bool,int)
		treeview.set_row_separator_func(self.treeview_separator_func)
		treeview.set_model(treemodel)
		
		treeview = self.wTree.get_widget('treeview_snapshots')
		cell = gtk.CellRendererText()
		cell.set_property('editable',True)
		cell.connect('edited', self.on_snapshotname_edited)
		treeview.connect('cursor_changed', self.on_snapshotlist_selection_changed)
		treeview.insert_column_with_attributes(0, 'Description', cell, text = 0)
		treemodel = gtk.ListStore(str)
		treeview.set_model(treemodel)

		treeview = self.wTree.get_widget('treeview_manage_disk')
		treeview.insert_column_with_attributes(0, 'Name', gtk.CellRendererText(), text = 0)
		treeview.insert_column_with_attributes(1, 'Location', gtk.CellRendererText(), text = 1)
		treeview.insert_column_with_attributes(2, 'Used by', gtk.CellRendererText(), text = 2)
		treeview.connect('cursor_changed', self.on_vdilist_selection_changed)
		treemodel = gtk.ListStore(str,str,str,int)
		treeview.set_model(treemodel)
	
		treeview = self.wTree.get_widget('treeview_manage_dvd')
		treeview.insert_column_with_attributes(0, 'Name', gtk.CellRendererText(), text = 0)
		treeview.insert_column_with_attributes(1, 'Location', gtk.CellRendererText(), text = 1)
		treeview.insert_column_with_attributes(2, 'Used by', gtk.CellRendererText(), text = 2)
		treeview.connect('cursor_changed', self.on_vdilist_selection_changed)
		treemodel = gtk.ListStore(str,str,str,int)
		treeview.set_model(treemodel)

		combobox = self.wTree.get_widget('combobox_ostype')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 1)
		comboboxmodel = gtk.ListStore(str, str)
		combobox.set_model(comboboxmodel)
		for ostype_elem in self.vboxmgr.ostypes:
			comboboxmodel.append([ostype_elem[0], ostype_elem[1]])
		
		combobox = self.wTree.get_widget('combobox_newvm_ostype')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 1)
		comboboxmodel = gtk.ListStore(str, str)
		combobox.set_model(comboboxmodel)
		for ostype_elem in self.vboxmgr.ostypes:
			comboboxmodel.append([ostype_elem[0], ostype_elem[1]])

		combobox = self.wTree.get_widget('combobox_nic')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 1)
		comboboxmodel = gtk.ListStore(str, str)
		combobox.set_model(comboboxmodel)
		for nic_elem in self.vboxmgr.attachments:
			comboboxmodel.append([nic_elem[0], nic_elem[1]])

		combobox = self.wTree.get_widget('combobox_nicdevice')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 1)
		comboboxmodel = gtk.ListStore(str, str)
		combobox.set_model(comboboxmodel)
		for nic_elem in self.vboxmgr.nicdevices:
			comboboxmodel.append([nic_elem[0], nic_elem[1]])

		combobox = self.wTree.get_widget('combobox_audiodev')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 1)
		comboboxmodel = gtk.ListStore(str, str)
		combobox.set_model(comboboxmodel)
		for audiodev_elem in self.vboxmgr.audiodevs:
			comboboxmodel.append([audiodev_elem[0], audiodev_elem[1]])

		combobox = self.wTree.get_widget('combobox_newvm_vdi')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 0)
		comboboxmodel = gtk.ListStore(str, int, str)
		combobox.set_model(comboboxmodel)
		combobox.set_row_separator_func(self.combobox_separator_func)
		comboboxmodel.append(['New VDI',-2, ''])
	
		combobox = self.wTree.get_widget('combobox_disk')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 0)
		comboboxmodel = gtk.ListStore(str, int, str)
		combobox.set_model(comboboxmodel)
		combobox.set_row_separator_func(self.combobox_separator_func)

		combobox = self.wTree.get_widget('combobox_dvd')
		combobox_cell = gtk.CellRendererText()
		combobox.pack_start(combobox_cell, True)
		combobox.add_attribute(combobox_cell, 'text', 0)
		comboboxmodel = gtk.ListStore(str, int, str)
		combobox.set_model(comboboxmodel)
		combobox.set_row_separator_func(self.combobox_separator_func)

## End of class

if __name__ == '__main__': 
	vboxdir = os.path.expanduser('~') + '/.VirtualBox/'
	if not os.path.isdir(vboxdir):
		os.mkdir(vboxdir)
		
	#lockfile = vboxdir + '.lock.client'
	#if os.path.isfile(lockfile):
	#	print 'Lock file detected, exiting.'
	#	exit(1)
	#f = open(lockfile,'w')
	#f.close()
	
	if sys.argv[-1] == '-t':
		vboxrunner = vboxrunner_sdl_thr.VBoxRunnerSDLThreaded()
	else:
		vboxrunner = vboxrunner_sdl_cs.VBoxRunnerSDLClient()

	vboxmgr = vboxmgr.VBoxMgr(vboxrunner)
	VBoxGtk(vboxmgr)
	gtk.gdk.threads_init()
	gtk.main()
	
	#os.remove(lockfile)
