示例#1
0
	def __init__(self, policy, widgets, download_only, select_only = False):
		self.policy = policy
		self.select_only = select_only

		def update_ok_state():
			self.window.set_response_sensitive(gtk.RESPONSE_OK, policy.solver.ready)
			if policy.solver.ready and self.window.get_focus() is None:
				run_button.grab_focus()
		policy.watchers.append(update_ok_state)

		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.progress_area = widgets.get_widget('progress_area')
		self.comment = widgets.get_widget('comment')

		widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())

		self.refresh_button = widgets.get_widget('refresh')

		# Tree view
		self.browser = InterfaceBrowser(policy, widgets)

		prefs = widgets.get_widget('preferences')
		self.window.action_area.set_child_secondary(prefs, True)

		# Glade won't let me add this to the template!
		if select_only:
			run_button = dialog.MixedButton(_("_Select"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		elif download_only:
			run_button = dialog.MixedButton(_("_Download"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		else:
			run_button = dialog.MixedButton(_("_Run"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
		run_button.show_all()
		run_button.set_flags(gtk.CAN_DEFAULT)
		self.run_button = run_button

		run_button.grab_focus()

		def response(dialog, resp):
			if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
				self.window.destroy()
				sys.exit(1)
			elif resp == gtk.RESPONSE_OK:
				if self.cancel_download_and_run:
					self.cancel_download_and_run.trigger()
				if run_button.get_active():
					self.cancel_download_and_run = tasks.Blocker("cancel downloads")
					self.download_and_run(run_button, self.cancel_download_and_run)
			elif resp == gtk.RESPONSE_HELP:
				gui_help.display()
			elif resp == SHOW_PREFERENCES:
				import preferences
				preferences.show_preferences(policy.config, notify_cb = lambda: policy.solve_with_downloads())
		self.window.connect('response', response)
		self.window.realize()	# Make busy pointer work, even with --systray
class MainWindow:
    progress = None
    progress_area = None
    browser = None
    window = None
    cancel_download_and_run = None
    policy = None
    comment = None
    systray_icon = None
    systray_icon_blocker = None

    def __init__(self, policy, widgets, download_only, select_only=False):
        self.policy = policy
        self.select_only = select_only

        def update_ok_state():
            self.window.set_response_sensitive(gtk.RESPONSE_OK,
                                               policy.solver.ready)
            if policy.solver.ready and self.window.get_focus() is None:
                run_button.grab_focus()

        policy.watchers.append(update_ok_state)

        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.progress_area = widgets.get_widget('progress_area')
        self.comment = widgets.get_widget('comment')

        widgets.get_widget('stop').connect(
            'clicked', lambda b: policy.handler.abort_all_downloads())

        self.refresh_button = widgets.get_widget('refresh')

        # Tree view
        self.browser = InterfaceBrowser(policy, widgets)

        prefs = widgets.get_widget('preferences')
        self.window.action_area.set_child_secondary(prefs, True)

        # Glade won't let me add this to the template!
        if select_only:
            run_button = dialog.MixedButton(_("_Select"),
                                            gtk.STOCK_EXECUTE,
                                            button=gtk.ToggleButton())
        elif download_only:
            run_button = dialog.MixedButton(_("_Download"),
                                            gtk.STOCK_EXECUTE,
                                            button=gtk.ToggleButton())
        else:
            run_button = dialog.MixedButton(_("_Run"),
                                            gtk.STOCK_EXECUTE,
                                            button=gtk.ToggleButton())
        self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
        run_button.show_all()
        run_button.set_flags(gtk.CAN_DEFAULT)
        self.run_button = run_button

        run_button.grab_focus()

        def response(dialog, resp):
            if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
                self.window.destroy()
                sys.exit(1)
            elif resp == gtk.RESPONSE_OK:
                if self.cancel_download_and_run:
                    self.cancel_download_and_run.trigger()
                if run_button.get_active():
                    self.cancel_download_and_run = tasks.Blocker(
                        "cancel downloads")
                    self.download_and_run(run_button,
                                          self.cancel_download_and_run)
            elif resp == gtk.RESPONSE_HELP:
                gui_help.display()
            elif resp == SHOW_PREFERENCES:
                import preferences
                preferences.show_preferences(
                    policy.config,
                    notify_cb=lambda: policy.solve_with_downloads())

        self.window.connect('response', response)
        self.window.realize()  # Make busy pointer work, even with --systray

    def destroy(self):
        self.window.destroy()

    def show(self):
        self.window.show()

    def set_response_sensitive(self, response, sensitive):
        self.window.set_response_sensitive(response, sensitive)

    @tasks. async
    def download_and_run(self, run_button, cancelled):
        try:
            if not self.select_only:
                downloaded = self.policy.download_uncached_implementations()

                if downloaded:
                    # We need to wait until everything is downloaded...
                    blockers = [downloaded, cancelled]
                    yield blockers
                    tasks.check(blockers)

                    if cancelled.happened:
                        return

                uncached = self.policy.get_uncached_implementations()
            else:
                uncached = None  # (we don't care)

            if uncached:
                missing = '\n- '.join([
                    _('%(iface_name)s %(impl_version)s') % {
                        'iface_name': iface.get_name(),
                        'impl_version': impl.get_version()
                    } for iface, impl in uncached
                ])
                dialog.alert(
                    self.window,
                    _('Not all downloads succeeded; cannot run program.\n\nFailed to get:'
                      ) + '\n- ' + missing)
            else:
                sels = self.policy.solver.selections
                doc = sels.toDOM()
                reply = doc.toxml('utf-8')
                sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
                self.window.destroy()
                sys.exit(0)  # Success
        except SystemExit:
            raise
        except download.DownloadAborted as ex:
            run_button.set_active(False)
            # Don't bother reporting this to the user
        except Exception as ex:
            run_button.set_active(False)
            self.report_exception(ex)

    def update_download_status(self, only_update_visible=False):
        """Called at regular intervals while there are downloads in progress,
		and once at the end. Update the display."""
        monitored_downloads = self.policy.handler.monitored_downloads

        self.browser.update_download_status(only_update_visible)

        if not monitored_downloads:
            self.progress_area.hide()
            self.window.window.set_cursor(None)
            return

        if not self.progress_area.get_property('visible'):
            self.progress_area.show()
            self.window.window.set_cursor(gtkutils.get_busy_pointer())

        any_known = False
        done = total = self.policy.handler.total_bytes_downloaded  # Completed downloads
        n_downloads = self.policy.handler.n_completed_downloads
        # Now add downloads in progress...
        for x in monitored_downloads:
            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))
        self.progress.set_text(
            ngettext('Downloading one file (%(progress)s)',
                     'Downloading %(number)d files (%(progress)s)',
                     n_downloads) % {
                         'progress': progress_text,
                         'number': n_downloads
                     })

        if total == 0 or (n_downloads < 2 and not any_known):
            self.progress.pulse()
        else:
            self.progress.set_fraction(float(done) / total)

    def set_message(self, message):
        import pango
        self.comment.set_text(message)
        attrs = pango.AttrList()
        attrs.insert(
            pango.AttrWeight(pango.WEIGHT_BOLD, end_index=len(message)))
        self.comment.set_attributes(attrs)
        self.comment.show()

    def use_systray_icon(self):
        try:
            self.systray_icon = gtk.status_icon_new_from_icon_name(
                "zeroinstall")
        except Exception as ex:
            info(_("No system tray support: %s"), ex)
        else:
            root_iface = iface_cache.iface_cache.get_interface(
                self.policy.root)
            self.systray_icon.set_tooltip(
                _('Checking for updates for %s') % root_iface.get_name())
            self.systray_icon.connect('activate', self.remove_systray_icon)
            self.systray_icon_blocker = tasks.Blocker('Tray icon clicked')

    def remove_systray_icon(self, i=None):
        assert self.systray_icon, i
        self.show()
        self.systray_icon.set_visible(False)
        self.systray_icon = None
        self.systray_icon_blocker.trigger()
        self.systray_icon_blocker = None

    def report_exception(self, ex, tb=None):
        if not isinstance(ex, SafeException):
            import traceback
            traceback.print_exception(ex, None, tb)
        if self.systray_icon:
            self.systray_icon.set_blinking(True)
            self.systray_icon.set_tooltip(
                str(ex) + '\n' + _('(click for details)'))
        else:
            dialog.alert(self.window, str(ex) or repr(ex))
示例#3
0
class MainWindow(Dialog):
    progress = None
    browser = None

    def __init__(self, prog_args):
        Dialog.__init__(self)
        self.set_title('Dependency Injector')
        self.set_default_size(400, 300)

        tips = gtk.Tooltips()

        # Network use
        hbox = gtk.HBox(False, 2)
        self.vbox.pack_start(hbox, False, True, 0)
        hbox.set_border_width(4)

        network = gtk.combo_box_new_text()
        for level in network_levels:
            network.append_text(level.capitalize())
        network.set_active(list(network_levels).index(policy.network_use))
        hbox.pack_start(gtk.Label('Network use:'), False, True, 0)
        hbox.pack_start(network, True, True, 2)

        def set_network_use(combo):
            policy.network_use = network_levels[network.get_active()]
            policy.save_config()
            policy.recalculate()

        network.connect('changed', set_network_use)

        hbox.show_all()

        # Freshness
        hbox = gtk.HBox(False, 2)
        self.vbox.pack_start(hbox, False, True, 0)
        hbox.set_border_width(4)

        times = [x.time for x in freshness_levels]
        if policy.freshness not in times:
            freshness_levels.append(
                Freshness(policy.freshness, '%d seconds' % policy.freshness))
            times.append(policy.freshness)
        freshness = gtk.combo_box_new_text()
        for level in freshness_levels:
            freshness.append_text(str(level))
        freshness.set_active(times.index(policy.freshness))
        hbox.pack_start(gtk.Label('Freshness:'), False, True, 0)
        hbox.pack_start(freshness, True, True, 2)

        def set_freshness(combo):
            policy.freshness = freshness_levels[freshness.get_active()].time
            policy.save_config()
            policy.recalculate()

        freshness.connect('changed', set_freshness)

        button = gtk.Button('Refresh all now')

        def refresh_all(b):
            for x in policy.walk_interfaces():
                policy.begin_iface_download(x, True)

        button.connect('clicked', refresh_all)
        hbox.pack_start(button, False, True, 2)

        hbox.show_all()

        # Tree view
        self.browser = InterfaceBrowser()
        self.vbox.pack_start(self.browser, True, True, 0)
        self.browser.show()

        # Select versions
        hbox = gtk.HBox(False, 2)
        self.vbox.pack_start(hbox, False, True, 0)
        hbox.set_border_width(4)

        button = gtk.Button()
        self.browser.edit_properties.connect_proxy(button)
        hbox.pack_start(button, False, True, 0)

        stable_toggle = gtk.CheckButton('Help test new versions')
        hbox.pack_start(stable_toggle, False, True, 0)
        tips.set_tip(
            stable_toggle,
            "Try out new versions as soon as they are available, instead of "
            "waiting for them to be marked as 'stable'. "
            "This sets the default policy. Click on 'Interface Properties...' "
            "to set the policy for an individual interface.")
        stable_toggle.set_active(policy.help_with_testing)

        def toggle_stability(toggle):
            policy.help_with_testing = toggle.get_active()
            policy.save_config()
            policy.recalculate()

        stable_toggle.connect('toggled', toggle_stability)

        hbox.show_all()

        # Progress bar
        self.progress = gtk.ProgressBar()
        self.vbox.pack_start(self.progress, False, True, 0)

        # Responses

        self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
        self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.add_button(gtk.STOCK_EXECUTE, gtk.RESPONSE_OK)
        self.set_default_response(gtk.RESPONSE_OK)

        def response(dialog, resp):
            if resp == gtk.RESPONSE_CANCEL:
                self.destroy()
            elif resp == gtk.RESPONSE_OK:
                import download_box
                download_box.download_and_run(self, prog_args)
            elif resp == gtk.RESPONSE_HELP:
                gui_help.display()

        self.connect('response', response)
示例#4
0
class MainWindow:
	progress = None
	progress_area = None
	browser = None
	window = None
	cancel_download_and_run = None
	policy = None
	comment = None
	systray_icon = None
	systray_icon_blocker = None

	def __init__(self, policy, widgets, download_only, select_only = False):
		self.policy = policy
		self.select_only = select_only

		def update_ok_state():
			self.window.set_response_sensitive(gtk.RESPONSE_OK, policy.solver.ready)
			if policy.solver.ready and self.window.get_focus() is None:
				run_button.grab_focus()
		policy.watchers.append(update_ok_state)

		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.progress_area = widgets.get_widget('progress_area')
		self.comment = widgets.get_widget('comment')

		widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())

		self.refresh_button = widgets.get_widget('refresh')

		# Tree view
		self.browser = InterfaceBrowser(policy, widgets)

		prefs = widgets.get_widget('preferences')
		self.window.action_area.set_child_secondary(prefs, True)

		# Glade won't let me add this to the template!
		if select_only:
			run_button = dialog.MixedButton(_("_Select"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		elif download_only:
			run_button = dialog.MixedButton(_("_Download"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		else:
			run_button = dialog.MixedButton(_("_Run"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
		self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
		run_button.show_all()
		run_button.set_flags(gtk.CAN_DEFAULT)
		self.run_button = run_button

		run_button.grab_focus()

		def response(dialog, resp):
			if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
				self.window.destroy()
				sys.exit(1)
			elif resp == gtk.RESPONSE_OK:
				if self.cancel_download_and_run:
					self.cancel_download_and_run.trigger()
				if run_button.get_active():
					self.cancel_download_and_run = tasks.Blocker("cancel downloads")
					self.download_and_run(run_button, self.cancel_download_and_run)
			elif resp == gtk.RESPONSE_HELP:
				gui_help.display()
			elif resp == SHOW_PREFERENCES:
				import preferences
				preferences.show_preferences(policy.config, notify_cb = lambda: policy.solve_with_downloads())
		self.window.connect('response', response)
		self.window.realize()	# Make busy pointer work, even with --systray

	def destroy(self):
		self.window.destroy()

	def show(self):
		self.window.show()

	def set_response_sensitive(self, response, sensitive):
		self.window.set_response_sensitive(response, sensitive)

	@tasks.async
	def download_and_run(self, run_button, cancelled):
		try:
			if not self.select_only:
				downloaded = self.policy.download_uncached_implementations()

				if downloaded:
					# We need to wait until everything is downloaded...
					blockers = [downloaded, cancelled]
					yield blockers
					tasks.check(blockers)

					if cancelled.happened:
						return

				uncached = self.policy.get_uncached_implementations()
			else:
				uncached = None		# (we don't care)

			if uncached:
				missing = '\n- '.join([_('%(iface_name)s %(impl_version)s') % {'iface_name': iface.get_name(), 'impl_version': impl.get_version()} for iface, impl in uncached])
				dialog.alert(self.window, _('Not all downloads succeeded; cannot run program.\n\nFailed to get:') + '\n- ' + missing)
			else:
				from zeroinstall.injector import selections
				sels = selections.Selections(self.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
		except SystemExit:
			raise
		except download.DownloadAborted as ex:
			run_button.set_active(False)
			# Don't bother reporting this to the user
		except Exception as ex:
			run_button.set_active(False)
			self.report_exception(ex)

	def update_download_status(self):
		"""Called at regular intervals while there are downloads in progress,
		and once at the end. Update the display."""
		monitored_downloads = self.policy.handler.monitored_downloads

		self.browser.update_download_status()

		if not monitored_downloads:
			self.progress_area.hide()
			self.window.window.set_cursor(None)
			return

		if not self.progress_area.get_property('visible'):
			self.progress_area.show()
			self.window.window.set_cursor(gtkutils.get_busy_pointer())

		any_known = False
		done = total = self.policy.handler.total_bytes_downloaded	# Completed downloads
		n_downloads = self.policy.handler.n_completed_downloads
		# Now add downloads in progress...
		for x in 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))
		self.progress.set_text(
			ngettext('Downloading one file (%(progress)s)',
					 'Downloading %(number)d files (%(progress)s)', n_downloads)
			% {'progress': progress_text, 'number': n_downloads})

		if total == 0 or (n_downloads < 2 and not any_known):
			self.progress.pulse()
		else:
			self.progress.set_fraction(float(done) / total)

	def set_message(self, message):
		import pango
		self.comment.set_text(message)
		attrs = pango.AttrList()
		attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, end_index = len(message)))
		self.comment.set_attributes(attrs)
		self.comment.show()

	def use_systray_icon(self):
		try:
			self.systray_icon = gtk.status_icon_new_from_icon_name("zeroinstall")
		except Exception as ex:
			info(_("No system tray support: %s"), ex)
		else:
			root_iface = iface_cache.iface_cache.get_interface(self.policy.root)
			self.systray_icon.set_tooltip(_('Checking for updates for %s') % root_iface.get_name())
			self.systray_icon.connect('activate', self.remove_systray_icon)
			self.systray_icon_blocker = tasks.Blocker('Tray icon clicked')

	def remove_systray_icon(self, i = None):
		assert self.systray_icon, i
		self.show()
		self.systray_icon.set_visible(False)
		self.systray_icon = None
		self.systray_icon_blocker.trigger()
		self.systray_icon_blocker = None

	def report_exception(self, ex, tb = None):
		if not isinstance(ex, SafeException):
			import traceback
			traceback.print_exception(ex, None, tb)
		if self.systray_icon:
			self.systray_icon.set_blinking(True)
			self.systray_icon.set_tooltip(str(ex) + '\n' + _('(click for details)'))
		else:
			dialog.alert(self.window, str(ex))
示例#5
0
class MainWindow:
    progress = None
    progress_area = None
    browser = None
    window = None
    cancel_download_and_run = None
    driver = None
    comment = None
    systray_icon = None
    systray_icon_blocker = None

    def __init__(self, driver, widgets, download_only, select_only=False):
        self.driver = driver
        self.select_only = select_only

        def update_ok_state():
            self.window.set_response_sensitive(gtk.RESPONSE_OK, driver.solver.ready)
            if driver.solver.ready and self.window.get_focus() is None:
                run_button.grab_focus()

        driver.watchers.append(update_ok_state)

        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.progress_area = widgets.get_widget("progress_area")
        self.comment = widgets.get_widget("comment")

        widgets.get_widget("stop").connect("clicked", lambda b: driver.config.handler.abort_all_downloads())

        self.refresh_button = widgets.get_widget("refresh")

        # Tree view
        self.browser = InterfaceBrowser(driver, widgets)

        prefs = widgets.get_widget("preferences")
        self.window.get_action_area().set_child_secondary(prefs, True)

        # Glade won't let me add this to the template!
        if select_only:
            run_button = dialog.MixedButton(_("_Select"), gtk.STOCK_EXECUTE, button=gtk.ToggleButton())
        elif download_only:
            run_button = dialog.MixedButton(_("_Download"), gtk.STOCK_EXECUTE, button=gtk.ToggleButton())
        else:
            run_button = dialog.MixedButton(_("_Run"), gtk.STOCK_EXECUTE, button=gtk.ToggleButton())
        self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
        run_button.show_all()
        run_button.set_can_default(True)
        self.run_button = run_button

        run_button.grab_focus()

        def response(dialog, resp):
            if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
                self.window.destroy()
                sys.exit(1)
            elif resp == gtk.RESPONSE_OK:
                if self.cancel_download_and_run:
                    self.cancel_download_and_run.trigger()
                if run_button.get_active():
                    self.cancel_download_and_run = tasks.Blocker("cancel downloads")
                    self.download_and_run(run_button, self.cancel_download_and_run)
            elif resp == gtk.RESPONSE_HELP:
                gui_help.display()
            elif resp == SHOW_PREFERENCES:
                import preferences

                preferences.show_preferences(driver.config, notify_cb=lambda: driver.solve_with_downloads())

        self.window.connect("response", response)
        self.window.realize()  # Make busy pointer work, even with --systray

    def destroy(self):
        self.window.destroy()

    def show(self):
        self.window.show()

    def set_response_sensitive(self, response, sensitive):
        self.window.set_response_sensitive(response, sensitive)

    @tasks.async
    def download_and_run(self, run_button, cancelled):
        try:
            if not self.select_only:
                downloaded = self.driver.download_uncached_implementations()

                if downloaded:
                    # We need to wait until everything is downloaded...
                    blockers = [downloaded, cancelled]
                    yield blockers
                    tasks.check(blockers)

                    if cancelled.happened:
                        return

                uncached = self.driver.get_uncached_implementations()
            else:
                uncached = None  # (we don't care)

            if uncached:
                missing = "\n- ".join(
                    [
                        _("%(iface_name)s %(impl_version)s")
                        % {"iface_name": iface.get_name(), "impl_version": impl.get_version()}
                        for iface, impl in uncached
                    ]
                )
                dialog.alert(
                    self.window,
                    _("Not all downloads succeeded; cannot run program.\n\nFailed to get:") + "\n- " + missing,
                )
            else:
                sels = self.driver.solver.selections
                doc = sels.toDOM()
                reply = doc.toxml("utf-8")
                if sys.version_info[0] > 2:
                    stdout = sys.stdout.buffer
                else:
                    stdout = sys.stdout
                stdout.write(("Length:%8x\n" % len(reply)).encode("utf-8") + reply)
                self.window.destroy()
                sys.exit(0)  # Success
        except SystemExit:
            raise
        except download.DownloadAborted as ex:
            run_button.set_active(False)
            # Don't bother reporting this to the user
        except Exception as ex:
            run_button.set_active(False)
            self.report_exception(ex)

    def update_download_status(self, only_update_visible=False):
        """Called at regular intervals while there are downloads in progress,
		and once at the end. Update the display."""
        monitored_downloads = self.driver.config.handler.monitored_downloads

        self.browser.update_download_status(only_update_visible)

        if not monitored_downloads:
            self.progress_area.hide()
            self.window.get_window().set_cursor(None)
            return

        if not self.progress_area.get_property("visible"):
            self.progress_area.show()
            self.window.get_window().set_cursor(gtkutils.get_busy_pointer())

        any_known = False
        done = total = self.driver.config.handler.total_bytes_downloaded  # Completed downloads
        n_downloads = self.driver.config.handler.n_completed_downloads
        # Now add downloads in progress...
        for x in monitored_downloads:
            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))
        self.progress.set_text(
            ngettext("Downloading one file (%(progress)s)", "Downloading %(number)d files (%(progress)s)", n_downloads)
            % {"progress": progress_text, "number": n_downloads}
        )

        if total == 0 or (n_downloads < 2 and not any_known):
            self.progress.pulse()
        else:
            self.progress.set_fraction(float(done) / total)

    def set_message(self, message):
        import pango

        self.comment.set_text(message)
        attrs = pango.AttrList()
        attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, end_index=len(message)))
        self.comment.set_attributes(attrs)
        self.comment.show()

    def use_systray_icon(self):
        try:
            if sys.version_info[0] > 2:
                self.systray_icon = gtk.StatusIcon.new_from_icon_name("zeroinstall")
            else:
                self.systray_icon = gtk.status_icon_new_from_icon_name("zeroinstall")
        except Exception as ex:
            info(_("No system tray support: %s"), ex)
        else:
            root_iface = iface_cache.iface_cache.get_interface(self.driver.requirements.interface_uri)
            self.systray_icon.set_tooltip(_("Checking for updates for %s") % root_iface.get_name())
            self.systray_icon.connect("activate", self.remove_systray_icon)
            self.systray_icon_blocker = tasks.Blocker("Tray icon clicked")

    def remove_systray_icon(self, i=None):
        assert self.systray_icon, i
        self.show()
        self.systray_icon.set_visible(False)
        self.systray_icon = None
        self.systray_icon_blocker.trigger()
        self.systray_icon_blocker = None

    def report_exception(self, ex, tb=None):
        if not isinstance(ex, SafeException):
            if isinstance(ex, AssertionError):
                # Assertions often don't say that they're errors (and are frequently
                # blank).
                ex = repr(ex)
            if tb is None:
                warn(ex, exc_info=True)
            else:
                warn(ex, exc_info=(type(ex), ex, tb))
        if self.systray_icon:
            self.systray_icon.set_blinking(True)
            self.systray_icon.set_tooltip(str(ex) + "\n" + _("(click for details)"))
        else:
            dialog.alert(self.window, str(ex) or repr(ex))