Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / python / crossoverui.py

# (c) Copyright 2012-2015, CodeWeavers, Inc.

import collections
import os
import subprocess
import traceback

import gobject
gobject.threads_init()
import gtk
import pango

import bottlequery
import cxfsnotifier
import cxlog
import cxmenu
import cxproduct
import cxutils
import demoutils

import bottlecollection
import bottlemanagement
import cxguitools
import cxinstallerui
import cxprefsui
import cxregisterui
import cxrunui
import distversion
import packagebottledialog
import pyop

from cxutils import cxgettext as _

# Global dialog management

DIALOG = None

def open_or_show(restore=None):
    # pylint: disable=W0603
    global DIALOG
    if DIALOG is None:
        DIALOG = CrossOver(restore)
    else:
        DIALOG.present()
        DIALOG.handle_arguments(restore)
    return DIALOG

FAVORITES = 'FAVORITES'

CONTROL_PANEL = 'CONTROL_PANEL'

RUN_COMMAND = 'RUN_COMMAND'

ICON_VIEW = 'ICON_VIEW'
TREE_VIEW = 'TREE_VIEW'

_LARGE_ICON_SIZE = 48

_SMALL_ICON_SIZE = 16

def _icon_size_list(width, height):
    return ('%sx%s' % (width, height), '256x256', '128x128', '64x64', '48x48', '32x32', '16x16', '')

def _get_icon(icon_filename, width, height):
    icon = None
    if icon_filename:
        try:
            icon = gtk.gdk.pixbuf_new_from_file(icon_filename)
        except gobject.GError:
            cxlog.warn("couldn't load icon file %s:\n%s" % (cxlog.debug_str(icon_filename), traceback.format_exc()))
    if icon is None:
        icon = cxguitools.get_std_icon('cxrun', _icon_size_list(width, height))
    if icon is not None:
        icon = icon.scale_simple(height, width, gtk.gdk.INTERP_BILINEAR)
    return icon


ControlPanelApplet = collections.namedtuple('ControlPanelApplet', ('bottle', 'info'))

class CrossOver(object):
    """This is the CrossOver application"""

    ccenter = None

    def __init__(self, restore=None):

        # Load & show the main CrossOver window

        self.bottles = bottlecollection.sharedCollection(('basic',))
        self.bottles.addChangeDelegate(self)
        self.bottles.addBottleChangeDelegate(self)

        toolbar_style = """
style "bottlebar"
{
    GtkToolbar::button-relief = GTK_RELIEF_NORMAL
    GtkToolbar::shadow-type = GTK_SHADOW_NONE
    GtkToolbar::internal-padding = 4
    GtkToolbar::space-size = 4
}
widget "*.*.BottleListToolbar" style "bottlebar"
        """

        gtk.rc_parse_string(toolbar_style)

        # Import widget modules so gtk.Builder() finds them
        import placeholderentry # pylint: disable=W0612

        self.crossover_gui = gtk.Builder()
        self.crossover_gui.set_translation_domain("crossover")
        self.crossover_gui.add_from_file(cxguitools.get_ui_path("crossover"))
        self.crossover_gui.connect_signals(self)

        self.main_window = self.crossover_gui.get_object("MainWindow")

        self.aboutDialog = None
        self.mainMenuBar = None
        self.buttonDict = None

        self.search_string = None
        self.expanded_rows = None
        self.update_applist_source = None
        # This doesn't seem to be set by the builder xml?
        self.crossover_gui.get_object("SearchEntry").set_property("secondary-icon-sensitive", False)

        self.shutdown_failed_bottles = set() # bottles for which the last shutdown attempt failed

        self.updating_description_bottles = set() # bottles for which we are updating the description

        self.updating_default_checkbox = False

        self.updating_csmt_checkbox = False

        self.updating_dxvk_checkbox = False

        self.bottle_menu_open = False

        self.drag_start_x = None
        self.drag_start_y = None
        self.drag_obj = None
        self.drag_pixbuf = None

        global_config = cxproduct.get_config()

        favorites_str = global_config["OfficeSetup"].get("Favorites")
        if favorites_str:
            self.favorites = set(favorites_str.decode('unicode_escape').split(u'\0'))
        else:
            self.favorites = set()

        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.main_window.set_default_size(width, height)
            if width is None:
                cxlog.warn("Invalid BottleManagerSize %s" % cxlog.debug_str(size))

        size = global_config["OfficeSetup"].get("BottleSidebarSize")
        if size and size.isdigit():
            self.crossover_gui.get_object("BottlePaned").set_position(int(size))

        self._pane_size = None
        self.crossover_gui.get_object("BottlePaned").connect('notify::position', self.on_bottlepane_move)

        self.crossover_gui.get_object("BottleDescriptionText").get_buffer().connect('changed', self.on_description_changed)
        self.crossover_gui.get_object("BottleDescriptionText").get_buffer().connect('insert-text', self.on_description_insert_text)

        icon_theme = gtk.icon_theme_get_default()
        try:
            folder_icon = icon_theme.load_icon('folder', _SMALL_ICON_SIZE, gtk.ICON_LOOKUP_USE_BUILTIN)
            self.folder_icon = folder_icon.scale_simple(_SMALL_ICON_SIZE, _SMALL_ICON_SIZE, gtk.gdk.INTERP_BILINEAR)
        except: # pylint: disable=W0702
            # No folder icon in theme?
            self.folder_icon = None

        toolbar_icon_size = gtk.icon_size_lookup(gtk.settings_get_default().get_property('gtk-toolbar-icon-size'))

        try:
            icon_theme.load_icon('emblem-favorite', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
            self.crossover_gui.get_object("ToggleFavoriteButton").set_icon_name('emblem-favorite')
        except: # pylint: disable=W0702
            pass

        self.notifier_bottles = set()

        # do some fussing with the window layout before drawing.

        iconImage = self.crossover_gui.get_object("CrossOverIconImage")
        iconImage.set_from_file(os.path.join(cxutils.CX_ROOT,
                                             "share",
                                             "images",
                                             "welcomeCrossOverIcon.png"))

        self.selected_bottle = None

        self.editing_bottle = None

        self.view_with_selection = None

        self.view = global_config["OfficeSetup"].get("MainWindowView")
        if self.view not in (ICON_VIEW, TREE_VIEW):
            self.view = ICON_VIEW

        self.bottle_list_store = gtk.ListStore(object, str, str, object) #name, display markup, sort key, object
        self.add_builtin_bottle_items()
        self.update_bottle_list()
        self.bottle_list_store.set_sort_column_id(2, gtk.SORT_ASCENDING)
        bottle_list_view = self.crossover_gui.get_object("BottleListView")
        bottle_list_view.set_model(self.bottle_list_store)
        bottle_label = gtk.CellRendererText()
        bottle_label.set_property('ellipsize', pango.ELLIPSIZE_END)
        bottle_label.set_property('ellipsize-set', True)
        bottle_label.connect('edited', self.bottle_name_edited)
        bottle_label.connect('editing-started', self.bottle_name_edit_begin)
        bottle_label.connect('editing-canceled', self.bottle_name_edit_cancel)
        bottle_column = gtk.TreeViewColumn("Name", bottle_label, markup=1)
        bottle_list_view.append_column(bottle_column)

        self.bottle_label = bottle_label
        self.bottle_column = bottle_column

        bottle_list_view.get_selection().select_path((1,))
        bottle_list_view.get_selection().connect("changed", self.on_BottleListView_select)

        self.bottle_pane_showing = global_config["OfficeSetup"].get("ShowBottleSidebar") == '1'

        self.installed_applications_store = gtk.ListStore(str)
        self.installed_applications_store.set_sort_column_id(0, gtk.SORT_ASCENDING)

        installed_applications_view = self.crossover_gui.get_object("BottleApplicationsView")
        installed_applications_view.set_model(self.installed_applications_store)
        app_label = gtk.CellRendererText()
        app_label.set_property('ellipsize', pango.ELLIPSIZE_END)
        app_label.set_property('ellipsize-set', True)
        app_column = gtk.TreeViewColumn("Name", app_label, text=0)
        installed_applications_view.append_column(app_column)

        # item list store columns:
        #  0: object
        #  1: display markup
        #  2: sort key
        #  3: tooltip
        #  4: identity key (for favorites, and checking if an item is the same as one in the store)
        #  5: large icon
        #  6: bottle name
        #  7: small icon
        #  8: bottle sort key
        self.programs_list_store = gtk.ListStore(object, str, str, str, object, gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, str)
        self.programs_list_store.set_sort_column_id(2, gtk.SORT_ASCENDING)
        programs_icon_view = self.crossover_gui.get_object("ProgramsSectionIconView")
        programs_icon_view.set_model(self.programs_list_store)

        self.favorites_list_store = gtk.ListStore(object, str, str, str, object, gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, str)
        self.favorites_list_store.set_sort_column_id(2, gtk.SORT_ASCENDING)
        favorites_icon_view = self.crossover_gui.get_object("FavoritesSectionIconView")
        favorites_icon_view.set_model(self.favorites_list_store)

        self.control_panel_list_store = gtk.ListStore(object, str, str, str, object, gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, str)
        self.control_panel_list_store.set_sort_column_id(2, gtk.SORT_ASCENDING)
        control_panel_icon_view = self.crossover_gui.get_object("ControlPanelSectionIconView")
        control_panel_icon_view.set_model(self.control_panel_list_store)

        self.launchers_tree_store = gtk.TreeStore(object, str, str, str, object, gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, str)
        self.launchers_tree_store.set_sort_column_id(2, gtk.SORT_ASCENDING)
        launchers_tree_view = self.crossover_gui.get_object("LaunchersTreeView")
        launchers_tree_view.set_model(self.launchers_tree_store)
        launchers_tree_view.set_tooltip_column(3)

        launchers_name = gtk.TreeViewColumn(_("Name"))
        icon = gtk.CellRendererPixbuf()
        icon.set_fixed_size(_SMALL_ICON_SIZE, _SMALL_ICON_SIZE)
        launchers_name.pack_start(icon, expand=False)
        launchers_name.add_attribute(icon, 'pixbuf', 7)
        label = gtk.CellRendererText()
        label.set_property('ellipsize', pango.ELLIPSIZE_END)
        label.set_property('ellipsize-set', True)
        launchers_name.pack_start(label)
        launchers_name.add_attribute(label, 'markup', 1)
        launchers_name.set_resizable(True)
        launchers_name.set_expand(True)
        launchers_name.set_clickable(True)
        launchers_name.set_sort_column_id(2)
        launchers_tree_view.append_column(launchers_name)

        bottle_column = gtk.TreeViewColumn(_("Bottle"))
        bottle_label = gtk.CellRendererText()
        bottle_label.set_property('ellipsize', pango.ELLIPSIZE_END)
        bottle_label.set_property('ellipsize-set', True)
        bottle_column.pack_start(bottle_label)
        bottle_column.add_attribute(bottle_label, 'text', 6)
        bottle_column.set_resizable(True)
        bottle_column.set_expand(False)
        bottle_column.set_clickable(True)
        bottle_column.set_min_width(170) # not ideal, but the only way I could get this to have a reasonable width
        bottle_column.set_sort_column_id(8)
        launchers_tree_view.append_column(bottle_column)

        launchers_tree_view.get_selection().connect('changed', self.on_TreeSelection_changed)

        if distversion.HAS_MULTI_USER and \
            (cxproduct.is_root_install() or global_config["OfficeSetup"].get("NonRootPublish", "0") == "1"):
            self.crossover_gui.get_object("PublishBottleMenu").show()
            self.crossover_gui.get_object("PublishBottleCMenu").show()
            self.crossover_gui.get_object("UpdatePublishedBottleMenu").show()
            self.crossover_gui.get_object("UpdatePublishedBottleCMenu").show()
        else:
            self.crossover_gui.get_object("PublishBottleMenu").hide()
            self.crossover_gui.get_object("PublishBottleCMenu").hide()
            self.crossover_gui.get_object("UpdatePublishedBottleMenu").hide()
            self.crossover_gui.get_object("UpdatePublishedBottleCMenu").hide()

        self.crossover_gui.get_object("BottlePropertiesSidebar").hide()

        cxfsnotifier.add_observer(
            os.path.join(cxutils.CX_ROOT, "etc", "license.txt"),
            self.on_license_changed)
        cxfsnotifier.add_observer(
            os.path.join(cxutils.CX_ROOT, "etc", "license.sig"),
            self.on_license_changed)

        self.check_registration()

        self.toggle_bottle_pane(self.bottle_pane_showing)
        self.set_view(self.view)
        self.updateAppList()
        self.update_file_menu()
        self.main_window.show()

        self.handle_arguments(restore)

    def handle_arguments(self, restore=None):
        if restore is not None:
            RestoreArchiveController(self, cxutils.uri_to_path(restore))

    def present(self):
        self.main_window.present()

    def check_registration(self):
        license_file = os.path.join(cxutils.CX_ROOT, "etc", "license.txt")
        sig_file = os.path.join(cxutils.CX_ROOT, "etc", "license.sig")
        (isdemo, _username, _date, _licenseid, _revoked) = demoutils.demo_status(license_file, sig_file)

        self.crossover_gui.get_object("UnlockCrossOverMenu").set_property('visible', isdemo)

    def on_license_changed(self, _event, _path, _observer_data):
        gobject.idle_add(self.check_registration)

    def on_ToggleShowBottlesButton_style_set(self, widget, _previous_style):
        toolbar_icon_size = gtk.icon_size_lookup(gtk.settings_get_default().get_property('gtk-toolbar-icon-size'))
        bottles_icon = cxguitools.get_std_icon('bottles', ('%sx%s' % toolbar_icon_size, '24x24'))
        if bottles_icon:
            bottles_icon = cxguitools.recolor_pixbuf(bottles_icon, widget.style.fg[gtk.STATE_NORMAL])
            bottles_icon = bottles_icon.scale_simple(toolbar_icon_size[0], toolbar_icon_size[1], gtk.gdk.INTERP_BILINEAR)
            bottles_image = gtk.Image()
            bottles_image.set_from_pixbuf(bottles_icon)
            widget.set_icon_widget(bottles_image)
            bottles_image.show()

    def on_ViewAsIconsButton_style_set(self, widget, _previous_style):
        toolbar_icon_size = gtk.icon_size_lookup(gtk.settings_get_default().get_property('gtk-toolbar-icon-size'))
        icon_theme = gtk.icon_theme_get_default()

        try:
            icon_theme.load_icon('view-grid', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
            icon_theme.load_icon('view-list', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
            widget.set_icon_name('view-grid')
        except: # pylint: disable=W0702
            try:
                icon_theme.load_icon('view-list-symbolic', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
                button_icon = icon_theme.load_icon('view-grid-symbolic', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
                button_icon = cxguitools.recolor_pixbuf(button_icon, widget.style.fg[gtk.STATE_NORMAL], symbolic=True)
                button_icon = button_icon.scale_simple(toolbar_icon_size[0], toolbar_icon_size[1], gtk.gdk.INTERP_BILINEAR)
                button_image = gtk.Image()
                button_image.set_from_pixbuf(button_icon)
                widget.set_icon_widget(button_image)
                button_image.show()
            except: # pylint: disable=W0702
                button_icon = cxguitools.get_std_icon('cxviewicons', ('%sx%s' % toolbar_icon_size, '24x24'))
                if button_icon:
                    button_icon = cxguitools.recolor_pixbuf(button_icon, widget.style.text[gtk.STATE_NORMAL])
                    button_icon = button_icon.scale_simple(toolbar_icon_size[0], toolbar_icon_size[1], gtk.gdk.INTERP_BILINEAR)
                    button_image = gtk.Image()
                    button_image.set_from_pixbuf(button_icon)
                    widget.set_icon_widget(button_image)
                    button_image.show()

    def on_ViewAsTreeButton_style_set(self, widget, _previous_style):
        toolbar_icon_size = gtk.icon_size_lookup(gtk.settings_get_default().get_property('gtk-toolbar-icon-size'))
        icon_theme = gtk.icon_theme_get_default()

        try:
            icon_theme.load_icon('view-list', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
            icon_theme.load_icon('view-grid', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
            widget.set_icon_name('view-list')
        except: # pylint: disable=W0702
            try:
                icon_theme.load_icon('view-grid-symbolic', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
                button_icon = icon_theme.load_icon('view-list-symbolic', toolbar_icon_size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
                button_icon = cxguitools.recolor_pixbuf(button_icon, widget.style.fg[gtk.STATE_NORMAL], symbolic=True)
                button_icon = button_icon.scale_simple(toolbar_icon_size[0], toolbar_icon_size[1], gtk.gdk.INTERP_BILINEAR)
                button_image = gtk.Image()
                button_image.set_from_pixbuf(button_icon)
                widget.set_icon_widget(button_image)
                button_image.show()
            except: # pylint: disable=W0702
                button_icon = cxguitools.get_std_icon('cxviewlist', ('%sx%s' % toolbar_icon_size, '24x24'))
                if button_icon:
                    button_icon = button_icon.scale_simple(toolbar_icon_size[0], toolbar_icon_size[1], gtk.gdk.INTERP_BILINEAR)
                    button_icon = cxguitools.recolor_pixbuf(button_icon, widget.style.fg[gtk.STATE_NORMAL])
                    button_image = gtk.Image()
                    button_image.set_from_pixbuf(button_icon)
                    widget.set_icon_widget(button_image)
                    button_image.show()

    def can_use_bottle(self, bottle_name):
        try:
            bottle = self.bottles.bottleObject(bottle_name)
        except KeyError:
            return False
        else:
            for status in bottle.status_overrides:
                if status not in (bottle.STATUS_INIT, bottle.STATUS_UPGRADE,
                                  bottle.STATUS_READY, bottle.STATUS_DEFAULTING,
                                  bottle.STATUS_DOWN):
                    return False

        return True

    def get_bottle_status_text(self, bottle):
        for status in bottle.status_overrides:
            if status not in (bottle.STATUS_INIT, bottle.STATUS_UPGRADE,
                              bottle.STATUS_READY, bottle.STATUS_DEFAULTING,
                              bottle.STATUS_DOWN):
                return status
        return None

    def get_bottle_markup(self, bottle, indent="  "):
        html_text = cxutils.html_escape(bottle.name)
        if bottle.is_default:
            html_text = '<b>' + html_text + '</b>'
        status_text = self.get_bottle_status_text(bottle)
        if status_text:
            html_text += (" (%s)" % cxutils.html_escape(status_text))
        return indent + html_text

    def _get_iconview_cell_renderers(self, show_bottle):
        result = []

        icon = gtk.CellRendererPixbuf()
        icon.set_fixed_size(_LARGE_ICON_SIZE, _LARGE_ICON_SIZE)
        result.append((icon, (('pixbuf', 5),)))

        label = gtk.CellRendererText()
        label.set_property('width', _LARGE_ICON_SIZE * 3)
        label.set_property('wrap-width', _LARGE_ICON_SIZE * 3)
        label.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
        label.set_property('alignment', pango.ALIGN_CENTER)
        result.append((label, (('markup', 1),)))

        if show_bottle:
            label = gtk.CellRendererText()
            label.set_property('width', _LARGE_ICON_SIZE * 3)
            label.set_property('ellipsize', pango.ELLIPSIZE_END)
            label.set_property('ellipsize-set', True)
            label.set_property('scale', 0.8)
            label.set_property('scale-set', True)
            label.set_property('alignment', pango.ALIGN_CENTER)
            result.append((label, (('text', 6),)))

        return result

    def _get_iconview_item_box(self, iconview, path):
        # How is this not part of the API?
        model = iconview.get_model()

        item_sizes = []
        max_width = 0

        show_bottle = not self.selected_bottle or iconview == self.crossover_gui.get_object("FavoritesSectionIconView") or self.bottles.bottleCount() == 1

        cell_renderers = self._get_iconview_cell_renderers(show_bottle)

        num_items = 0

        iterator = model.get_iter_first()
        while iterator:
            item_height = 0
            item_width = 0
            for renderer, attributes in cell_renderers:
                for attribute, column in attributes:
                    renderer.set_property(attribute, model.get_value(iterator, column))
                _xofs, _yofs, width, height = renderer.get_size(iconview)
                item_width = max(item_width, width)
                item_height += height

            item_sizes.append((item_width, item_height))

            max_width = max(item_width, max_width)

            iterator = model.iter_next(iterator)

            num_items += 1

        margin = iconview.get_margin()

        padding = iconview.get_property('item-padding')

        row_spacing = iconview.get_row_spacing()
        column_spacing = iconview.get_column_spacing()

        focus_line_width = iconview.style_get_property('focus-line-width')

        allocation = iconview.get_allocation()

        num_columns = max(1, (allocation.width - margin * 2 + column_spacing) // (max_width + padding * 2 + column_spacing + focus_line_width * 2))

        y = margin

        for i in range(0, path[0] + 1, num_columns):
            x = margin
            row_height = 0
            for j in range(num_columns):
                if i + j < num_items:
                    row_height = max(row_height, item_sizes[i + j][1])
                    if i + j < path[0]:
                        x += item_sizes[i + j][0] + padding * 2 + column_spacing + focus_line_width * 2
            if i + num_columns <= path[0]:
                y += row_height + padding * 2 + row_spacing + focus_line_width * 2

        return x, y, item_sizes[path[0]][0]+padding*2+focus_line_width*2, row_height+padding*2+focus_line_width*2

    def setup_icon_view(self, icon_view, show_bottle):
        icon_view.clear()

        for renderer, attributes in self._get_iconview_cell_renderers(show_bottle):
            icon_view.pack_start(renderer)
            for attribute, column in attributes:
                icon_view.add_attribute(renderer, attribute, column)

        icon_view.set_tooltip_column(3)

    def add_builtin_bottle_items(self):
        newrow = self.bottle_list_store.append()
        self.bottle_list_store.set_value(newrow, 0, 0)
        self.bottle_list_store.set_value(newrow, 1, "<b>"+_("Collections")+"</b>")
        self.bottle_list_store.set_value(newrow, 2, "A")

        newrow = self.bottle_list_store.append()
        self.bottle_list_store.set_value(newrow, 0, 1)
        self.bottle_list_store.set_value(newrow, 1, "  "+_("All Bottles"))
        self.bottle_list_store.set_value(newrow, 2, "B")

        newrow = self.bottle_list_store.append()
        self.bottle_list_store.set_value(newrow, 0, 2)
        self.bottle_list_store.set_value(newrow, 1, "<b>"+_("Bottles")+"</b>")
        self.bottle_list_store.set_value(newrow, 2, "C")

    def update_bottle_list(self):
        current_bottles = set(self.bottles.bottles())
        seen_bottles = set()

        for bottle in current_bottles - self.notifier_bottles:
            for path in (cxproduct.get_bottle_path(), cxproduct.get_managed_bottle_path()):
                for dirpath in path.split(":"):
                    cxfsnotifier.add_observer(
                        os.path.join(dirpath, bottle.name, 'cxmenu.conf'),
                        self.on_cxmenuconf_changed)
        for bottle in self.notifier_bottles - current_bottles:
            for path in (cxproduct.get_bottle_path(), cxproduct.get_managed_bottle_path()):
                for dirpath in path.split(":"):
                    cxfsnotifier.remove_observer(
                        os.path.join(dirpath, bottle.name, 'cxmenu.conf'),
                        self.on_cxmenuconf_changed)
        self.notifier_bottles = current_bottles.copy()

        if self.selected_bottle and self.selected_bottle not in current_bottles:
            self.selected_bottle = None
            self.crossover_gui.get_object("BottleListView").get_selection().select_path((1,))

        any_published_bottles = any(bottle.is_managed for bottle in current_bottles)
        seen_published_section = False

        iterator = self.bottle_list_store.get_iter_first()
        while iterator:
            iter_next = self.bottle_list_store.iter_next(iterator)
            current_sort = self.bottle_list_store.get_value(iterator, 2)
            bottle_obj = self.bottle_list_store.get_value(iterator, 3)
            if current_sort == 'C':
                # Private bottles list, or maybe just bottles
                current_markup = self.bottle_list_store.get_value(iterator, 1)
                if any_published_bottles:
                    new_markup = "<b>"+_("Private Bottles")+"</b>"
                else:
                    new_markup = "<b>"+_("Bottles")+"</b>"
                if not isinstance(new_markup, str):
                    new_markup = new_markup.encode('utf8')
                if current_markup != new_markup:
                    self.bottle_list_store.set_value(iterator, 1, new_markup)
            elif current_sort == 'F':
                seen_published_section = True
                if not any_published_bottles:
                    self.bottle_list_store.remove(iterator)
            elif bottle_obj is None or bottle_obj in seen_bottles:
                pass
            elif bottle_obj in current_bottles:
                current_name = self.bottle_list_store.get_value(iterator, 0)
                if current_name != bottle_obj.name:
                    self.bottle_list_store.set_value(iterator, 0, bottle_obj.name)

                current_markup = self.bottle_list_store.get_value(iterator, 1)
                new_markup = self.get_bottle_markup(bottle_obj)
                if not isinstance(new_markup, str):
                    new_markup = new_markup.encode('utf8')
                if current_markup != new_markup:
                    self.bottle_list_store.set_value(iterator, 1, new_markup)

                current_sort = self.bottle_list_store.get_value(iterator, 2)
                if bottle_obj.is_managed:
                    if bottle_obj.is_default:
                        new_sort = "G"
                    else:
                        new_sort = "H"+bottle_obj.name
                else:
                    if bottle_obj.is_default:
                        new_sort = "D"
                    else:
                        new_sort = "E"+bottle_obj.name
                if not isinstance(new_sort, str):
                    new_sort = new_sort.encode('utf8')
                if current_sort != new_sort:
                    self.bottle_list_store.set_value(iterator, 2, new_sort)

                current_bottles.remove(bottle_obj)
                seen_bottles.add(bottle_obj)
            else:
                self.bottle_list_store.remove(iterator)
            iterator = iter_next

        for bottle in current_bottles:
            newrow = self.bottle_list_store.append()
            self.bottle_list_store.set_value(newrow, 0, bottle.name)
            self.bottle_list_store.set_value(newrow, 1, self.get_bottle_markup(bottle))
            if bottle.is_managed:
                if bottle.is_default:
                    self.bottle_list_store.set_value(newrow, 2, "G")
                else:
                    self.bottle_list_store.set_value(newrow, 2, "H"+bottle.name)
            else:
                if bottle.is_default:
                    self.bottle_list_store.set_value(newrow, 2, "D")
                else:
                    self.bottle_list_store.set_value(newrow, 2, "E"+bottle.name)
            self.bottle_list_store.set_value(newrow, 3, bottle)

        if any_published_bottles and not seen_published_section:
            newrow = self.bottle_list_store.append()
            self.bottle_list_store.set_value(newrow, 0, 3)
            self.bottle_list_store.set_value(newrow, 1, "<b>"+_("Published Bottles")+"</b>")
            self.bottle_list_store.set_value(newrow, 2, "F")

    def on_BottleListView_popup_menu(self, _widget):
        menu = self.crossover_gui.get_object("BottleContextMenu")
        if menu:
            # FIXME: position menu based on the selection location?
            menu.popup(None, None, None, 0, 0)
            return True # popped up
        return False

    def on_BottleListView_button_press_event(self, treeview, event):
        if event.button == 3: # right click
            path = treeview.get_path_at_pos(int(event.x), int(event.y))
            if path is None:
                return False # propagate
            path, _column, _x, _y = path
            treeview.get_selection().select_path(path)
            menu = self.crossover_gui.get_object("BottleContextMenu")
            if menu:
                menu.popup(None, None, None, event.button, event.time)
                return True
        return False # propagate

    def bottle_button_pos(self, menu):
        _menu_width, menu_height = menu.size_request()

        button = self.crossover_gui.get_object("BottleMenuButton")

        button_x, button_y = button.window.get_origin()
        button_allocation = button.get_allocation()
        button_x += button_allocation.x
        button_y += button_allocation.y
        button_height = button_allocation.height
        button_screen = button.window.get_screen()
        monitor_frame = button_screen.get_monitor_geometry(button_screen.get_monitor_at_window(button.window))

        fits_below = (button_y + button_height + menu_height <= monitor_frame.y + monitor_frame.height)
        fits_above = (button_y - menu_height >= monitor_frame.y)

        if fits_below:
            return button_x, button_y + button_height, True
        if fits_above:
            return button_x, button_y - menu_height, True
        return button_x, button_y + button_height, False

    def on_BottleMenuButton_button_press_event(self, widget, event):
        if self.bottle_menu_open or event.button != 1:
            return False # propagate

        menu = self.crossover_gui.get_object("BottleContextMenu")
        menu.popup(None, None, self.bottle_button_pos, event.button, event.time)

        self.bottle_menu_open = True

        widget.set_active(True)

        return True

    def on_BottleMenuButton_clicked(self, widget):
        if self.bottle_menu_open or not widget.get_active():
            return

        menu = self.crossover_gui.get_object("BottleContextMenu")
        menu.popup(None, None, self.bottle_button_pos, 0, 0)

        self.bottle_menu_open = True

    def on_BottleContextMenu_deactivate(self, _menushell):
        self.bottle_menu_open = False
        button = self.crossover_gui.get_object("BottleMenuButton")
        button.set_active(False)

    def bottle_name_insert_text(self, caller, new_text, _length, _user_data):
        # pylint: disable=R0201
        name = caller.get_text()
        position = caller.get_position()
        name = name[:position] + new_text + name[position:]
        if not cxutils.is_valid_bottlename(name):
            caller.emit_stop_by_name("insert-text")

    def bottle_name_edit_begin(self, cellrenderer, editable, path):
        editable.connect("insert-text", self.bottle_name_insert_text)
        editing_bottle = self.bottle_list_store.get_value(self.bottle_list_store.get_iter(path), 3)
        if not editing_bottle or not self.can_use_bottle(editing_bottle.name):
            cellrenderer.stop_editing(True)
            return

        self.editing_bottle = editing_bottle

        editable.set_text(cxutils.sanitize_bottlename(editing_bottle.name))

    def bottle_name_edit_cancel(self, _cellrenderer):
        self.editing_bottle = None

    def bottle_name_edited(self, _cellrenderer, _path, new_text):
        if not self.can_use_bottle(self.editing_bottle.name):
            return

        old_text = self.editing_bottle.name
        new_text = new_text.decode('utf8')

        if old_text != new_text:
            op = RenameBottleOperation(self.editing_bottle, new_text, self)
            pyop.sharedOperationQueue.enqueue(op)

        self.editing_bottle = None

    def on_RenameBottleMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        iterator = self.bottle_list_store.get_iter_first()
        while iterator:
            bottle_obj = self.bottle_list_store.get_value(iterator, 3)
            if bottle_obj == self.selected_bottle:
                path = self.bottle_list_store.get_path(iterator)
                break
            else:
                iterator = self.bottle_list_store.iter_next(iterator)
        else:
            return

        self.crossover_gui.get_object("BottleListView").set_cursor_on_cell(path, self.bottle_column, self.bottle_label, True)

    def on_OpenCDriveMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        environ = bottlequery.get_win_environ(self.selected_bottle.name, ("SystemDrive",))
        drive = bottlequery.expand_win_string(environ, "%SystemDrive%")
        drive = bottlequery.get_native_path(self.selected_bottle.name, drive)

        args = ["xdg-open", drive]
        try:
            subprocess.Popen(args, close_fds=True)
        except: # pylint: disable=W0702
            traceback.print_exc()

    def on_ExportArchiveMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        bottle = self.selected_bottle

        file_chooser = gtk.FileChooserDialog(_("Choose an Archive Name and Location"), self.main_window, gtk.FILE_CHOOSER_ACTION_SAVE)
        cxguitools.add_filters(file_chooser, cxguitools.FILTERS_CXARCHIVES)
        file_chooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        file_chooser.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_OK)
        file_chooser.set_current_name(bottle.name + ".cxarchive")

        file_chooser.set_do_overwrite_confirmation(True)

        cxguitools.set_default_extension(file_chooser, 'cxarchive')

        if file_chooser.run() == gtk.RESPONSE_OK:
            filename = file_chooser.get_filename()
            archiveOp = ArchiveBottleOperation(bottle, filename)
            pyop.sharedOperationQueue.enqueue(archiveOp)

        file_chooser.destroy()

    def on_PublishBottleMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        PublishBottleController(self, self.selected_bottle.name)

    def on_UpdatePublishedBottleMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name) or \
            self.selected_bottle.up_to_date:
            return

        pyop.sharedOperationQueue.enqueue(UpgradeBottleOperation(self.selected_bottle))

    def on_CreatePackageMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        if ' ' in self.selected_bottle.name:
            cxguitools.CXMessageDlg(
                _("The bottle '%s' cannot be packaged because it has a space in the name. Please rename it and try again.") % self.selected_bottle.name,
                buttons=gtk.BUTTONS_OK,
                parent=self.main_window,
                message_type=gtk.MESSAGE_ERROR)
            return

        packagebottledialog.PackageBottleController(self.selected_bottle)

    def on_DefaultBottleMenu_activate(self, menuitem):
        if self.updating_default_checkbox or not self.selected_bottle:
            return

        if menuitem.get_active() != self.selected_bottle.is_default:
            setDefaultBottleOp = SetDefaultBottleOperation(self.selected_bottle, menuitem.get_active())
            pyop.sharedOperationQueue.enqueue(setDefaultBottleOp)

    def on_CsmtToggleMenu_activate(self, menuitem):
        if self.updating_csmt_checkbox or not self.selected_bottle:
            return

        if menuitem.get_active() != (self.selected_bottle.is_csmt_disabled_state == self.selected_bottle.STATUS_CSMT_ENABLED):
            setCsmtOp = SetCsmtOperation(self.selected_bottle, menuitem.get_active())
            pyop.sharedOperationQueue.enqueue(setCsmtOp)

    def on_DxvkToggleMenu_activate(self, menuitem):
        if self.updating_dxvk_checkbox or not self.selected_bottle:
            return

        if menuitem.get_active() != (self.selected_bottle.is_dxvk_enabled_state == self.selected_bottle.STATUS_DXVK_ENABLED):
            setDxvkOp = SetDxvkOperation(self.selected_bottle, menuitem.get_active())
            pyop.sharedOperationQueue.enqueue(setDxvkOp)

    def on_ResetWebBrowserMenu_activate(self, _menuitem):
        if not self.selected_bottle:
            return

        bottlequery.set_native_browser(self.selected_bottle.name)

    def on_BottleInstallSoftwareMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

        cxinstallerui.open_or_show(bottle=self.selected_bottle.name)

    def on_RunCommandMenu_activate(self, _menuitem=None):
        if self.selected_bottle:
            cxrunui.open_or_show(self.selected_bottle.name)
        else:
            cxrunui.open_or_show(None)

    def on_QuitBottleMenu_activate(self, _menuitem):
        if not self.selected_bottle or not self.can_use_bottle(self.selected_bottle.name):
            return

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

    def on_ForceQuitBottleMenu_activate(self, _menuitem):
        if not self.selected_bottle:
            return

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

    def on_BottlePropertiesMenu_activate(self, _menuitem):
        self.crossover_gui.get_object("BottlePropertiesSidebar").show()

    def on_BottlePropertiesClose_clicked(self, _menuitem):
        self.crossover_gui.get_object("BottlePropertiesSidebar").hide()

    def on_BottlePropertiesSidebar_show(self, _widget):
        self.updateAppList()

    def opFinished(self, operation):
        if isinstance(operation, bottlecollection.QuitBottleOperation):
            for bottle in operation.bottleList:
                if operation.succeeded:
                    self.shutdown_failed_bottles.discard(bottle)
                else:
                    self.shutdown_failed_bottles.add(bottle)
            self.update_bottle_list()
            self.updateAppList()

    def update_item_list(self, treestore, contents, parent_iterator=None):
        result = False

        contents = contents.copy()

        any_preserved_contents = False

        dummy_row = None

        if parent_iterator:
            iterator = treestore.iter_children(parent_iterator)
        else:
            iterator = treestore.get_iter_first()
        while iterator:
            key = treestore.get_value(iterator, 4)
            if key in contents:
                any_preserved_contents = True
                treestore.set_value(iterator, 0, contents[key])
                if isinstance(contents[key], dict):
                    if self.update_item_list(treestore, contents[key], iterator):
                        result = True
                del contents[key]
                iterator = treestore.iter_next(iterator)
            else:
                result = True
                if not any_preserved_contents and parent_iterator and \
                    contents and not treestore.iter_next(iterator):
                    # If we remove all the items and then add other items, GTK
                    # will collapse the section, so add a dummy item to prevent this.
                    dummy_row = treestore.append(parent_iterator)
                if not treestore.remove(iterator) or dummy_row:
                    # no more rows
                    break

        if contents:
            result = True

        for (key, obj) in contents.iteritems():
            if isinstance(treestore, gtk.TreeStore):
                newrow = treestore.append(parent_iterator)
            else:
                newrow = treestore.append()
            treestore.set_value(newrow, 0, obj)
            treestore.set_value(newrow, 4, key)
            if isinstance(obj, dict):
                if key.startswith('folder/'):
                    name = key.split('/')[1]
                    treestore.set_value(newrow, 1, cxutils.html_escape(name))
                    treestore.set_value(newrow, 2, 'B'+name)
                    treestore.set_value(newrow, 8, 'B'+name)
                elif key == FAVORITES:
                    treestore.set_value(newrow, 1, cxutils.html_escape(_("Favorites")))
                    treestore.set_value(newrow, 2, 'A')
                    treestore.set_value(newrow, 8, 'A')
                elif key == CONTROL_PANEL:
                    treestore.set_value(newrow, 1, cxutils.html_escape(_("Control Panels")))
                    treestore.set_value(newrow, 2, 'D')
                    treestore.set_value(newrow, 8, 'D')
                treestore.set_value(newrow, 3, '')
                treestore.set_value(newrow, 5, None) # Folder large icon is unused
                treestore.set_value(newrow, 6, '')
                treestore.set_value(newrow, 7, self.folder_icon)
                self.update_item_list(treestore, obj, newrow)
            elif isinstance(obj, cxmenu.MenuItem):
                treestore.set_value(newrow, 1, cxutils.html_escape(obj.menu_name()))
                treestore.set_value(newrow, 2, 'C'+obj.menu_name())
                treestore.set_value(newrow, 3, obj.parent.bottlename)
                treestore.set_value(newrow, 5, _get_icon(
                    obj.get_iconfile(_icon_size_list(_LARGE_ICON_SIZE, _LARGE_ICON_SIZE), 'cxrun'),
                    _LARGE_ICON_SIZE, _LARGE_ICON_SIZE))
                treestore.set_value(newrow, 6, obj.parent.bottlename)
                treestore.set_value(newrow, 7, _get_icon(
                    obj.get_iconfile(_icon_size_list(_SMALL_ICON_SIZE, _SMALL_ICON_SIZE), 'cxrun'),
                    _SMALL_ICON_SIZE, _SMALL_ICON_SIZE))
                treestore.set_value(newrow, 8, 'C'+obj.parent.bottlename+'/'+obj.menu_name())
            elif isinstance(obj, ControlPanelApplet):
                treestore.set_value(newrow, 1, cxutils.html_escape(obj.info['name']))
                treestore.set_value(newrow, 2, 'C'+obj.info['name'])
                treestore.set_value(newrow, 3, obj.info['description'])
                treestore.set_value(newrow, 5, _get_icon(obj.info['icon'], _LARGE_ICON_SIZE, _LARGE_ICON_SIZE))
                treestore.set_value(newrow, 6, obj.bottle.name)
                treestore.set_value(newrow, 7, _get_icon(obj.info['icon'], _SMALL_ICON_SIZE, _SMALL_ICON_SIZE))
                treestore.set_value(newrow, 8, 'C'+obj.bottle.name+'/'+obj.info['name'])
            elif obj == RUN_COMMAND:
                treestore.set_value(newrow, 1, _(u"Run Command\u2026"))
                treestore.set_value(newrow, 2, 'E')
                treestore.set_value(newrow, 3, '')
                treestore.set_value(newrow, 5, _get_icon(None, _LARGE_ICON_SIZE, _LARGE_ICON_SIZE))
                treestore.set_value(newrow, 6, '')
                treestore.set_value(newrow, 7, _get_icon(None, _SMALL_ICON_SIZE, _SMALL_ICON_SIZE))
                treestore.set_value(newrow, 8, 'E')

        if dummy_row:
            treestore.remove(dummy_row)

        return result

    def is_favorite(self, key):
        return cxutils.string_to_unicode(key) in self.favorites

    def save_favorites_config(self):
        cxproduct.save_setting('OfficeSetup', 'Favorites',
                               u'\0'.join(self.favorites).encode('unicode_escape'))

    def add_favorite(self, key):
        self.favorites.add(cxutils.string_to_unicode(key))
        self.update_file_menu()
        self.updateAppList()
        self.save_favorites_config()

    def remove_favorite(self, key):
        self.favorites.discard(cxutils.string_to_unicode(key))
        self.update_file_menu()
        self.updateAppList()
        self.save_favorites_config()

    def get_selected_item(self):
        if isinstance(self.view_with_selection, gtk.IconView):
            selected_items = self.view_with_selection.get_selected_items()
            if selected_items:
                path = selected_items[0]
                model = self.view_with_selection.get_model()
                iterator = model.get_iter(path)
                return model.get_value(iterator, 0)
        elif isinstance(self.view_with_selection, gtk.TreeView):
            model, iterator = self.view_with_selection.get_selection().get_selected()
            if iterator:
                return model.get_value(iterator, 0)
        return None

    def get_selected_key(self):
        if isinstance(self.view_with_selection, gtk.IconView):
            selected_items = self.view_with_selection.get_selected_items()
            if selected_items:
                path = selected_items[0]
                model = self.view_with_selection.get_model()
                iterator = model.get_iter(path)
                return model.get_value(iterator, 4)
        elif isinstance(self.view_with_selection, gtk.TreeView):
            model, iterator = self.view_with_selection.get_selection().get_selected()
            if iterator:
                return model.get_value(iterator, 4)
        return None

    def update_file_menu(self):
        obj = self.get_selected_item()
        name = None
        if isinstance(obj, cxmenu.MenuItem):
            name = obj.menu_name()
        elif obj == RUN_COMMAND:
            name = _(u"Run Command\u2026")
        if isinstance(obj, cxmenu.MenuItem) or obj == RUN_COMMAND:
            self.crossover_gui.get_object("OpenAppMenuItem").set_label(_("Open '%s'") % name)
            self.crossover_gui.get_object("OpenAppMenuItem").set_sensitive(True)
        else:
            self.crossover_gui.get_object("OpenAppMenuItem").set_label(_("Open"))
            self.crossover_gui.get_object("OpenAppMenuItem").set_sensitive(False)

        if isinstance(obj, cxmenu.MenuItem) and obj.type == 'windows':
            self.crossover_gui.get_object("RunWithOptionsMenuItem").set_label(_(u"Run '%s' with Options\u2026") % name)
            self.crossover_gui.get_object("RunWithOptionsMenuItem").set_sensitive(True)
        else:
            self.crossover_gui.get_object("RunWithOptionsMenuItem").set_label(_(u"Run with Options\u2026"))
            self.crossover_gui.get_object("RunWithOptionsMenuItem").set_sensitive(False)

        if not obj or isinstance(obj, dict):
            self.crossover_gui.get_object("ToggleFavoriteMenuItem").set_label(_("Add to Favorites"))
            self.crossover_gui.get_object("ToggleFavoriteMenuItem").set_sensitive(False)
            self.crossover_gui.get_object("ToggleFavoriteButton").set_tooltip_text(_("Add to Favorites"))
            self.crossover_gui.get_object("ToggleFavoriteButton").set_sensitive(False)
        else:
            key = self.get_selected_key()
            if self.is_favorite(key):
                self.crossover_gui.get_object("ToggleFavoriteMenuItem").set_label(_("Remove '%s' from Favorites") % name)
                self.crossover_gui.get_object("ToggleFavoriteButton").set_tooltip_text(_("Remove '%s' from Favorites") % name)
                self.crossover_gui.get_object("ToggleFavoriteButton").set_active(True)
            else:
                self.crossover_gui.get_object("ToggleFavoriteMenuItem").set_label(_("Add '%s' to Favorites") % name)
                self.crossover_gui.get_object("ToggleFavoriteButton").set_tooltip_text(_("Add '%s' to Favorites") % name)
                self.crossover_gui.get_object("ToggleFavoriteButton").set_active(False)
            self.crossover_gui.get_object("ToggleFavoriteMenuItem").set_sensitive(True)
            self.crossover_gui.get_object("ToggleFavoriteButton").set_sensitive(True)

    def build_item_popup_menu(self):
        obj = self.get_selected_item()
        if not obj:
            return None

        result = gtk.Menu()

        open_item = gtk.MenuItem(_("Open"))
        open_item.show()
        open_item.connect('activate', self.on_OpenAppMenuItem_activate)
        result.append(open_item)

        run_item = gtk.MenuItem(_(u"Run with Options\u2026"))
        run_item.show()
        run_item.connect('activate', self.on_RunWithOptionsMenuItem_activate)
        result.append(run_item)

        sep = gtk.SeparatorMenuItem()
        sep.show()
        result.append(sep)

        fav_item = gtk.MenuItem()
        fav_item.show()
        fav_item.connect('activate', self.on_ToggleFavoriteMenuItem_activate)
        result.append(fav_item)

        if isinstance(obj, cxmenu.MenuItem) or obj == RUN_COMMAND:
            open_item.set_sensitive(True)
        else:
            open_item.set_sensitive(False)

        if isinstance(obj, cxmenu.MenuItem) and obj.type == 'windows':
            run_item.set_sensitive(True)
        else:
            run_item.set_sensitive(False)

        if isinstance(obj, dict):
            fav_item.set_label(_("Add to Favorites"))
            fav_item.set_sensitive(False)
        else:
            key = self.get_selected_key()
            if self.is_favorite(key):
                fav_item.set_label(_("Remove from Favorites"))
            else:
                fav_item.set_label(_("Add to Favorites"))
            fav_item.set_sensitive(True)

        return result

    def on_IconView_popup_menu(self, iconview):
        if iconview is not self.view_with_selection:
            return False
        menu = self.build_item_popup_menu()
        if menu:
            # FIXME: position menu based on the selection location?
            menu.popup(None, None, None, 0, 0)
            return True # popped up
        return False

    def on_TreeView_popup_menu(self, treeview):
        if treeview is not self.view_with_selection:
            return False
        menu = self.build_item_popup_menu()
        if menu:
            # FIXME: position menu based on the selection location?
            menu.popup(None, None, None, 0, 0)
            return True # popped up
        return False

    def on_IconView_button_press_event(self, iconview, event):
        if event.button == 3: # right click
            path = iconview.get_path_at_pos(int(event.x), int(event.y))
            if path is None:
                return False # propagate
            iconview.select_path(path)
            menu = self.build_item_popup_menu()
            if menu:
                menu.popup(None, None, None, event.button, event.time)
                return True
        elif event.button == 1: # left click
            path = iconview.get_path_at_pos(int(event.x), int(event.y))
            if path is None:
                self.drag_start_x = self.drag_start_y = None
                self.drag_obj = None
                self.drag_pixbuf = None
                return False # propagate
            model = iconview.get_model()
            iterator = model.get_iter(path)
            obj = model.get_value(iterator, 0)
            if isinstance(obj, cxmenu.MenuItem):
                self.drag_start_x = int(event.x)
                self.drag_start_y = int(event.y)
                self.drag_obj = obj
                self.drag_pixbuf = model.get_value(iterator, 5)
            else:
                self.drag_start_x = self.drag_start_y = None
                self.drag_obj = None
                self.drag_pixbuf = None
        return False # propagate

    def on_TreeView_button_press_event(self, treeview, event):
        if event.button == 3: # right click
            path = treeview.get_path_at_pos(int(event.x), int(event.y))
            if path is None:
                return False # propagate
            path, _column, _x, _y = path
            treeview.get_selection().select_path(path)
            menu = self.build_item_popup_menu()
            if menu:
                menu.popup(None, None, None, event.button, event.time)
                return True
        elif event.button == 1: # left click
            path = treeview.get_path_at_pos(int(event.x), int(event.y))
            if path is None:
                self.drag_start_x = self.drag_start_y = None
                self.drag_obj = None
                self.drag_pixbuf = None
                return False # propagate
            path, _column, _x, _y = path
            model = treeview.get_model()
            iterator = model.get_iter(path)
            obj = model.get_value(iterator, 0)
            if isinstance(obj, cxmenu.MenuItem):
                self.drag_start_x = int(event.x)
                self.drag_start_y = int(event.y)
                self.drag_obj = obj
                self.drag_pixbuf = model.get_value(iterator, 5)
            else:
                self.drag_start_x = self.drag_start_y = None
                self.drag_obj = None
                self.drag_pixbuf = None
        return False # propagate

    def on_TreeView_motion_notify_event(self, treeview, event):
        if (event.state & gtk.gdk.BUTTON1_MASK) and self.drag_start_x is not None:
            if treeview.drag_check_threshold(self.drag_start_x, self.drag_start_y, int(event.x), int(event.y)):
                context = treeview.drag_begin(gtk.target_list_add_uri_targets(),
                                              gtk.gdk.ACTION_COPY,
                                              1,
                                              event)

                context.set_icon_pixbuf(self.drag_pixbuf, 0, 0)

                self.drag_start_x = self.drag_start_y = None

    def on_TreeView_drag_end(self, _treeview, _drag_context):
        self.drag_start_x = self.drag_start_y = None
        self.drag_obj = None
        self.drag_pixbuf = None

    def on_TreeView_drag_data_get(self, _treeview, _drag_context, selection_data, _info, _timestamp):
        path = self.drag_obj.launcher_path
        if not os.path.exists(path):
            self.drag_obj.parent.install_menus()
        selection_data.set_uris([cxutils.path_to_uri(self.drag_obj.launcher_path)])

    def activate_item(self, obj):
        if isinstance(obj, cxmenu.MenuItem):
            obj.start()
        elif isinstance(obj, ControlPanelApplet):
            obj.bottle.launch_control_panel_applet(obj.info['exe'])
        elif obj == RUN_COMMAND:
            self.on_RunCommandMenu_activate()

    def on_item_activate(self, view, path, _column=None):
        model = view.get_model()
        obj = model.get_value(model.get_iter(path), 0)

        if isinstance(obj, dict):
            if view.row_expanded(path):
                view.collapse_row(path)
            else:
                view.expand_row(path, False)
        else:
            self.activate_item(obj)

    def on_OpenAppMenuItem_activate(self, _menuitem):
        self.activate_item(self.get_selected_item())

    def on_RunWithOptionsMenuItem_activate(self, _menuitem):
        item = self.get_selected_item()
        lnkfile = item.lnkfile
        if not lnkfile:
            return
        lnkfile = lnkfile.replace('/', '\\')
        lnkfile = cxutils.argvtocmdline((lnkfile,))
        bottle = item.parent.bottlename
        cxrunui.RunCommandController(bottle).set_command(lnkfile)

    def on_ToggleFavoriteMenuItem_activate(self, _menuitem):
        key = self.get_selected_key()
        if self.is_favorite(key):
            self.remove_favorite(key)
        else:
            self.add_favorite(key)

    def on_ToggleFavoriteButton_toggled(self, button):
        key = self.get_selected_key()
        if not key:
            return
        if self.is_favorite(key) and not button.get_active():
            self.remove_favorite(key)
        elif not self.is_favorite(key) and button.get_active():
            self.add_favorite(key)

    def selection_updated(self, view):
        if self.view_with_selection is not view:
            if isinstance(self.view_with_selection, gtk.IconView):
                self.view_with_selection.unselect_all()
            elif isinstance(self.view_with_selection, gtk.TreeView):
                self.view_with_selection.get_selection().unselect_all()
        self.view_with_selection = view

    def on_IconView_selection_changed(self, view):
        if view.get_selected_items():
            self.selection_updated(view)
            _x, y, _w, h = self._get_iconview_item_box(view, view.get_selected_items()[0])
            y = y + view.get_allocation().y
            self.crossover_gui.get_object("SectionsScrolledWindow").get_vadjustment().clamp_page(y, y+h)
        self.update_file_menu()

    def on_TreeSelection_changed(self, treeselection):
        _model, iterator = treeselection.get_selected()
        if iterator:
            self.selection_updated(treeselection.get_tree_view())
        self.update_file_menu()

    def update_applications_list(self):
        applications = set()
        for package in self.selected_bottle.installed_packages.itervalues():
            if isinstance(package.name, str):
                applications.add(package.name)
            else:
                applications.add(package.name.encode('utf8'))

        iterator = self.installed_applications_store.get_iter_first()
        while iterator:
            name = self.installed_applications_store.get_value(iterator, 0)
            if name in applications:
                applications.remove(name)
                iterator = self.installed_applications_store.iter_next(iterator)
            else:
                if not self.installed_applications_store.remove(iterator):
                    # no more rows
                    break

        for name in applications:
            self.installed_applications_store.append((name,))

    def _add_expanded_row(self, treeview, path, rows):
        model = treeview.get_model()

        for i in range(len(path)):
            iterator = model.get_iter(path[0:i+1])
            key = model.get_value(iterator, 4)
            if key not in rows:
                rows[key] = {}
            rows = rows[key]

    def _expand_rows(self, treeview, rows, parent_iterator):
        if not rows:
            return
        treestore = treeview.get_model()
        if parent_iterator:
            iterator = treestore.iter_children(parent_iterator)
        else:
            iterator = treestore.get_iter_first()
        while iterator:
            key = treestore.get_value(iterator, 4)
            if key in rows:
                path = treestore.get_path(iterator)
                treeview.expand_row(path, False)
                self._expand_rows(treeview, rows[key], iterator)
            iterator = treestore.iter_next(iterator)

    def updateAppList(self):
        new_search_string = self.crossover_gui.get_object("SearchEntry").get_real_text()

        if new_search_string and not self.search_string:
            expanded_rows = {}
            self.crossover_gui.get_object("LaunchersTreeView").map_expanded_rows(self._add_expanded_row, expanded_rows)
            self.expanded_rows = expanded_rows

        self.search_string = new_search_string.lower()

        programs_section_contents = {}
        favorites_section_contents = {}
        control_panel_contents = {}
        tree_contents = {}

        needed_bottle_updates = {}

        filtered_favorites = False # whether there are any favorites filtered by the search string

        all_bottles = set(bottlequery.get_bottle_list())

        if self.selected_bottle is None:
            bottle_names = all_bottles
        else:
            bottle_names = set([self.selected_bottle.name])

        # Update the 'default bottle' checkbox to match the selected bottle
        self.updating_default_checkbox = True
        cond = self.selected_bottle is not None and \
               self.selected_bottle.is_default
        for item in ("DefaultBottleMenu", "DefaultBottleCMenu"):
            self.crossover_gui.get_object(item).set_active(cond)
        self.updating_default_checkbox = False

        # Disable the bottle-specific menus if no bottle is selected
        cond = self.selected_bottle is not None and \
               self.can_use_bottle(self.selected_bottle.name)
        for item in ("DuplicateBottleAMenu",
                     "DuplicateBottleMenu", "DuplicateBottleCMenu",
                     "RemoveBottleButton",
                     "RemoveBottleMenu", "RemoveBottleCMenu",
                     "OpenCDriveMenu", "OpenCDriveCMenu",
                     "ExportArchiveMenu", "ExportArchiveCMenu",
                     "CreatePackageMenu", "CreatePackageCMenu",
                     "ResetWebBrowserMenu", "ResetWebBrowserCMenu",
                     "QuitBottleMenu", "QuitBottleCMenu"):
            self.crossover_gui.get_object(item).set_sensitive(cond)

        # Only allow force-quitting if a regular quit has been attempted
        cond = self.selected_bottle is not None and \
               (self.selected_bottle.STATUS_DOWNING in self.selected_bottle.status_overrides or
                self.selected_bottle.name in self.shutdown_failed_bottles)
        for item in ("ForceQuitBottleMenu", "ForceQuitBottleCMenu"):
            self.crossover_gui.get_object(item).set_sensitive(cond)

        self.updating_csmt_checkbox = True
        self.updating_dxvk_checkbox = True
        if not self.selected_bottle or \
            self.selected_bottle.is_csmt_disabled_state == self.selected_bottle.STATUS_CSMT_UNKNOWN:
            self.crossover_gui.get_object("CsmtToggleMenu").set_sensitive(False)
            self.crossover_gui.get_object("CsmtToggleMenu").set_active(False)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_sensitive(False)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_active(False)
        elif self.selected_bottle.is_csmt_disabled_state == self.selected_bottle.STATUS_CSMT_ENABLED:
            self.crossover_gui.get_object("CsmtToggleMenu").set_sensitive(True)
            self.crossover_gui.get_object("CsmtToggleMenu").set_active(True)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_sensitive(True)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_active(True)
        elif self.selected_bottle.is_csmt_disabled_state == self.selected_bottle.STATUS_CSMT_DISABLED:
            self.crossover_gui.get_object("CsmtToggleMenu").set_sensitive(True)
            self.crossover_gui.get_object("CsmtToggleMenu").set_active(False)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_sensitive(True)
            self.crossover_gui.get_object("CsmtToggleCMenu").set_active(False)
        self.updating_csmt_checkbox = False

        if not self.selected_bottle or \
            self.selected_bottle.is_dxvk_enabled_state == self.selected_bottle.STATUS_DXVK_UNKNOWN:
            self.crossover_gui.get_object("DxvkToggleMenu").set_sensitive(False)
            self.crossover_gui.get_object("DxvkToggleMenu").set_active(False)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_sensitive(False)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_active(False)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_tooltip_text(_("Install DXVK via CrossTie to enable this menu entry"))
        elif self.selected_bottle.is_dxvk_enabled_state == self.selected_bottle.STATUS_DXVK_ENABLED:
            self.crossover_gui.get_object("DxvkToggleMenu").set_sensitive(True)
            self.crossover_gui.get_object("DxvkToggleMenu").set_active(True)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_sensitive(True)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_active(True)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_tooltip_text(None)
        elif self.selected_bottle.is_dxvk_enabled_state == self.selected_bottle.STATUS_DXVK_DISABLED:
            self.crossover_gui.get_object("DxvkToggleMenu").set_sensitive(True)
            self.crossover_gui.get_object("DxvkToggleMenu").set_active(False)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_sensitive(True)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_active(False)
            self.crossover_gui.get_object("DxvkToggleCMenu").set_tooltip_text(None)
        self.updating_dxvk_checkbox = False

        # Only allow updating if the current bottle is managed and out-of-date
        cond = self.selected_bottle is not None and \
               self.selected_bottle.is_managed and \
               not self.selected_bottle.up_to_date and \
               self.can_use_bottle(self.selected_bottle.name)
        for item in ("UpdatePublishedBottleMenu", "UpdatePublishedBottleCMenu"):
            self.crossover_gui.get_object(item).set_sensitive(cond)

        # Disable the operations that cannot be performed on managed bottles
        cond = self.selected_bottle is not None and \
               not self.selected_bottle.is_managed and \
               self.can_use_bottle(self.selected_bottle.name)
        for item in ("RenameBottleMenu", "RenameBottleCMenu",
                     "PublishBottleMenu", "PublishBottleCMenu",
                     "BottleInstallSoftwareMenu", "BottleInstallSoftwareCMenu"):
            self.crossover_gui.get_object(item).set_sensitive(cond)
        self.bottle_label.set_property('editable', cond)
        self.crossover_gui.get_object("BottleDescriptionText").set_editable(cond)

        if self.selected_bottle:
            self.crossover_gui.get_object("DuplicateBottleMenu").set_label(_(u"Duplicate '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("DuplicateBottleAMenu").set_label(_(u"Duplicate '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("RemoveBottleMenu").set_label(_(u"Delete '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("RenameBottleMenu").set_label(_(u"Rename '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("BottleInstallSoftwareMenu").set_label(_(u"Install Software into '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("ExportArchiveMenu").set_label(_(u"Export '%s' to Archive\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("PublishBottleMenu").set_label(_(u"Publish '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("CreatePackageMenu").set_label(_(u"Create Package from '%s'\u2026") % self.selected_bottle.name)
            self.crossover_gui.get_object("QuitBottleMenu").set_label(_(u"Quit '%s'") % self.selected_bottle.name)
            self.crossover_gui.get_object("ForceQuitBottleMenu").set_label(_(u"Force Quit '%s'") % self.selected_bottle.name)
            self.crossover_gui.get_object("DefaultBottleMenu").set_sensitive(True)
            self.crossover_gui.get_object("DefaultBottleCMenu").set_sensitive(True)
            self.crossover_gui.get_object("BottleTypeLabel").set_sensitive(True)
            self.crossover_gui.get_object("BottleTypeValue").set_sensitive(True)
            self.crossover_gui.get_object("BottleTypeValue").set_text(bottlemanagement.get_template_name(self.selected_bottle.bottletemplate))
            self.crossover_gui.get_object("BottleArchValue").set_sensitive(True)
            if self.selected_bottle.arch == 'win64':
                self.crossover_gui.get_object("BottleArchValue").set_text(_("64-bit"))
            else:
                self.crossover_gui.get_object("BottleArchValue").set_text(_("32-bit"))
            self.crossover_gui.get_object("BottleStatusLabel").set_sensitive(True)
            self.crossover_gui.get_object("BottleStatusValue").set_sensitive(True)
            self.crossover_gui.get_object("BottleStatusValue").set_text(self.selected_bottle.status)
            self.crossover_gui.get_object("BottleDescriptionLabel").set_sensitive(True)
            self.crossover_gui.get_object("BottleDescriptionText").set_sensitive(True)
            description_buffer = self.crossover_gui.get_object("BottleDescriptionText").get_buffer()
            ui_description = description_buffer.get_text(description_buffer.get_start_iter(), description_buffer.get_end_iter())
            if ui_description != self.selected_bottle.current_description:
                description_buffer.set_text(self.selected_bottle.current_description)

            self.crossover_gui.get_object("BottleApplicationsLabel").set_sensitive(True)
            self.crossover_gui.get_object("BottleApplicationsView").set_sensitive(True)
            if self.crossover_gui.get_object("BottlePropertiesSidebar").get_property('visible'):
                if self.selected_bottle.installed_packages_ready:
                    self.crossover_gui.get_object("BottleApplicationsLabel").set_text(_("Installed Applications:"))
                    self.update_applications_list()
                else:
                    self.crossover_gui.get_object("BottleApplicationsLabel").set_text(_(u"Installed Applications (Loading\u2026):"))
                    self.installed_applications_store.clear()
                    if not self.selected_bottle.installed_packages_loading:
                        needed_bottle_updates[self.selected_bottle] = needed_bottle_updates.get(self.selected_bottle, []) + ['applications']
            if not self.selected_bottle.is_csmt_disabled_ready and \
                not self.selected_bottle.is_csmt_disabled_loading:
                needed_bottle_updates[self.selected_bottle] = needed_bottle_updates.get(self.selected_bottle, []) + ['csmt']
            if not self.selected_bottle.is_dxvk_enabled_ready and \
                not self.selected_bottle.is_dxvk_enabled_loading:
                needed_bottle_updates[self.selected_bottle] = needed_bottle_updates.get(self.selected_bottle, []) + ['dxvk']
        else:
            self.crossover_gui.get_object("DuplicateBottleMenu").set_label(_(u"Duplicate Bottle\u2026"))
            self.crossover_gui.get_object("DuplicateBottleAMenu").set_label(_(u"Duplicate Bottle\u2026"))
            self.crossover_gui.get_object("RemoveBottleMenu").set_label(_(u"Delete Bottle\u2026"))
            self.crossover_gui.get_object("RenameBottleMenu").set_label(_(u"Rename Bottle\u2026"))
            self.crossover_gui.get_object("BottleInstallSoftwareMenu").set_label(_(u"Install Software into Bottle\u2026"))
            self.crossover_gui.get_object("ExportArchiveMenu").set_label(_(u"Export Bottle to Archive\u2026"))
            self.crossover_gui.get_object("PublishBottleMenu").set_label(_(u"Publish Bottle\u2026"))
            self.crossover_gui.get_object("CreatePackageMenu").set_label(_(u"Create Package from Bottle\u2026"))
            self.crossover_gui.get_object("QuitBottleMenu").set_label(_("Quit Bottle"))
            self.crossover_gui.get_object("ForceQuitBottleMenu").set_label(_("Force Quit Bottle"))
            self.crossover_gui.get_object("DefaultBottleMenu").set_sensitive(False)
            self.crossover_gui.get_object("DefaultBottleCMenu").set_sensitive(False)
            self.crossover_gui.get_object("BottleTypeLabel").set_sensitive(False)
            self.crossover_gui.get_object("BottleTypeValue").set_sensitive(False)
            self.crossover_gui.get_object("BottleTypeValue").set_text('')
            self.crossover_gui.get_object("BottleArchValue").set_sensitive(False)
            self.crossover_gui.get_object("BottleArchValue").set_text('')
            self.crossover_gui.get_object("BottleStatusLabel").set_sensitive(False)
            self.crossover_gui.get_object("BottleStatusValue").set_sensitive(False)
            self.crossover_gui.get_object("BottleStatusValue").set_text('')
            self.crossover_gui.get_object("BottleDescriptionLabel").set_sensitive(False)
            self.crossover_gui.get_object("BottleDescriptionText").set_sensitive(False)
            self.crossover_gui.get_object("BottleDescriptionText").get_buffer().set_text('')
            self.crossover_gui.get_object("BottleApplicationsLabel").set_sensitive(False)
            self.crossover_gui.get_object("BottleApplicationsLabel").set_text(_("Installed Applications:"))
            self.crossover_gui.get_object("BottleApplicationsView").set_sensitive(False)
            self.installed_applications_store.clear()

        # find other bottles we have to scan to get favorites
        programs_bottles = bottle_names.copy()

        control_panel_bottles = set()
        if self.selected_bottle:
            control_panel_bottles.add(self.selected_bottle.name)

        for key in self.favorites:
            if key.startswith('cxmenu/'):
                bottle_name = key.split('/', 2)[1]
                if bottle_name in all_bottles:
                    programs_bottles.add(bottle_name)
            elif key.startswith('controlpanel/'):
                bottle_name = key.split('/', 2)[1]
                if bottle_name in all_bottles:
                    control_panel_bottles.add(bottle_name)

        for bottle_name in programs_bottles:
            if not self.can_use_bottle(bottle_name):
                continue
            prefs = cxmenu.MenuPrefs(bottle_name, True)
            prefs.read_config()

            for app in prefs.data:
                if app.startswith('Desktop'):
                    continue

                if prefs.data[app].mode != 'install':
                    continue

                key = "cxmenu/" + bottle_name + '/' + app

                if self.search_string and self.search_string not in prefs.data[app].menu_name().lower():
                    if self.is_favorite(key):
                        filtered_favorites = True
                    continue

                if self.is_favorite(key):
                    favorites_section_contents[key] = prefs.data[app]

                if bottle_name not in bottle_names:
                    continue

                programs_section_contents[key] = prefs.data[app]

                path = app.split('/', 1)[1]
                if path.startswith('Programs/'):
                    path = path.split('/', 1)[1]
                if path.endswith(('.lnk', '.url')):
                    path = path.rsplit('.', 1)[0]
                tree = tree_contents
                for part in path.split('/')[:-1]:
                    part_key = 'folder/' + part
                    if part_key not in tree:
                        tree[part_key] = {}
                    tree = tree[part_key]
                tree[key] = prefs.data[app]

        for bottle_name in control_panel_bottles:
            if not self.can_use_bottle(bottle_name):
                continue

            bottle_obj = self.bottles.bottleObject(bottle_name)

            if not bottle_obj.control_panel_ready:
                if not bottle_obj.control_panel_loading:
                    needed_bottle_updates[bottle_obj] = needed_bottle_updates.get(bottle_obj, []) + ['controlpanel']
                continue

            for applet_info in bottle_obj.control_panel_table:
                key = "controlpanel/" + bottle_name + '/' + applet_info['exe']

                if self.search_string and self.search_string not in applet_info['name'].lower():
                    if self.is_favorite(key):
                        filtered_favorites = True
                    continue

                applet = ControlPanelApplet(bottle_obj, applet_info)

                if self.is_favorite(key):
                    favorites_section_contents[key] = applet

                if bottle_obj != self.selected_bottle:
                    continue

                control_panel_contents[key] = applet

        if not self.search_string or self.search_string in _(u"Run Command\u2026").lower():
            if self.is_favorite(RUN_COMMAND):
                favorites_section_contents[RUN_COMMAND] = RUN_COMMAND

            programs_section_contents[RUN_COMMAND] = RUN_COMMAND

            tree_contents[RUN_COMMAND] = RUN_COMMAND
        elif self.is_favorite(RUN_COMMAND):
            filtered_favorites = True

        if favorites_section_contents:
            tree_contents[FAVORITES] = favorites_section_contents

        if control_panel_contents:
            tree_contents[CONTROL_PANEL] = control_panel_contents

        added_first_favorite = (self.favorites_list_store.get_iter_first() is None) and favorites_section_contents

        self.update_item_list(self.favorites_list_store, favorites_section_contents)

        self.update_item_list(self.programs_list_store, programs_section_contents)

        self.update_item_list(self.control_panel_list_store, control_panel_contents)

        items_changed = self.update_item_list(self.launchers_tree_store, tree_contents)

        if added_first_favorite:
            # expand favorites folder
            treeview = self.crossover_gui.get_object("LaunchersTreeView")
            iterator = self.launchers_tree_store.get_iter_first()
            while iterator:
                key = self.launchers_tree_store.get_value(iterator, 4)
                if key == FAVORITES:
                    treeview.expand_row(self.launchers_tree_store.get_path(iterator), False)
                    break
                iterator = self.launchers_tree_store.iter_next(iterator)

        if items_changed:
            # expand programs folders as long as they have no sibling folders or shortcuts
            depth = 0
            tree = tree_contents
            while tree:
                sub_item = None
                for key, item in tree.iteritems():
                    if key.startswith(('folder/', 'cxmenu/')):
                        if sub_item:
                            sub_item = None
                            break
                        sub_item = item

                if isinstance(sub_item, dict):
                    depth = depth + 1
                    tree = sub_item
                else:
                    break

            iterator = None
            treeview = self.crossover_gui.get_object("LaunchersTreeView")
            while depth:
                iterator = self.launchers_tree_store.iter_children(iterator)
                while iterator:
                    key = self.launchers_tree_store.get_value(iterator, 4)
                    if key.startswith('folder/'):
                        treeview.expand_row(self.launchers_tree_store.get_path(iterator), False)
                        break
                    iterator = self.launchers_tree_store.iter_next(iterator)
                depth = depth - 1

        if self.expanded_rows is not None and not self.search_string:
            treeview = self.crossover_gui.get_object("LaunchersTreeView")
            treeview.collapse_all()
            self._expand_rows(treeview, self.expanded_rows, None)
            self.expanded_rows = None
        elif self.search_string:
            self.crossover_gui.get_object("LaunchersTreeView").expand_all()

        if bottle_names:
            self.crossover_gui.get_object("CrossOverLogo").hide()
            self.crossover_gui.get_object("LaunchersBox").show()
            self.setup_icon_view(self.crossover_gui.get_object("ProgramsSectionIconView"),
                                 show_bottle=len(bottle_names) > 1)
            self.setup_icon_view(self.crossover_gui.get_object("FavoritesSectionIconView"),
                                 show_bottle=True)
            self.setup_icon_view(self.crossover_gui.get_object("ControlPanelSectionIconView"),
                                 show_bottle=False)
        else:
            self.crossover_gui.get_object("CrossOverLogo").show()
            self.crossover_gui.get_object("LaunchersBox").hide()

        if favorites_section_contents:
            self.crossover_gui.get_object("FavoritesSectionLabel").show()
            self.crossover_gui.get_object("FavoritesSectionLabel").set_text(_("Favorites"))
            self.crossover_gui.get_object("FavoritesSectionIconView").show()
        elif filtered_favorites:
            self.crossover_gui.get_object("FavoritesSectionLabel").show()
            self.crossover_gui.get_object("FavoritesSectionLabel").set_text(_("Favorites (No Matches)"))
            self.crossover_gui.get_object("FavoritesSectionIconView").show()
        else:
            self.crossover_gui.get_object("FavoritesSectionLabel").hide()
            self.crossover_gui.get_object("FavoritesSectionIconView").hide()

        if control_panel_contents:
            self.crossover_gui.get_object("ControlPanelSectionLabel").show()
            self.crossover_gui.get_object("ControlPanelSectionIconView").show()
            self.crossover_gui.get_object("ControlPanelSectionLabel").set_text(_("Control Panels"))
        elif self.selected_bottle and self.selected_bottle.control_panel_loading:
            self.crossover_gui.get_object("ControlPanelSectionLabel").show()
            self.crossover_gui.get_object("ControlPanelSectionIconView").show()
            self.crossover_gui.get_object("ControlPanelSectionLabel").set_text(_(u"Control Panels (Loading\u2026)"))
        elif self.selected_bottle:
            self.crossover_gui.get_object("ControlPanelSectionLabel").show()
            self.crossover_gui.get_object("ControlPanelSectionIconView").show()
            self.crossover_gui.get_object("ControlPanelSectionLabel").set_text(_("Control Panels (No Matches)"))
        else:
            self.crossover_gui.get_object("ControlPanelSectionLabel").hide()
            self.crossover_gui.get_object("ControlPanelSectionIconView").hide()

        if programs_section_contents:
            self.crossover_gui.get_object("ProgramsSectionLabel").set_text(_("Programs"))
        else:
            self.crossover_gui.get_object("ProgramsSectionLabel").set_text(_("Programs (No Matches)"))

        for bottle, updates in needed_bottle_updates.iteritems():
            self.bottles.queue_bottle_updates((bottle,), updates)

    def update_applist_idle(self):
        self.updateAppList()
        self.update_applist_source = None
        return False

    def on_SearchEntry_changed(self, entry, text):
        if not self.update_applist_source:
            self.update_applist_source = gobject.idle_add(self.update_applist_idle)
        if text:
            entry.set_property('secondary-icon-sensitive', True)
        else:
            entry.set_property('secondary-icon-sensitive', False)

    def on_SearchEntry_icon_release(self, entry, icon_pos, _event):
        if icon_pos == gtk.ENTRY_ICON_SECONDARY:
            entry.set_text('')

    def bottleCollectionChanged(self):
        self.update_bottle_list()
        self.updateAppList()

    def bottleChanged(self, _bottle):
        # This can be called from outside the main thread
        gobject.idle_add(self.bottleCollectionChanged)

    def on_cxmenuconf_changed(self, _event, _path, _observer_data):
        gobject.idle_add(self.bottleCollectionChanged)

    def on_ShowHelp_activate(self, _unused):
        # pylint: disable=R0201
        cxguitools.show_help("index.html")

    def on_description_changed(self, text_buffer):
        if not self.crossover_gui.get_object("BottleDescriptionText").get_editable():
            return
        if not self.selected_bottle:
            return
        new_description = text_buffer.get_text(text_buffer.get_start_iter(), text_buffer.get_end_iter())
        self.selected_bottle.change_description(new_description)
        if self.selected_bottle not in self.updating_description_bottles and \
            not self.selected_bottle.is_description_written():
            pyop.sharedOperationQueue.enqueue(UpdateDescriptionOperation(self.selected_bottle, self))

    def on_description_insert_text(self, text_buffer, _iter, text, _length):
        if '\n' in text:
            text_buffer.stop_emission('insert-text')

    def on_ShowAboutCrossOver_activate(self, _caller):
        if self.aboutDialog is None:
            versionString = "%s %s" % (distversion.PRODUCT_NAME,
                                       distversion.PUBLIC_VERSION)
            self.aboutDialog = gtk.Dialog(title=_("About CrossOver"),
                                          parent=self.main_window,
                                          flags=gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_DESTROY_WITH_PARENT,

                                          buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))

            versionLabel = gtk.Label(versionString)
            versionLabel.show()
            crossOverIcon = gtk.Image()
            crossOverIcon.set_from_file(os.path.join(cxutils.CX_ROOT,
                                                     "share",
                                                     "images",
                                                     "welcomeCrossOverIcon.png"))
            crossOverIcon.show()

            codeWeaversLink = gtk.Label()
            codeWeaversLink.set_markup("<a href=\"http://www.codeweavers.com\">CodeWeavers, Inc.</a>")
            codeWeaversLink.show()

            # pylint is unable to figure out the
            # correct class of the dialog's vbox
            self.aboutDialog.vbox.pack_start(crossOverIcon) # pylint: disable=E1101
            self.aboutDialog.vbox.pack_start(versionLabel) # pylint: disable=E1101
            self.aboutDialog.vbox.pack_start(codeWeaversLink) # pylint: disable=E1101
            self.aboutDialog.vbox.show() # pylint: disable=E1101

            self.aboutDialog.connect("response", self.on_HideAboutCrossOver_activate)

        self.aboutDialog.present()

    def on_SystemInformation_activate(self, _caller):
        # pylint: disable=R0201
        import systeminfo
        systeminfo.SystemInfoDialog()

    def on_License_activate(self, _caller):
        # pylint: disable=R0201
        import systeminfo
        systeminfo.LicenseDialog()

    def on_HideAboutCrossOver_activate(self, _unused, _unused_2):
        # pylint: disable=R0201
        self.aboutDialog.destroy()
        self.aboutDialog = None

    def on_DoRegistration_activate(self, _unused):
        # pylint: disable=R0201
        cxregisterui.open_or_show()

    def on_PreferencesMenuItem_activate(self, _unused):
        # pylint: disable=R0201
        cxprefsui.open_or_show()

    def on_InstallWindowsSoftware_clicked(self, _unused):
        dlg = cxinstallerui.open_or_show()
        dlg.add_observer(cxinstallerui.INSTALL_FINISHED, self.installWizardDidFinishInstalling)

    def installWizardDidFinishInstalling(self, _event, _data):
        self.updateAppList()

    def quit_crossover_requested(self, _unused, _unused2=None):
        self.main_window.destroy()

    def on_MainWindow_configure_event(self, _widget, _event):
        # get_size doesn't work in on_destroy
        self._size = self.main_window.get_size()

    def on_bottlepane_move(self, _obj, _propertyspec):
        if self.crossover_gui.get_object("BottlePaneVBox").get_property('visible'):
            self._pane_size = self.crossover_gui.get_object("BottlePaned").get_position()

    def on_destroy(self, _unused):
        global DIALOG # pylint: disable=W0603
        DIALOG = None
        cxfsnotifier.remove_observer(
            os.path.join(cxutils.CX_ROOT, "etc", "license.txt"),
            self.on_license_changed)
        cxfsnotifier.remove_observer(
            os.path.join(cxutils.CX_ROOT, "etc", "license.sig"),
            self.on_license_changed)
        self.bottles.removeChangeDelegate(self)
        if self._size:
            cxproduct.save_setting('OfficeSetup', 'BottleManagerSize',
                                   '%sx%s' % self._size)
        if self._pane_size:
            cxproduct.save_setting('OfficeSetup', 'BottleSidebarSize',
                                   str(self._pane_size))
        cxproduct.save_setting('OfficeSetup', 'ShowBottleSidebar',
                               '1' if self.bottle_pane_showing else '')
        cxproduct.save_setting('OfficeSetup', 'MainWindowView', self.view)
        cxguitools.toplevel_quit()

    def on_ToggleShowBottles_activate(self, _unused):
        self.toggle_bottle_pane()

    def on_ToggleShowBottlesButton_activate(self, widget):
        active = widget.get_active()
        if active != self.bottle_pane_showing:
            self.toggle_bottle_pane(active)

    def on_BottleListView_select(self, selection):
        model, iterator = selection.get_selected()
        bottle = model.get_value(iterator, 3)

        if bottle is None:
            selection.select_path((1,))

        if self.selected_bottle is not bottle:
            self.selected_bottle = bottle
            self.updateAppList()

    def on_BottleListView_query_tooltip(self, treeview, x, y, keyboard_mode, tooltip):
        if keyboard_mode:
            return False # FIXME
        res = treeview.get_path_at_pos(x, y)
        if not res:
            return False
        path, _column, _xofs, _yofs = res

        iterator = self.bottle_list_store.get_iter(path)
        bottle = self.bottle_list_store.get_value(iterator, 3)

        if bottle is None:
            return False

        tooltip.set_markup(self.get_bottle_markup(bottle, indent=''))

        return True

    def toggle_bottle_pane(self, show=None):
        if show is None:
            show = not self.bottle_pane_showing

        self.bottle_pane_showing = show

        if show:
            self.crossover_gui.get_object("ToggleShowBottlesMenu").set_label(
                _("Hide Bottles"))
            self.crossover_gui.get_object("ToggleShowBottlesButton").set_active(True)
            self.crossover_gui.get_object("BottlePaneVBox").show()
        else:
            self.crossover_gui.get_object("ToggleShowBottlesMenu").set_label(
                _("Show Bottles"))
            self.crossover_gui.get_object("ToggleShowBottlesButton").set_active(False)
            self.crossover_gui.get_object("BottlePaneVBox").hide()
            if self.selected_bottle:
                self.selected_bottle = None
                self.crossover_gui.get_object("BottleListView").get_selection().select_path((1,))
                self.updateAppList()

    def set_view(self, view):
        if self.view is None:
            # Hack to avoid recursively calling this while modifying the gui
            return

        self.view = None

        self.crossover_gui.get_object("SectionsScrolledWindow").set_property('visible', view != TREE_VIEW)
        self.crossover_gui.get_object("TreeViewScrolledWindow").set_property('visible', view == TREE_VIEW)
        self.crossover_gui.get_object("ViewAsIconsMenu").set_active(view == ICON_VIEW)
        self.crossover_gui.get_object("ViewAsIconsButton").set_active(view == ICON_VIEW)
        self.crossover_gui.get_object("ViewAsTreeMenu").set_active(view == TREE_VIEW)
        self.crossover_gui.get_object("ViewAsTreeButton").set_active(view == TREE_VIEW)

        self.crossover_gui.get_object("LaunchersTreeView").columns_autosize()

        self.view = view

    def on_ViewAsIcons_activate(self, _obj):
        self.set_view(ICON_VIEW)

    def on_ViewAsTree_activate(self, _obj):
        self.set_view(TREE_VIEW)

    def on_AddBottle_clicked(self, _button):
        NewBottleController(self)

    def on_DuplicateBottleMenu_activate(self, _button):
        if self.selected_bottle:
            NewBottleController(self, bottle_to_copy=self.selected_bottle.name)

    def on_ImportArchiveMenu_activate(self, _menu):
        RestoreArchiveController(self)

    def on_RemoveBottle_clicked(self, _caller):
        selectedBottleName = self.selected_bottle.name

        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.delete_bottle_response, user_data=self.selected_bottle, message_type=gtk.MESSAGE_WARNING)

    def delete_bottle_response(self, response, bottle):
        if response == gtk.RESPONSE_OK:
            deleteBottleOp = DeleteBottleOperation(bottle, self)
            pyop.sharedOperationQueue.enqueue(deleteBottleOp)

class NewBottleController(object):

    def __init__(self, main_window, bottle_to_copy=None):
        self.main_window = main_window

        self.timeout_source = None

        self.modaldialogxml = gtk.Builder()
        self.modaldialogxml.set_translation_domain("crossover")
        self.modaldialogxml.add_from_file(cxguitools.get_ui_path("newbottledialog"))
        self.modaldialogxml.connect_signals(self)
        newbottledlg = self.modaldialogxml.get_object("NewBottleDialog")

        newbottledlg.connect('destroy', self.on_destroy)

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

            copyBottleName = _("Copy of %(bottle)s") % {'bottle': bottle_to_copy}
            # Eliminate spaces from the bottle being copied while we're at it
            copyBottleName = copyBottleName.replace(' ', '_')
            bottleNameWidget.set_text(copyBottleName)

        else:
            caption.hide()

            liststore = gtk.ListStore(str, str) # display name, template
            templatewidget.set_model(liststore)
            index = 0
            for template in sorted(bottlemanagement.template_list(), None,
                                   bottlemanagement.get_template_key):
                display_name = bottlemanagement.get_template_name(template, True)
                new_row = liststore.append()
                liststore.set_value(new_row, 0, display_name)
                liststore.set_value(new_row, 1, template)
                if template == "win7":
                    templatewidget.set_active(index)
                index += 1
            cell = gtk.CellRendererText()
            templatewidget.pack_start(cell, True)
            templatewidget.add_attribute(cell, "text", 0)

            bottle_name_hint = _("New Bottle")
            bottle_name_hint = bottle_name_hint.replace(' ', '_')
            bottleNameWidget.set_text(bottlecollection.unique_bottle_name(bottle_name_hint))

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

        newbottledlg.show()

    def bottle_name_delete_text(self, caller, start, stop):
        # pylint: disable=R0201
        name = caller.get_text()
        name = name[:start] + name[stop:]
        if not cxutils.is_valid_bottlename(name):
            caller.emit_stop_by_name("delete-text")
        else:
            okButton = self.modaldialogxml.get_object("okbutton1")
            okButton.set_sensitive(not bottlequery.is_existing_bottle(name))

    def bottle_name_insert_text(self, caller, new_text, _length, _user_data):
        # pylint: disable=R0201
        name = caller.get_text()
        position = caller.get_position()
        name = name[:position] + new_text + name[position:]
        if not cxutils.is_valid_bottlename(name):
            caller.emit_stop_by_name("insert-text")
        else:
            okButton = self.modaldialogxml.get_object("okbutton1")
            okButton.set_sensitive(not bottlequery.is_existing_bottle(name))

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

        if self.bottle_to_copy is None:
            templatewidget = self.modaldialogxml.get_object("bottletype")
            templatemodel = templatewidget.get_model()
            template = templatemodel.get_value(templatewidget.get_active_iter(), 1)
        else:
            template = None

        newBottleOp = NewBottleOperation(bottleNameWidget.get_text().decode('utf8'), template, self, self.bottle_to_copy)
        progbar.show()
        dlgWidget.set_sensitive(False)

        self.timeout_source = gobject.timeout_add(100, self.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)

    def pulse(self):
        self.modaldialogxml.get_object("NewBottleProgbar").pulse()
        return True

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

    def AddBottleFinished(self, op):
        dlgWidget = self.modaldialogxml.get_object("NewBottleDialog")
        progbar = self.modaldialogxml.get_object("NewBottleProgbar")
        gobject.source_remove(self.timeout_source)

        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()

    def on_destroy(self, _unused):
        cxguitools.toplevel_quit()

class NewBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle_name, template, new_bottle_controller, bottle_to_copy=None, publish=False):
        pyop.PythonOperation.__init__(self)
        self.exitStatus = None
        self.bottle_to_copy = bottle_to_copy
        self.new_bottle_name = bottle_name
        self.template = template
        self.publish = publish
        self.new_bottle_controller = new_bottle_controller

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

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

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

    def finish(self):
        bottlecollection.sharedCollection().remove_ignored_bottle(self.new_bottle_name)
        self.new_bottle_controller.AddBottleFinished(self)
        pyop.PythonOperation.finish(self)

class RestoreArchiveController(object):
    def __init__(self, main_window, archive=None):
        self.main_window = main_window

        self.timeout_source = None

        self.modaldialogxml = gtk.Builder()
        self.modaldialogxml.set_translation_domain("crossover")
        self.modaldialogxml.add_from_file(cxguitools.get_ui_path("bottlearchivepicker"))
        self.modaldialogxml.connect_signals(self)
        pickerdlg = self.modaldialogxml.get_object("BottleArchivePicker")
        cxguitools.add_filters(pickerdlg, cxguitools.FILTERS_CXARCHIVES)

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

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

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

    def pulse(self):
        self.modaldialogxml.get_object("RestoringProgbar").pulse()
        return True

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

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

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

        self.timeout_source = gobject.timeout_add(100, self.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)

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

    def suggestBottleName(self, archiveFile):
        openButton = self.modaldialogxml.get_object("openbutton")
        bottleNameWidget = self.modaldialogxml.get_object("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 bottle_name_delete_text(self, caller, start, stop):
        # pylint: disable=R0201
        name = caller.get_text()
        name = name[:start] + name[stop:]
        if not cxutils.is_valid_bottlename(name):
            caller.emit_stop_by_name("delete-text")
        else:
            okButton = self.modaldialogxml.get_object("openbutton")
            okButton.set_sensitive(not bottlequery.is_existing_bottle(name))

    def bottle_name_insert_text(self, caller, new_text, _length, _user_data):
        # pylint: disable=R0201
        name = caller.get_text()
        position = caller.get_position()
        name = name[:position] + new_text + name[position:]
        if not cxutils.is_valid_bottlename(name):
            caller.emit_stop_by_name("insert-text")
        else:
            okButton = self.modaldialogxml.get_object("openbutton")
            okButton.set_sensitive(not bottlequery.is_existing_bottle(name))

    def RestoreBottleFinished(self, op):
        pickerdlg = self.modaldialogxml.get_object("BottleArchivePicker")
        progbar = self.modaldialogxml.get_object("RestoringProgbar")
        gobject.source_remove(self.timeout_source)

        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 RestoreBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle_name, archive_file, controller_window):
        pyop.PythonOperation.__init__(self)
        self.exitStatus = None
        self.archive_file = archive_file
        self.bottle_name = bottle_name
        self.controller_window = controller_window

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

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

    def main(self):
        self.exitStatus = bottlemanagement.restore_bottle(self.bottle_name, self.archive_file)

    def finish(self):
        bottlecollection.sharedCollection().remove_ignored_bottle(self.bottle_name)
        self.controller_window.RestoreBottleFinished(self)
        pyop.PythonOperation.finish(self)


class PublishBottleController(object):

    def __init__(self, main_window, orig_bottle_name=None):
        self.main_window = main_window

        self.timeout_source = None

        self.modaldialogxml = gtk.Builder()
        self.modaldialogxml.set_translation_domain("crossover")
        self.modaldialogxml.add_from_file(cxguitools.get_ui_path("publishbottledialog"))
        self.modaldialogxml.connect_signals(self)
        publishbottledlg = self.modaldialogxml.get_object("PublishBottleDialog")

        self.orig_bottle_name = orig_bottle_name

        publishedNameEntry = self.modaldialogxml.get_object("publishedNameEntry")
        publishedNameEntry.set_text(_("published_%(bottlename)s") % {'bottlename': orig_bottle_name})

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

    def pulse(self):
        self.modaldialogxml.get_object("publishBottleProgbar").pulse()
        return True

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

        name = bottleNameWidget.get_text().decode('utf8')

        if bottlequery.is_existing_bottle(name):
            cxguitools.CXMessageDlg(primary=_("Could not publish bottle"), secondary=_("A bottle with this name already exists."), message_type=gtk.MESSAGE_ERROR)
            return

        newBottleOp = NewBottleOperation(name, "", self, self.orig_bottle_name, True)
        progbar.show()
        dlgWidget.set_sensitive(False)

        self.timeout_source = gobject.timeout_add(100, self.pulse)

        pyop.sharedOperationQueue.enqueue(newBottleOp)

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

    def AddBottleFinished(self, op):
        dlgWidget = self.modaldialogxml.get_object("PublishBottleDialog")
        progbar = self.modaldialogxml.get_object("publishBottleProgbar")
        gobject.source_remove(self.timeout_source)

        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()

class DeleteBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle, main_window):
        pyop.PythonOperation.__init__(self)
        self.bottle_name = bottle.name
        self.bottle = bottle
        self.main_window = main_window
        self.deleted = False

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

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

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

    def finish(self):
        self.bottle.marked_for_death = False
        self.bottle.remove_status_override(self.bottle.STATUS_DELETING)
        if self.deleted:
            bottlecollection.sharedCollection().refresh()
        pyop.PythonOperation.finish(self)

class RenameBottleOperation(pyop.PythonOperation):
    def __init__(self, bottle_obj, new_name, main_window):
        pyop.PythonOperation.__init__(self)
        self.bottle = bottle_obj
        self.new_name = new_name
        self.main_window = main_window
        self.success = False
        self.err = None

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

    def enqueued(self):
        self.bottle.changeablename = self.new_name
        self.bottle.pre_rename()
        bottlecollection.sharedCollection().add_ignored_bottle(self.new_name)
        #self.main_window.update_bottle_list()
        pyop.PythonOperation.enqueued(self)

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

    def finish(self):
        self.bottle.post_rename(self.success)
        bottlecollection.sharedCollection().remove_ignored_bottle(self.new_name)

        if self.success:
            bottlecollection.sharedCollection().refresh()

        pyop.PythonOperation.finish(self)

class ArchiveBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle, archive_path):
        pyop.PythonOperation.__init__(self)
        self.exitStatus = None
        self.bottle = bottle
        self.archive_path = archive_path

    def __unicode__(self):
        return "ArchiveBottleOperation for " + self.bottle.name

    def enqueued(self):
        self.bottle.add_status_override(self.bottle.STATUS_ARCHIVING)
        pyop.PythonOperation.enqueued(self)

    def main(self):
        self.exitStatus = bottlecollection.sharedCollection().archiveBottle(self.bottle.name, self.archive_path)

    def finish(self):
        if not self.exitStatus[0]:
            cxguitools.CXMessageDlg(primary=_("An error occurred while archiving %s") % self.bottle.name, secondary=self.exitStatus[1], message_type=gtk.MESSAGE_ERROR)
        self.bottle.remove_status_override(self.bottle.STATUS_ARCHIVING)
        pyop.PythonOperation.finish(self)

class UpgradeBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle):
        pyop.PythonOperation.__init__(self)
        self.bottle = bottle

    def __unicode__(self):
        return "UpgradeBottleOperation for " + self.bottle.name

    def enqueued(self):
        self.bottle.add_status_override(self.bottle.STATUS_UPGRADING)
        pyop.PythonOperation.enqueued(self)

    def main(self):
        wine = os.path.join(cxutils.CX_ROOT, "bin", "wine")

        if cxproduct.is_root_install():
            cxsu = os.path.join(cxutils.CX_ROOT, "bin", "cxsu")
            cxsu_args = [cxsu, '--ignore-home']
        else:
            cxsu_args = []

        args = cxsu_args + [wine, "--bottle", self.bottle.name,
                            "--scope", "managed", "--ux-app", "true"]
        cxutils.run(args)

    def finish(self):
        self.bottle.refresh_up_to_date()
        self.bottle.remove_status_override(self.bottle.STATUS_UPGRADING)
        pyop.PythonOperation.finish(self)

class SetDefaultBottleOperation(pyop.PythonOperation):

    def __init__(self, bottle, state):
        pyop.PythonOperation.__init__(self)
        self.state = state
        self.bottle = bottle
        self.bottle_name = bottle.name

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

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

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

    def finish(self):
        bottlecollection.sharedCollection().refreshDefaultBottle()
        self.bottle.remove_status_override(self.bottle.STATUS_DEFAULTING)
        pyop.PythonOperation.finish(self)

class SetCsmtOperation(pyop.PythonOperation):

    def __init__(self, bottle, enabled):
        pyop.PythonOperation.__init__(self)
        self.enabled = enabled
        self.bottle = bottle
        self.bottle_name = bottle.name

    def __unicode__(self):
        return "SetCsmtOperation for " + self.bottle_name

    def enqueued(self):
        pyop.PythonOperation.enqueued(self)

    def main(self):
        if self.enabled:
            self.bottle.enable_csmt()
        else:
            self.bottle.disable_csmt()

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

class SetDxvkOperation(pyop.PythonOperation):

    def __init__(self, bottle, enabled):
        pyop.PythonOperation.__init__(self)
        self.enabled = enabled
        self.bottle = bottle
        self.bottle_name = bottle.name

    def __unicode__(self):
        return "SetDxvkOperation for " + self.bottle_name

    def enqueued(self):
        pyop.PythonOperation.enqueued(self)

    def main(self):
        if self.enabled:
            self.bottle.enable_dxvk()
        else:
            self.bottle.disable_dxvk()

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

class UpdateDescriptionOperation(pyop.PythonOperation):
    def __init__(self, bottle, main_window):
        pyop.PythonOperation.__init__(self)
        self.bottle = bottle
        self.bottle_name = bottle.name
        self.main_window = main_window
        self.succeeded = False

    def __unicode__(self):
        return "UpdateDescriptionOperation for " + self.bottle_name

    def enqueued(self):
        pyop.PythonOperation.enqueued(self)
        self.main_window.updating_description_bottles.add(self.bottle)
        self.succeeded = False

    def main(self):
        self.bottle.write_description()
        self.succeeded = True

    def finish(self):
        pyop.PythonOperation.finish(self)
        if self.succeeded:
            if self.bottle.is_description_written():
                self.main_window.updating_description_bottles.remove(self.bottle)
            else:
                pyop.sharedOperationQueue.enqueue(self)