#!/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_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').run()
        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_OK:
            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)
