Start development series 0.42.1-post
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / 0launch-gui / mainwindow.py
blob57d3191ae2b3744c4bdc3bf766dc07daf85d1893
1 # Copyright (C) 2009, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import gtk
5 import sys
6 from logging import info
8 from zeroinstall import SafeException
9 from zeroinstall.support import tasks, pretty_size
10 from zeroinstall.injector import download, iface_cache
11 from iface_browser import InterfaceBrowser
12 import dialog
13 from zeroinstall.gtkui import gtkutils
14 from zeroinstall.gtkui import help_box
16 SHOW_PREFERENCES = 0
18 class MainWindow:
19 progress = None
20 progress_area = None
21 browser = None
22 window = None
23 cancel_download_and_run = None
24 policy = None
25 comment = None
26 systray_icon = None
27 systray_icon_blocker = None
29 def __init__(self, policy, widgets, download_only):
30 self.policy = policy
32 policy.watchers.append(lambda: self.window.set_response_sensitive(gtk.RESPONSE_OK, policy.solver.ready))
34 self.window = widgets.get_widget('main')
35 self.window.set_default_size(gtk.gdk.screen_width() * 2 / 5, 300)
37 self.progress = widgets.get_widget('progress')
38 self.progress_area = widgets.get_widget('progress_area')
39 self.comment = widgets.get_widget('comment')
41 widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())
43 self.refresh_button = widgets.get_widget('refresh')
45 # Tree view
46 self.browser = InterfaceBrowser(policy, widgets)
48 prefs = widgets.get_widget('preferences')
49 self.window.action_area.set_child_secondary(prefs, True)
51 # Glade won't let me add this to the template!
52 if download_only:
53 run_button = dialog.MixedButton(_("_Download"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
54 else:
55 run_button = dialog.MixedButton(_("_Run"), gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
56 self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
57 run_button.show_all()
58 run_button.set_flags(gtk.CAN_DEFAULT)
59 self.run_button = run_button
61 self.window.set_default_response(gtk.RESPONSE_OK)
62 self.window.default_widget.grab_focus()
64 def response(dialog, resp):
65 if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
66 self.window.destroy()
67 sys.exit(1)
68 elif resp == gtk.RESPONSE_OK:
69 if self.cancel_download_and_run:
70 self.cancel_download_and_run.trigger()
71 if run_button.get_active():
72 self.cancel_download_and_run = tasks.Blocker("cancel downloads")
73 self.download_and_run(run_button, self.cancel_download_and_run)
74 elif resp == gtk.RESPONSE_HELP:
75 gui_help.display()
76 elif resp == SHOW_PREFERENCES:
77 import preferences
78 preferences.show_preferences(policy)
79 self.window.connect('response', response)
80 self.window.realize() # Make busy pointer work, even with --systray
82 def destroy(self):
83 self.window.destroy()
85 def show(self):
86 self.window.show()
88 def set_response_sensitive(self, response, sensitive):
89 self.window.set_response_sensitive(response, sensitive)
91 @tasks.async
92 def download_and_run(self, run_button, cancelled):
93 try:
94 downloaded = self.policy.download_uncached_implementations()
96 if downloaded:
97 # We need to wait until everything is downloaded...
98 blockers = [downloaded, cancelled]
99 yield blockers
100 tasks.check(blockers)
102 if cancelled.happened:
103 return
105 if self.policy.get_uncached_implementations():
106 dialog.alert(self.window, _('Not all downloads succeeded; cannot run program.'))
107 else:
108 from zeroinstall.injector import selections
109 sels = selections.Selections(self.policy)
110 doc = sels.toDOM()
111 reply = doc.toxml('utf-8')
112 sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
113 self.window.destroy()
114 sys.exit(0) # Success
115 except SystemExit:
116 raise
117 except Exception, ex:
118 run_button.set_active(False)
119 self.report_exception(ex)
121 def update_download_status(self):
122 """Called at regular intervals while there are downloads in progress,
123 and once at the end. Update the display."""
124 monitored_downloads = self.policy.handler.monitored_downloads
126 self.browser.update_download_status()
128 if not monitored_downloads:
129 self.progress_area.hide()
130 self.window.window.set_cursor(None)
131 return
133 if not self.progress_area.get_property('visible'):
134 self.progress_area.show()
135 self.window.window.set_cursor(gtkutils.get_busy_pointer())
137 any_known = False
138 done = total = self.policy.handler.total_bytes_downloaded # Completed downloads
139 n_downloads = self.policy.handler.n_completed_downloads
140 # Now add downloads in progress...
141 for x in monitored_downloads.values():
142 if x.status != download.download_fetching: continue
143 n_downloads += 1
144 if x.expected_size:
145 any_known = True
146 so_far = x.get_bytes_downloaded_so_far()
147 total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons
148 done += so_far
150 progress_text = '%s / %s' % (pretty_size(done), pretty_size(total))
151 if n_downloads == 1:
152 self.progress.set_text(_('Downloading one file (%s)') % progress_text)
153 else:
154 self.progress.set_text(_('Downloading %(number)d files (%(progress)s)') % {'number': n_downloads, 'progress': progress_text})
156 if total == 0 or (n_downloads < 2 and not any_known):
157 self.progress.pulse()
158 else:
159 self.progress.set_fraction(float(done) / total)
161 def set_message(self, message):
162 import pango
163 self.comment.set_text(message)
164 attrs = pango.AttrList()
165 attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, end_index = len(message)))
166 self.comment.set_attributes(attrs)
167 self.comment.show()
169 def use_systray_icon(self):
170 try:
171 self.systray_icon = gtk.status_icon_new_from_icon_name("zeroinstall-zero2desktop")
172 except Exception, ex:
173 info(_("No system tray support: %s"), ex)
174 else:
175 root_iface = iface_cache.iface_cache.get_interface(self.policy.root)
176 self.systray_icon.set_tooltip(_('Checking for updates for %s') % root_iface.get_name())
177 self.systray_icon.connect('activate', self.remove_systray_icon)
178 self.systray_icon_blocker = tasks.Blocker('Tray icon clicked')
180 def remove_systray_icon(self, i = None):
181 assert self.systray_icon, i
182 self.show()
183 self.systray_icon.set_visible(False)
184 self.systray_icon = None
185 self.systray_icon_blocker.trigger()
186 self.systray_icon_blocker = None
188 def report_exception(self, ex):
189 if not isinstance(ex, SafeException):
190 import traceback
191 traceback.print_exc()
192 if self.systray_icon:
193 self.systray_icon.set_blinking(True)
194 self.systray_icon.set_tooltip(str(ex) + '\n' + _('(click for details)'))
195 else:
196 dialog.alert(self.window, str(ex))
198 gui_help = help_box.HelpBox(_("Injector Help"),
199 (_('Overview'), '\n' +
200 _("""A program is made up of many different components, typically written by different \
201 groups of people. Each component is available in multiple versions. Zero Install is \
202 used when starting a program. Its job is to decide which implementation of each required \
203 component to use.
205 Zero Install starts with the program you want to run (like 'The Gimp') and chooses an \
206 implementation (like 'The Gimp 2.2.0'). However, this implementation \
207 will in turn depend on other components, such as 'GTK' (which draws the menus \
208 and buttons). Thus, it must choose implementations of \
209 each dependency (each of which may require further components, and so on).""")),
211 (_('List of components'), '\n' +
212 _("""The main window displays all these components, and the version of each chosen \
213 implementation. The top-most one represents the program you tried to run, and each direct \
214 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
215 downloaded, or '(cached)' if it is already on this computer.
217 If you are happy with the choices shown, click on the Download (or Run) button to \
218 download (and run) the program.""")),
220 (_('Choosing different versions'), '\n' +
221 _("""To control which implementations (versions) are chosen you can click on Preferences \
222 and adjust the network policy and the overall stability policy. These settings affect \
223 all programs run using Zero Install.
225 Alternatively, you can edit the policy of an individual component by clicking on the \
226 button at the end of its line in the table and choosing "Show Versions" from the menu. \
227 See that dialog's help text for more information.""") + '\n'),
229 (_('Reporting bugs'), '\n' +
230 _("""To report a bug, right-click over the component which you think contains the problem \
231 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
232 choose the top one (i.e. the program itself). The program's author can reassign the \
233 bug if necessary, or switch to using a different version of the library.""") + '\n'),
235 (_('The cache'), '\n' +
236 _("""Each version of a program that is downloaded is stored in the Zero Install cache. This \
237 means that it won't need to be downloaded again each time you run the program. The \
238 "0store manage" command can be used to view the cache.""") + '\n'),