# (c) Copyright 2009-2015. CodeWeavers, Inc.
# for localization
from cxutils import cxgettext as _
import os
import thread
import time
import gobject
gobject.threads_init()
import gtk
import cxproduct
import cxutils
import bottlequery
import cxlog
import iso8601
import pyop
import c4parser
import c4profiles
import c4profilesmanager
import installtask
import cxaiebase
import cxaiecore
import cxaiengine
import cxaiemedia
import cxfixes
import cxguitools
import bottlecollection
import bottlemanagement
import mountpoints
import cxobservable
import systeminfo
# These are the observable events
DIALOG_CLOSED = 'dialog_closed'
INSTALL_FINISHED = 'install_finished'
download_queue = pyop.PythonOperationQueue(maxThreads=2)
#
# Dialog instance
#
def string_start_match_points(search_string, candidate_string):
if candidate_string.find(search_string) == 0:
return 10
return 0
def word_start_match_points(search_string, candidate_string):
words = candidate_string.split()
for word in words:
if word.find(search_string) == 0:
return 5
return 0
class InstallWizardController(installtask.InstallTaskDelegate, cxobservable.Object):
media_markup_format = '<b>%s</b>\n<small>%s</small>'
registered_delegate = False
# This is the list of observable events
observable_events = frozenset((DIALOG_CLOSED, INSTALL_FINISHED))
def __init__(self, **kwargs):
cxobservable.Object.__init__(self)
installtask.InstallTaskDelegate.__init__(self)
self.options = kwargs
self.auto_update = False
# Import widget modules so gtk.Builder() finds them
import infobar
import cxhtmltextview # pylint: disable=W0612
import placeholderentry # pylint: disable=W0612
# Setup the GUI
self.xml = gtk.Builder()
self.xml.set_translation_domain("crossover")
self.xml.add_from_file(cxguitools.get_ui_path("cxinstaller"))
self.xml.connect_signals(self)
# Restore settings
global_config = cxproduct.get_config()
self.show_untested_apps = (global_config['OfficeSetup'].get('ShowUntestedApps', '0') != '0')
self.installTask = installtask.InstallTask(self.show_untested_apps, delegate=self)
self.installTask.profiles = c4profilesmanager.C4ProfilesSet()
self.installEngine = None
self.install_in_progress = False
self.install_finished = False
self.install_ui = False # True if we're doing something in an install that requires user interaction
self.nonempty_install_notes = False
self.failing_tasks = []
self.profile_tree = {}
self.autorun_file = None
# Tweaks not allowed by gtk.Builder.
install_notes = self.xml.get_object('InstallNotes')
install_notes.set_size_request(1, -1)
warning_image = install_notes.render_icon(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU)
if warning_image:
install_notes.images[u'warning'] = warning_image
self.xml.get_object('BottleErrorText').images[u'warning'] = warning_image
self.xml.get_object('InstallSummary').images[u'warning'] = warning_image
error_image = install_notes.render_icon(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU)
if error_image:
install_notes.images[u'error'] = error_image
self.xml.get_object('BottleErrorText').images[u'error'] = error_image
self.xml.get_object('InstallSummary').images[u'error'] = error_image
check_image = install_notes.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU)
if check_image:
self.xml.get_object('InstallSummary').images[u'check'] = check_image
self.xml.get_object('PackagesNotes').set_size_request(1, -1)
self.xml.get_object('PackageDescription').set_size_request(1, -1)
self.xml.get_object('DownloadInstallationNotes').set_size_request(1, -1)
self.xml.get_object('InstallResults').set_size_request(1, -1)
self.xml.get_object('AddDependencyList').append_column(gtk.TreeViewColumn("Name", gtk.CellRendererText(), text=0))
self.xml.get_object('AddDependencyList').get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.xml.get_object('AddDependencyList').get_selection().connect('changed', self.on_AddDependencyList_selection_changed)
# Add indents to debug channel menu items
for item in self.xml.get_object("DebugChannelMenu").get_children()[3:]:
item.set_label(' '+item.get_label())
# name, appid
self.removeDependenciesListModel = gtk.ListStore(str, object)
self.xml.get_object('RemoveDependencyList').set_model(self.removeDependenciesListModel)
self.xml.get_object('RemoveDependencyList').append_column(gtk.TreeViewColumn("Name", gtk.CellRendererText(), text=0))
self.xml.get_object('RemoveDependencyList').get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.xml.get_object('RemoveDependencyList').get_selection().connect('changed', self.on_RemoveDependencyList_selection_changed)
# The fields are: label, icon, mountpoint, sort key, markup, temporary
self.volumesListModel = gtk.ListStore(str, gtk.gdk.Pixbuf, str, str, str, bool)
self.volumesListModel.set_sort_column_id(3, gtk.SORT_ASCENDING)
volumesWidget = self.xml.get_object("MediaTreeView")
renderer = gtk.CellRendererPixbuf()
renderer.set_property('xpad', 6)
renderer.set_property('ypad', 6)
column = gtk.TreeViewColumn("Icon", renderer, pixbuf=1)
volumesWidget.append_column(column)
column = gtk.TreeViewColumn("Caption", gtk.CellRendererText(), markup=4)
volumesWidget.append_column(column)
volumesWidget.set_model(self.volumesListModel)
# Add the special 'Browse...' options
newrow = self.volumesListModel.append()
self.volumesListModel.set_value(newrow, 0, self.chooseInstallerFileString)
pixbuf = volumesWidget.render_icon(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_DIALOG)
self.volumesListModel.set_value(newrow, 1, pixbuf)
self.volumesListModel.set_value(newrow, 3, '2')
self.volumesListModel.set_value(newrow, 4, self.media_markup_format %
(self.chooseInstallerFileString,
_("The file you select will be launched to install the application")))
newrow = self.volumesListModel.append()
self.volumesListModel.set_value(newrow, 0, self.chooseInstallerFolderString)
pixbuf = volumesWidget.render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_DIALOG)
self.volumesListModel.set_value(newrow, 1, pixbuf)
self.volumesListModel.set_value(newrow, 3, '3')
self.volumesListModel.set_value(newrow, 4, self.media_markup_format %
(self.chooseInstallerFolderString,
_("The folder you select will be treated like an installer disc")))
bottlesWidget = self.xml.get_object("BottleSelectionView")
column = gtk.TreeViewColumn(_("Name"), gtk.CellRendererText(), markup=1)
column.set_resizable(True)
bottlesWidget.append_column(column)
column = gtk.TreeViewColumn(_("Type"), gtk.CellRendererText(), text=2)
column.set_resizable(False)
bottlesWidget.append_column(column)
self.bottleListModel = gtk.TreeStore(object, str, str, object) #name, display markup, template, sort key
self.bottleListModel.set_sort_func(3, self._bottle_list_cmp)
self.bottleListModel.set_sort_column_id(3, gtk.SORT_ASCENDING)
bottlesWidget.set_model(self.bottleListModel)
# locale name, locale id, sortkey
self.languageTreeStore = gtk.ListStore(str, str, str)
self.languageTreeStore.set_sort_column_id(2, gtk.SORT_ASCENDING)
self.package_search_keys = {}
# appid, sortkey, markup
self.packageCompletionStore = gtk.ListStore(object, str, str)
self.packageCompletionStore.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.packageCompletion = gtk.EntryCompletion()
self.packageCompletion.set_model(self.packageCompletionStore)
self.packageCompletion.set_match_func(self.package_completion_match, None)
self.packageCompletion.connect('match_selected', self.package_completion_selected)
renderer = gtk.CellRendererText()
self.packageCompletion.pack_start(renderer)
self.packageCompletion.add_attribute(renderer, 'markup', 2)
self.xml.get_object("SimpleSearchBox").set_completion(self.packageCompletion)
# The fields are: name, appid, sortkey
self.packageTreeStore = gtk.TreeStore(str, object, str)
packageTreeView = self.xml.get_object("PackageTreeView")
packageTreeView.set_model(self.packageTreeStore)
# The column header is not shown so there is no point localizing it
column = gtk.TreeViewColumn("Installable Packages", gtk.CellRendererText(), text=0)
column.set_resizable(False)
packageTreeView.append_column(column)
self.packageSearchString = ""
# text, pulse, value, cxinstallerui.InstallTask, counter
self.installingTaskStore = gtk.ListStore(str, gobject.TYPE_INT, gobject.TYPE_INT, object, long)
self.installingTaskStore.set_sort_func(3, _install_task_sort)
self.installingTaskStore.set_sort_column_id(3, gtk.SORT_ASCENDING)
installingTaskView = self.xml.get_object("InstallerTasksView")
installingTaskView.set_model(self.installingTaskStore)
renderer = gtk.CellRendererProgress()
renderer.set_property('xpad', 24)
renderer.set_property('ypad', 24)
column = gtk.TreeViewColumn("Task", renderer, text=0, pulse=1, value=2)
column.set_resizable(False)
installingTaskView.append_column(column)
# set up tasks view to "stick" to the bottom
self.tasks_scroll_at_bottom = True
tasksScroll = self.xml.get_object("InstallerTasksScroll")
vadjustment = tasksScroll.get_vadjustment()
vadjustment.connect('changed', self.tasks_scroll_changed)
vadjustment.connect('value-changed', self.tasks_scroll_moved)
update = global_config['OfficeSetup'].get('AutoUpdate')
c4p_url = global_config['OfficeSetup'].get('TieURL', '')
self.updateOp = UpdateProfilesOperation(c4p_url, self)
pyop.sharedOperationQueue.enqueue(self.updateOp)
self.package_update_bar = gtk.ProgressBar()
self.package_update_bar.set_text(_("Updating"))
self.package_update_bar.set_pulse_step(0.1)
self.package_update_bar.show()
if update == '1':
self.auto_update_response(None, 3)
elif update != '0':
infobar = self.xml.get_object('PackageInfoBar')
infobar_label = gtk.Label(_("For best results, please allow CrossOver to update its installation recipes:"))
infobar_label.set_alignment(0.0, 0.5)
infobar_label.show()
infobar.get_content_area().add(infobar_label)
infobar.add_button(_("Update Now"), 3)
infobar.add_button(_("Never Update"), 1)
infobar.add_button(_("Always Update"), 0)
close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
close_image.show()
close_button = gtk.Button()
close_button.add(close_image)
close_button.set_relief(gtk.RELIEF_NONE)
close_button.show()
infobar.add_action_widget(close_button, 2)
infobar.response_callback = self.auto_update_response
infobar.show()
# Start parsing package information now
if self.options.get('c4pfile'):
self.parse_c4pfile(self.options['c4pfile'])
collection = bottlecollection.sharedCollection(('basic', 'applications'))
collection.addChangeDelegate(self)
collection.addBottleChangeDelegate(self)
self.registered_delegate = True
self.bottleCollectionChanged()
self.profile_id_to_set = self.options.get('profileid')
self.add_menu_open = False
if global_config['OfficeSetup'].get('ApplyCxfixes', '1') != '1':
self.xml.get_object('UnixPackagesInstallCB').set_active(False)
self.show_dialog()
self.mountpoints = mountpoints.MountPointsNotifier(self.volumeAdded, self.volumeDeleted)
def present(self):
self.xml.get_object("InstallWizard").present()
def auto_update_response(self, _widget, response):
if response in (0, 3):
if response == 0:
cxproduct.save_setting('OfficeSetup', 'AutoUpdate', '1')
self.auto_update = True
if not self.updateOp.is_queued:
self.updateOp.network = True
pyop.sharedOperationQueue.enqueue(self.updateOp)
infobar = self.xml.get_object('PackageInfoBar')
infobar.clear()
infobar.get_content_area().pack_start(self.package_update_bar, expand=True, fill=True)
gobject.timeout_add(300, self.package_update_pulse)
close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
close_image.show()
close_button = gtk.Button()
close_button.add(close_image)
close_button.set_relief(gtk.RELIEF_NONE)
close_button.show()
infobar.add_action_widget(close_button, 2)
infobar.response_callback = self.auto_update_response
infobar.show()
else:
if response == 1:
cxproduct.save_setting('OfficeSetup', 'AutoUpdate', '0')
self.xml.get_object('PackageInfoBar').hide()
def package_update_pulse(self):
if self.updateOp.is_queued:
self.package_update_bar.pulse()
return True
return False
def dropin_c4p(self, c4pfile):
self.autorun_file = c4profilesmanager.C4ProfilesSet.add_c4p(c4pfile)
if not self.autorun_file.useful_for_current_product:
title = _("Invalid file")
failstring = _("The file '%s' does not contain installation profiles for anything that can be installed with this product.") % c4pfile.filename
cxguitools.CXMessageDlg(primary=title, secondary=failstring,
message_type=gtk.MESSAGE_WARNING,
parent=self.xml.get_object('InstallWizard'))
def parseResponse(self, response, userdata):
if response == 0:
self.dropin_c4p(userdata)
pyop.sharedOperationQueue.enqueue(UpdateProfilesOperation(None, self))
def parse_c4pfile(self, filename):
# Check if this CrossTie has a valid signature
(trusted, tmptie, tmpsig) = c4profilesmanager.C4ProfilesSet.taste_new_c4p_file(filename)
if trusted:
os.remove(tmptie)
os.remove(tmpsig)
# Parse and load in into memory then reject it if it contains malware
# Note that we load it from the original filename so future error
# messages are understandable.
c4pfile = c4parser.C4PFile(filename, trusted, c4profiles.dropin)
if c4pfile.malware_appid:
cxguitools.show_malware_dialog(c4pfile, parent=self.xml.get_object('InstallWizard'))
return
# Warn about untrusted files
if not trusted:
primary = _("'%(filename)s' is not from a trusted source!") % {'filename': cxutils.html_escape(os.path.basename(filename))}
secondary = _("CodeWeavers has no knowledge or control over what actions this CrossTie file may attempt. <b>Be VERY cautious</b> loading untrusted CrossTies as they can cause great damage to your computer. CodeWeavers will not provide technical support for issues related to the use of untrusted CrossTie files.")
markup = "<span weight='bold' size='larger'>%s</span>\n\n%s" % (primary, secondary)
cxguitools.CXMessageDlg(markup=markup,
button_array=[[_("Don't Load"), 1],
[_("Load"), 0]],
user_data=c4pfile,
response_function=self.parseResponse,
message_type=gtk.MESSAGE_WARNING)
else:
self.dropin_c4p(c4pfile)
def cancel_clicked(self, _caller):
if self.install_in_progress:
self.cancel_install()
else:
self.destroy()
def destroy(self):
if self.registered_delegate:
collection = bottlecollection.sharedCollection()
collection.removeChangeDelegate(self)
collection.removeBottleChangeDelegate(self)
self.registered_delegate = False
self.xml.get_object("InstallWizard").destroy()
self.quit_requested(None)
self.emit_event(DIALOG_CLOSED)
def quit_requested(self, _caller):
# pylint: disable=R0201
cxguitools.toplevel_quit()
def close_clicked(self, _arg1, _arg2):
if not self.install_in_progress:
self.destroy()
return False
primary = _("Abort the installation?")
secondary = _("Closing this window will interrupt the installation and leave your software partially installed. Do you really want to do this?")
cxguitools.CXMessageDlg(secondary=secondary, primary=primary,
button_array=[[_("Continue Installation"), 0],
[_("Close"), 1]],
response_function=self.close_dlg_response,
message_type=gtk.MESSAGE_WARNING)
return True
def close_dlg_response(self, response):
if response == 1:
self.cancel_install()
self.destroy()
def show_dialog(self):
dialog = self.xml.get_object("InstallWizard")
self.updatePackageListing()
self.profileChanged()
self.updateBottleListing()
self.sourceChanged()
self.bottleCreateChanged()
self.bottleNewnameChanged()
self.bottleTemplateChanged()
if self.options.get('bottle'):
bottlename = cxutils.string_to_unicode(self.options['bottle'])
try:
bottle = bottlecollection.sharedCollection().bottleObject(bottlename)
except KeyError:
self.installTask.SetNewBottleName(bottlename)
else:
if bottle.canInstall():
self.installTask.bottlename = bottlename
else:
cxlog.err("The bottle '%s' cannot be used as an install target." % bottlename)
if self.options.get('installersource'):
self.set_custom_installer_source(self.options['installersource'])
self.set_expanded_section('PackageSelect')
dialog.show()
def set_expanded_section(self, name):
for section_name in ['PackageSelect', 'MediaSelect', 'BottleSelect', 'Installer']:
if section_name == name:
self.xml.get_object('%sContent' % section_name).show()
self.xml.get_object('%sToggle' % section_name).set_active(True)
else:
self.xml.get_object('%sContent' % section_name).hide()
self.xml.get_object('%sToggle' % section_name).set_active(False)
if name == 'PackageSelect':
self.xml.get_object('SimpleSearchBox').grab_focus()
self.xml.get_object('BrowseApplicationsButton').show()
self.xml.get_object('ManageApplicationsButton').show()
if name != 'PackageSelect':
self.xml.get_object('BrowseApplicationsButton').hide()
self.xml.get_object('ManageApplicationsButton').hide()
if name == 'Installer' and not self.install_in_progress and not self.install_finished:
self.xml.get_object('AdvancedOptionsButton').show()
else:
self.xml.get_object('AdvancedOptionsButton').hide()
self.on_section_changed[name](self)
def advance_section(self, can_install=False):
# Expand the first section that needs work.
# If can_install is True and no sections need work, start the install.
if self.installEngine:
# Install has started already.
self.destroy()
elif self.wantPackageSelection():
self.set_expanded_section('PackageSelect')
if self.installTask.GetInstallerSource():
self.xml.get_object('BrowseApplicationsButton').set_active(True)
elif self.wantMediaSelection():
self.set_expanded_section('MediaSelect')
elif self.wantBottleSelection():
self.set_expanded_section('BottleSelect')
elif not self.xml.get_object('InstallerToggle').get_active() or \
self.xml.get_object('AdvancedOptionsButton').get_active():
self.set_expanded_section('Installer')
self.xml.get_object('AdvancedOptionsButton').set_active(False)
elif can_install and self.installTask.can_install():
self.displayInstallationProgressPanel()
self.doInstall()
else:
self.set_expanded_section(None)
def next_clicked(self, _widget):
self.advance_section(True)
def update_common_widgets(self):
# update next button
nextButton = self.xml.get_object("InstallerNext")
if (self.xml.get_object('PackageSelectContent').get_property('visible') and self.wantPackageSelection()) or \
(self.xml.get_object('MediaSelectContent').get_property('visible') and self.wantMediaSelection()) or \
(self.xml.get_object('BottleSelectContent').get_property('visible') and self.wantBottleSelection()):
nextButton.set_label(_("Continue"))
nextButton.set_sensitive(False)
elif self.wantPackageSelection() or self.wantMediaSelection() or self.wantBottleSelection() or \
not self.xml.get_object('InstallerToggle').get_active() or \
self.xml.get_object('AdvancedOptionsButton').get_active():
nextButton.set_label(_("Continue"))
nextButton.set_sensitive(True)
elif not self.installTask.can_install():
# Can't install but no sections need work. Install notes should say why.
nextButton.set_label(_("Continue"))
nextButton.set_sensitive(False)
else:
nextButton.set_label(_("Install"))
nextButton.set_sensitive(True)
self.update_install_notes(True)
if self.wantPackageSelection() or self.wantMediaSelection() or self.wantBottleSelection():
if self.xml.get_object('InstallerToggle').get_active():
self.advance_section(True)
self.xml.get_object('InstallerToggle').set_sensitive(False)
else:
self.xml.get_object('InstallerToggle').set_sensitive(True)
def get_cxdiag_install_notes(self):
# Collect the lists of issues cxfixes can take care of, and the
# remaining cxdiag errors and warnings
pkg_errids = {}
warnings = []
errors = []
for errid, (level, title, description) in self.installTask.get_cxdiag_messages().iteritems():
packages = cxfixes.get_packages(errid)
if packages:
for pkg in packages:
if pkg in pkg_errids:
pkg_errids[pkg].append(errid)
else:
pkg_errids[pkg] = [errid]
if not packages or not self.installTask.apply_cxfixes:
if level == 'Require':
errors.append('<a style="color:red;" href="%(url)s">%(title)s</a>: %(description)s' % {
'url': 'http://www.codeweavers.com/support/wiki/Diag/' + errid,
'title': cxutils.html_escape(title),
'description': cxutils.html_escape(description)})
elif level == 'Recommend':
warnings.append('<a href="%(url)s">%(title)s</a>: %(description)s' % {
'url': 'http://www.codeweavers.com/support/wiki/Diag/' + errid,
'title': cxutils.html_escape(title),
'description': cxutils.html_escape(description)})
# Build the package list
pkg_notes = []
for pkg in sorted(pkg_errids):
# pylint: disable=W0631
if len(pkg_errids[pkg]) == 1:
pkg_notes.append('<a href="%(url)s">%(pkg)s</a>' % {
'url': 'http://www.codeweavers.com/support/wiki/Diag/' + pkg_errids[pkg][0],
'pkg': cxutils.html_escape(pkg)})
else:
refs = []
for i in xrange(len(pkg_errids[pkg])):
refs.append('<a href="%(url)s">%(index)s</a>' % {
'url': 'http://www.codeweavers.com/support/wiki/Diag/' + pkg_errids[pkg][i],
'index': i})
pkg_notes.append('%s <small>[%s]</small>' % (pkg, ' '.join(refs)))
return (errors, warnings, pkg_notes)
def get_install_notes(self):
if self.installTask.profile is None:
return ""
notes = self.installTask.installationNotes
if notes is None:
notes = ""
return notes
def get_install_issues(self):
errors = []
warnings = []
# Update bottle warning text
if self.installTask.GetCreateNewBottle() and self.installTask.bottlename and \
self.installTask.bottlename in bottlecollection.sharedCollection().bottleList():
self.xml.get_object("BottleErrorText").get_buffer().set_text('')
msg = _("There is already a bottle named '%s'. Select the existing bottle from the list or choose a different bottle name.") % self.installTask.bottlename
self.xml.get_object("BottleErrorText").display_html('<body><p><img src="error" /> %s</p></body>' % cxutils.html_escape(msg))
self.xml.get_object("BottleErrorText").show()
errors.append(msg)
self.xml.get_object("BottleSelectCheckmark").set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU)
self.xml.get_object("BottleSelectCheckmark").show()
elif self.installTask.target_bottle and self.installTask.target_bottle.analyzed() and \
not self.installTask.target_bottle.compatible:
self.xml.get_object("BottleErrorText").get_buffer().set_text('')
if self.installTask.target_bottle.bottlename:
msg = _("You have selected a bottle which is incompatible with this application. It is strongly recommended to install in a new or compatible bottle.")
else:
msg = _("You have chosen to create a new bottle of a type which is incompatible with this application. It is strongly recommended to install in a compatible bottle.")
self.xml.get_object("BottleErrorText").display_html('<body><p><img src="warning" /> %s</p></body>' % cxutils.html_escape(msg))
self.xml.get_object("BottleErrorText").show()
self.xml.get_object("BottleSelectCheckmark").set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU)
self.xml.get_object("BottleSelectCheckmark").show()
else:
self.xml.get_object("BottleErrorText").hide()
if self.installTask.bottlename:
self.xml.get_object("BottleSelectCheckmark").set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU)
self.xml.get_object("BottleSelectCheckmark").show()
else:
self.xml.get_object("BottleSelectCheckmark").hide()
self.xml.get_object("BottleSideWidgets").set_property('visible', self.xml.get_object("NewBottleWidgets").get_property('visible') or
self.xml.get_object("BottleErrorText").get_property('visible'))
if self.installTask.target_bottle and self.installTask.target_bottle.analyzed():
if self.installTask.target_bottle.missing_profiles:
missing = ", ".join(self.installTask.target_bottle.missing_profiles)
errors.append(_("The install cannot continue because one or more profiles it depends on could not be found: %s") % cxutils.html_escape(missing))
elif self.installTask.target_bottle.missing_media:
missing = ", ".join(self.installTask.get_profile_names(self.installTask.target_bottle.missing_media))
errors.append(_("The install cannot continue because no installer could be found for the following dependencies: %s") % cxutils.html_escape(missing))
if self.installTask.installerSource:
src_filename = self.installTask.installerSource
if os.path.isdir(src_filename):
src_filename = None
if self.installTask.profile:
installer_profile = self.installTask.get_installer_profile()
src_filename = cxaiemedia.locate_installer(installer_profile, self.installTask.installerSource)
if src_filename:
permissions = cxutils.check_mmap_permissions(src_filename)
if permissions == cxutils.PERMISSIONS_NOREAD:
errors.append(cxutils.html_escape(_("The installer file '%s' cannot be read.") % src_filename))
elif permissions == cxutils.PERMISSIONS_NOEXEC:
warnings.append(cxutils.html_escape(_("The installer file '%s' is on a noexec filesystem. This may cause the install to fail.") % src_filename))
return (errors, warnings)
def get_combined_notes(self, installNotes, installErrors, installWarnings, cxdiagErrors, cxdiagWarnings):
# pylint: disable=R0201
chunks = ['<body>']
if installNotes:
chunks.append(installNotes)
if installWarnings or installErrors or cxdiagWarnings or cxdiagErrors:
chunks.append(_('<p><b>The following issues may prevent this application from working.</b> Please follow the links to see how to resolve them:</p>'))
error_icon_html = '<img src="error" alt="ERROR" />'
for chunk in installErrors:
chunks.extend(('<p>', error_icon_html, chunk, '</p>'))
for chunk in cxdiagErrors:
chunks.extend(('<p>', error_icon_html, chunk, '</p>'))
warning_icon_html = '<img src="warning" alt="WARNING" />'
for chunk in installWarnings:
chunks.extend(('<p>', warning_icon_html, chunk, '</p>'))
for chunk in cxdiagWarnings:
chunks.extend(('<p>', warning_icon_html, chunk, '</p>'))
chunks.append('</body>')
return ''.join(chunks)
def _list_dependencies(self, appid, reasons_text, reasons_rev):
if appid not in self.installTask.profiles:
return
other_app_name = cxutils.html_escape(self.installTask.profiles[appid].name)
for dep_appid in reasons_rev.get((installtask.REASON_DEPENDENCY, appid), ()):
if dep_appid not in self.installTask.profiles:
continue
name = self.installTask.profiles[dep_appid].name
if dep_appid not in self.installTask.target_bottle.installers:
if self.installTask.dependency_overrides.get(dep_appid) == installtask.OVERRIDE_EXCLUDE:
format_str = cxutils.html_escape(_("%(app_name)s (Required by %(other_app_name)s, Manually Removed) %(undo_link_start)sUndo%(undo_link_end)s"))
reasons_text.append(format_str % {'app_name': '<span style="text-decoration:line-through"><b>'+cxutils.html_escape(name)+'</b></span>',
'other_app_name': other_app_name,
'undo_link_start': u'<small><a href="app:%s">' % dep_appid,
'undo_link_end': u'</a></small>'})
continue
format_str = cxutils.html_escape(_("%(app_name)s (Required by %(other_app_name)s)"))
self.removeDependenciesListModel.append(row=(name, dep_appid))
reasons_text.append(format_str % {'app_name': '<b>'+cxutils.html_escape(name)+'</b>',
'other_app_name': other_app_name})
for dep_appid in reasons_rev.get((installtask.REASON_DEPENDENCY, appid), ()):
if dep_appid not in self.installTask.target_bottle.installers:
continue
self._list_dependencies(dep_appid, reasons_text, reasons_rev)
def on_AddDependencySearch_real_text_changed(self, _widget, _text):
self.xml.get_object('AddDependencyList').get_model().refilter()
def _add_deps_filter(self, model, iterator):
search_text = self.xml.get_object('AddDependencySearch').get_real_text()
if search_text:
text = model.get_value(iterator, 0)
return search_text.lower() in text.lower()
return True
def on_AddDependenciesButton_clicked(self, _widget):
if self.xml.get_object('AddDependencyDialog').get_property('visible'):
self.xml.get_object('AddDependencyDialog').present()
else:
# name, sort key, appid
add_dependencies_store = gtk.ListStore(str, str, object)
main_appid = self.installTask.profile.appid
for appid in self.installTask.SuggestedDependencyOverrides():
profile = self.installTask.profiles[appid]
name = profile.name
if main_appid in profile.app_profile.extra_fors:
sortkey = 'A' + name
else:
sortkey = 'B' + name
add_dependencies_store.append((name, sortkey, appid))
add_dependencies_store.set_sort_column_id(1, gtk.SORT_ASCENDING)
add_dependencies_store = add_dependencies_store.filter_new()
add_dependencies_store.set_visible_func(self._add_deps_filter)
self.xml.get_object('AddDependencyList').set_model(add_dependencies_store)
self.xml.get_object('AddDependencyDialog').show()
def on_AddDependencyDialog_delete_event(self, widget, _event):
widget.hide()
return True
def on_AddDependencyDialog_response(self, widget, response_id):
if response_id == 1:
model, rows = self.xml.get_object('AddDependencyList').get_selection().get_selected_rows()
if not rows:
return
appids = []
for row in rows:
appids.append(model.get_value(model.get_iter(row), 2))
self.installTask.AddDependencyOverrides(appids, installtask.OVERRIDE_INCLUDE)
widget.hide()
def on_AddDependencyList_button_press_event(self, _widget, event):
if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: #pylint: disable=W0212
self.xml.get_object('AddDependencyDialog').response(1)
def on_AddDependencyList_selection_changed(self, selection):
self.xml.get_object('AddDependencyDialog').set_response_sensitive(1, selection.count_selected_rows() != 0)
def on_DependenciesList_url_clicked(self, widget, url, _type):
if url.startswith(u'app:'):
self.installTask.RemoveDependencyOverrides((url[4:],))
widget.emit_stop_by_name('url-clicked')
def on_RevertDependenciesButton_clicked(self, _widget):
self.installTask.ClearDependencyOverrides()
def on_RemoveDependenciesButton_clicked(self, _widget):
if self.xml.get_object('RemoveDependencyDialog').get_property('visible'):
self.xml.get_object('RemoveDependencyDialog').present()
else:
self.xml.get_object('RemoveDependencyDialog').show()
def on_RemoveDependencyDialog_response(self, widget, response_id):
if response_id == 1:
model, rows = self.xml.get_object('RemoveDependencyList').get_selection().get_selected_rows()
if not rows:
return
appids = []
for row in rows:
appids.append(model.get_value(model.get_iter(row), 1))
self.installTask.AddDependencyOverrides(appids, installtask.OVERRIDE_EXCLUDE)
widget.hide()
def on_RemoveDependencyList_button_press_event(self, _widget, event):
if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: #pylint: disable=W0212
self.xml.get_object('RemoveDependencyDialog').response(1)
def on_RemoveDependencyList_selection_changed(self, selection):
self.xml.get_object('RemoveDependencyDialog').set_response_sensitive(1, selection.count_selected_rows() != 0)
def update_install_notes(self, show_warnings):
installNotesHtmlView = self.xml.get_object("InstallNotes")
unixPackagesInstallCB = self.xml.get_object("UnixPackagesInstallCB")
packagesNotesHtmlView = self.xml.get_object("PackagesNotes")
self.removeDependenciesListModel.clear()
self.xml.get_object("DependenciesList").get_buffer().set_text("")
reasons_rev = {}
if self.installTask.target_bottle and self.installTask.target_bottle.analyzed():
for appid, reason in self.installTask.target_bottle.reasons.iteritems():
if reason not in reasons_rev:
reasons_rev[reason] = []
reasons_rev[reason].append(appid)
reasons_text = []
for appid in reasons_rev.get((installtask.REASON_MAIN, None), ()):
if appid not in self.installTask.target_bottle.installers:
continue
format_str = cxutils.html_escape(_("%(app_name)s (Selected for install)"))
reasons_text.append(format_str % {'app_name': '<b>'+cxutils.html_escape(self.installTask.profiles[appid].name)+'</b>'})
self._list_dependencies(appid, reasons_text, reasons_rev)
for appid in reasons_rev.get((installtask.REASON_STEAM, None), ()):
name = self.installTask.profiles[appid].name
if appid not in self.installTask.target_bottle.installers:
if self.installTask.dependency_overrides.get(appid) == installtask.OVERRIDE_EXCLUDE:
format_str = cxutils.html_escape(_("%(app_name)s (Installing via Steam, Manually Removed) %(undo_link_start)sUndo%(undo_link_end)s"))
reasons_text.append(format_str % {'app_name': '<span style="text-decoration:line-through"><b>'+cxutils.html_escape(name)+'</b></span>',
'undo_link_start': u'<small><a href="app:%s">' % appid,
'undo_link_end': u'</a></small>'})
continue
self.removeDependenciesListModel.append(row=(name, appid))
format_str = cxutils.html_escape(_("%(app_name)s (Installing via Steam)"))
reasons_text.append(format_str % {'app_name': '<b>'+cxutils.html_escape(name)+'</b>'})
self._list_dependencies(appid, reasons_text, reasons_rev)
for appid in reasons_rev.get((installtask.REASON_OVERRIDE, None), ()):
if appid not in self.installTask.target_bottle.installers:
continue
format_str = cxutils.html_escape(_("%(app_name)s (Manually added) %(undo_link_start)sUndo%(undo_link_end)s"))
reasons_text.append(format_str % {'app_name': u'<b>'+cxutils.html_escape(self.installTask.profiles[appid].name)+u'</b>',
'undo_link_start': u'<small><a href="app:%s">' % appid,
'undo_link_end': u'</a></small>'})
self._list_dependencies(appid, reasons_text, reasons_rev)
self.xml.get_object("DependenciesList").display_html_safe('<body>%s</body>' % ''.join('<p>%s</p>' % line for line in reasons_text))
self.xml.get_object("AddDependenciesButton").set_sensitive(True)
else:
self.xml.get_object("AddDependenciesButton").set_sensitive(False)
self.xml.get_object("RemoveDependenciesButton").set_sensitive(self.removeDependenciesListModel.get_iter_first() is not None)
if 'CX_LOG' in os.environ:
self.xml.get_object('LogFileEntry').set_property('placeholder-string', os.environ['CX_LOG'])
else:
parts = ['install']
if self.installTask.bottlename:
parts.append(self.installTask.bottlename)
if self.installTask.profile and (not self.installTask.createNewBottle or self.installTask.newBottleNameRequested):
parts.append(cxutils.sanitize_bottlename(self.installTask.profile.name))
logpath = os.path.join(cxproduct.get_user_dir(), 'logs')
logfile = os.path.join(logpath, '%s.cxlog' % '_'.join(str(x) for x in parts))
if os.path.exists(logfile):
parts.append(2)
logfile = os.path.join(logpath, '%s.cxlog' % '_'.join(str(x) for x in parts))
while os.path.exists(logfile):
parts[-1] += 1
logfile = os.path.join(logpath, '%s.cxlog' % '_'.join(str(x) for x in parts))
self.xml.get_object('LogFileEntry').set_property('placeholder-string', logfile)
installNotes = self.get_install_notes()
if installNotes:
html = '<body>%s</body>' % installNotes
installNotesHtmlView.get_buffer().set_text("")
try:
installNotesHtmlView.display_html(html.encode("utf8"))
except Exception, exception: # pylint: disable=W0703
cxlog.warn("unable to display the HTML snippet below: %s\n\n%s" % (exception, cxlog.debug_str(html)))
# Assume the CrossTie's HTML markup is broken and sanitize it
installNotes = cxutils.html_to_text(installNotes)
installNotes = cxutils.html_escape(installNotes)
# Only preserve line feeds
installNotes = installNotes.replace('\n', '<br/>')
html = '<body>%s</body>' % installNotes
installNotesHtmlView.display_html_safe(html.encode("utf8"))
self.nonempty_install_notes = True
self.xml.get_object("InstallNotesContent").show()
else:
self.nonempty_install_notes = False
self.xml.get_object("InstallNotesContent").set_property('visible', self.nonempty_install_notes and not self.xml.get_object("AdvancedContent").get_property('visible'))
if show_warnings:
(installErrors, installWarnings) = self.get_install_issues()
(cxdiagErrors, cxdiagWarnings, cxdiagPackages) = self.get_cxdiag_install_notes()
else:
installErrors = installWarnings = []
cxdiagErrors = cxdiagWarnings = cxdiagPackages = []
self.xml.get_object("InstallNotesContent").set_property('visible', self.nonempty_install_notes and not self.xml.get_object("AdvancedContent").get_property('visible'))
has_dependencies = False
packagesNotesHtmlView.get_buffer().set_text("")
if cxdiagPackages:
html = '<body>'
if cxdiagPackages:
html += '<p>' + ', '.join(cxdiagPackages) + '</p>'
html += '</body>'
packagesNotesHtmlView.display_html(html.encode("utf8"))
if cxdiagPackages:
unixPackagesInstallCB.show()
else:
unixPackagesInstallCB.hide()
packagesNotesHtmlView.show()
if self.installTask.apply_cxfixes:
has_dependencies = True
else:
unixPackagesInstallCB.hide()
packagesNotesHtmlView.hide()
notes = []
if self.installTask.target_bottle and self.installTask.target_bottle.analyzed() and \
len(self.installTask.target_bottle.installers) >= 2:
has_dependencies = True
if not self.wantPackageSelection() and \
not self.wantMediaSelection() and not self.wantBottleSelection() and \
not self.installTask.target_bottle.analyzed():
# We have a greyed out Install button
notes.append('<p>' + cxutils.html_escape(_("The install cannot continue until the '%s' bottle is scanned for dependencies. Please wait.") % self.installTask.bottlename) + '</p>')
if self.installTask.installerLocale:
langid = self.installTask.installerLocale
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will install the %(language)s version") % {'language':c4profiles.LANGUAGES.get(langid, langid)}) + '</p>')
if self.installTask.virtual:
if has_dependencies:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will install dependencies only")) + '</p>')
has_dependencies = False
else:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will make configuration changes only")) + '</p>')
elif self.installTask.installerSource:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will install from %s") % self.installTask.installerSource) + '</p>')
elif self.installTask.installerDownloadSource:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will download the installer from %s") % self.installTask.installerDownloadSource) + '</p>')
elif self.installTask.installWithSteam:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will install via Steam")) + '</p>')
if self.installTask.target_bottle and self.installTask.target_bottle.analyzed():
if self.installTask.target_bottle.compatible:
img = 'check'
else:
img = 'warning'
if self.installTask.target_bottle.bottlename:
msg = _("CrossOver will install into the bottle '%s'") % self.installTask.target_bottle.bottlename
elif self.installTask.target_bottle.template == 'win98':
msg = _("CrossOver will install into a new Windows 98 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win2000':
msg = _("CrossOver will install into a new Windows 2000 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'winxp':
msg = _("CrossOver will install into a new Windows XP bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'winvista':
msg = _("CrossOver will install into a new Windows Vista bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win7':
msg = _("CrossOver will install into a new Windows 7 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win8':
msg = _("CrossOver will install into a new Windows 8 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win10':
msg = _("CrossOver will install into a new Windows 10 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'winxp_64':
msg = _("CrossOver will install into a new 64-bit Windows XP bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'winvista_64':
msg = _("CrossOver will install into a new 64-bit Windows Vista bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win7_64':
msg = _("CrossOver will install into a new 64-bit Windows 7 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win8_64':
msg = _("CrossOver will install into a new 64-bit Windows 8 bottle named '%s'") % self.installTask.newBottleName
elif self.installTask.target_bottle.template == 'win10_64':
msg = _("CrossOver will install into a new 64-bit Windows 10 bottle named '%s'") % self.installTask.newBottleName
else:
# shouldn't happen
msg = "CrossOver will install into a new %s bottle named '%s'" % (self.installTask.target_bottle.template, self.installTask.newBottleName)
notes.append('<p><img src="%s" /> ' % img + cxutils.html_escape(msg) + '</p>')
if has_dependencies:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will also install additional dependencies")) + '</p>')
if reasons_rev:
if (installtask.REASON_OVERRIDE, None) in reasons_rev:
names = []
for appid in reasons_rev[installtask.REASON_OVERRIDE, None]:
if appid not in self.installTask.target_bottle.installers:
continue
names.append(self.installTask.profiles[appid].name)
if names:
notes.append('<p><img src="check" /> ' + cxutils.html_escape(_("CrossOver will also install the following manually added items: %s") % ', '.join(names)) + '</p>')
exclude_names = []
for appid in self.installTask.dependency_overrides:
if self.installTask.dependency_overrides[appid] != installtask.OVERRIDE_EXCLUDE:
continue
if appid not in self.installTask.target_bottle.reasons:
continue
if appid not in self.installTask.profiles:
continue
exclude_names.append(self.installTask.profiles[appid].name)
if exclude_names:
notes.append('<p><img src="warning" /> ' + cxutils.html_escape(_("The following dependencies were manually excluded from the install: %s") % ', '.join(exclude_names)) + '</p>')
html = self.get_combined_notes(''.join(notes), installErrors, installWarnings, cxdiagErrors, cxdiagWarnings)
self.xml.get_object('InstallSummary').get_buffer().set_text("")
self.xml.get_object('InstallSummary').display_html(html.encode("utf8"))
def set_apply_cxfixes(self, _widget):
self.installTask.apply_cxfixes = _widget.get_active()
#-----------------------------------------------------------------------
# Package Selection
#-----------------------------------------------------------------------
def wantPackageSelection(self):
return self.installTask.profile is None
def package_toggle(self, widget):
if widget.get_active() and not self.xml.get_object('PackageSelectContent').get_property('visible'):
self.set_expanded_section('PackageSelect')
elif not widget.get_active() and self.xml.get_object('PackageSelectContent').get_property('visible'):
widget.set_active(True)
def package_completion_match(self, _completion, _key_string, _iter, _key_data):
return True
def package_completion_selected(self, _completion, model, iterator):
appid = model.get_value(iterator, 0)
self.installTask.profile = self.installTask.profiles[appid]
return True
def package_match_sortkey_and_markup(self, appid, key_string):
if appid == 'com.codeweavers.unknown':
markup = cxutils.html_escape(_("Unlisted application '%s'") % self.xml.get_object('SimpleSearchBox').get_real_text())
sortkey = 'C'
return sortkey, markup
profile = self.installTask.profiles[appid]
name = profile.name
index = name.lower().find(key_string)
if index == 0:
sortkey = 'A' + name
else:
sortkey = 'B' + name
if index == -1:
markup = cxutils.html_escape(name)
else:
markup = cxutils.html_escape(name[0:index]) + '<b>' + \
cxutils.html_escape(name[index:index+len(key_string)]) + '</b>' + \
cxutils.html_escape(name[index+len(key_string):])
return sortkey, markup
def on_BrowseApplicationsButton_clicked(self, widget):
self.xml.get_object("BrowseApplicationsBox").set_property('visible', widget.get_active())
def on_ManageApplicationsButton_clicked(self, _widget):
__import__('crossoverui').open_or_show(None)
def refilter_packages(self):
if not self.installTask.profiles:
self.packageCompletionStore.clear()
self.packageCompletionStore.append(row=(None, 'a', cxutils.html_escape(_(u"Please wait while CrossOver gathers information about available applications\u2026"))))
self.packageCompletion.complete()
return
key_string = self.xml.get_object("SimpleSearchBox").get_real_text().lower()
if key_string:
results = set()
seen_results = set()
i = 0
for appid, names in self.package_search_keys.iteritems():
if key_string in names:
results.add(appid)
i += 1
if i >= 128:
# gtk performs poorly if we try to display too many results
self.packageCompletionStore.clear()
return
results.add(u'com.codeweavers.unknown')
iterator = self.packageCompletionStore.get_iter_first()
while iterator:
iter_next = self.packageCompletionStore.iter_next(iterator)
appid = self.packageCompletionStore.get_value(iterator, 0)
if appid in results:
seen_results.add(appid)
results.remove(appid)
sortkey, markup = self.package_match_sortkey_and_markup(appid, key_string)
old_sortkey = self.packageCompletionStore.get_value(iterator, 1)
old_markup = self.packageCompletionStore.get_value(iterator, 2)
if old_sortkey != sortkey:
self.packageCompletionStore.set_value(iterator, 1, sortkey)
if old_markup != markup:
self.packageCompletionStore.set_value(iterator, 2, markup)
elif appid not in seen_results:
self.packageCompletionStore.remove(iterator)
iterator = iter_next
for appid in results:
sortkey, markup = self.package_match_sortkey_and_markup(appid, key_string)
self.packageCompletionStore.append(row=(appid, sortkey, markup))
if self.xml.get_object("SimpleSearchBox").is_focus():
self.packageCompletion.complete()
else:
self.packageCompletionStore.clear()
def on_SimpleSearchBox_real_text_changed(self, entry, real_text):
self.refilter_packages()
self.installTask.SetNewBottleNameForUnknown(real_text)
if self.installTask.profile and self.installTask.profile.appid == u'com.codeweavers.unknown':
self.profileChanged()
if real_text:
entry.set_property('secondary-icon-sensitive', True)
else:
entry.set_property('secondary-icon-sensitive', False)
def on_SimpleSearchBox_icon_release(self, entry, icon_pos, _event):
if icon_pos == gtk.ENTRY_ICON_SECONDARY:
entry.set_text('')
def update_package_tree(self, treestore, contents, parent_iterator=None):
to_add = set(contents)
if parent_iterator:
iterator = treestore.iter_children(parent_iterator)
else:
iterator = treestore.get_iter_first()
while iterator:
key = treestore.get_value(iterator, 1)
iter_next = treestore.iter_next(iterator)
if key in to_add:
if isinstance(contents[key], dict):
self.update_package_tree(treestore, contents[key], iterator)
else:
current_name = cxutils.string_to_unicode(treestore.get_value(iterator, 0))
if current_name != contents[key].name:
treestore.set(iterator, 0, contents[key].name, 2, 'B'+contents[key].name)
to_add.remove(key)
elif key in contents:
pass
else:
treestore.remove(iterator)
iterator = iter_next
for key in to_add:
obj = contents[key]
if isinstance(obj, dict):
newrow = treestore.append(parent_iterator, (key, key, 'A'+key))
self.update_package_tree(treestore, obj, newrow)
else:
treestore.append(parent_iterator, (obj.name, key, 'B'+obj.name))
def updatePackageListing(self):
self.package_search_keys = {}
for appid, profile in self.installTask.profiles.iteritems():
if not profile.is_for_current_product:
continue
if not self.show_untested_apps and not profile.is_ranked and \
appid != c4profilesmanager.UNKNOWN:
continue
names = []
for lang in cxutils.get_preferred_languages():
if lang in profile.localized_names:
name = profile.localized_names[lang].lower()
if name not in names:
names.append(name)
self.package_search_keys[appid] = '\n'.join(names)
self.refilter_packages()
if self.profile_tree:
self.update_package_tree(self.packageTreeStore, self.profile_tree)
if self.packageTreeStore.get_sort_column_id() != (2, gtk.SORT_ASCENDING):
self.packageTreeStore.set_sort_column_id(2, gtk.SORT_ASCENDING)
else:
self.packageTreeStore.clear()
self.packageTreeStore.append(None, (_(u"Please wait while CrossOver gathers information about available applications\u2026"), None, ''))
def package_changed_gui(self, _widget):
packageTreeView = self.xml.get_object("PackageTreeView")
cursor = packageTreeView.get_cursor()
if cursor and cursor[0]:
selectedID = self.packageTreeStore.get_value(self.packageTreeStore.get_iter(cursor[0]), 1)
if selectedID in self.installTask.profiles:
self.installTask.profile = self.installTask.profiles[selectedID]
if cxlog.is_on('c4profiles'):
cxlog.log_('c4profiles', "selected %s" % cxlog.to_str(self.installTask.profile.appid))
self.installTask.profile.dump(cxlog.get_file(), "| ")
def package_row_activated(self, _widget, path, _column):
packageTreeView = self.xml.get_object("PackageTreeView")
appid = self.packageTreeStore.get_value(self.packageTreeStore.get_iter(path), 1)
if appid in self.installTask.profiles:
self.advance_section(False)
else:
if packageTreeView.row_expanded(path):
packageTreeView.collapse_row(path)
else:
packageTreeView.expand_row(path, False)
def select_package_by_appid(self, model, path, treeiter, appid):
if model.get_value(treeiter, 1) == appid:
treeview = self.xml.get_object("PackageTreeView")
treeview.expand_to_path(path)
treeview.set_cursor(path)
treeview.scroll_to_cell(path)
return True # stop iteration
return False # continue iteration
def select_language_by_id(self, model, path, treeiter, langid):
if model.get_value(treeiter, 1) == langid:
self.xml.get_object("LanguageSelectorCombo").set_active(path[0])
return True # stop iteration
return False # continue iteration
medal_names = {
'gold': _("Gold Medal (Officially Supported)"),
'silver': _("Silver Medal (Officially Supported)"),
'bronze': _("Bronze Medal (Officially Supported)"),
'ungold': _("Gold Medal (Unsupported)"),
'unsilver': _("Silver Medal (Unsupported)"),
'unbronze': _("Bronze Medal (Unsupported)"),
'knownnottowork': _("Known not to work"),
'untested': _("Untested"),
'untrusted': _("Untrusted"),
}
medal_descriptions = {
'gold': _("Installs and runs as you would expect in Microsoft Windows. We expect that you can use this application on an everyday basis with good results and only minor bugs."),
'silver': _("Installs and runs well enough to be usable. However, this application has bugs that prevent it from running flawlessly."),
'bronze': _("Installs, runs, and can accomplish some portion of its fundamental mission. However, this application has enough bugs that we recommend you use it with caution. Save early/save often, and don't be surprised if there are some bumps along the way."),
'ungold': _("Installs and runs as you would expect in Microsoft Windows. Our users expect that you can use this application on an everyday basis with good results and only minor bugs."),
'unsilver': _("Installs and runs well enough to be usable. However, our users find that this application has bugs that prevent it from running flawlessly."),
'unbronze': _("Installs, runs, and can accomplish some portion of its fundamental mission. However, this application has enough bugs that we recommend you use it with caution. Save early/save often, and don't be surprised if there are some bumps along the way."),
'knownnottowork': _("This application either does not install or does not run."),
'untested': _("We do not know whether or not this application will install and run. If you try it, consider letting us know how it runs, using the compatibility database."),
'untrusted': _("This profile is from an unknown source. Installing may pose a security risk to your system."),
}
rating_descriptions = {
5: _("Runs Great"),
4: _("Runs Well"),
3: _("Limited Functionality"),
2: _("Installs, Will Not Run"),
1: _("Will Not Install"),
0: _("Untested"),
}
rating_tooltips = {
5: _("This application installs and runs great, the same as it would in Windows.\n\nPractically all of the application is functional. Using this application in CrossOver should be the same as running the application on native Windows OS."),
4: _("This application installs and runs quite well, with only minor glitches found.\n\nThe core functionality of this Windows application works fine under CrossOver. There still may be some extra functions that don't work right, but overall this application should be mostly usable. We don't maintain a list of what specific functions work/don't work per-application in our compatibility database, but you may want to check this application's Forums at the CodeWeavers website and see if other users have reported any particular details about running this Windows application in CrossOver."),
3: _("This application installs and runs, but has problems.\n\nThis application runs, but there are significant functional gaps. CrossOver translates Windows commands to their corresponding Linux commands and back. When a Windows program uses commands that CrossOver doesn't know how to translate the Windows program may not respond when you click things, have garbled output, or crash. You might be able to add different dependencies or try workarounds to get this application to run better, so please check this application's page at the CodeWeavers website. It's also possible that programming added in a future release of CrossOver might help this Windows application run better, so check back later."),
2: _("This application installs, but fails to run.\n\nThe Windows application immediately closes or freezes when you try to run it. You may be able to add various dependencies or try different workarounds to get this application to start, so please check the Support section of the CodeWeavers website for more information and tutorials. If the program still won't launch then whatever programming this Windows application needs to work doesn't exist yet in CrossOver."),
1: _("This application won't get through installation.\n\nMost installers are standardized and CrossOver will be able to run them without a problem. However some companies customize the installer, or write their own, and the custom programming fails in CrossOver. The Windows application itself might be functional, but there's no way to get it out of the installer."),
0: _("No ratings have been reported.\n\nAll application entries start at this rating. Over time staff and users can visit the What Runs page of the CodeWeavers website and rate how far they got in running this Windows application in CrossOver. If you try this application please submit a rating for how this application worked, or didn't, in CrossOver."),
-1: _("This installation recipe is from an unknown source. Installing may pose a security risk to your system."),
}
def update_profile_caption(self):
button = self.xml.get_object("PackageSelectToggle")
if self.installTask.profile.appid == u'com.codeweavers.unknown' and \
self.xml.get_object('SimpleSearchBox').get_real_text():
name = self.xml.get_object('SimpleSearchBox').get_real_text()
else:
name = self.installTask.profile.name
if self.installTask.installerLocale:
langid = self.installTask.installerLocale
button.set_tooltip_text(_("Will install %(application)s (%(language)s version)") % {
'application': name,
'language': c4profiles.LANGUAGES.get(langid, langid)})
self.xml.get_object("PackageSelectCheckmark").show()
else:
button.set_tooltip_text(_("Will install %s") % name)
self.xml.get_object("PackageSelectCheckmark").show()
def update_package_rating(self):
self.xml.get_object("PackageRatingView").get_buffer().set_text('')
self.xml.get_object("PackageRatingView1").get_buffer().set_text('')
if self.installTask.profile:
rating = self.installTask.profile.app_profile.medal_rating
if 1 <= rating <= 5:
icons = ['star'] * rating + ['star-empty'] * (5 - rating)
elif rating == 0:
icons = ['question'] * 5
else:
icons = ['untrusted'] * 5
text = self.rating_descriptions.get(rating, _("Untrusted"))
tooltip = self.rating_tooltips.get(rating, self.rating_tooltips[-1])
app = self.installTask.profile.app_profile
if app.medal_rating and app.medal_version:
version_text = _("Last Tested: CrossOver %s") % app.medal_version
try:
timestamp = iso8601.parse_date(app.medal_date)
version_tooltip = ((_("Last Ranked: %s") % timestamp.strftime('%Y-%m-%d')) + '\n' +
(_("Number of Rankings: %s") % app.medal_count))
except iso8601.ParseError:
version_tooltip = None
else:
version_text = None
version_tooltip = None
html = '<body><p>%s</p></body>' % ' '.join('<img src="%s"/>' % i for i in icons)
self.xml.get_object("PackageRatingView").display_html(html)
self.xml.get_object("PackageRatingView1").display_html(html)
self.xml.get_object("PackageRatingLabel").set_text(text)
self.xml.get_object("PackageRatingLabel").show()
self.xml.get_object("PackageRatingLabel1").set_text(text)
self.xml.get_object("PackageRatingLabel1").show()
if version_text:
self.xml.get_object("PackageLastTested").set_text(version_text)
self.xml.get_object("PackageLastTested").set_tooltip_text(version_tooltip)
self.xml.get_object("PackageLastTested").show()
self.xml.get_object("PackageLastTested1").set_text(version_text)
self.xml.get_object("PackageLastTested1").set_tooltip_text(version_tooltip)
self.xml.get_object("PackageLastTested1").show()
else:
self.xml.get_object("PackageLastTested").hide()
self.xml.get_object("PackageLastTested1").hide()
self.xml.get_object("PackageRatingView").set_tooltip_text(tooltip)
self.xml.get_object("PackageRatingView1").set_tooltip_text(tooltip)
self.xml.get_object("PackageRatingLabel").set_tooltip_text(tooltip)
self.xml.get_object("PackageRatingLabel1").set_tooltip_text(tooltip)
else:
self.xml.get_object("PackageRatingLabel").hide()
self.xml.get_object("PackageRatingLabel1").hide()
self.xml.get_object("PackageLastTested").hide()
self.xml.get_object("PackageLastTested1").hide()
def on_PackageRatingView_style_set(self, widget, _previous_style):
blended_fg = cxguitools.blend_colors(widget.style.text[gtk.STATE_NORMAL], widget.style.base[gtk.STATE_NORMAL], 3, 1)
blended_fg2 = cxguitools.blend_colors(widget.style.text[gtk.STATE_NORMAL], widget.style.base[gtk.STATE_NORMAL], 1, 3)
brightness = max(blended_fg.red, blended_fg.green, blended_fg.blue, blended_fg2.red, blended_fg2.green, blended_fg2.blue)
star_icon = gtk.gdk.pixbuf_new_from_file(os.path.join(cxutils.CX_ROOT, 'share/images/rating_star.png'))
if star_icon:
star_icon = cxguitools.recolor_pixbuf(star_icon, gtk.gdk.Color(brightness, brightness, brightness//3))
widget.images[u'star'] = star_icon
star_empty_icon = gtk.gdk.pixbuf_new_from_file(os.path.join(cxutils.CX_ROOT, 'share/images/rating_star_empty.png'))
if star_empty_icon:
star_empty_icon = cxguitools.recolor_pixbuf(star_empty_icon, cxguitools.blend_colors(widget.style.text[gtk.STATE_NORMAL], widget.style.base[gtk.STATE_NORMAL], 1, 2))
widget.images[u'star-empty'] = star_empty_icon
untrusted_icon = gtk.gdk.pixbuf_new_from_file(os.path.join(cxutils.CX_ROOT, 'share/images/rating_untrusted.png'))
if untrusted_icon:
untrusted_icon = cxguitools.recolor_pixbuf(untrusted_icon, widget.style.text[gtk.STATE_NORMAL])
widget.images[u'untrusted'] = untrusted_icon
question_icon = gtk.gdk.pixbuf_new_from_file(os.path.join(cxutils.CX_ROOT, 'share/images/rating_question.png'))
if question_icon:
question_icon = cxguitools.recolor_pixbuf(question_icon, widget.style.text[gtk.STATE_NORMAL])
widget.images[u'question'] = question_icon
self.update_package_rating()
def profileChanged(self):
descriptionScroll = self.xml.get_object("PackageDescriptionScroll")
descriptionTextView = self.xml.get_object("PackageDescription")
package_button = self.xml.get_object("PackageSelectToggle")
MediaSelectToggle = self.xml.get_object("MediaSelectToggle")
packageTreeView = self.xml.get_object("PackageTreeView")
installNotesScroll = self.xml.get_object("InstallNotesScroll")
self.update_package_rating()
if self.installTask.profile:
description_lines = []
cursor = packageTreeView.get_cursor()
if not (cursor and cursor[0] and
self.packageTreeStore.get_value(self.packageTreeStore.get_iter(cursor[0]), 1) == self.installTask.profile.appid):
# selection changed
self.packageTreeStore.foreach(self.select_package_by_appid, self.installTask.profile.appid)
if self.installTask.profile.appid == u'com.codeweavers.unknown' and \
self.xml.get_object('SimpleSearchBox').get_real_text():
name = _("Unlisted application '%s'") % self.xml.get_object('SimpleSearchBox').get_real_text()
else:
name = self.installTask.profile.name
self.xml.get_object("PackageNameLabel").set_text(name)
ready_text = _("CrossOver is ready to install\n%s")
ready_markup = '<big><big><big><big>%s</big></big></big></big>' % (ready_text % ('<b>%s</b>' % cxutils.html_escape(name)))
self.xml.get_object("ReadyToInstallLabel").set_markup(ready_markup)
self.xml.get_object("InstallingHeader").set_text(_("Installing %(application)s") % {'application':name})
description_lines.append('%s' % self.installTask.profile.app_profile.description)
if self.installTask.profile.details_url:
description_lines.append('<a href="%s">%s</a>' % (
cxutils.html_escape(self.installTask.profile.details_url),
cxutils.html_escape(_("More info"))))
if self.installTask.profile.contributors:
description_lines.append(_("Tie contributors: %s") % self.installTask.profile.contributors_html.decode('utf8'))
description = '<body>%s</body>' % ''.join('<p>%s</p>' % line for line in description_lines)
# Encode description as utf8 because htmltextview likes that better.
descriptionTextView.display_html_safe(description.encode('utf8'))
# Move the description view back to the top-left corner
adjust = descriptionScroll.get_hadjustment()
adjust.set_value(adjust.get_lower())
adjust = descriptionScroll.get_vadjustment()
adjust.set_value(adjust.get_lower())
# Move the installation notes view back to the top-left corner
adjust = installNotesScroll.get_hadjustment()
adjust.set_value(adjust.get_lower())
adjust = installNotesScroll.get_vadjustment()
adjust.set_value(adjust.get_lower())
self.update_profile_caption()
self.xml.get_object("PackageDescriptionBox").show()
else:
packageTreeView = self.xml.get_object("PackageTreeView")
packageTreeView.get_selection().unselect_all()
descriptionTextView.get_buffer().set_text("")
package_button.set_tooltip_text(_("Select an application to install"))
self.xml.get_object("PackageSelectCheckmark").hide()
self.xml.get_object("PackageDescriptionBox").hide()
languages = self.installTask.profile_languages()
if languages:
self.xml.get_object("LanguageSelectorCombo").set_model(None)
self.languageTreeStore.clear()
for langid in languages:
if langid == '':
langname = _("Other language")
langsort = '1'
else:
langname = c4profiles.LANGUAGES.get(langid, langid)
langsort = u'0'+langname
self.languageTreeStore.append((langname, langid, langsort))
self.xml.get_object("LanguageSelectorCombo").set_model(self.languageTreeStore)
self.xml.get_object("LanguageSelectorBox").show()
self.languageTreeStore.foreach(self.select_language_by_id, self.installTask.installerLocale)
else:
self.xml.get_object("LanguageSelectorBox").hide()
self.update_download_item()
self.update_steam_item()
MediaSelectToggle.set_sensitive(not self.installTask.virtual)
self.updateBottleListing()
self.updateMediaListing()
self.sourceChanged() # update "Select Installer" button if changing between virtual/non-virtual
self.update_common_widgets()
def locale_changed(self, _widget):
index = self.xml.get_object('LanguageSelectorCombo').get_active()
treeiter = self.languageTreeStore.get_iter((index,))
langid = self.languageTreeStore.get_value(treeiter, 1)
self.installTask.SetLocale(langid)
self.update_download_item()
self.update_steam_item()
self.update_profile_caption()
self.update_common_widgets()
def on_AdvancedOptionsButton_clicked(self, widget):
self.xml.get_object('SummaryContent').set_property('visible', not widget.get_active())
self.xml.get_object('AdvancedContent').set_property('visible', widget.get_active())
self.xml.get_object("InstallNotesContent").set_property('visible', self.nonempty_install_notes and not self.xml.get_object("AdvancedContent").get_property('visible'))
self.update_common_widgets()
#-----------------------------------------------------------------------
# Installer Media Selection
#-----------------------------------------------------------------------
def browse_for_installer_file(self, caller):
filePicker = gtk.FileChooserDialog(_("Choose a Windows installer file"), caller, gtk.FILE_CHOOSER_ACTION_OPEN)
cxguitools.add_filters(filePicker, cxguitools.FILTERS_INSTALLABLE | cxguitools.FILTERS_ALLFILES)
filePicker.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
filePicker.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
if filePicker.run() == gtk.RESPONSE_OK:
selectedFile = filePicker.get_filename()
else:
selectedFile = None
filePicker.destroy()
return selectedFile
def browse_for_installer_dir(self, caller):
filePicker = gtk.FileChooserDialog(_("Choose the directory containing your installer"), caller, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
filePicker.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
filePicker.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
if filePicker.run() == gtk.RESPONSE_OK:
selectedDir = filePicker.get_filename()
else:
selectedDir = None
filePicker.destroy()
return selectedDir
def volumeAdded(self, volume, temporary=False):
if volume.mountpoint == "/" or not volume.is_disc:
# unlikely that the user wants to use this.
return
# We need to check this is not a duplicate because volumeAdded() is
# also used for the user-selected installers and directories
volumesIter = self.volumesListModel.iter_children(None)
while volumesIter != None:
if self.volumesListModel.get_value(volumesIter, 2) == volume.mountpoint:
# This is a duplicate
if not temporary:
# Just in case this was added by installtask first
self.volumesListModel.set_value(volumesIter, 5, False)
return
volumesIter = self.volumesListModel.iter_next(volumesIter)
if volume.label:
label = volume.label
else:
label = os.path.basename(volume.mountpoint)
volumesWidget = self.xml.get_object("MediaTreeView")
self.installTask.add_source_media(volume.mountpoint, volume.label, volume.device)
newrow = self.volumesListModel.append()
self.volumesListModel.set_value(newrow, 0, label)
self.volumesListModel.set_value(newrow, 2, volume.mountpoint)
if volume.is_disc:
pixbuf = volumesWidget.render_icon(gtk.STOCK_CDROM, gtk.ICON_SIZE_DIALOG)
else:
pixbuf = volumesWidget.render_icon(gtk.STOCK_HARDDISK, gtk.ICON_SIZE_DIALOG)
self.volumesListModel.set_value(newrow, 1, pixbuf)
self.volumesListModel.set_value(newrow, 3, '1'+self.volumesListModel.get_value(newrow, 0))
self.volumesListModel.set_value(newrow, 4, self.media_markup_format % (cxutils.html_escape(label), cxutils.html_escape(volume.mountpoint)))
self.volumesListModel.set_value(newrow, 5, temporary)
def volumeDeleted(self, volume, temporary=False):
volumesIter = self.volumesListModel.iter_children(None)
while volumesIter != None:
if self.volumesListModel.get_value(volumesIter, 2) == volume.mountpoint and \
(not temporary or self.volumesListModel.get_value(volumesIter, 5)):
self.volumesListModel.remove(volumesIter)
self.installTask.remove_source_media(volume.mountpoint)
if self.installTask.GetInstallerSource() == volume.mountpoint:
self.installTask.ClearInstallerSource()
break
else:
volumesIter = self.volumesListModel.iter_next(volumesIter)
else:
# currentVolumeLabel not found
cxlog.log("Warning: removed volume does not exist: %s" % cxlog.debug_str(volume))
def profileMediaAdded_(self, filename):
volume = mountpoints.Volume()
volume.mountpoint = filename
volume.is_disc = True # Lie so it's taken into account
self.volumeAdded(volume, True)
def profileMediaRemoved_(self, filename):
volume = mountpoints.Volume()
volume.mountpoint = filename
volume.is_disc = True # Lie so it's taken into account
self.volumeDeleted(volume, True)
def wantMediaSelection(self):
return not (self.installTask.GetInstallerSource() or
self.installTask.installerDownloadSource or
self.installTask.virtual or
self.installTask.installWithSteam)
def media_toggle(self, widget):
if widget.get_active() and not self.xml.get_object('MediaSelectContent').get_property('visible'):
self.set_expanded_section('MediaSelect')
elif not widget.get_active() and self.xml.get_object('MediaSelectContent').get_property('visible'):
widget.set_active(True)
chooseInstallerFileString = _(u"Choose Installer File\u2026")
chooseInstallerFolderString = _(u"Choose Installer Folder\u2026")
downloadInstallerString = _("Download installer")
steamInstallerString = _("Install Via Steam")
def updateMediaListing(self):
app_profile = self.installTask.profile and self.installTask.profile.app_profile
downloadNotes = self.xml.get_object("DownloadInstallationNotes")
if app_profile and app_profile.download_page_urls:
if app_profile.download_page_urls:
_lang, url = cxutils.get_language_value(app_profile.download_page_urls)
note = _('This software can be downloaded from its <a href="%(url)s">home page</a>.') % {'url': cxutils.html_escape(url)}
fullNote = "<body> %s </body>" % note
downloadNotes.display_html_safe(fullNote.encode('utf8'))
downloadNotes.show()
else:
downloadNotes.hide()
else:
downloadNotes.hide()
def select_media_by_mountpoint(self, model, path, treeiter, mountpoint):
if model.get_value(treeiter, 2) == mountpoint:
self.xml.get_object("MediaTreeView").set_cursor(path)
return True # stop iteration
return False # continue iteration
def select_download(self, model, path, treeiter, _userdata):
if model.get_value(treeiter, 0) == self.downloadInstallerString:
self.xml.get_object("MediaTreeView").set_cursor(path)
return True # stop iteration
return False # continue iteration
def select_steam(self, model, path, treeiter, _userdata):
if model.get_value(treeiter, 0) == self.steamInstallerString:
self.xml.get_object("MediaTreeView").set_cursor(path)
return True # stop iteration
return False # continue iteration
def sourceChanged(self):
ToggleButton = self.xml.get_object("MediaSelectToggle")
if self.installTask.virtual:
self.xml.get_object("MediaTreeView").get_selection().unselect_all()
ToggleButton.set_tooltip_text(_("Selecting an installer is not required for '%s'") % self.installTask.profile.name)
self.xml.get_object("MediaSelectCheckmark").show()
elif self.installTask.installWithSteam:
self.volumesListModel.foreach(self.select_steam, self.installTask.installerSource)
ToggleButton.set_tooltip_text(_("Will install via Steam"))
self.xml.get_object("MediaSelectCheckmark").show()
elif self.installTask.installerSource:
self.volumesListModel.foreach(self.select_media_by_mountpoint, self.installTask.installerSource)
ToggleButton.set_tooltip_text(_("Will install from %s") % self.installTask.installerSource)
self.xml.get_object("MediaSelectCheckmark").show()
elif self.installTask.installerDownloadSource:
self.volumesListModel.foreach(self.select_download, self.installTask.installerSource)
ToggleButton.set_tooltip_text(_("Will download the installer from %s") % self.installTask.installerDownloadSource)
self.xml.get_object("MediaSelectCheckmark").show()
else:
self.xml.get_object("MediaTreeView").get_selection().unselect_all()
ToggleButton.set_tooltip_text(_("Select an installer"))
self.xml.get_object("MediaSelectCheckmark").hide()
self.update_common_widgets()
def update_download_item(self):
url = self.installTask.GetDownloadURL()
volumesIter = self.volumesListModel.iter_children(None)
while volumesIter != None:
if self.volumesListModel.get_value(volumesIter, 0) == self.downloadInstallerString:
if url:
break
else:
self.volumesListModel.remove(volumesIter)
return
volumesIter = self.volumesListModel.iter_next(volumesIter)
else:
if url:
# No download item; add it.
volumesIter = self.volumesListModel.append()
self.volumesListModel.set_value(volumesIter, 0, self.downloadInstallerString)
pixbuf = self.xml.get_object('MediaTreeView').render_icon(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
self.volumesListModel.set_value(volumesIter, 1, pixbuf)
self.volumesListModel.set_value(volumesIter, 3, '0')
if self.installTask.installerDownloadSource:
self.xml.get_object("MediaTreeView").set_cursor(self.volumesListModel.get_path(volumesIter))
else:
return
self.volumesListModel.set_value(volumesIter, 4, self.media_markup_format % (self.downloadInstallerString, cxutils.html_escape(url)))
def update_steam_item(self):
volumesIter = self.volumesListModel.iter_children(None)
while volumesIter != None:
if self.volumesListModel.get_value(volumesIter, 0) == self.steamInstallerString:
if self.installTask.GetSteamID():
break
else:
self.volumesListModel.remove(volumesIter)
return
volumesIter = self.volumesListModel.iter_next(volumesIter)
else:
if self.installTask.GetSteamID():
volumesIter = self.volumesListModel.append()
pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(cxutils.CX_ROOT, 'share/images/steam_logo.png'))
self.volumesListModel.set_value(volumesIter, 1, pixbuf)
self.volumesListModel.set_value(volumesIter, 3, '0')
self.xml.get_object("MediaTreeView").set_cursor(self.volumesListModel.get_path(volumesIter))
self.volumesListModel.set_value(volumesIter, 0, self.steamInstallerString)
self.volumesListModel.set_value(volumesIter, 4, self.media_markup_format % (self.steamInstallerString, ""))
if self.installTask.installWithSteam:
self.xml.get_object("MediaTreeView").set_cursor(self.volumesListModel.get_path(volumesIter))
def set_custom_installer_source(self, path):
volume = mountpoints.Volume()
volume.mountpoint = path
volume.is_disc = True # Lie so it's taken into account
self.volumeAdded(volume)
self.installTask.SetInstallerSource(path)
self.update_common_widgets()
def source_changed_gui(self, _widget):
mediaTreeView = self.xml.get_object("MediaTreeView")
selections = mediaTreeView.get_selection().get_selected_rows()[1]
if selections:
selectedPath = selections[0]
selectedIter = self.volumesListModel.get_iter(selectedPath)
volumeName = self.volumesListModel.get_value(selectedIter, 0)
mountPoint = self.volumesListModel.get_value(selectedIter, 2)
if volumeName == self.chooseInstallerFileString:
selectedFile = self.browse_for_installer_file(self.xml.get_object("InstallWizard"))
if selectedFile:
self.set_custom_installer_source(selectedFile)
else:
self.installTask.ClearInstallerSource()
mediaTreeView.get_selection().unselect_all()
elif volumeName == self.chooseInstallerFolderString:
selectedDir = self.browse_for_installer_dir(self.xml.get_object("InstallWizard"))
if selectedDir:
self.set_custom_installer_source(selectedDir)
else:
self.installTask.ClearInstallerSource()
mediaTreeView.get_selection().unselect_all()
elif volumeName == self.downloadInstallerString:
self.installTask.SetInstallerDownload()
elif volumeName == self.steamInstallerString:
self.installTask.SetInstallerSteam()
else:
self.installTask.SetInstallerSource(mountPoint)
else:
self.installTask.ClearInstallerSource()
self.update_common_widgets()
#-----------------------------------------------------------------------
# Bottle Selection
#-----------------------------------------------------------------------
category_markup = {
installtask.CAT_NONE: "<b>"+_("Bottles")+"</b>",
installtask.CAT_RECOMMENDED: "<b>"+_("Recommended Bottles")+"</b>",
installtask.CAT_COMPATIBLE: "<b>"+_("Compatible Bottles")+"</b>",
installtask.CAT_INCOMPATIBLE: "<b>"+_("Incompatible Bottles")+"</b>"
}
category_sort = {
installtask.CAT_NONE: '0',
installtask.CAT_RECOMMENDED: '1',
installtask.CAT_COMPATIBLE: '2',
installtask.CAT_INCOMPATIBLE: '3',
}
def categorizedBottle_(self, _target_bottle):
self.updateBottleListing()
def categorizedAllBottles(self):
pass
def analyzedBottle_(self, target_bottle):
if target_bottle is self.installTask.target_bottle:
self.update_common_widgets()
def invalidNewBottleName(self):
cxlog.log("invalid new bottle name [%s]" % cxlog.to_str(self.installTask.newBottleName))
# FIXME: Todo
def wantBottleSelection(self):
if not self.installTask.bottlename:
# no bottle selected
return True
if self.installTask.GetCreateNewBottle():
# check that the name doesn't match an existing bottle
for bottlename in bottlecollection.sharedCollection().bottleList():
if self.installTask.bottlename.lower() == bottlename.lower():
return True
return False
def bottle_toggle(self, widget):
if widget.get_active() and not self.xml.get_object('BottleSelectContent').get_property('visible'):
self.set_expanded_section('BottleSelect')
elif not widget.get_active() and self.xml.get_object('BottleSelectContent').get_property('visible'):
widget.set_active(True)
def update_bottle_selection_label(self):
ToggleButton = self.xml.get_object("BottleSelectToggle")
bottlename = self.installTask.bottlename
if not bottlename:
ToggleButton.set_tooltip_text(_("Select a bottle into which to install"))
elif self.installTask.GetCreateNewBottle():
ToggleButton.set_tooltip_text(_("Will install into a new %(template)s bottle '%(name)s'") % {
'template': bottlemanagement.get_template_name(self.installTask.newBottleTemplate),
'name': bottlename})
else:
ToggleButton.set_tooltip_text(_("Will install into the bottle '%(name)s'") % {'name': bottlename})
if bottlename and \
(self.installTask.newBottleNameRequested if self.installTask.GetCreateNewBottle() else self.installTask.targetBottleRequested):
self.xml.get_object("PackageSelectHeader").set_text(_("Select an Application to Install Into '%s'") % bottlename)
else:
self.xml.get_object("PackageSelectHeader").set_text(_("Select an Application to Install"))
def select_bottle(self, model, path, treeiter, _userdata):
if self.installTask.GetCreateNewBottle():
bottle_string = '.' + self.installTask.GetNewBottleTemplate()
elif self.installTask.bottlename:
bottle_string = self.installTask.bottlename
else:
bottle_string = None
if model.get_value(treeiter, 0) == bottle_string:
self.xml.get_object("BottleSelectionView").set_cursor(path)
return True # stop iteration
return False # continue iteration
def _bottle_list_cmp(self, treemodel, iter1, iter2):
key1 = treemodel.get(iter1, 3)
key2 = treemodel.get(iter2, 3)
return cmp(key1, key2)
def updateBottleListing(self):
if self.installTask.profile:
bottles = self.installTask.bottles.values()
bottles_completed = 0
for target_bottle in bottles:
if target_bottle.has_category():
bottles_completed += 1
if bottles_completed != len(bottles):
progressbar = self.xml.get_object("BottleSelectProgress")
progressbar.set_fraction(float(bottles_completed) / len(bottles))
self.xml.get_object("BottleSelectProgress").show()
self.xml.get_object("BottleSelectListing").hide()
return
self.bottleListModel.clear()
bottles_by_category = {}
for target_bottle in self.installTask.bottles.values() + self.installTask.templates.values():
category = target_bottle.get_category()
if category in bottles_by_category:
bottles_by_category[category].append(target_bottle)
else:
bottles_by_category[category] = [target_bottle]
for category in bottles_by_category:
category_row = self.bottleListModel.append(None, row=('', self.category_markup[category], '', self.category_sort[category]))
for target_bottle in bottles_by_category[category]:
if isinstance(target_bottle, installtask.TargetTemplate):
name = '.' + target_bottle.template
markup = _("(New %(template)s bottle)") % {'template': bottlemanagement.get_template_name(target_bottle.template)}
sortkey = (0,) + bottlemanagement.get_template_key(target_bottle.template)
else:
name = target_bottle.bottlename
markup = cxutils.html_escape(target_bottle.bottlename)
sortkey = (1, target_bottle.bottlename)
template = target_bottle.template
self.bottleListModel.append(parent=category_row, row=(name, markup, bottlemanagement.get_template_name(template), sortkey))
self.xml.get_object("BottleSelectionView").expand_all()
self.bottleListModel.foreach(self.select_bottle, None)
self.xml.get_object("BottleSelectProgress").hide()
self.xml.get_object("BottleSelectListing").show()
def bottleCreateChanged(self):
self.bottleListModel.foreach(self.select_bottle, None)
if self.installTask.GetCreateNewBottle():
self.xml.get_object("NewBottleWidgets").show()
else:
self.xml.get_object("NewBottleWidgets").hide()
self.xml.get_object("BottleSideWidgets").set_property('visible', self.xml.get_object("NewBottleWidgets").get_property('visible') or
self.xml.get_object("BottleErrorText").get_property('visible'))
self.update_bottle_selection_label()
self.update_common_widgets()
def bottleNameChanged(self):
self.bottleListModel.foreach(self.select_bottle, None)
self.update_bottle_selection_label()
self.update_common_widgets()
def bottleNewnameChanged(self):
if self.installTask.newBottleName:
self.xml.get_object("NewBottleNameEntry").set_text(self.installTask.newBottleName)
self.update_bottle_selection_label()
self.update_common_widgets()
def bottleTemplateChanged(self):
self.bottleListModel.foreach(self.select_bottle, None)
self.update_bottle_selection_label()
self.update_download_item()
self.update_common_widgets()
# BottleCollection delegate functions
def bottleCollectionChanged(self):
if self.install_in_progress or self.install_finished:
return
removed_bottles = set(self.installTask.bottles)
for bottle in bottlecollection.sharedCollection().bottles():
if not bottle.canInstall():
continue
if bottle.name in removed_bottles:
removed_bottles.remove(bottle.name)
else:
self.installTask.add_bottle(bottle)
for bottlename in removed_bottles:
self.installTask.remove_bottle_by_name(bottlename)
self.updateBottleListing()
# BottleWrapper delegate functions
def bottleChanged(self, bottle):
if self.install_in_progress or self.install_finished:
return
if bottle.canInstall() != (bottle.name in self.installTask.bottles):
self.bottleCollectionChanged()
if bottle.installed_packages_ready:
self.installTask.installed_applications_ready(bottle)
def bottle_selection_changed_gui(self, _widget):
bottlesWidget = self.xml.get_object("BottleSelectionView")
tmodel, titer = bottlesWidget.get_selection().get_selected()
if titer is None:
# This can happen while we're rebuilding the bottle list
return
selectedBottleName = tmodel.get_value(titer, 0)
if selectedBottleName == '':
# For categories, select the first child.
self.xml.get_object("BottleSelectionView").set_cursor(tmodel.get_path(tmodel.iter_children(titer)))
elif selectedBottleName.startswith('.'):
# A bottle template.
self.installTask.SetCreateNewBottle(create=True, template=selectedBottleName[1:])
else:
self.installTask.bottlename = selectedBottleName
def bottle_newname_changed_gui(self, _widget):
self.installTask.SetCreateNewBottle(
newbottlename=self.xml.get_object("NewBottleNameEntry").get_text().decode('utf8'))
def new_bottle_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")
def new_bottle_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 no_section_activated(self):
if self.installEngine:
# Install was started already.
return
self.update_common_widgets()
def add_button_pos(self, menu):
_menu_width, menu_height = menu.size_request()
button = self.xml.get_object("DbgChannelButton")
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_DbgChannelButton_button_press_event(self, widget, event):
if self.add_menu_open or event.button != 1:
return False # propagate
menu = self.xml.get_object("DebugChannelMenu")
menu.popup(None, None, self.add_button_pos, event.button, event.time)
self.add_menu_open = True
widget.set_active(True)
return True
def on_DbgChannelButton_clicked(self, widget):
if self.add_menu_open or not widget.get_active():
return
menu = self.xml.get_object("DebugChannelMenu")
menu.popup(None, None, self.add_button_pos, 0, 0)
self.add_menu_open = True
def on_DebugChannelMenu_deactivate(self, _menushell):
self.add_menu_open = False
button = self.xml.get_object("DbgChannelButton")
button.set_active(False)
def on_DbgClearMenu_activate(self, _menu):
self.xml.get_object("DbgChannelEntry").set_text('')
def add_debug_channels(self, menu):
channels = self.xml.get_object("DbgChannelEntry").get_real_text().split(',')
for channel in menu.get_tooltip_text().split(','):
if channel not in channels:
channels.append(channel)
self.xml.get_object("DbgChannelEntry").set_text(','.join(x for x in channels if x))
def on_LogFileBrowse_clicked(self, _widget):
fileSaver = gtk.FileChooserDialog(_("Specify where to save the log file"), self.xml.get_object("InstallWizard"), gtk.FILE_CHOOSER_ACTION_SAVE)
fileFilter = gtk.FileFilter()
fileFilter.add_pattern("*.cxlog")
fileFilter.set_name(_("CrossOver Log Files"))
fileSaver.add_filter(fileFilter)
fileSaver.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
fileSaver.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_OK)
fileSaver.set_do_overwrite_confirmation(True)
logpath = os.path.join(cxproduct.get_user_dir(), 'logs')
cxutils.mkdirs(logpath)
fileSaver.set_current_folder(logpath)
cxguitools.set_default_extension(fileSaver, 'cxlog')
if fileSaver.run() == gtk.RESPONSE_OK:
self.xml.get_object('LogFileEntry').set_text(fileSaver.get_filename())
fileSaver.destroy()
#-----------------------------------------------------------------------
# Install
#-----------------------------------------------------------------------
def doInstall(self):
self.tasks = {}
self.tasklist = []
self.cancelInstallation = False
self.install_in_progress = True
if self.xml.get_object('DbgChannelEntry').get_real_text():
log_channels = self.xml.get_object('DbgChannelEntry').get_real_text()
else:
log_channels = None
if self.xml.get_object('LogFileEntry').get_real_text() or 'CX_LOG' not in os.environ:
log_filename = os.path.join(cxproduct.get_user_dir(), 'logs', self.xml.get_object('LogFileEntry').get_text())
else:
log_filename = None
self.installEngine = cxaiengine.Engine(self.installTask,
log_filename=log_filename,
log_channels=log_channels)
if 'logfifo_file' in self.installEngine.state:
log_file = self.installEngine.state['logfifo_file']
log_file.write(systeminfo.system_info_string())
log_file.flush()
elif 'logfile' in self.installEngine.state:
try:
f = open(self.installEngine.state['logfile'], 'a')
except IOError, ioe:
cxlog.log("unable to open log file: " + ioe.strerror)
else:
f.write(systeminfo.system_info_string())
f.close()
self.totalTasks = len(self.installEngine.get_sorted_tasks())
self.someTasksCanceled = False
self.someTasksSkipped = False
if self.registered_delegate:
collection = bottlecollection.sharedCollection()
collection.removeChangeDelegate(self)
collection.removeBottleChangeDelegate(self)
self.registered_delegate = False
self.spoolAvailableTasks()
def displayInstallationProgressPanel(self):
self.xml.get_object("PackageSelectToggle").set_sensitive(False)
self.xml.get_object("MediaSelectToggle").set_sensitive(False)
self.xml.get_object("BottleSelectToggle").set_sensitive(False)
self.xml.get_object("InstallerNext").hide()
self.xml.get_object("InstallerEject").show()
self.xml.get_object("SummaryContent").hide()
self.xml.get_object("AdvancedContent").hide()
self.xml.get_object("PackageInfoBar").hide()
self.xml.get_object("InstallingContent").show()
gobject.timeout_add(100, self.update_tasks)
self.set_expanded_section('Installer')
self.xml.get_object("AdvancedOptionsButton").hide()
self.update_install_notes(False)
def continueInstallation(self):
if not self.install_in_progress:
return
if self.cancelInstallation:
cxlog.log("Canceled Installation")
self.install_in_progress = False
self.displayInstallationFinishedPanel(True)
elif self.installEngine.all_done():
cxlog.log("Installation finished")
self.install_in_progress = False
self.displayInstallationFinishedPanel(False)
else:
self.spoolAvailableTasks()
def spoolAvailableTasks(self):
# Show a failure dialog if necessary
if self.failing_tasks and not self.install_ui:
self.failing_tasks.pop(0).show_failure_dialog()
# Add all the tasks that are currently runnable to the operation queue.
for task in sorted(self.installEngine.runnable(), cmp=cxaiebase.tasks_cmp):
if not self.install_ui or not isinstance(task, interactive_tasks):
if self.installEngine.schedule(task):
self.tasks[task] = InstallTask(task, self)
self.tasklist.append(self.tasks[task])
def add_failing_task(self, task):
self.failing_tasks.append(task)
self.continueInstallation()
def cancel_install(self):
self.cancelInstallation = True
self.someTasksCanceled = True
for task in self.tasks.itervalues():
task.cancel()
self.continueInstallation()
def install_toggle(self, widget):
if widget.get_active() and not self.xml.get_object('InstallerContent').get_property('visible'):
self.set_expanded_section('Installer')
elif not widget.get_active() and self.xml.get_object('InstallerContent').get_property('visible'):
widget.set_active(True)
def eject_clicked(self, _widget):
bottlemanagement.wine_eject(self.installTask.bottlename)
def displayInstallationFinishedPanel(self, inCanceled):
self.install_finished = True
completionMessage = ""
if self.someTasksCanceled:
completionMessage = _("The installation failed.")
elif self.installTask.installWithSteam:
completionMessage = _("CrossOver has been successfully configured for '%s'. You will need to finish the installation in Steam.") % self.installTask.profile.name
elif self.someTasksSkipped:
completionMessage = _("The installation completed, but some tasks were skipped.")
else:
completionMessage = _("The installation completed.")
if 'logfile' in self.installEngine.state:
url = self.installEngine.state["logfile"]
elif 'CX_LOG' in self.installEngine.state["environ"]:
url = self.installEngine.state["environ"]["CX_LOG"]
else:
url = None
if url:
captionHTML = _("<body> <p> %(completion)s </p> <p> More diagnostic information can be found in the <a href=\"file://%(url)s\">debug log</a>. </p> </body>") % {'completion': cxutils.html_escape(completionMessage), 'url': cxutils.html_escape(url)}
else:
captionHTML = "<body> <p> %s </p> </body>" % cxutils.html_escape(completionMessage)
# Encode description as utf8 because htmltextview likes that better.
caption = self.xml.get_object("InstallResults")
caption.display_html_safe(captionHTML.encode('utf8'))
self.xml.get_object("InstallerCancel").hide()
self.xml.get_object("InstallingContent").hide()
self.xml.get_object("FinishedContent").show()
self.xml.get_object("InstallerCheckmark").show()
nextButton = self.xml.get_object("InstallerNext")
nextButton.set_sensitive(True)
nextButton.show()
stock_close = gtk.stock_lookup(gtk.STOCK_CLOSE)
if stock_close:
_stock_id, label, _modifier, _keyval, _translation_domain = stock_close
nextButton.set_label(label)
self.xml.get_object("InstallerEject").hide()
bottle = bottlecollection.sharedCollection().bottleObject(self.installTask.bottlename)
bottlecollection.sharedCollection().queue_bottle_updates((bottle,), ("dxvk"))
try:
# Touch the config file so the bottle manager knows that
# something is happening.
if self.installTask.createNewBottle:
os.utime(bottlequery.config_file_path(self.installTask.newBottleName), None)
else:
os.utime(bottlequery.config_file_path(self.installTask.existingBottleName), None)
except bottlequery.NotFoundError:
# The bottle wasn't created yet. Do nothing.
pass
if not inCanceled and not self.someTasksCanceled:
postinstallurl = self.installTask.get_installer_profile().post_install_url
if postinstallurl:
cxutils.launch_url(postinstallurl)
def finished_toggle(self, widget):
if widget.get_active() and not self.xml.get_object('FinishedContent').get_property('visible'):
self.set_expanded_section('Finished')
elif not widget.get_active() and self.xml.get_object('FinishedContent').get_property('visible'):
widget.set_active(True)
def finished_section_activated(self):
self.emit_event(INSTALL_FINISHED)
#Used to update global state (such as the "Continue" button name/sensitivity)
# when the active section changes.
on_section_changed = {
'PackageSelect': update_common_widgets,
'MediaSelect': update_common_widgets,
'BottleSelect': update_common_widgets,
'Installer': update_common_widgets,
'Finished': finished_section_activated,
None: no_section_activated,
}
def update_tasks(self):
if self.install_finished:
return False
model = self.installingTaskStore
iterator = model.get_iter_first()
while iterator is not None:
iter_next = model.iter_next(iterator)
pulse, value, task, counter = model.get(iterator, 1, 2, 3, 4)
use_value = isinstance(task.aietask, cxaiemedia.AIEDownload) and \
task.aietask.size > 0
if task.finished:
if use_value:
if value != 100:
model.set_value(iterator, 2, 100)
else:
if pulse != gobject.G_MAXINT:
model.set_value(iterator, 1, gobject.G_MAXINT)
elif isinstance(task.aietask, cxaiemedia.AIEDownload):
size = task.aietask.size
downloaded = task.aietask.downloaded
if use_value:
new_value = downloaded * 99 // size + 1 # always display incomplete downloads as 1-99%
if new_value != value:
model.set_value(iterator, 2, new_value)
else:
if downloaded != counter:
model.set_value(iterator, 1, max(pulse, 0)+1)
model.set_value(iterator, 4, downloaded)
else:
if counter % 3 == 0:
model.set_value(iterator, 1, max(pulse, 0)+1)
counter += 1
model.set_value(iterator, 4, counter)
iterator = iter_next
return True
def tasks_scroll_changed(self, adjustment):
if self.tasks_scroll_at_bottom:
adjustment.set_value(adjustment.upper - adjustment.page_size)
def tasks_scroll_moved(self, adjustment):
self.tasks_scroll_at_bottom = (adjustment.value + adjustment.page_size >= adjustment.upper)
def create_task_menu(self):
model, iterator = self.xml.get_object('InstallerTasksView').get_selection().get_selected()
if not iterator:
return None
task = model.get_value(iterator, 3)
if not task or task.finished:
return None
result = gtk.Menu()
cancel_item = gtk.ImageMenuItem(gtk.STOCK_CANCEL)
cancel_item.show()
cancel_item.connect('activate', task.cancel)
result.append(cancel_item)
return result
def on_InstallerTasksView_popup_menu(self, _widget):
menu = self.create_task_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_InstallerTasksView_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.create_task_menu()
if menu:
menu.popup(None, None, None, event.button, event.time)
return True
return False # propagate
#
# Global dialog management
#
DIALOG = None
def _dialog_closed(_dialog, _event, _data):
# pylint: disable=W0603
global DIALOG
DIALOG = None
def open_or_show(**kwargs):
# pylint: disable=W0603
global DIALOG
if DIALOG is None:
DIALOG = InstallWizardController(**kwargs)
DIALOG.add_observer(DIALOG_CLOSED, _dialog_closed)
else:
DIALOG.present()
return DIALOG
#
# InstallTask
#
def _install_task_sort(treemodel, iter1, iter2):
task1 = treemodel.get_value(iter1, 3)
task2 = treemodel.get_value(iter2, 3)
if task1 is None or task2 is None:
# wtf
return 0
# Show pending downloads first
task1_pending_download = not task1.finished and \
isinstance(task1.aietask, cxaiemedia.AIEDownload) and \
task1.aietask.size == -1
task2_pending_download = not task1.finished and \
isinstance(task2.aietask, cxaiemedia.AIEDownload) and \
task2.aietask.size == -1
if task1_pending_download and not task2_pending_download:
return -1
if not task1_pending_download and task2_pending_download:
return 1
# Show finished tasks before in-progress tasks
if task1.finished != task2.finished:
if task1.finished and not task2.finished:
return -1
elif not task1.finished and task2.finished:
return 1
# For finished tasks, order by when they finished
if task1.finished:
return cmp(task1.finished_time, task2.finished_time)
# Show in-progress downloads before other kinds of tasks
if isinstance(task1.aietask, cxaiemedia.AIEDownload) and not isinstance(task2.aietask, cxaiemedia.AIEDownload):
return -1
if not isinstance(task1.aietask, cxaiemedia.AIEDownload) and isinstance(task2.aietask, cxaiemedia.AIEDownload):
return 1
# Otherwise, sort by started time, then enqueued time
return cmp((task1.started_time, task1.enqueued_time), (task2.started_time, task2.enqueued_time))
interactive_tasks = (cxaiecore.AIECore, )
class InstallTask(object):
finished = False
enqueued_time = 0.0
started_time = 0.0
finished_time = 0.0
def __init__(self, aietask, wizard):
self.aietask = aietask
self.wizard = wizard
if aietask.label:
store = self.wizard.installingTaskStore
store.append(row=(aietask.label, -1, 0, self, 0))
self.enqueue()
def get_progress(self):
if self.finished:
return 1.0
if isinstance(self.aietask, cxaiemedia.AIEDownload):
return self.aietask.progress
return 0.0
progress = property(get_progress)
def enqueue(self):
self.enqueued_time = time.time()
if isinstance(self.aietask, interactive_tasks):
self.wizard.install_ui = True
self.operation = InstallTaskOperation(self)
if isinstance(self.aietask, cxaiemedia.AIEDownload):
download_queue.enqueue(self.operation)
else:
pyop.sharedOperationQueue.enqueue(self.operation)
def done(self):
# This task has either succeeded or failed and will not retry. Update
# widgets and notify the ai engine.
if isinstance(self.aietask, interactive_tasks):
self.wizard.install_ui = False
self.finished = True
self.finished_time = time.time()
self.aietask.done()
self.wizard.continueInstallation()
def task_returned(self, success):
if self.finished:
return
if isinstance(self.aietask, interactive_tasks):
self.wizard.install_ui = False
self.operation = None
if success:
self.done()
else:
self.wizard.add_failing_task(self)
def show_failure_dialog(self):
if isinstance(self.aietask, cxaiemedia.AIEDownload):
primary = _("Could not download the %(appname)s installer") % {'appname': cxutils.html_escape(self.aietask.aiecore.name)}
elif isinstance(self.aietask, cxaiecore.AIECore):
primary = _("An error occurred while installing %(appname)s") % {'appname': cxutils.html_escape(self.aietask.aiecore.name)}
else:
label = self.aietask.label
if not label:
label = unicode(self.aietask)
primary = _("An error occurred while running the '%(task)s' task") % {'task': cxutils.html_escape(label)}
if self.aietask.error:
secondary = cxutils.html_escape(self.aietask.error)
else:
secondary = _("An unexpected error occurred.")
if hasattr(self.aietask, "needs_download") and self.aietask.needs_download and hasattr(self.aietask, "url"):
secondary += _("\n\nIf you have a working Internet connection via your browser, you can download this file here:\n\n<a href=\"%(url)s\">%(url)s</a>") % {'url': cxutils.html_escape(self.aietask.url)}
message = '<span weight="bold" size="larger">%s</span>\n\n%s' % (primary, secondary)
buttons = [[_("Try Again"), 1]]
if isinstance(self.aietask, cxaiemedia.AIEDownload):
buttons.append([_("Pick Installer File"), 3])
buttons.extend(([_("Skip This Step"), 0],
[_("Cancel Installation"), 2]))
cxguitools.CXMessageDlg(markup=message,
button_array=buttons,
response_function=self.abortRetryFail,
parent=self.wizard.xml.get_object("InstallWizard"),
message_type=gtk.MESSAGE_WARNING)
self.wizard.install_ui = True
def skip(self):
self.wizard.someTasksSkipped = True
self.done()
def retry(self):
self.enqueue()
def cancel(self, _menuitem=None):
if self.finished:
return
if self.aietask.can_cancel():
self.aietask.cancel()
self.done()
def abortRetryFail(self, response):
self.wizard.install_ui = False
if response == 0:
self.skip()
elif response == 1:
self.retry()
elif response == 2:
self.wizard.cancel_install()
elif response == 3:
panel = self.wizard.xml.get_object("InstallationProgress")
filePicker = gtk.FileChooserDialog(_("Please locate the installer file for %s") % self.aietask.aiecore.name, panel, gtk.FILE_CHOOSER_ACTION_OPEN)
cxguitools.add_filters(filePicker, cxguitools.FILTERS_INSTALLABLE)
filePicker.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
filePicker.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
if filePicker.run() == gtk.RESPONSE_OK:
self.aietask.aiecore.state['install_source'] = filePicker.get_filename()
self.retry()
else:
# Go back to the previous error dialog
self.show_failure_dialog()
filePicker.destroy()
return
# Just in case we need to show another failure dialog
self.wizard.continueInstallation()
def cancel_clicked(self, _widget):
self.cancel()
#
# Operations
#
class UpdateProfilesOperation(pyop.PythonOperation):
_refresh_lock = thread.allocate_lock()
def __init__(self, url, wizard):
pyop.PythonOperation.__init__(self)
self.url = url
self.wizard = wizard
self.refresh = False
self.network = False
self.profile_tree = None
self.is_queued = False
def __unicode__(self):
return "%s - %s" % (self.__class__.__name__, unicode(self.url))
def enqueued(self):
pyop.PythonOperation.enqueued(self)
self.is_queued = True
def main(self):
if self.network and self.url:
self.refresh = c4profilesmanager.update_online_profiles(self.url)
else:
self.refresh = True
if self.refresh:
self._refresh_lock.acquire()
self.profiles = c4profilesmanager.C4ProfilesSet.all_profiles()
self.profile_tree = {}
categories = {}
for appid, profile in self.profiles.iteritems():
if not profile.is_for_current_product:
continue
if not self.wizard.show_untested_apps and not profile.is_ranked and \
appid != c4profilesmanager.UNKNOWN:
continue
category = self.profile_tree
category_path = profile.app_profile.category
if appid == c4profilesmanager.UNKNOWN:
pass # Put this in the top level
elif category_path in categories:
category = categories[category_path]
else:
category = self.profile_tree
for category_name in category_path.split(u'/'):
if category_name not in category:
category[category_name] = {}
category = category[category_name]
categories[category_path] = category
category[appid] = profile
def finish(self):
self.is_queued = False
if self.refresh:
self._refresh_lock.release()
if self.refresh and not (self.wizard.install_in_progress or self.wizard.install_finished):
self.wizard.installTask.profiles = self.profiles
self.wizard.profile_tree = self.profile_tree
if self.wizard.xml.get_object('InstallWizard').get_property('visible'):
self.wizard.updatePackageListing()
self.wizard.profileChanged()
self.wizard.package_changed_gui(None)
if self.wizard.autorun_file:
self.wizard.installTask.use_autorun_file(self.wizard.autorun_file)
self.wizard.autorun_file = None
if self.wizard.profile_id_to_set and self.wizard.profile_id_to_set in self.profiles:
self.wizard.installTask.profile = self.profiles[self.wizard.profile_id_to_set]
self.wizard.profile_id_to_set = None
self.wizard.installTask.AutoFillSettings()
if not self.network and self.url and self.wizard.auto_update:
self.network = True
pyop.sharedOperationQueue.enqueue(self)
if self.network and not self.is_queued:
self.wizard.xml.get_object('PackageInfoBar').hide()
class InstallTaskOperation(pyop.PythonOperation):
def __init__(self, task):
pyop.PythonOperation.__init__(self)
self.task = task
self.success = False
def __unicode__(self):
return "%s - %s" % (self.__class__.__name__, unicode(self.task))
def main(self):
if not self.task.wizard.cancelInstallation and not self.task.finished:
self.task.started_time = time.time()
self.success = self.task.aietask.main()
else:
self.success = False
def finish(self):
self.task.task_returned(self.success)