#! /usr/bin/env python
# (c) Copyright 2009-2010. CodeWeavers, Inc.

import os

# Portable which(1) implementation
def which(path, app):
    """Looks for an executable in the specified directory list.

    path is an os.pathsep-separated list of directories and app is the
    executable name. If app contains a path separator then path is ignored.
    If the file is not found, then None is returned.
    """
    if os.path.isabs(app):
        if os.path.isfile(app) and os.access(app, os.X_OK):
            return app
    elif os.sep in app or (os.altsep and os.altsep in app):
        app_path = os.path.join(os.getcwd(), app)
        if os.path.isfile(app_path) and os.access(app_path, os.X_OK):
            return app_path
    else:
        for directory in path.split(os.pathsep):
            if directory == "":
                continue
            app_path = os.path.join(directory, app)
            if os.path.isfile(app_path) and os.access(app_path, os.X_OK):
                return app_path
    return None

import sys
def locate_cx_root():
    """Locate where CrossOver is installed.

    We start by locating our own python script file and walking back up the
    path, traversing symbolic links on the way. Then we verify what we have
    found the right directory by checking for the presence of the cxmenu
    script.
    """
    # pylint: disable-msg=I0011,W0601,W0603
    global CX_ROOT
    if "CX_DEVELOP_ROOT" in os.environ:
        CX_ROOT = os.environ["CX_DEVELOP_ROOT"]
        return

    # figure out argv0
    argv0 = which(os.environ["PATH"], sys.argv[0])
    if not argv0:
        argv0 = sys.argv[0]
        if not os.path.isabs(argv0):
            argv0 = os.path.join(os.getcwd(), argv0)

    # traverse the symbolic links
    dir0 = os.path.dirname(argv0)
    while True:
        if dir0.endswith("/lib"):
            bindir = dir0[0:-3] + "bin"
        else:
            bindir = dir0
        landmark = os.path.join(bindir, "cxmenu")
        if os.path.isfile(landmark):
            break
        if not os.path.islink(argv0):
            break
        argv0 = os.readlink(argv0)
        if not os.path.isabs(argv0):
            argv0 = os.path.join(dir0, argv0)
        dir0 = os.path.dirname(argv0)

    # compute CX_ROOT
    CX_ROOT = os.path.dirname(os.path.normpath(bindir))

    # check CX_ROOT
    landmark = os.path.join(CX_ROOT, "bin", "cxmenu")
    if not os.path.isfile(landmark) or not os.access(landmark, os.X_OK):
        sys.stderr.write("%s:error: could not find CrossOver in '%s'\n" % (os.path.dirname(sys.argv[0]), CX_ROOT))
        sys.exit(1)

    sys.path.append(os.path.join(CX_ROOT, "lib", "python"))

locate_cx_root()
import cxutils
cxutils.CX_ROOT = CX_ROOT


import subprocess
import traceback

import checkgtk
checkgtk.check_gtk()

import gobject
import gtk
import gtk.glade
gtk.gdk.threads_init()

import crossover
import cxlog
import distversion
import bottlecollection
import bottlequery
import bottlemanagement
import packagebottledialog
import cxmenu
import cxassoc

import pyop
import cxguitools
import runcommanddlg
import installwizard
# This only looks like it's doing nothing
# pylint: disable-msg=W0611
import cheatconsole

# for localization
from cxutils import cxgettext as _
import cxopt


class BottleManagerController(object):

    mainWindow = None
    xml = None
    controlPanelListModel = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str)
    installedPackagesListModel = gtk.ListStore(str, gobject.TYPE_PYOBJECT)

    modeldialogxml = None
    animateFunctions = []

    def __init__(self):

        try:
            gladepath = os.environ["CX_GLADEPATH"]
        except KeyError:
            gladepath = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")

        self.gladefile = os.path.join(gladepath,"bottlemanager.glade")

        if not gtk.glade.textdomain() == "crossover":
            locale_path = os.path.join(CX_ROOT, "share", "locale")
            gtk.glade.bindtextdomain("crossover", locale_path)
            gtk.glade.textdomain("crossover")
        self.xml = gtk.glade.XML(self.gladefile)
        self.xml.signal_autoconnect(self)

        self.mainWindow = self.xml.get_widget('BottleManagerDialog')

        global_config = crossover.get_config()
        size = global_config["OfficeSetup"].get("BottleManagerSize")
        if size:
            width = height = None
            if size.count('x') == 1:
                widthstr, heightstr = size.split('x', 1)
                if widthstr.isdigit() and heightstr.isdigit():
                    width = int(widthstr)
                    height = int(heightstr)
                    self.mainWindow.set_default_size(width, height)
            if width is None:
                cxlog.warn("Invalid BottleManagerSize %s" % cxlog.debug_str(size))

        self.mainWindow.show()

        #------------------------------------------------------
        # Parse Arguments
        #------------------------------------------------------
        opt_parser = cxopt.Parser(usage="%prog [--restore RESTORE] [--help]",
                                  description="CrossOver's configuration and bottle management tool.")
        opt_parser.add_option("--restore", dest="restore", help="Restores the bottle contained in the specified archive")
        (self.options, args) = opt_parser.parse_args()
        if args:
            opt_parser.error("unexpected argument '%s'" % args[0])

        bottleprogbar = self.xml.get_widget("BottleTabProgBar")
        bottleprogbar.set_pulse_step(.05)
        self.addAnimationFunction(bottleprogbar.pulse)

        cplprogbar = self.xml.get_widget("ControlPanelTabProgbar")
        cplprogbar.set_pulse_step(.05)
        self.addAnimationFunction(cplprogbar.pulse)

        cplprogbar = self.xml.get_widget("ApplicationTabProgBar")
        cplprogbar.set_pulse_step(.05)
        self.addAnimationFunction(cplprogbar.pulse)

        bottlecollection.sharedCollection(bottlecollection.ALL_INFO)

        listviewwidget = self.xml.get_widget("BottlePickList")

        if listviewwidget:
            renderer = gtk.CellRendererText()
            renderer.set_property('editable', True)
            renderer.connect('edited', self.bottle_name_edited)
            column = gtk.TreeViewColumn(_("Bottle"), renderer, text = 0)
            column.set_resizable(True)
            listviewwidget.append_column(column)
            listviewwidget.get_column(0).set_sort_column_id(0)

            column = gtk.TreeViewColumn(_("Status"), gtk.CellRendererText(), text = 1)
            column.set_resizable(True)
            listviewwidget.append_column(column)
            listviewwidget.get_column(1).set_sort_column_id(1)

            self.bottleListStore = gtk.ListStore(str, str)
            self.bottleListStore.set_sort_column_id(0, gtk.SORT_ASCENDING)
            listviewwidget.set_model(self.bottleListStore)

        self.bottles_being_renamed = {}

        installedpackageswidget = self.xml.get_widget("InstalledApplicationsListview")
        if installedpackageswidget:
            column = gtk.TreeViewColumn(_("Installed Applications"), gtk.CellRendererText(), text=0)
            installedpackageswidget.append_column(column)
            installedpackageswidget.get_column(0).set_sort_column_id(0)

            self.installedPackagesListModel.set_sort_column_id(0, gtk.SORT_ASCENDING)

            installedpackageswidget.set_model(self.installedPackagesListModel)

        cpllistviewwidget = self.xml.get_widget("ControlPanelListview")

        if cpllistviewwidget:
            column = gtk.TreeViewColumn(_("Icon"), gtk.CellRendererPixbuf(), pixbuf=0)
            column.set_resizable(False)
            cpllistviewwidget.append_column(column)

            column = gtk.TreeViewColumn(_("Name"), gtk.CellRendererText(), text=1)
            column.set_resizable(True)
            cpllistviewwidget.append_column(column)
            cpllistviewwidget.get_column(1).set_sort_column_id(1)

            self.controlPanelListModel.set_sort_column_id(1, gtk.SORT_ASCENDING)

            if hasattr(cpllistviewwidget, 'set_tooltip_column'):
                # GTK >= 2.12
                cpllistviewwidget.set_tooltip_column(2)

            cpllistviewwidget.set_model(self.controlPanelListModel)
            self.updateBottleList()

        gobject.timeout_add(100, self.animate)
        self.disableMakeDefault = False

        bottlecollection.sharedCollection().addChangeDelegate(self)
        bottlecollection.sharedCollection().addBottleChangeDelegate(self)

        if distversion.HAS_MULTI_USER:
            self.xml.get_widget("publishBottleWidgets").show()
            if crossover.is_root_install():
                self.xml.get_widget("publishBottleButton").show()
            else:
                self.xml.get_widget("publishBottleButton").hide()
        else:
            self.xml.get_widget("publishBottleWidgets").hide()

        cxguitools.set_widget_background(self.xml.get_widget("BottleTabBox"),
            "bottleManagerWatermark.png")
        cxguitools.set_widget_background(self.xml.get_widget("ApplicationsTabBox"),
            "bottleManagerWatermark.png")
        cxguitools.set_widget_background(self.xml.get_widget("ControlPanelTabBox"),
            "bottleManagerWatermark.png")
        cxguitools.set_widget_background(self.xml.get_widget("AdvancedTabBox"),
            "bottleManagerWatermark.png")

        versionString = "%s %s" % (distversion.PRODUCT_NAME, distversion.PUBLIC_VERSION)
        self.xml.get_widget("VersionLabel").set_text(versionString)

        if self.options.restore:
            RestoreArchiveController(self, self.options.restore)

    def selectedBottleName(self):
        bottlelistviewwidget = self.xml.get_widget("BottlePickList")
        cursor =  bottlelistviewwidget.get_cursor()
        if not cursor:
            return None

        if len(self.bottleListStore) == 0:
            return None

        selectedIter = self.bottleListStore.get_iter(cursor[0])
        selectedBottleName = self.bottleListStore.get_value(selectedIter, 0)
        return selectedBottleName

    def selectedBottleObject(self):
        collection = bottlecollection.sharedCollection()
        selectedName = self.selectedBottleName
        if selectedName:
            selectedBottleObject = collection.bottleObject(self.selectedBottleName())
            return selectedBottleObject
        else:
            return None

    def bottle_selection_changed(self, _treeview):
        if self.selectedBottleName():
            self.updateBottleTab()
            self.updateControlPanelTab()
            self.updateInstalledPackagesTab()
            self.update_sensitivities(None)
            self.xml.get_widget('QuitBottleButton').set_sensitive(True)
            self.xml.get_widget('ForceQuitButton').set_sensitive(False)
            bottlecollection.boost_priority(self.selectedBottleObject())


    def updateBottleList(self):
        collection = bottlecollection.sharedCollection()

        updated_bottle_names = set(collection.bottleList())

        bottlelistviewwidget = self.xml.get_widget("BottlePickList")

        previous_selection = bottlelistviewwidget.get_cursor()[0]

        # get the current list of bottle names
        current_bottle_names = []
        iterator = self.bottleListStore.get_iter_first()
        while iterator:
            current_bottle_names.append(self.bottleListStore.get_value(iterator, 0))
            iterator = self.bottleListStore.iter_next(iterator)

        # update the current bottles, yes this is O(n**2)
        for bottle_name in current_bottle_names:
            iterator = self.bottleListStore.get_iter_first()
            while iterator:
                if self.bottleListStore.get_value(iterator, 0) == bottle_name:
                    if bottle_name in updated_bottle_names:
                        bottle_obj = collection.bottleObject(bottle_name)
                        self.bottleListStore.set_value(iterator, 1, bottle_obj.status)
                        updated_bottle_names.remove(bottle_name)
                    elif bottle_name in self.bottles_being_renamed and \
                            self.bottles_being_renamed[bottle_name] in updated_bottle_names:
                        new_name = self.bottles_being_renamed[bottle_name]
                        bottle_obj = collection.bottleObject(new_name)
                        self.bottleListStore.set_value(iterator, 0, new_name)
                        self.bottleListStore.set_value(iterator, 1, bottle_obj.status)
                        updated_bottle_names.remove(new_name)
                        del self.bottles_being_renamed[bottle_name]
                    else:
                        self.bottleListStore.remove(iterator)
                    break
                iterator = self.bottleListStore.iter_next(iterator)

        # add any new bottles
        for bottle_name in updated_bottle_names:
            bottle_obj = collection.bottleObject(bottle_name)
            newrow = self.bottleListStore.append()
            self.bottleListStore.set_value(newrow, 0, bottle_name)
            self.bottleListStore.set_value(newrow, 1, bottle_obj.status)

        if len(collection.bottleList()) == 0:
            self.xml.get_widget("BottleListWindow").hide()
            self.xml.get_widget("BottleInfoNotebook").hide()
            self.xml.get_widget("NoBottlesFrame").show_all()
            self.xml.get_widget("DeleteBottleButton").set_sensitive(False)
            self.xml.get_widget("AddDuplicateButton").set_sensitive(False)
        else:
            self.xml.get_widget("BottleListWindow").show()
            self.xml.get_widget("BottleInfoNotebook").show()
            self.xml.get_widget("NoBottlesFrame").hide()
            self.xml.get_widget("DeleteBottleButton").set_sensitive(True)
            self.xml.get_widget("AddDuplicateButton").set_sensitive(True)

            # Make sure something is selected
            if bottlelistviewwidget.get_cursor()[0] is None:
                if previous_selection is not None:
                    if previous_selection >= len(collection.bottleList()):
                        previous_selection = len(collection.bottleList())-1
                    bottlelistviewwidget.set_cursor(previous_selection)
                else:
                    bottlelistviewwidget.set_cursor(0)

    def updateBottleTab(self, _userData=None):
        bottleObj = self.selectedBottleObject()

        if bottleObj.bottle_info_ready:
            typeLabel = self.xml.get_widget('type')
            if typeLabel:
                typeLabel.set_text(bottleObj.bottletemplate)

            descriptionText = self.xml.get_widget('description')
            if descriptionText:
                descriptionText.set_text(bottleObj.current_description)
        else:
            pass

        self.update_sensitivities(None)


    def updateInstalledPackagesTab(self):
        bottleObj = self.selectedBottleObject()
        self.xml.get_widget("InstalledApplicationsListview")

        if bottleObj.installed_packages_ready:
            self.installedPackagesListModel.clear()
            for package in bottleObj.installed_packages.values():
                newrow = self.installedPackagesListModel.append()
                self.installedPackagesListModel.set_value(newrow, 0, package.name)
                self.installedPackagesListModel.set_value(newrow, 1, package.uninstallcommand)
        else:
            pass

    def package_selection_changed(self, _treeview):
        packagelistviewwidget = self.xml.get_widget("InstalledApplicationsListview")
        tmodel, titer =  packagelistviewwidget.get_selection().get_selected()
        if not tmodel or not titer:
            uninstallerArgs = None
        else:
            uninstallerArgs = tmodel.get_value(titer, 1)

        if uninstallerArgs:
            self.xml.get_widget("RemoveButton").set_sensitive(True)
        else:
            self.xml.get_widget("RemoveButton").set_sensitive(False)

    def uninstall_package(self, _caller=None):
        packagelistviewwidget = self.xml.get_widget("InstalledApplicationsListview")
        tmodel, titer =  packagelistviewwidget.get_selection().get_selected()
        if not tmodel or not titer:
            return

        selectedBottleObject = self.selectedBottleObject()
        uninstallerArgs = tmodel.get_value(titer, 1)

        if not uninstallerArgs or not selectedBottleObject:
            return

        RandROp = LaunchRepairOrRemoveOperation(selectedBottleObject, uninstallerArgs, self)
        pyop.sharedOperationQueue.enqueue(RandROp)


    pixbufcache = {}
    # FIXME: Naming does not follow PEP8, is inconsistent with other functions
    #        Caused by gtk framework? If so document.
    def updateControlPanelTab(self, _userData=None):
        bottleObj = self.selectedBottleObject()

        if bottleObj.control_panel_ready:
            self.controlPanelListModel.clear()
            cpltable = bottleObj.control_panel_table
            for applet in cpltable:
                newrow = self.controlPanelListModel.append()
                self.controlPanelListModel.set_value(newrow, 1, applet["name"])
                self.controlPanelListModel.set_value(newrow, 2, applet["description"])

                # Hang on to the executable name -- we'll want it later
                self.controlPanelListModel.set_value(newrow, 3, applet["exe"])

                iconpath = applet["icon"]
                if iconpath != "":
                    try:
                        pixbuf = self.pixbufcache[iconpath]
                    except KeyError:
                        pixbuf = gtk.gdk.pixbuf_new_from_file(iconpath)
                        pixbuf = pixbuf.scale_simple(24, 24, gtk.gdk.INTERP_BILINEAR)
                        self.pixbufcache[iconpath] = pixbuf

                    if pixbuf:
                        self.controlPanelListModel.set_value(newrow, 0, pixbuf)
        else:
            pass

        self.update_sensitivities(None)


    def launch_control_panel_applet(self, _caller, _path=None, _view_column=None):
        cpllistviewwidget = self.xml.get_widget("ControlPanelListview")
        tmodel, titer =  cpllistviewwidget.get_selection().get_selected()
        applet = tmodel.get_value(titer, 3)
        self.selectedBottleObject().launch_control_panel_applet(applet)


    def update_sensitivities(self, _caller):
        selectedBottleObject = self.selectedBottleObject()

        launchcplbutton = self.xml.get_widget("LaunchControlPanelButton")

        # Bottle tab:
        tab = self.xml.get_widget("BottleTab")
        tabwait = self.xml.get_widget("BottleTabWait")
        if selectedBottleObject.bottle_info_ready:
            tab.show()
            tabwait.hide()
        else:
            tab.hide()
            tabwait.show()

        descriptionText = self.xml.get_widget('description')
        if selectedBottleObject.is_managed:
            descriptionText.set_sensitive(False)
        else:
            descriptionText.set_sensitive(True)


        # Application tab:
        tab = self.xml.get_widget("ApplicationsTab")
        tabwait = self.xml.get_widget("ApplicationsTabWait")
        if selectedBottleObject.installed_packages_ready:
            tabwait.hide()
            tab.show()
            self.xml.get_widget("InstallButton").set_sensitive(True)
            self.xml.get_widget("RemoveButton").set_sensitive(False)
        else:
            tab.hide()
            tabwait.show()
            self.xml.get_widget("InstallButton").set_sensitive(False)
            self.xml.get_widget("RemoveButton").set_sensitive(False)

        # Control-panel tab:
        tab = self.xml.get_widget("ControlPanelTab")
        tabwait = self.xml.get_widget("ControlPanelTabWait")
        launchcplbutton = self.xml.get_widget("LaunchControlPanelButton")

        cpllistviewwidget = self.xml.get_widget("ControlPanelListview")
        if cpllistviewwidget.get_selection().get_selected()[1]:
            launchcplbutton.set_sensitive(True)
        else:
            launchcplbutton.set_sensitive(False)

        if selectedBottleObject.control_panel_ready:
            tab.show()
            tabwait.hide()
        else:
            tab.hide()
            tabwait.show()

        # Advanced tab:
        self.disableMakeDefault = True  #  Protect our callback from thinking thata user is clicking on the box.
        checkbox =  self.xml.get_widget("defaultCheckbox")
        if selectedBottleObject.name == bottlecollection.sharedCollection().getDefaultBottle():
            checkbox.set_active(True)
        else:
            checkbox.set_active(False)
        self.disableMakeDefault = False

        if distversion.HAS_MULTI_USER:
            if selectedBottleObject.is_managed:
                self.xml.get_widget("bottleTypeLabel").set_text(_("Managed"))
                self.xml.get_widget("publishBottleButton").hide()
            else:
                self.xml.get_widget("bottleTypeLabel").set_text(_("Private"))
                if crossover.is_root_install():
                    self.xml.get_widget("publishBottleButton").show()

        if selectedBottleObject.name.find(" ") != -1:
            # Warn the user that this bottle cannot be packged.
            self.xml.get_widget("packageSpacesWarningBox").show()
            self.xml.get_widget("createPackageButton").set_sensitive(False)
        else:
            self.xml.get_widget("packageSpacesWarningBox").hide()
            self.xml.get_widget("createPackageButton").set_sensitive(True)


    def launch_command(self, _caller):
        selectedBottleName = self.selectedBottleName()

        try:
            runcommanddlg.RunCommandController(selectedBottleName)

        except:
            traceback.print_exc()
            return False

    def window_deleted(self, _widget, _event):
        crossover.save_setting('OfficeSetup', 'BottleManagerSize',
                               '%sx%s' % self.mainWindow.get_size())

    def quit_requested(self, _caller):
        cxguitools.toplevel_quit()

    def change_description(self, caller):
        bottleObj = self.selectedBottleObject()
        bottleObj.change_description(caller.get_text())

    def description_lost_focus(self, _caller, _event):
        # We might have lost track of which bottle we
        #  were looking at, so we'll just call 'write'
        #  on every bottle.  It only writes if there's been
        #  a change, anyway.
        # FIXME: This is broken. The bottle we're editing is something we just
        # shouldn't lose track of.
        collection = bottlecollection.sharedCollection()
        for bottleName in collection.bottleList():
            bottleObj = collection.bottleObject(bottleName)
            bottleObj.write_description()

    def add_bottle(self, _caller):
        NewBottleController(self)

    def restore_bottle(self, _caller):
        RestoreArchiveController(self)

    def duplicate_bottle(self, _caller):
        selectedBottleName = self.selectedBottleName()
        NewBottleController(self, selectedBottleName)

    def publish_bottle(self, _caller):
        selectedBottleName = self.selectedBottleName()
        PublishBottleController(self, selectedBottleName)

    def package_bottle(self, _caller):
        packagebottledialog.PackageBottleController(self.selectedBottleObject())

    def quit_bottle(self, _caller):
        selectedBottleName = self.selectedBottleName()

        quitBottleOp = bottlecollection.QuitBottleOperation([selectedBottleName], False, self)
        pyop.sharedOperationQueue.enqueue(quitBottleOp)

        self.xml.get_widget('QuitBottleButton').set_sensitive(False)
        self.xml.get_widget('ForceQuitButton').set_sensitive(True)

    def force_quit_bottle(self, _caller):
        selectedBottleName = self.selectedBottleName()

        quitBottleOp = bottlecollection.QuitBottleOperation([selectedBottleName], True, self)
        pyop.sharedOperationQueue.enqueue(quitBottleOp)

        self.xml.get_widget('QuitBottleButton').set_sensitive(False)
        self.xml.get_widget('ForceQuitButton').set_sensitive(False)

    def opFinished(self, operation):
        if isinstance(operation, bottlecollection.QuitBottleOperation) and \
            operation.bottleList == [self.selectedBottleName()]:
            if operation.force:
                self.xml.get_widget('QuitBottleButton').set_sensitive(True)
                self.xml.get_widget('ForceQuitButton').set_sensitive(False)
            else:
                self.xml.get_widget('QuitBottleButton').set_sensitive(True)
                if operation.succeeded:
                    self.xml.get_widget('ForceQuitButton').set_sensitive(False)

    def delete_bottle(self, _caller):
        selectedBottleName = self.selectedBottleName()

        primary_text = _("Are you sure you want to delete the '%s' bottle?") % selectedBottleName
        secondary_text = _("This will remove all Windows applications contained in that bottle, as well as any documents stored in its C: drive.")

        buttons = ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
                   (gtk.STOCK_DELETE, gtk.RESPONSE_OK))

        cxguitools.CXMessageDlg(primary=primary_text, secondary=secondary_text, button_array=buttons, response_function=self.deleteBottle, user_data=selectedBottleName, message_type=gtk.MESSAGE_WARNING)

    # FIXME: Naming does not follow PEP8, is inconsistent with other functions.
    #        Document relation to delete_bottle()
    def deleteBottle(self, response, bottlename):
        if response == gtk.RESPONSE_OK:
            deleteBottleOp = DeleteBottleOperation(bottlename, self)
            pyop.sharedOperationQueue.enqueue(deleteBottleOp)

    def bottleCollectionChanged(self):
        self.updateBottleList()

    def updateBottleStatus(self, inBottle):

        #  If this bottle is selected, update the tab contents right away.
        selectedBottleName = self.selectedBottleName()
        if selectedBottleName == inBottle.name:
            self.updateBottleTab()
            self.updateControlPanelTab()
            self.updateInstalledPackagesTab()
            self.update_sensitivities(None)

        #  Update bottle status
        iterator = self.bottleListStore.get_iter_first()
        while iterator:
            if self.bottleListStore.get_value(iterator, 0) == inBottle.name:
                self.bottleListStore.set_value(iterator, 1, inBottle.status)
            iterator = self.bottleListStore.iter_next(iterator)

    def bottleChanged(self, inBottle):
        # This function can be called on non-main threads so we have to queue
        # the real gui work for the main thread.
        gobject.idle_add(self.updateBottleStatus, inBottle)

    def browse_c_drive(self, _caller):
        bottleObj = self.selectedBottleObject()
        environ = bottlequery.get_win_environ(bottleObj.name, ("SystemDrive",))
        drive = bottlequery.expand_win_string(environ, "%SystemDrive%")
        drive = bottlequery.get_native_path(bottleObj.name, drive)

        args = ["xdg-open", drive]
        try:
            subprocess.Popen(args, close_fds=True)
        except:
            traceback.print_exc()
            return False

        return True

    def set_native_browser(self, _caller):
        bottleObj = self.selectedBottleObject()
        bottlequery.set_native_browser(bottleObj.name)

    def make_default(self, caller):
        if not self.disableMakeDefault:
            selectedBottleName = self.selectedBottleName()
            if caller.get_active():
                setDefaultBottleOp = SetDefaultBottleOperation(selectedBottleName, self, True)
                pyop.sharedOperationQueue.enqueue(setDefaultBottleOp)
            else:
                setDefaultBottleOp = SetDefaultBottleOperation(selectedBottleName, self, False)
                pyop.sharedOperationQueue.enqueue(setDefaultBottleOp)


    def skip_confirm_extensionless(self, filechooser):
        """If we're going to append an extension to a filename,
        skip the overwrite confirm dialog.
        """
        filename = os.path.basename(filechooser.get_filename())
        extension = os.path.splitext(filename)[1]
        if extension == "":
            return gtk.FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME
        else:
            return gtk.FILE_CHOOSER_CONFIRMATION_CONFIRM

    def append_extension(self, filechooser, responseID):
        """Append .cxarchive to a filename if it lacks an extension."""
        if responseID == gtk.RESPONSE_OK:
            filename = os.path.basename(filechooser.get_filename())
            basename = os.path.splitext(filename)[0]
            extension = os.path.splitext(filename)[1]

            if extension == "":
                # we have to restart the 'response' event because the overwrite check already happened
                filechooser.stop_emission('response')

                filename = basename + ".cxarchive"

                filechooser.set_current_name(filename)
                filechooser.response(gtk.RESPONSE_OK)
        return True

    def revert_bottle_name(self, oldBottleName, _newBottleName):
        """  This function is called if the rename provoked by bottle_name_edited fails. 
        """
        if oldBottleName in self.bottles_being_renamed:
            del self.bottles_being_renamed[oldBottleName]

    def bottle_name_edited(self, _cellrenderertext, path, new_text):

        pathiter = self.bottleListStore.get_iter(path)
        old_text = self.bottleListStore.get(pathiter, 0)[0]

        if (old_text != new_text):
            # Let's make sure we are renaming from a valid bottle name.
            if (bottlecollection.sharedCollection().bottleObject(old_text)):
                op = RenameBottleOperation(old_text, new_text, self)
                pyop.sharedOperationQueue.enqueue(op)
                self.bottles_being_renamed[old_text] = new_text
            else:
                print "Bogus rename from %s to %s." % (path, new_text)

    def archive_overwrite_response(self, response, user_data):
        if response == gtk.RESPONSE_OK:
            selectedBottleName, filename = user_data
            archiveOp = bottlecollection.ArchiveBottleOperation(selectedBottleName, filename)
            pyop.sharedOperationQueue.enqueue(archiveOp)

    def make_archive(self, _caller):
        selectedBottleName = self.selectedBottleName()

        filePicker = gtk.FileChooserDialog(_("Choose an Archive Name and Location"), self.xml.get_widget("BottleManagerDialog"), gtk.FILE_CHOOSER_ACTION_SAVE)
        cxguitools.add_filters(filePicker, cxguitools.FILTERS_CXARCHIVES)
        filePicker.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        filePicker.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_OK)
        filePicker.set_current_name(selectedBottleName + ".cxarchive")
        if hasattr(filePicker, 'set_do_overwrite_confirmation'):
            # GTK >= 2.8
            filePicker.set_do_overwrite_confirmation(True)
            # Hook append_extension so that we encourage use of the
            # .cxarchive extension.
            filePicker.connect("response", self.append_extension)
            filePicker.connect("confirm-overwrite", self.skip_confirm_extensionless)

        if filePicker.run() == gtk.RESPONSE_OK:
            filename = filePicker.get_filename()
            if hasattr(filePicker, 'set_do_overwrite_confirmation') or not os.path.exists(filename):
                archiveOp = bottlecollection.ArchiveBottleOperation(selectedBottleName, filename)
                pyop.sharedOperationQueue.enqueue(archiveOp)
            else:
                # Overwrite could not be confirmed, so do that now.
                filename = filePicker.get_filename()
                cxguitools.CXMessageDlg(
                    _("The file %s already exists. Do you want to replace it?") % cxlog.debug_str(filename),
                    buttons = gtk.BUTTONS_OK_CANCEL,
                    response_function = self.archive_overwrite_response,
                    parent = self.xml.get_widget("BottleManagerDialog"),
                    user_data = (selectedBottleName, filename),
                    message_type = gtk.MESSAGE_QUESTION)

        filePicker.destroy()

    # FIXME: Naming does not follow PEP8, is inconsistent with other functions.
    #        Caused by gtk framework? If so document.
    def addAnimationFunction(self, inFunction):
        self.animateFunctions.append(inFunction)

    # FIXME: Naming does not follow PEP8, is inconsistent with other functions.
    #        Caused by gtk framework? If so document.
    def removeAnimationFunction(self, inFunction):
        self.animateFunctions.remove(inFunction)

    def animate(self):
        for function in self.animateFunctions:
            function()
        return True


    def install_software(self, _caller):
        selectedBottleName = self.selectedBottleName()

        try:
            installwizard.InstallerAssistantController(bottle=selectedBottleName)
        except:
            traceback.print_exc()
            return False

    def show_help(self, _caller):
        cxguitools.show_help("officesetup.html")

class RestoreArchiveController(object):
    def __init__(self, inBottleManager, inArchive=None):
        try:
            gladepath = os.environ["CX_GLADEPATH"]
        except KeyError:
            gladepath = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")


        self.bottleManagerObject = inBottleManager
        self.gladefile = os.path.join(gladepath, "bottlemanager.glade")
        if not gtk.glade.textdomain() == "crossover":
            locale_path = os.path.join(CX_ROOT, "share", "locale")
            gtk.glade.bindtextdomain("crossover", locale_path)
            gtk.glade.textdomain("crossover")
        self.modaldialogxml = gtk.glade.XML(self.gladefile, "BottleArchivePicker")
        self.modaldialogxml.signal_autoconnect(self)
        pickerdlg = self.modaldialogxml.get_widget("BottleArchivePicker")
        cxguitools.add_filters(pickerdlg, cxguitools.FILTERS_CXARCHIVES)

        if (inArchive):
            pickerdlg.set_filename(inArchive)
            self.suggestBottleName(inArchive)

        progbar = self.modaldialogxml.get_widget("RestoringProgbar")
        progbar.hide()
        pickerdlg.show()

    def cancel(self, _caller):
        pickerdlg = self.modaldialogxml.get_widget("BottleArchivePicker")
        pickerdlg.destroy()

    def open(self, _caller):
        pickerdlg = self.modaldialogxml.get_widget("BottleArchivePicker")
        bottleNameWidget = self.modaldialogxml.get_widget("newbottlename")

        progbar = self.modaldialogxml.get_widget("RestoringProgbar")

        archiveFilename = pickerdlg.get_filename()
        newBottleOp = RestoreBottleOperation(bottleNameWidget.get_text(), archiveFilename, self)
        progbar.show()
        pickerdlg.set_sensitive(False)

        self.bottleManagerObject.addAnimationFunction(progbar.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)

    def file_selection_changed(self, _caller):
        pickerdlg = self.modaldialogxml.get_widget("BottleArchivePicker")
        selectedFilename = pickerdlg.get_filename()
        self.suggestBottleName(selectedFilename)

    def suggestBottleName(self, archiveFile):
        openButton = self.modaldialogxml.get_widget("openbutton")
        bottleNameWidget = self.modaldialogxml.get_widget("newbottlename")
        if archiveFile and not os.path.isdir(archiveFile):
            root = os.path.splitext(os.path.basename(archiveFile))[0]
            bottleNameWidget.set_text(bottlequery.unique_bottle_name(root))
            openButton.set_sensitive(True)
        else:
            bottleNameWidget.set_text("")
            openButton.set_sensitive(False)


    def RestoreBottleFinished(self, op):
        pickerdlg = self.modaldialogxml.get_widget("BottleArchivePicker")
        progbar = self.modaldialogxml.get_widget("RestoringProgbar")
        self.bottleManagerObject.removeAnimationFunction(progbar.pulse)

        progbar.hide()
        pickerdlg.set_sensitive(True)

        if not op.exitStatus[0]:
            cxguitools.CXMessageDlg(primary=_("Could not restore bottle"), secondary=op.exitStatus[1], message_type=gtk.MESSAGE_ERROR)
        else:
            bottlecollection.sharedCollection().refresh()
            pickerdlg.destroy()



class NewBottleController(object):

    def __init__(self, inBottleManager, inBottleToCopy=None):
        try:
            gladepath = os.environ["CX_GLADEPATH"]
        except KeyError:
            gladepath = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")


        self.bottleManagerObject = inBottleManager
        self.gladefile = os.path.join(gladepath, "bottlemanager.glade")
        if not gtk.glade.textdomain() == "crossover":
            locale_path = os.path.join(CX_ROOT, "share", "locale")
            gtk.glade.bindtextdomain("crossover", locale_path)
            gtk.glade.textdomain("crossover")
        self.modaldialogxml = gtk.glade.XML(self.gladefile, "NewBottleDialog")
        self.modaldialogxml.signal_autoconnect(self)
        newbottledlg = self.modaldialogxml.get_widget("NewBottleDialog")

        self.bottleToCopy = inBottleToCopy
        templatewidget = self.modaldialogxml.get_widget("bottletype")
        templatelabel = self.modaldialogxml.get_widget("bottletypelabel")
        caption = self.modaldialogxml.get_widget("newbottlecaption")
        if inBottleToCopy:
            newbottledlg.set_title(_("Copy a Bottle"))
            caption.set_text(_("This will copy the '%(bottle)s' bottle. The copy will be named:") % {'bottle': inBottleToCopy})
            templatewidget.hide()
            templatelabel.hide()
            self.modaldialogxml.get_widget("bottlenamelabel").hide()

            bottleNameWidget = self.modaldialogxml.get_widget("newbottlename")
            bottleNameWidget.set_text(_("Copy of %(bottle)s") % {'bottle': inBottleToCopy})

        else:
            caption.hide()

            liststore = gtk.ListStore(gobject.TYPE_STRING)
            templatewidget.set_model(liststore)
            index = 0
            for template in sorted(bottlemanagement.template_list()):
                liststore.set_value(liststore.append(), 0, template)
                if template == "winxp":
                    templatewidget.set_active(index)
                index += 1
            bottleNameWidget = self.modaldialogxml.get_widget("newbottlename")
            bottleNameWidget.set_text(bottlequery.unique_bottle_name("New Bottle"))

        progbar = self.modaldialogxml.get_widget("NewBottleProgbar")
        progbar.hide()


        newbottledlg.show()

    def insert_text(self, caller, new_text, position, user_data):
        filtered = cxutils.bottlename_sanitize(new_text)
        if filtered != new_text:
            caller.emit_stop_by_name("insert-text")
            caller.emit("insert-text", filtered, position, user_data)

    def add_bottle_ok(self, _caller):
        dlgWidget = self.modaldialogxml.get_widget("NewBottleDialog")
        bottleNameWidget = self.modaldialogxml.get_widget("newbottlename")
        progbar = self.modaldialogxml.get_widget("NewBottleProgbar")

        templatewidget = self.modaldialogxml.get_widget("bottletype")
        template = templatewidget.get_active_text()

        newBottleOp = NewBottleOperation(bottleNameWidget.get_text(), template, self, self.bottleToCopy)
        progbar.show()
        dlgWidget.set_sensitive(False)

        self.bottleManagerObject.addAnimationFunction(progbar.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)


    def add_bottle_cancel(self, _caller):
        newbottledlg = self.modaldialogxml.get_widget("NewBottleDialog")
        newbottledlg.destroy()

    def AddBottleFinished(self, op):
        dlgWidget = self.modaldialogxml.get_widget("NewBottleDialog")
        progbar = self.modaldialogxml.get_widget("NewBottleProgbar")
        self.bottleManagerObject.removeAnimationFunction(progbar.pulse)

        progbar.hide()
        dlgWidget.set_sensitive(True)

        if not op.exitStatus[0]:
            cxguitools.CXMessageDlg(primary=_("Could not create bottle"), secondary=op.exitStatus[1], message_type=gtk.MESSAGE_ERROR)
        else:
            bottlecollection.sharedCollection().refresh()
            dlgWidget.destroy()


class SetDefaultBottleOperation(pyop.PythonOperation):

    def __init__(self, inBottleName, inBottleManagerObject, inState):
        pyop.PythonOperation.__init__(self)
        self.bottleName = inBottleName
        self.state = inState
        self.bottleManagerObject = inBottleManagerObject
        self.bottleObj = bottlecollection.sharedCollection().bottleObject(inBottleName)

    def __unicode__(self):
        return "SetDefaultBottleOperation for " + self.bottleName

    def enqueued(self):
        self.bottleObj.add_status_override(self.bottleObj.STATUS_DEFAULTING)
        pyop.PythonOperation.enqueued(self)

    def main(self):
        bottlemanagement.set_default_bottle(self.bottleName, self.state)

    def finish(self):
        self.bottleObj.remove_status_override(self.bottleObj.STATUS_DEFAULTING)
        bottlecollection.sharedCollection().refreshDefaultBottle()
        self.bottleManagerObject.update_sensitivities(None)
        pyop.PythonOperation.finish(self)

class RenameBottleOperation(pyop.PythonOperation):

    def __init__(self, inBottleName, inNewBottleName, inBottleManagerObject):
        pyop.PythonOperation.__init__(self)
        self.bottleName = inBottleName
        self.newBottleName = inNewBottleName
        self.bottleManagerObject = inBottleManagerObject
        self.bottleObj = bottlecollection.sharedCollection().bottleObject(inBottleName)
        self.success = False
        self.err = None

    def __unicode__(self):
        return "RenameBottleOperation for " + self.bottleName

    def enqueued(self):
        self.bottleObj.pre_rename()
        self.bottleObj.changeablename = self.newBottleName
        bottlecollection.sharedCollection().add_ignored_bottle(self.newBottleName)
        self.bottleManagerObject.update_sensitivities(self)
        pyop.PythonOperation.enqueued(self)

    def main(self):
        (self.success, self.err) = self.bottleObj.rename()

    def finish(self):
        self.bottleObj.post_rename()
        bottlecollection.sharedCollection().remove_ignored_bottle(self.newBottleName)

        if self.success:
            bottlecollection.sharedCollection().refresh()
        else:
            self.bottleManagerObject.revert_bottle_name(self.bottleName, self.newBottleName)
            
        pyop.PythonOperation.finish(self)


class DeleteBottleOperation(pyop.PythonOperation):

    def __init__(self, inBottleName, inBottleManagerObject):
        pyop.PythonOperation.__init__(self)
        self.bottleName = inBottleName
        self.bottleObj = bottlecollection.sharedCollection().bottleObject(inBottleName)
        self.bottleManagerObject = inBottleManagerObject
        self.deleted = False

    def __unicode__(self):
        return "DeleteBottleOperation for " + self.bottleName

    def enqueued(self):
        self.bottleObj.add_status_override(self.bottleObj.STATUS_DELETING)
        self.bottleObj.marked_for_death = True
        pyop.PythonOperation.enqueued(self)

    def main(self):
        if self.bottleObj.is_managed:
            bottlemanagement.delete_bottle(self.bottleName, True)
        else:
            bottlemanagement.delete_bottle(self.bottleName, False)
        self.deleted = True

    def finish(self):
        if self.deleted:
            bottlecollection.sharedCollection().refresh()
        else:
            self.bottleObj.marked_for_death = False
        self.bottleObj.remove_status_override(_("Deleting"))
        pyop.PythonOperation.finish(self)


class NewBottleOperation(pyop.PythonOperation):

    def __init__(self, inBottleName, inTemplate, inAddBottleController, inBottleToCopy=None, inPublish=False):
        pyop.PythonOperation.__init__(self)
        self.exitStatus = None
        self.bottleToCopy = inBottleToCopy
        self.newBottleName = inBottleName
        self.template = inTemplate
        self.publish = inPublish
        self.addBottleController = inAddBottleController

    def __unicode__(self):
        return "NewBottleOperation for " + self.newBottleName

    def enqueued(self):
        bottlecollection.sharedCollection().add_ignored_bottle(self.newBottleName)
        pyop.PythonOperation.enqueued(self)

    def main(self):
        if self.publish:
            self.exitStatus = bottlemanagement.publish_bottle(self.newBottleName, self.bottleToCopy)
        elif self.bottleToCopy:
            self.exitStatus = bottlemanagement.copy_bottle(self.newBottleName, self.bottleToCopy)
        else:
            self.exitStatus = bottlemanagement.create_bottle(self.newBottleName, self.template)

    def finish(self):
        bottlecollection.sharedCollection().remove_ignored_bottle(self.newBottleName)
        self.addBottleController.AddBottleFinished(self)
        pyop.PythonOperation.finish(self)


class LaunchRepairOrRemoveOperation(pyop.PythonOperation):

    def __init__(self, inBottleObject, inRepairArgs, inBottleManager):
        pyop.PythonOperation.__init__(self)
        self.args = inRepairArgs
        self.bottleObject = inBottleObject
        self.bottleManager = inBottleManager

    def __unicode__(self):
        return "LaunchRepairOrRemoveOperation for " + self.bottleObject.name

    def main(self):
        subprocess.call(self.args, close_fds=True)

        bottlecollection.sharedCollection().queue_bottle_updates([self.bottleObject], ['applications', 'controlpanel'])
        syncArgs = ["--bottle", self.bottleObject.name, "--sync"]
        cxassoc.run_cxassoc(syncArgs)
        cxmenu.run_cxmenu(syncArgs)

class RestoreBottleOperation(pyop.PythonOperation):

    def __init__(self, inBottleName, inArchiveFile, inRestoreBottleController):
        pyop.PythonOperation.__init__(self)
        self.exitStatus = None
        self.archiveFile = inArchiveFile
        self.newBottleName = inBottleName
        self.restoreBottleController = inRestoreBottleController

    def __unicode__(self):
        return "RestoreBottleOperation for " + self.newBottleName

    def main(self):
        self.exitStatus = bottlemanagement.restore_bottle(self.newBottleName, self.archiveFile)

    def finish(self):
        self.restoreBottleController.RestoreBottleFinished(self)
        pyop.PythonOperation.finish(self)


class PublishBottleController(object):

    def __init__(self, inBottleManager, inBottleToPublish=None):
        try:
            gladepath = os.environ["CX_GLADEPATH"]
        except KeyError:
            gladepath = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")


        self.bottleManagerObject = inBottleManager
        self.gladefile = os.path.join(gladepath,"bottlemanager.glade")
        if not gtk.glade.textdomain() == "crossover":
            locale_path = os.path.join(CX_ROOT, "share", "locale")
            gtk.glade.bindtextdomain("crossover", locale_path)
            gtk.glade.textdomain("crossover")
        self.modaldialogxml = gtk.glade.XML(self.gladefile,"PublishBottleDialog")
        self.modaldialogxml.signal_autoconnect(self)
        publishbottledlg = self.modaldialogxml.get_widget("PublishBottleDialog")

        self.bottleToCopy = inBottleToPublish

        publishedNameEntry = self.modaldialogxml.get_widget("publishedNameEntry")
        if inBottleToPublish:
            publishedNameEntry.set_text("managed_" + inBottleToPublish)
        else:
            publishedNameEntry.set_text("new_managed_bottle")

        progbar = self.modaldialogxml.get_widget("publishBottleProgbar")
        progbar.hide()
        publishbottledlg.show()


    def publish_bottle_ok(self, _caller):
        dlgWidget = self.modaldialogxml.get_widget("PublishBottleDialog")
        bottleNameWidget = self.modaldialogxml.get_widget("publishedNameEntry")
        progbar = self.modaldialogxml.get_widget("publishBottleProgbar")

        newBottleOp = NewBottleOperation(bottleNameWidget.get_text(), "", self, self.bottleToCopy, True)
        progbar.show()
        dlgWidget.set_sensitive(False)

        self.bottleManagerObject.addAnimationFunction(progbar.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)

    def publish_bottle_cancel(self, _caller):
        publishbottledlg = self.modaldialogxml.get_widget("PublishBottleDialog")
        publishbottledlg.destroy()

    def AddBottleFinished(self, op):
        dlgWidget = self.modaldialogxml.get_widget("PublishBottleDialog")
        progbar = self.modaldialogxml.get_widget("publishBottleProgbar")
        self.bottleManagerObject.removeAnimationFunction(progbar.pulse)

        progbar.hide()
        dlgWidget.set_sensitive(True)

        if not op.exitStatus[0]:
            cxguitools.CXMessageDlg(primary=_("Could not publish bottle"), secondary=op.exitStatus[1], message_type=gtk.MESSAGE_ERROR)
        else:
            bottlecollection.sharedCollection().refresh()
            dlgWidget.destroy()





if __name__ == "__main__":
    cxguitools.set_default_icon()
    # give the controller a name so it can be accessed from the console as __main__.bmc
    bmc = BottleManagerController()
    cxguitools.warn_if_root()
    gtk.main()
    sys.exit()
