From 64a8ea64e198fe83d2eb49b54b2cf893d5117c84 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sat, 9 Feb 2008 19:24:22 +0000 Subject: [PATCH] Remove checking box from GUI. The checking box isn't very useful now that we have background update checks in 0launch, so remove it to simplify the code. Also some internal refactoring: - Pass Policy objects around instead of using globals. - GUIPolicy becomes just GUI and no longer subclasses. --- zeroinstall/0launch-gui/0launch-gui | 18 +- zeroinstall/0launch-gui/checking.py | 55 ----- zeroinstall/0launch-gui/compile.py | 3 +- zeroinstall/0launch-gui/dialog.py | 12 ++ zeroinstall/0launch-gui/gui.py | 348 +++++++++++-------------------- zeroinstall/0launch-gui/iface_browser.py | 55 +++-- zeroinstall/0launch-gui/impl_list.py | 24 ++- zeroinstall/0launch-gui/mainwindow.py | 16 +- zeroinstall/0launch-gui/preferences.py | 15 +- zeroinstall/0launch-gui/properties.py | 182 ++++++++-------- 10 files changed, 302 insertions(+), 426 deletions(-) delete mode 100644 zeroinstall/0launch-gui/checking.py rewrite zeroinstall/0launch-gui/gui.py (60%) diff --git a/zeroinstall/0launch-gui/0launch-gui b/zeroinstall/0launch-gui/0launch-gui index 81c2394..0c9bdc1 100755 --- a/zeroinstall/0launch-gui/0launch-gui +++ b/zeroinstall/0launch-gui/0launch-gui @@ -84,6 +84,8 @@ if len(args) > 1: sys.exit(1) from zeroinstall.injector import model, autopolicy, namespaces +from zeroinstall.injector.policy import Policy +from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.support import tasks restrictions = [] @@ -91,9 +93,13 @@ if options.before or options.not_before: restrictions.append(model.Restriction(model.parse_version(options.before), model.parse_version(options.not_before))) -policy = gui.GUIPolicy(interface_uri, - download_only = bool(options.download_only), - src = options.source, - restrictions = restrictions) -main = tasks.Task(policy.main(bool(options.refresh)), "main") -policy.handler.wait_for_blocker(main.finished) +handler = gui.GUIHandler() +policy = Policy(interface_uri, handler, src = bool(options.source)) +root_iface = iface_cache.get_interface(interface_uri) +policy.solver.extra_restrictions[root_iface] = restrictions +policy.solver.record_details = True + +gui = gui.GUI(policy, download_only = bool(options.download_only)) +handler.policy = gui +main = tasks.Task(gui.main(bool(options.refresh)), "main") +handler.wait_for_blocker(main.finished) diff --git a/zeroinstall/0launch-gui/checking.py b/zeroinstall/0launch-gui/checking.py deleted file mode 100644 index b0854ea..0000000 --- a/zeroinstall/0launch-gui/checking.py +++ /dev/null @@ -1,55 +0,0 @@ -import gtk, gobject - -from dialog import Dialog -from zeroinstall.support import tasks - -class CheckingBox(Dialog): - hint_timeout = None - - def __init__(self, root): - Dialog.__init__(self) - self.prog_name = root.get_name() - self.set_title("Checking for updates") - self.label = gtk.Label("Checking for updates to '%s'..." % self.prog_name) - self.label.set_padding(10, 10) - self.vbox.pack_start(self.label, True, True, 0) - self.vbox.show_all() - - self.progress = gtk.ProgressBar() - self.vbox.pack_start(self.progress, False, True, 0) - self.progress.show() - - self.show_details_clicked = tasks.Blocker("click Show Details") - self.cancelled = tasks.Blocker("cancel checking box") - - self.add_mixed_button('Details...', gtk.STOCK_ZOOM_IN, gtk.RESPONSE_OK) - def response(w, r): - if r == gtk.RESPONSE_OK: - self.show_details_clicked.trigger() - else: - self.cancelled.trigger() - self.connect('response', response) - - def show_hint(): - hint = gtk.Label("(if you want to skip this, click on\n" - "Details, and then on Run)") - hint.set_justify(gtk.JUSTIFY_CENTER) - self.vbox.pack_start(hint, False, True, 0) - self.vbox.show_all() - self.hint_timeout = None - return False - self.hint_timeout = self.hint_timeout = gobject.timeout_add(8000, show_hint) - def destroy(box): - if self.hint_timeout is not None: - gobject.source_remove(self.hint_timeout) - self.hint_timeout = None - self.connect('destroy', destroy) - - def updates_done(self, changes): - """Close the dialog after a short delay""" - if changes: - self.label.set_text("Updates found for '%s'" % self.prog_name) - else: - self.label.set_text("No updates for '%s'" % self.prog_name) - self.progress.set_fraction(1) - self.set_response_sensitive(gtk.RESPONSE_OK, False) diff --git a/zeroinstall/0launch-gui/compile.py b/zeroinstall/0launch-gui/compile.py index e0fbef6..9fb0d64 100644 --- a/zeroinstall/0launch-gui/compile.py +++ b/zeroinstall/0launch-gui/compile.py @@ -5,7 +5,6 @@ from logging import info from zeroinstall.injector import reader, iface_cache, model from zeroinstall.injector.policy import Policy -from gui import policy XMLNS_0COMPILE = 'http://zero-install.sourceforge.net/2006/namespaces/0compile' @@ -43,7 +42,7 @@ class Command: dialog.alert(None, "Command failed:\n%s\n" % self.error) return False -def compile(interface): +def compile(policy, interface): def add_feed(): # A new local feed may have been registered, so update the interface from the cache info("0compile command completed successfully. Reloading interface details.") diff --git a/zeroinstall/0launch-gui/dialog.py b/zeroinstall/0launch-gui/dialog.py index 15f91c2..da5927c 100644 --- a/zeroinstall/0launch-gui/dialog.py +++ b/zeroinstall/0launch-gui/dialog.py @@ -1,4 +1,5 @@ import gtk +from zeroinstall.support import tasks n_windows = 0 @@ -20,6 +21,17 @@ class Dialog(gtk.Dialog): button.show_all() return button +class DialogResponse(tasks.Blocker): + response = None + def __init__(self, dialog): + tasks.Blocker.__init__(self, dialog.get_title()) + a = None + def response(d, resp): + self.response = resp + d.disconnect(a) + self.trigger() + a = dialog.connect('response', response) + def alert(parent, message, type = gtk.MESSAGE_ERROR): if type == gtk.MESSAGE_ERROR: global last_error diff --git a/zeroinstall/0launch-gui/gui.py b/zeroinstall/0launch-gui/gui.py dissimilarity index 60% index 2cdb73a..7abfe01 100644 --- a/zeroinstall/0launch-gui/gui.py +++ b/zeroinstall/0launch-gui/gui.py @@ -1,229 +1,119 @@ -import gtk, os, gobject, sys -import gtk.glade - -from zeroinstall.injector.iface_cache import iface_cache -from zeroinstall.injector.policy import Policy -from zeroinstall.injector import download, handler -from zeroinstall.injector.model import SafeException -from zeroinstall.injector.reader import InvalidInterface -from zeroinstall.support import tasks, pretty_size -import dialog -from checking import CheckingBox - -version = '0.31' - -# Singleton Policy -policy = None - -gladefile = os.path.join(os.path.dirname(__file__), 'zero-install.glade') - -# Wrapped for glade widget tree that throws a sensible exception if the widget isn't found -class Template: - def __init__(self, root): - self.widgets = gtk.glade.XML(gladefile, root) - self.root = root - - def get_widget(self, name = None): - if not name: - name = self.root - widget = self.widgets.get_widget(name) - assert widget, "Widget '%s' not found in glade file '%s'" % (name, gladefile) - return widget - -class GUIHandler(handler.Handler): - dl_callbacks = None # Download -> [ callback ] - pulse = None - policy = None - - def __init__(self, policy): - handler.Handler.__init__(self) - self.policy = policy - - def downloads_changed(self): - if self.monitored_downloads and self.pulse is None: - def pulse(): - if self.policy.checking: - progress = self.policy.checking.progress - else: - progress = self.policy.window.progress - - any_known = False - done = total = self.total_bytes_downloaded # Completed downloads - n_downloads = self.n_completed_downloads - # Now add downloads in progress... - for x in self.monitored_downloads.values(): - if x.status != download.download_fetching: continue - n_downloads += 1 - if x.expected_size: - any_known = True - so_far = x.get_bytes_downloaded_so_far() - total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons - done += so_far - - progress_text = '%s / %s' % (pretty_size(done), pretty_size(total)) - if n_downloads == 1: - progress.set_text('Downloading one file (%s)' % progress_text) - else: - progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text)) - - if total == 0 or (n_downloads < 2 and not any_known): - progress.pulse() - else: - progress.set_fraction(float(done) / total) - - return True - pulse() - self.pulse = gobject.timeout_add(50, pulse) - self.policy.window.progress.show() - elif len(self.monitored_downloads) == 0: - # Reset counters - self.n_completed_downloads = 0 - self.total_bytes_downloaded = 0 - - # Stop animation - if self.pulse: - gobject.source_remove(self.pulse) - self.policy.window.progress.hide() - self.pulse = None - - if self.policy.checking: - self.policy.checking.updates_done(self.policy.versions_changed()) - - def confirm_trust_keys(self, interface, sigs, iface_xml): - if self.policy.checking: - # Switch to main view if there are keys to be confirmed - self.policy.checking.updates_done(True) - self.policy.window.show() - import trust_box - return trust_box.confirm_trust(interface, sigs, iface_xml, parent = self.policy.window.window) - - def report_error(self, ex): - dialog.alert(None, str(ex)) - -class GUIPolicy(Policy): - window = None - checking = None # GtkDialog ("Checking for updates...") - original_implementation = None - download_only = None - widgets = None # Glade - - def __init__(self, interface, download_only, src = False, restrictions = None): - Policy.__init__(self, interface, GUIHandler(self), src = src) - self.solver.record_details = True - global policy - assert policy is None - policy = self - - self.widgets = Template('main') - - if restrictions: - for r in restrictions: - self.root_restrictions.append(r) - - self.download_only = download_only - - import mainwindow - self.window = mainwindow.MainWindow(download_only) - root = iface_cache.get_interface(self.root) - self.window.browser.set_root(root) - - self.watchers.append(self.update_display) - - def show_details(self): - """The checking box has disappeared. Should we show the details window, or - just run the program right now?""" - if self.checking.show_details_clicked.happened: - return True # User clicked on the Details button - if not self.ready: - return True # Not ready to start (can't find an implementation) - if self.versions_changed(): - return True # Confirm that the new version should be used - if self.get_uncached_implementations(): - return True # Need to download something; check first - return False - - def update_display(self): - self.window.set_response_sensitive(gtk.RESPONSE_OK, self.ready) - - def main(self, refresh): - if refresh: - # If we have feeds then treat this as an update check, - # even if we've never seen the main interface before. - # Used the first time the GUI is used, for example. - root = iface_cache.get_interface(self.root) - if root.name is not None or root.feeds: - self.checking = CheckingBox(root) - - solved = self.solve_with_downloads(force = refresh) - - if self.checking: - self.checking.show() - - error = None - blockers = [solved, self.checking.show_details_clicked, self.checking.cancelled] - yield blockers - try: - tasks.check(blockers) - except Exception, ex: - error = ex - - if not (self.checking.show_details_clicked.happened or self.checking.cancelled.happened): - self.checking.updates_done(self.versions_changed()) - blockers = tasks.TimeoutBlocker(0.5, "checking result timeout") - yield blockers - tasks.check(blockers) - self.checking.destroy() - - show_details = self.show_details() or error - self.checking = None - if show_details: - self.window.show() - if error: - dialog.alert(self.window.window, "Failed to check for updates: %s" % ex) - yield [] - else: - from zeroinstall.injector import selections - sels = selections.Selections(policy) - doc = sels.toDOM() - reply = doc.toxml('utf-8') - sys.stdout.write(('Length:%8x\n' % len(reply)) + reply) - self.window.destroy() - sys.exit(0) # Success - else: - self.window.show() - yield solved - try: - tasks.check(solved) - except Exception, ex: - import traceback - traceback.print_exc() - dialog.alert(self.window.window, str(ex)) - yield [] - - def abort_all_downloads(self): - for dl in self.handler.monitored_downloads.values(): - dl.abort() - - def set_original_implementations(self): - assert self.original_implementation is None - self.original_implementation = policy.implementation.copy() - - def versions_changed(self): - """Return whether we have now chosen any different implementations. - If so, we want to show the dialog to the user to confirm the new ones.""" - if not self.ready: - return True - if not self.original_implementation: - return True # Shouldn't happen? - if len(self.original_implementation) != len(self.implementation): - return True - for iface in self.original_implementation: - old = self.original_implementation[iface] - if old is None: - return True - new = self.implementation.get(iface, None) - if new is None: - return True - if old.id != new.id: - return True - return False +import gtk, os, gobject, sys +import gtk.glade + +from zeroinstall.injector.iface_cache import iface_cache +from zeroinstall.injector.policy import Policy +from zeroinstall.injector import download, handler +from zeroinstall.injector.model import SafeException +from zeroinstall.injector.reader import InvalidInterface +from zeroinstall.support import tasks, pretty_size +import dialog + +version = '0.31' + +gladefile = os.path.join(os.path.dirname(__file__), 'zero-install.glade') + +# Wrapped for glade widget tree that throws a sensible exception if the widget isn't found +class Template: + def __init__(self, root): + self.widgets = gtk.glade.XML(gladefile, root) + self.root = root + + def get_widget(self, name = None): + if not name: + name = self.root + widget = self.widgets.get_widget(name) + assert widget, "Widget '%s' not found in glade file '%s'" % (name, gladefile) + return widget + +class GUIHandler(handler.Handler): + dl_callbacks = None # Download -> [ callback ] + pulse = None + policy = None + + def downloads_changed(self): + if self.monitored_downloads and self.pulse is None: + def pulse(): + progress = self.policy.window.progress + + any_known = False + done = total = self.total_bytes_downloaded # Completed downloads + n_downloads = self.n_completed_downloads + # Now add downloads in progress... + for x in self.monitored_downloads.values(): + if x.status != download.download_fetching: continue + n_downloads += 1 + if x.expected_size: + any_known = True + so_far = x.get_bytes_downloaded_so_far() + total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons + done += so_far + + progress_text = '%s / %s' % (pretty_size(done), pretty_size(total)) + if n_downloads == 1: + progress.set_text('Downloading one file (%s)' % progress_text) + else: + progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text)) + + if total == 0 or (n_downloads < 2 and not any_known): + progress.pulse() + else: + progress.set_fraction(float(done) / total) + + return True + pulse() + self.pulse = gobject.timeout_add(50, pulse) + self.policy.window.progress.show() + elif len(self.monitored_downloads) == 0: + # Reset counters + self.n_completed_downloads = 0 + self.total_bytes_downloaded = 0 + + # Stop animation + if self.pulse: + gobject.source_remove(self.pulse) + self.policy.window.progress.hide() + self.pulse = None + + def confirm_trust_keys(self, interface, sigs, iface_xml): + import trust_box + return trust_box.confirm_trust(interface, sigs, iface_xml, parent = self.policy.window.window) + + def report_error(self, ex): + dialog.alert(None, str(ex)) + +class GUI: + policy = None + window = None + + def __init__(self, policy, download_only): + widgets = Template('main') + self.policy = policy + + import mainwindow + self.window = mainwindow.MainWindow(policy, widgets, download_only) + root = iface_cache.get_interface(self.policy.root) + self.window.browser.set_root(root) + + policy.watchers.append(self.update_display) # XXX + self.window.window.connect('destroy', lambda w: self.abort_all_downloads()) + + def update_display(self): + self.window.set_response_sensitive(gtk.RESPONSE_OK, self.policy.solver.ready) + + def main(self, refresh): + solved = self.policy.solve_with_downloads(force = refresh) + + self.window.show() + yield solved + try: + tasks.check(solved) + except Exception, ex: + import traceback + traceback.print_exc() + dialog.alert(self.window.window, str(ex)) + yield [] + + def abort_all_downloads(self): + for dl in self.policy.handler.monitored_downloads.values(): + dl.abort() diff --git a/zeroinstall/0launch-gui/iface_browser.py b/zeroinstall/0launch-gui/iface_browser.py index 7c5e316..3f26e08 100644 --- a/zeroinstall/0launch-gui/iface_browser.py +++ b/zeroinstall/0launch-gui/iface_browser.py @@ -5,7 +5,6 @@ from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import model import properties from treetips import TreeTips -from gui import policy from zeroinstall import support from logging import warn @@ -19,6 +18,11 @@ ICON_SIZE = 20.0 CELL_TEXT_INDENT = int(ICON_SIZE) + 4 class InterfaceTips(TreeTips): + mainwindow = None + + def __init__(self, mainwindow): + self.mainwindow = mainwindow + def get_tooltip_text(self, item): interface, model_column = item assert interface @@ -30,7 +34,7 @@ class InterfaceTips(TreeTips): first_para = interface.description.split('\n\n', 1)[0] return first_para.replace('\n', ' ') - impl = policy.implementation.get(interface, None) + impl = self.mainwindow.policy.implementation.get(interface, None) if not impl: return _("No suitable implementation was found. Check the " "interface properties to find out why.") @@ -38,7 +42,7 @@ class InterfaceTips(TreeTips): if model_column == InterfaceBrowser.VERSION: text = _("Currently preferred version: %s (%s)") % \ (impl.get_version(), _stability(impl)) - old_impl = policy.original_implementation.get(interface, None) + old_impl = self.mainwindow.original_implementation.get(interface, None) if old_impl is not None and old_impl is not impl: text += _('\nPreviously preferred version: %s (%s)') % \ (old_impl.get_version(), _stability(old_impl)) @@ -46,17 +50,15 @@ class InterfaceTips(TreeTips): assert model_column == InterfaceBrowser.DOWNLOAD_SIZE - if policy.get_cached(impl): + if self.mainwindow.policy.get_cached(impl): return _("This version is already stored on your computer.") else: - src = policy.fetcher.get_best_source(impl) + src = self.mainwindow.policy.fetcher.get_best_source(impl) if not src: return _("No downloads available!") return _("Need to download %s (%s bytes)") % \ (support.pretty_size(src.size), src.size) -tips = InterfaceTips() - class IconAndTextRenderer(gtk.GenericCellRenderer): __gproperties__ = { "image": (gobject.TYPE_OBJECT, "Image", "Image", gobject.PARAM_READWRITE), @@ -113,6 +115,8 @@ class InterfaceBrowser: root = None edit_properties = None cached_icon = None + policy = None + original_implementation = None INTERFACE = 0 INTERFACE_NAME = 1 @@ -126,12 +130,17 @@ class InterfaceBrowser: (_('Fetch'), DOWNLOAD_SIZE), (_('Description'), SUMMARY)] - def __init__(self, tree_view): + def __init__(self, policy, widgets): + tips = InterfaceTips(self) + + tree_view = widgets.get_widget('components') + + self.policy = policy self.cached_icon = {} # URI -> GdkPixbuf self.default_icon = tree_view.style.lookup_icon_set(gtk.STOCK_EXECUTE).render_icon(tree_view.style, gtk.TEXT_DIR_NONE, gtk.STATE_NORMAL, gtk.ICON_SIZE_SMALL_TOOLBAR, tree_view, None) - self.edit_properties = policy.widgets.get_widget('properties') + self.edit_properties = widgets.get_widget('properties') self.edit_properties.set_property('sensitive', False) self.model = gtk.TreeStore(object, str, str, str, str, gtk.gdk.Pixbuf) @@ -199,13 +208,13 @@ class InterfaceBrowser: if not pos: return False path, col, x, y = pos - properties.edit(self.model[path][InterfaceBrowser.INTERFACE]) + properties.edit(policy, self.model[path][InterfaceBrowser.INTERFACE]) tree_view.connect('button-press-event', button_press) def edit_selected(action): store, iter = selection.get_selected() assert iter - properties.edit(self.model[iter][InterfaceBrowser.INTERFACE]) + properties.edit(policy, self.model[iter][InterfaceBrowser.INTERFACE]) self.edit_properties.connect('clicked', edit_selected) tree_view.connect('destroy', lambda s: policy.watchers.remove(self.build_tree)) @@ -245,7 +254,7 @@ class InterfaceBrowser: return icon else: # Try to download the icon - fetcher = policy.download_icon(iface) + fetcher = self.policy.download_icon(iface) if fetcher: @tasks.async def update_display(): @@ -256,14 +265,14 @@ class InterfaceBrowser: except Exception, ex: import traceback traceback.print_exc() - policy.handler.report_error(ex) + self.policy.handler.report_error(ex) update_display() return None def build_tree(self): - if policy.original_implementation is None: - policy.set_original_implementations() + if self.original_implementation is None: + self.set_original_implementations() done = {} # Detect cycles @@ -280,15 +289,15 @@ class InterfaceBrowser: self.model[iter][InterfaceBrowser.SUMMARY] = iface.summary self.model[iter][InterfaceBrowser.ICON] = self.get_icon(iface) or self.default_icon - impl = policy.implementation.get(iface, None) + impl = self.policy.implementation.get(iface, None) if impl: - old_impl = policy.original_implementation.get(iface, None) + old_impl = self.original_implementation.get(iface, None) version_str = impl.get_version() if old_impl is not None and old_impl is not impl: version_str += " (was " + old_impl.get_version() + ")" self.model[iter][InterfaceBrowser.VERSION] = version_str - if policy.get_cached(impl): + if self.policy.get_cached(impl): if impl.id.startswith('/'): fetch = '(local)' elif impl.id.startswith('package:'): @@ -296,7 +305,7 @@ class InterfaceBrowser: else: fetch = '(cached)' else: - src = policy.fetcher.get_best_source(impl) + src = self.policy.fetcher.get_best_source(impl) if src: fetch = support.pretty_size(src.size) else: @@ -324,7 +333,7 @@ class InterfaceBrowser: def show_popup_menu(self, iface, bev): import bugs - if properties.have_source_for(iface): + if properties.have_source_for(self.policy, iface): def compile_cb(): import compile compile.compile(iface) @@ -334,7 +343,7 @@ class InterfaceBrowser: menu = gtk.Menu() for label, cb in [(_('Show Feeds'), lambda: properties.edit(iface)), (_('Show Versions'), lambda: properties.edit(iface, show_versions = True)), - (_('Report a Bug...'), lambda: bugs.report_bug(policy, iface)), + (_('Report a Bug...'), lambda: bugs.report_bug(self.policy, iface)), (_('Compile...'), compile_cb)]: item = gtk.MenuItem(label) if cb: @@ -344,3 +353,7 @@ class InterfaceBrowser: item.show() menu.append(item) menu.popup(None, None, None, bev.button, bev.time) + + def set_original_implementations(self): + assert self.original_implementation is None + self.original_implementation = self.policy.implementation.copy() diff --git a/zeroinstall/0launch-gui/impl_list.py b/zeroinstall/0launch-gui/impl_list.py index a24758e..2602660 100644 --- a/zeroinstall/0launch-gui/impl_list.py +++ b/zeroinstall/0launch-gui/impl_list.py @@ -1,6 +1,5 @@ import gtk, gobject, os from zeroinstall.injector import model, writer -from gui import policy from zeroinstall import support from treetips import TreeTips @@ -29,7 +28,8 @@ RELEASED = 6 NOTES = 7 class ImplTips(TreeTips): - def __init__(self, interface): + def __init__(self, policy, interface): + self.policy = policy self.interface = interface def get_tooltip_text(self, impl): @@ -37,10 +37,10 @@ class ImplTips(TreeTips): return _("Local: %s") % impl.id if impl.id.startswith('package:'): return _("Native package: %s") % impl.id.split(':', 1)[1] - if policy.get_cached(impl): - return _("Cached: %s") % policy.get_implementation_path(impl) + if self.policy.get_cached(impl): + return _("Cached: %s") % self.policy.get_implementation_path(impl) - src = policy.fetcher.get_best_source(impl) + src = self.policy.fetcher.get_best_source(impl) if src: size = support.pretty_size(src.size) return _("Not yet downloaded (%s)") % size @@ -51,9 +51,11 @@ class ImplementationList: tree_view = None model = None interface = None + policy = None - def __init__(self, interface, widgets): + def __init__(self, policy, interface, widgets): self.interface = interface + self.policy = policy self.model = gtk.ListStore(object, str, str, str, gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN, @@ -76,7 +78,7 @@ class ImplementationList: gtk.TreeViewColumn('Notes', text, text = NOTES)): self.tree_view.append_column(column) - tips = ImplTips(interface) + tips = ImplTips(policy, interface) def motion(tree_view, ev): if ev.window is not tree_view.get_bin_window(): @@ -113,14 +115,14 @@ class ImplementationList: else: impl.user_stability = None writer.save_interface(interface) - policy.recalculate() + self.policy.recalculate() popup_menu(bev, ['Unset (%s)' % upstream, None] + choices, set) - elif bev.button == 3 and policy.get_cached(impl): + elif bev.button == 3 and self.policy.get_cached(impl): def open(item): os.spawnlp(os.P_WAIT, '0launch', '0launch', rox_filer, '-d', - policy.get_implementation_path(impl)) + self.policy.get_implementation_path(impl)) popup_menu(bev, ['Open cached copy'], open) self.tree_view.connect('button-press-event', button_press) @@ -134,7 +136,7 @@ class ImplementationList: self.model[new][ITEM] = item self.model[new][VERSION] = item.get_version() self.model[new][RELEASED] = item.released or "-" - self.model[new][CACHED] = policy.get_cached(item) + self.model[new][CACHED] = self.policy.get_cached(item) if item.user_stability: self.model[new][STABILITY] = str(item.user_stability).upper() else: diff --git a/zeroinstall/0launch-gui/mainwindow.py b/zeroinstall/0launch-gui/mainwindow.py index be4d8d9..5fe56d9 100644 --- a/zeroinstall/0launch-gui/mainwindow.py +++ b/zeroinstall/0launch-gui/mainwindow.py @@ -5,7 +5,6 @@ from zeroinstall import SafeException from zeroinstall.support import tasks from iface_browser import InterfaceBrowser import help_box -from gui import policy import dialog tips = gtk.Tooltips() @@ -18,16 +17,12 @@ class MainWindow: window = None cancel_download_and_run = None - def __init__(self, download_only): - widgets = policy.widgets - + def __init__(self, policy, widgets, download_only): self.window = widgets.get_widget('main') self.window.set_default_size(gtk.gdk.screen_width() * 2 / 5, 300) self.progress = widgets.get_widget('progress') - self.window.connect('destroy', lambda w: self.destroyed()) - cache = widgets.get_widget('show_cache') cache.connect('clicked', lambda b: os.spawnlp(os.P_WAIT, sys.argv[0], sys.argv[0], '-c')) @@ -35,7 +30,7 @@ class MainWindow: widgets.get_widget('refresh').connect('clicked', lambda b: policy.refresh_all()) # Tree view - self.browser = InterfaceBrowser(widgets.get_widget('components')) + self.browser = InterfaceBrowser(policy, widgets) prefs = widgets.get_widget('preferences') self.window.action_area.set_child_secondary(prefs, True) @@ -66,7 +61,7 @@ class MainWindow: gui_help.display() elif resp == SHOW_PREFERENCES: import preferences - preferences.show_preferences() + preferences.show_preferences(policy) self.window.connect('response', response) def destroy(self): @@ -78,9 +73,6 @@ class MainWindow: def set_response_sensitive(self, response, sensitive): self.window.set_response_sensitive(response, sensitive) - def destroyed(self): - policy.abort_all_downloads() - @tasks.async def download_and_run(self, run_button, cancelled): try: @@ -96,7 +88,7 @@ class MainWindow: policy.abort_all_downloads() return - if policy.get_uncached_implementations(): + if self.policy.get_uncached_implementations(): dialog.alert('Not all downloads succeeded; cannot run program.') else: from zeroinstall.injector import selections diff --git a/zeroinstall/0launch-gui/preferences.py b/zeroinstall/0launch-gui/preferences.py index 63439b8..c2f2bc9 100644 --- a/zeroinstall/0launch-gui/preferences.py +++ b/zeroinstall/0launch-gui/preferences.py @@ -2,7 +2,7 @@ import gtk from logging import warn import os, sys import help_box -from gui import policy, Template +from gui import Template from dialog import Dialog, MixedButton, frame from zeroinstall.injector.model import network_levels from zeroinstall.injector import trust, gpg @@ -14,7 +14,7 @@ tips = gtk.Tooltips() SHOW_CACHE = 0 class Preferences: - def __init__(self): + def __init__(self, policy): widgets = Template('preferences_box') self.window = widgets.get_widget('preferences_box') @@ -150,13 +150,12 @@ class KeyList: tv.connect('button-press-event', trusted_keys_button_press) preferences_box = None -def show_preferences(): +def show_preferences(policy): global preferences_box - if preferences_box is not None: - preferences_box.window.present() - else: - preferences_box = Preferences() - preferences_box.window.show() + if preferences_box: + preferences_box.destroy() + preferences_box = Preferences(policy) + preferences_box.window.show() gui_help = help_box.HelpBox("Zero Install Preferences Help", ('Overview', """ diff --git a/zeroinstall/0launch-gui/properties.py b/zeroinstall/0launch-gui/properties.py index c96322b..2a170b6 100644 --- a/zeroinstall/0launch-gui/properties.py +++ b/zeroinstall/0launch-gui/properties.py @@ -1,15 +1,15 @@ import zeroinstall +from zeroinstall.support import tasks from zeroinstall.injector.model import * from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import writer, namespaces, gpg import gtk, sys, os -import sets # Note: for Python 2.3; frozenset is only in Python 2.4 from logging import warn import help_box -from dialog import Dialog -from gui import policy, Template +from dialog import DialogResponse +from gui import Template from impl_list import ImplementationList import time import dialog @@ -45,7 +45,7 @@ def open_in_browser(link): os._exit(1) os.waitpid(child, 0) -def have_source_for(interface): +def have_source_for(policy, interface): # Note: we don't want to actually fetch the source interfaces at # this point, so we check whether: # - We have a feed of type 'src' (not fetched), or @@ -167,7 +167,8 @@ class Feeds: ARCH = 1 USED = 2 - def __init__(self, interface, widgets): + def __init__(self, policy, interface, widgets): + self.policy = policy self.interface = interface self.model = gtk.ListStore(str, str, bool) @@ -179,10 +180,10 @@ class Feeds: self.model.append(line) add_remote_feed_button = widgets.get_widget('add_remote_feed') - add_remote_feed_button.connect('clicked', lambda b: add_remote_feed(widgets.get_widget(), interface)) + add_remote_feed_button.connect('clicked', lambda b: add_remote_feed(policy, widgets.get_widget(), interface)) add_local_feed_button = widgets.get_widget('add_local_feed') - add_local_feed_button.connect('clicked', lambda b: add_local_feed(interface)) + add_local_feed_button.connect('clicked', lambda b: add_local_feed(policy, interface)) self.remove_feed_button = widgets.get_widget('remove_feed') def remove_feed(button): @@ -214,8 +215,8 @@ class Feeds: sel.select_path((0,)) def build_model(self): - usable_feeds = sets.ImmutableSet(policy.usable_feeds(self.interface)) - unusable_feeds = sets.ImmutableSet(self.interface.feeds) - usable_feeds + usable_feeds = frozenset(self.policy.usable_feeds(self.interface)) + unusable_feeds = frozenset(self.interface.feeds) - usable_feeds out = [[self.interface.uri, None, True]] @@ -254,8 +255,11 @@ class Properties: interface = None use_list = None window = None + policy = None + + def __init__(self, policy, interface, show_versions = False): + self.policy = policy - def __init__(self, interface, show_versions = False): widgets = Template('interface_properties') self.interface = interface @@ -266,7 +270,7 @@ class Properties: window.set_default_size(-1, gtk.gdk.screen_height() / 3) self.compile_button = widgets.get_widget('compile') - self.compile_button.connect('clicked', lambda b: compile.compile(interface)) + self.compile_button.connect('clicked', lambda b: compile.compile(policy, interface)) window.set_default_response(gtk.RESPONSE_CANCEL) def response(dialog, resp): @@ -279,7 +283,7 @@ class Properties: notebook = widgets.get_widget('interface_notebook') assert notebook - feeds = Feeds(interface, widgets) + feeds = Feeds(policy, interface, widgets) stability = widgets.get_widget('preferred_stability') stability.set_active(0) @@ -304,7 +308,7 @@ class Properties: policy.recalculate() stability.connect('changed', set_stability_policy) - self.use_list = ImplementationList(interface, widgets) + self.use_list = ImplementationList(policy, interface, widgets) self.update_list() @@ -325,82 +329,96 @@ class Properties: self.window.destroy() def shade_compile(self): - self.compile_button.set_sensitive(have_source_for(self.interface)) + self.compile_button.set_sensitive(have_source_for(self.policy, self.interface)) def update_list(self): - ranked_items = policy.solver.details.get(self.interface, None) + ranked_items = self.policy.solver.details.get(self.interface, None) if ranked_items is None: # The Solver didn't get this far, but we should still display them! ranked_items = self.interface.implementations.values() ranked_items.sort() self.use_list.set_items(ranked_items) - -def add_remote_feed(parent, interface): - d = gtk.MessageDialog(parent, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, - _('Enter the URL of the new source of implementations of this interface:')) - d.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK) - d.set_default_response(gtk.RESPONSE_OK) - entry = gtk.Entry() - - align = gtk.VBox(False, 0) - align.set_border_width(4) - align.add(entry) - d.vbox.pack_start(align) - entry.set_activates_default(True) - - entry.set_text('') - - d.vbox.show_all() - - error_label = gtk.Label('') - error_label.set_padding(4, 4) - align.pack_start(error_label) - - def error(message): - if message: - error_label.set_text(message) - error_label.show() - else: - error_label.hide() - - def download_done(iface): - d.set_sensitive(True) - if not iface.name: - error('Failed to read interface') - return - if not iface.feed_for: - error("Interface '%s' is not a feed." % iface.get_name()) - elif interface.uri not in iface.feed_for: - error("Interface is not a feed for '%s'.\nOnly for:\n%s" % - (interface.uri, '\n'.join(iface.feed_for))) - elif iface.uri in [f.uri for f in interface.feeds]: - error("Feed from '%s' has already been added!" % iface.uri) - else: - interface.extra_feeds.append(Feed(iface.uri, arch = None, user_override = True)) - writer.save_interface(interface) - d.destroy() - policy.recalculate() - def response(d, resp): - error(None) - if resp == gtk.RESPONSE_OK: - try: - url = entry.get_text() - if not url: - raise SafeException(_('Enter a URL')) - iface = iface_cache.get_interface(url) - policy.begin_iface_download(iface) # Force a refresh - d.set_sensitive(False) - policy.handler.add_dl_callback(url, lambda: download_done(iface)) - except SafeException, ex: - error(str(ex)) - else: - d.destroy() - return - d.connect('response', response) - d.show() +@tasks.async +def add_remote_feed(policy, parent, interface): + try: + d = gtk.MessageDialog(parent, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, + _('Enter the URL of the new source of implementations of this interface:')) + d.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK) + d.set_default_response(gtk.RESPONSE_OK) + entry = gtk.Entry() + + align = gtk.VBox(False, 0) + align.set_border_width(4) + align.add(entry) + d.vbox.pack_start(align) + entry.set_activates_default(True) -def add_local_feed(interface): + entry.set_text('') + + d.vbox.show_all() + + error_label = gtk.Label('') + error_label.set_padding(4, 4) + align.pack_start(error_label) + + d.show() + + def error(message): + if message: + error_label.set_text(message) + error_label.show() + else: + error_label.hide() + + while True: + got_response = DialogResponse(d) + yield got_response + tasks.check(got_response) + resp = got_response.response + + error(None) + if resp == gtk.RESPONSE_OK: + try: + url = entry.get_text() + if not url: + raise SafeException(_('Enter a URL')) + fetch = policy.fetcher.download_and_import_feed(url, iface_cache) + if fetch: + d.set_sensitive(False) + yield fetch + d.set_sensitive(True) + tasks.check(fetch) + + iface = iface_cache.get_interface(url) + + d.set_sensitive(True) + if not iface.name: + error('Failed to read interface') + return + if not iface.feed_for: + error("Feed '%s' is not a feed for '%s'." % (iface.get_name(), interface.get_name())) + elif interface.uri not in iface.feed_for: + error("This is not a feed for '%s'.\nOnly for:\n%s" % + (interface.uri, '\n'.join(iface.feed_for))) + elif iface.uri in [f.uri for f in interface.feeds]: + error("Feed from '%s' has already been added!" % iface.uri) + else: + interface.extra_feeds.append(Feed(iface.uri, arch = None, user_override = True)) + writer.save_interface(interface) + d.destroy() + policy.recalculate() + except SafeException, ex: + error(str(ex)) + else: + d.destroy() + return + except Exception, ex: + import traceback + traceback.print_exc() + policy.handler.report_error(ex) + +def add_local_feed(policy, interface): sel = gtk.FileSelection(_('Select XML feed file')) sel.set_has_separator(False) def ok(b): @@ -429,11 +447,11 @@ def add_local_feed(interface): sel.cancel_button.connect('clicked', lambda b: sel.destroy()) sel.show() -def edit(interface, show_versions = False): +def edit(policy, interface, show_versions = False): assert isinstance(interface, Interface) if interface in _dialogs: _dialogs[interface].destroy() - _dialogs[interface] = Properties(interface, show_versions) + _dialogs[interface] = Properties(policy, interface, show_versions) properties_help = help_box.HelpBox("Injector Properties Help", ('Interface properties', """ -- 2.11.4.GIT