1 # Copyright (C) 2009, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
6 from logging
import info
, warn
8 from zeroinstall
import _
, translation
9 from zeroinstall
import SafeException
10 from zeroinstall
.support
import tasks
, pretty_size
11 from zeroinstall
.injector
import download
, iface_cache
12 from iface_browser
import InterfaceBrowser
14 from zeroinstall
.gtkui
import gtkutils
15 from zeroinstall
.gtkui
import help_box
17 ngettext
= translation
.ngettext
26 cancel_download_and_run
= None
30 systray_icon_blocker
= None
32 def __init__(self
, driver
, widgets
, download_only
, select_only
= False):
34 self
.select_only
= select_only
36 def update_ok_state():
37 self
.window
.set_response_sensitive(gtk
.RESPONSE_OK
, driver
.solver
.ready
)
38 if driver
.solver
.ready
and self
.window
.get_focus() is None:
39 run_button
.grab_focus()
40 driver
.watchers
.append(update_ok_state
)
42 self
.window
= widgets
.get_widget('main')
43 self
.window
.set_default_size(gtk
.gdk
.screen_width() * 2 / 5, 300)
45 self
.progress
= widgets
.get_widget('progress')
46 self
.progress_area
= widgets
.get_widget('progress_area')
47 self
.comment
= widgets
.get_widget('comment')
49 widgets
.get_widget('stop').connect('clicked', lambda b
: driver
.config
.handler
.abort_all_downloads())
51 self
.refresh_button
= widgets
.get_widget('refresh')
54 self
.browser
= InterfaceBrowser(driver
, widgets
)
56 prefs
= widgets
.get_widget('preferences')
57 self
.window
.get_action_area().set_child_secondary(prefs
, True)
59 # Glade won't let me add this to the template!
61 run_button
= dialog
.MixedButton(_("_Select"), gtk
.STOCK_EXECUTE
, button
= gtk
.ToggleButton())
63 run_button
= dialog
.MixedButton(_("_Download"), gtk
.STOCK_EXECUTE
, button
= gtk
.ToggleButton())
65 run_button
= dialog
.MixedButton(_("_Run"), gtk
.STOCK_EXECUTE
, button
= gtk
.ToggleButton())
66 self
.window
.add_action_widget(run_button
, gtk
.RESPONSE_OK
)
68 run_button
.set_can_default(True)
69 self
.run_button
= run_button
71 run_button
.grab_focus()
73 def response(dialog
, resp
):
74 if resp
in (gtk
.RESPONSE_CANCEL
, gtk
.RESPONSE_DELETE_EVENT
):
77 elif resp
== gtk
.RESPONSE_OK
:
78 if self
.cancel_download_and_run
:
79 self
.cancel_download_and_run
.trigger()
80 if run_button
.get_active():
81 self
.cancel_download_and_run
= tasks
.Blocker("cancel downloads")
82 self
.download_and_run(run_button
, self
.cancel_download_and_run
)
83 elif resp
== gtk
.RESPONSE_HELP
:
85 elif resp
== SHOW_PREFERENCES
:
87 preferences
.show_preferences(driver
.config
, notify_cb
= lambda: driver
.solve_with_downloads())
88 self
.window
.connect('response', response
)
89 self
.window
.realize() # Make busy pointer work, even with --systray
97 def set_response_sensitive(self
, response
, sensitive
):
98 self
.window
.set_response_sensitive(response
, sensitive
)
101 def download_and_run(self
, run_button
, cancelled
):
103 if not self
.select_only
:
104 downloaded
= self
.driver
.download_uncached_implementations()
107 # We need to wait until everything is downloaded...
108 blockers
= [downloaded
, cancelled
]
110 tasks
.check(blockers
)
112 if cancelled
.happened
:
115 uncached
= self
.driver
.get_uncached_implementations()
117 uncached
= None # (we don't care)
120 missing
= '\n- '.join([_('%(iface_name)s %(impl_version)s') % {'iface_name': iface
.get_name(), 'impl_version': impl
.get_version()} for iface
, impl
in uncached
])
121 dialog
.alert(self
.window
, _('Not all downloads succeeded; cannot run program.\n\nFailed to get:') + '\n- ' + missing
)
123 sels
= self
.driver
.solver
.selections
125 reply
= doc
.toxml('utf-8')
126 if sys
.version_info
[0] > 2:
127 stdout
= sys
.stdout
.buffer
130 stdout
.write(('Length:%8x\n' % len(reply
)).encode('utf-8') + reply
)
131 self
.window
.destroy()
132 sys
.exit(0) # Success
135 except download
.DownloadAborted
as ex
:
136 run_button
.set_active(False)
137 # Don't bother reporting this to the user
138 except Exception as ex
:
139 run_button
.set_active(False)
140 self
.report_exception(ex
)
142 def update_download_status(self
, only_update_visible
= False):
143 """Called at regular intervals while there are downloads in progress,
144 and once at the end. Update the display."""
145 monitored_downloads
= self
.driver
.config
.handler
.monitored_downloads
147 self
.browser
.update_download_status(only_update_visible
)
149 if not monitored_downloads
:
150 self
.progress_area
.hide()
151 self
.window
.get_window().set_cursor(None)
154 if not self
.progress_area
.get_property('visible'):
155 self
.progress_area
.show()
156 self
.window
.get_window().set_cursor(gtkutils
.get_busy_pointer())
159 done
= total
= self
.driver
.config
.handler
.total_bytes_downloaded
# Completed downloads
160 n_downloads
= self
.driver
.config
.handler
.n_completed_downloads
161 # Now add downloads in progress...
162 for x
in monitored_downloads
:
163 if x
.status
!= download
.download_fetching
: continue
167 so_far
= x
.get_bytes_downloaded_so_far()
168 total
+= x
.expected_size
or max(4096, so_far
) # Guess about 4K for feeds/icons
171 progress_text
= '%s / %s' % (pretty_size(done
), pretty_size(total
))
172 self
.progress
.set_text(
173 ngettext('Downloading one file (%(progress)s)',
174 'Downloading %(number)d files (%(progress)s)', n_downloads
)
175 % {'progress': progress_text
, 'number': n_downloads
})
177 if total
== 0 or (n_downloads
< 2 and not any_known
):
178 self
.progress
.pulse()
180 self
.progress
.set_fraction(float(done
) / total
)
182 def set_message(self
, message
):
184 self
.comment
.set_text(message
)
185 attrs
= pango
.AttrList()
186 attrs
.insert(pango
.AttrWeight(pango
.WEIGHT_BOLD
, end_index
= len(message
)))
187 self
.comment
.set_attributes(attrs
)
190 def use_systray_icon(self
):
192 self
.systray_icon
= gtk
.status_icon_new_from_icon_name("zeroinstall")
193 except Exception as ex
:
194 info(_("No system tray support: %s"), ex
)
196 root_iface
= iface_cache
.iface_cache
.get_interface(self
.driver
.requirements
.interface_uri
)
197 self
.systray_icon
.set_tooltip(_('Checking for updates for %s') % root_iface
.get_name())
198 self
.systray_icon
.connect('activate', self
.remove_systray_icon
)
199 self
.systray_icon_blocker
= tasks
.Blocker('Tray icon clicked')
201 def remove_systray_icon(self
, i
= None):
202 assert self
.systray_icon
, i
204 self
.systray_icon
.set_visible(False)
205 self
.systray_icon
= None
206 self
.systray_icon_blocker
.trigger()
207 self
.systray_icon_blocker
= None
209 def report_exception(self
, ex
, tb
= None):
210 if not isinstance(ex
, SafeException
):
211 if isinstance(ex
, AssertionError):
212 # Assertions often don't say that they're errors (and are frequently
216 warn(ex
, exc_info
= True)
218 warn(ex
, exc_info
= (type(ex
), ex
, tb
))
219 if self
.systray_icon
:
220 self
.systray_icon
.set_blinking(True)
221 self
.systray_icon
.set_tooltip(str(ex
) + '\n' + _('(click for details)'))
223 dialog
.alert(self
.window
, str(ex
) or repr(ex
))
225 gui_help
= help_box
.HelpBox(_("Injector Help"),
226 (_('Overview'), '\n' +
227 _("""A program is made up of many different components, typically written by different \
228 groups of people. Each component is available in multiple versions. Zero Install is \
229 used when starting a program. Its job is to decide which implementation of each required \
232 Zero Install starts with the program you want to run (like 'The Gimp') and chooses an \
233 implementation (like 'The Gimp 2.2.0'). However, this implementation \
234 will in turn depend on other components, such as 'GTK' (which draws the menus \
235 and buttons). Thus, it must choose implementations of \
236 each dependency (each of which may require further components, and so on).""")),
238 (_('List of components'), '\n' +
239 _("""The main window displays all these components, and the version of each chosen \
240 implementation. The top-most one represents the program you tried to run, and each direct \
241 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
242 downloaded, or '(cached)' if it is already on this computer.
244 If you are happy with the choices shown, click on the Download (or Run) button to \
245 download (and run) the program.""")),
247 (_('Choosing different versions'), '\n' +
248 _("""To control which implementations (versions) are chosen you can click on Preferences \
249 and adjust the network policy and the overall stability policy. These settings affect \
250 all programs run using Zero Install.
252 Alternatively, you can edit the policy of an individual component by clicking on the \
253 button at the end of its line in the table and choosing "Show Versions" from the menu. \
254 See that dialog's help text for more information.""") + '\n'),
256 (_('Reporting bugs'), '\n' +
257 _("""To report a bug, right-click over the component which you think contains the problem \
258 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
259 choose the top one (i.e. the program itself). The program's author can reassign the \
260 bug if necessary, or switch to using a different version of the library.""") + '\n'),
262 (_('The cache'), '\n' +
263 _("""Each version of a program that is downloaded is stored in the Zero Install cache. This \
264 means that it won't need to be downloaded again each time you run the program. The \
265 "0store manage" command can be used to view the cache.""") + '\n'),