# (c) Copyright 2009-2012. CodeWeavers, Inc.
import traceback
import gobject
import gtk
import cxlog
import cxutils
import cxassoc
import cxguitools
import pyop
# for localization
from cxutils import cxgettext as _
# positions of columns in the ListStore
_COLUMN_EASSOC = 0
_COLUMN_MODE = 1
_COLUMN_LOCALIZED_MODE = 2
_COLUMN_EXTENSION = 3
_COLUMN_VERBNAME = 4
_COLUMN_APPLICATION = 5
_COLUMN_ICON_PIXBUF = 6
_COLUMN_MODE_COLOR = 7
_COLUMN_EXTENSION_SORT = 8
_mode_settings = ('default', 'alternative', 'mime', 'ignore')
_mode_colors = {
'default': gtk.gdk.color_parse('green'),
'alternative': gtk.gdk.color_parse('yellow'),
'mime': gtk.gdk.color_parse('#E4E4E4'),
'ignore': gtk.gdk.color_parse('#E4E4E4'),
}
class AssocEditorPrefs(cxassoc.CXAssocPrefs):
def mode_changed(self, eassoc, newmode, _user):
row_index = self.editor_widget.row_indexes[eassoc]
row_iter = self.editor_widget.liststore.get_iter((row_index,))
mode_color = _mode_colors[newmode]
self.editor_widget.liststore.set(row_iter,
_COLUMN_MODE, newmode,
_COLUMN_LOCALIZED_MODE, self.editor_widget.localized_modes[newmode],
_COLUMN_MODE_COLOR, mode_color)
def __init__(self, bottlename, managed, editor_widget):
cxassoc.CXAssocPrefs.__init__(self, bottlename, managed)
self.editor_widget = editor_widget
class AssocEditor(gtk.VBox):
__gtype_name__ = 'AssocEditor'
bottlename = None
managed = None
prefs = None
def set_bottle(self, bottlename, managed):
if self.bottlename:
raise ValueError("bottle is already set")
self.bottlename = bottlename
self.managed = managed
if not self.managed:
self.mode_renderer.connect('edited', self.on_mode_edited)
elif hasattr(self.mode_renderer, 'set_sensitive'):
self.mode_renderer.set_sensitive(False)
self.prefs = AssocEditorPrefs(bottlename, managed, self)
def __init__(self):
gtk.VBox.__init__(self)
self.row_indexes = {} # mapping of section names to liststore rows
self.localized_modes = {'default': _("Use when double-clicking"),
'alternative': _("Include in Open With"),
'mime': _("Register file type"),
'ignore': _("Ignore extension")}
self.localized_verbs = {'': _("&Open"),
'edit': _("&Edit"),
'install': _("&Install"), # for the CrossOver associations
'open': _("&Open"),
'opennew': _("&Open"),
'play': _("&Play"),
'preview': _("Pre&view"),
'print': _("&Print"),
'restore': _("&Restore"), # for the CrossOver associations
'run': _("&Run")} # for the CrossOver associations
self.filter_function = None
self.progbar = gtk.ProgressBar()
self.pack_start(self.progbar, expand=True, fill=False, padding=0)
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gtk.gdk.Pixbuf, gtk.gdk.Color, gobject.TYPE_STRING)
self.filteredmodel = self.liststore.filter_new()
self.filteredmodel.set_visible_func(self.row_is_visible)
self.sortedmodel = gtk.TreeModelSort(self.filteredmodel)
self.sortedmodel.set_sort_column_id(_COLUMN_EXTENSION_SORT, gtk.SORT_ASCENDING)
self.treeview = gtk.TreeView(self.sortedmodel)
self.treeview.set_property('headers-clickable', True)
self.treeview.show()
self.scrolledview = gtk.ScrolledWindow()
self.scrolledview.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.scrolledview.add(self.treeview)
icon_renderer = gtk.CellRendererPixbuf()
icon_column = gtk.TreeViewColumn(_("Icon"), icon_renderer)
icon_column.add_attribute(icon_renderer, 'pixbuf', _COLUMN_ICON_PIXBUF)
self.treeview.append_column(icon_column)
ext_renderer = gtk.CellRendererText()
ext_column = gtk.TreeViewColumn(_("Extension"), ext_renderer)
ext_column.set_resizable(True)
ext_column.add_attribute(ext_renderer, 'text', _COLUMN_EXTENSION)
ext_column.set_sort_column_id(_COLUMN_EXTENSION_SORT)
self.treeview.append_column(ext_column)
action_renderer = gtk.CellRendererText()
action_column = gtk.TreeViewColumn(_("Action"), action_renderer)
action_column.set_resizable(True)
action_column.add_attribute(action_renderer, 'text', _COLUMN_VERBNAME)
action_column.set_sort_column_id(_COLUMN_VERBNAME)
self.treeview.append_column(action_column)
app_renderer = gtk.CellRendererText()
app_column = gtk.TreeViewColumn(_("Application"), app_renderer)
app_column.set_resizable(True)
app_column.add_attribute(app_renderer, 'text', _COLUMN_APPLICATION)
app_column.set_sort_column_id(_COLUMN_APPLICATION)
self.treeview.append_column(app_column)
# create a liststore containing the possible mode values for the mode
# combo box
self.mode_model = gtk.ListStore(gobject.TYPE_STRING)
for mode in _mode_settings:
self.mode_model.append((self.localized_modes[mode],))
# create the mode column
self.mode_renderer = gtk.CellRendererCombo()
self.mode_renderer.set_property('has-entry', False)
self.mode_renderer.set_property('model', self.mode_model)
self.mode_renderer.set_property('text-column', 0)
self.mode_renderer.set_property('editable', True)
self.mode_renderer.set_property('editable-set', True)
self.mode_renderer.set_property('foreground-gdk', gtk.gdk.color_parse('black'))
self.mode_column = gtk.TreeViewColumn(_("State"), self.mode_renderer)
self.mode_column.add_attribute(self.mode_renderer, 'text', _COLUMN_LOCALIZED_MODE)
self.mode_column.add_attribute(self.mode_renderer, 'background-gdk', _COLUMN_MODE_COLOR)
self.mode_column.set_sort_column_id(_COLUMN_LOCALIZED_MODE)
self.treeview.append_column(self.mode_column)
if hasattr(self.treeview, 'set_has_tooltip'):
# GTK+ >= 2.12
self.treeview.set_has_tooltip(True)
self.treeview.connect('query-tooltip', self.on_query_tooltip)
self.pack_start(self.scrolledview, expand=True, fill=True, padding=0)
self.pulse_timer = None
def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
if not keyboard_mode:
res = widget.get_path_at_pos(x, y)
# res is (path, column, x, y)
if res and res[1] == self.mode_column:
tooltip.set_text(_("Click twice to change"))
return True
return False
def pulse(self):
self.progbar.pulse()
return True
def pulse_start(self, text):
self.progbar.set_text(text)
self.progbar.show()
self.pulse_timer = gobject.timeout_add(100, self.pulse)
self.scrolledview.set_sensitive(False)
def pulse_stop(self):
if self.pulse_timer:
gobject.source_remove(self.pulse_timer)
self.pulse_timer = None
self.progbar.hide()
self.scrolledview.set_sensitive(True)
def refresh(self, success_notify, failure_notify):
if self.pulse_timer:
return
self.pulse_start(_(u"Reading the associations of %(bottlename)s\u2026") % {'bottlename': self.prefs.bottlename})
operation = RefreshAssociationsOp(self, success_notify, failure_notify)
pyop.sharedOperationQueue.enqueue(operation)
def recreate_assocs(self, success_notify, failure_notify):
if self.pulse_timer:
return
self.pulse_start(_(u"Recreating the associations in %(bottlename)s\u2026") % {'bottlename': self.prefs.bottlename})
operation = RecreateAssocsOp(self, success_notify, failure_notify)
pyop.sharedOperationQueue.enqueue(operation)
def commit(self, success_notify, failure_notify):
if self.pulse_timer:
return
self.pulse_start(_(u"Saving changes\u2026"))
operation = CommitAssociationsOp(self, success_notify, failure_notify)
pyop.sharedOperationQueue.enqueue(operation)
def update_assoclist(self):
"read the list of associations from self.prefs"
self.liststore.clear()
self.row_indexes.clear()
for n, eassoc in enumerate(self.prefs):
assoc = self.prefs[eassoc]
icon_filename = assoc.get_iconfilename()
icon = None
if icon_filename:
try:
icon = gtk.gdk.pixbuf_new_from_file(icon_filename)
except gobject.GError:
cxlog.warn("couldn't load icon file %s:\n%s" % (cxlog.debug_str(icon_filename), traceback.format_exc()))
if icon is None:
icon = cxguitools.get_std_icon('crossover')
if icon is not None:
icon = icon.scale_simple(24, 24, gtk.gdk.INTERP_BILINEAR)
verbname = cxutils.remove_accelerators(self.localized_verbs.get(assoc.verbname, assoc.verbname))
mode_color = _mode_colors[assoc.new_mode]
sort_extension = '1'.join(assoc.extension) + '0' + eassoc
self.liststore.append((eassoc, assoc.new_mode, self.localized_modes[assoc.new_mode],
assoc.extension, verbname, assoc.appname, icon, mode_color, sort_extension))
self.row_indexes[eassoc] = n
# Don't pad the progress bar next time we show it; we want the treeview
# to fill that space.
self.set_child_packing(self.progbar, False, False, 0, gtk.PACK_START)
self.scrolledview.show()
def on_mode_edited(self, _cellrenderer, path, new_text):
row_iter = self.sortedmodel.get_iter(path)
eassoc = self.sortedmodel.get_value(row_iter, _COLUMN_EASSOC)
for mode, local_mode in self.localized_modes.iteritems():
if local_mode == new_text:
self.prefs.set_mode(eassoc, mode)
break
else:
cxlog.warn("got invalid association state from cell renderer: %s" % cxlog.debug_str(new_text))
return
def row_is_visible(self, model, row_iter):
"""See gtk.TreeModelFilter.set_visible_func()"""
eassoc = model.get_value(row_iter, _COLUMN_EASSOC)
if eassoc is not None: #FIXME: Why is this needed?
if self.filter_function:
return self.filter_function(eassoc)
return True
return False
def set_filter(self, filter_function):
self.filter_function = filter_function
self.filteredmodel.refilter()
class RefreshAssociationsOp(pyop.PythonOperation):
def __init__(self, editor, success_notify, failure_notify):
pyop.PythonOperation.__init__(self)
self.editor = editor
self.success_notify = success_notify
self.failure_notify = failure_notify
self.error_text = None
def main(self):
self.error_text = None
try:
self.editor.prefs.refresh()
except Exception: # pylint: disable=W0703
self.error_text = traceback.format_exc()
def finish(self):
self.editor.pulse_stop()
self.editor.update_assoclist()
if self.error_text is None:
self.success_notify()
else:
self.failure_notify(self.error_text)
class RecreateAssocsOp(pyop.PythonOperation):
def __init__(self, editor, success_notify, failure_notify):
pyop.PythonOperation.__init__(self)
self.editor = editor
self.success_notify = success_notify
self.failure_notify = failure_notify
self.error_text = None
def main(self):
self.error_text = None
try:
self.editor.prefs.recreate_assocs()
self.editor.prefs.refresh()
except Exception: # pylint: disable=W0703
self.error_text = traceback.format_exc()
def finish(self):
self.editor.pulse_stop()
self.editor.update_assoclist()
if self.error_text is None:
self.success_notify()
else:
self.failure_notify(self.error_text)
class CommitAssociationsOp(pyop.PythonOperation):
def __init__(self, editor, success_notify, failure_notify):
pyop.PythonOperation.__init__(self)
self.editor = editor
self.success_notify = success_notify
self.failure_notify = failure_notify
self.error_text = None
def main(self):
self.error_text = None
try:
self.editor.prefs.commit()
except Exception: # pylint: disable=W0703
self.error_text = traceback.format_exc()
def finish(self):
self.editor.pulse_stop()
if self.error_text is None:
self.success_notify()
else:
self.failure_notify(self.error_text)