1 # Copyright (C) 2009, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
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
13 from zeroinstall
.gtkui
import gtkutils
14 from zeroinstall
.gtkui
import help_box
25 cancel_download_and_run
= None
30 def __init__(self
, policy
, widgets
, download_only
):
33 policy
.watchers
.append(lambda: self
.window
.set_response_sensitive(gtk
.RESPONSE_OK
, policy
.solver
.ready
))
35 self
.window
= widgets
.get_widget('main')
36 self
.window
.set_default_size(gtk
.gdk
.screen_width() * 2 / 5, 300)
38 self
.progress
= widgets
.get_widget('progress')
39 self
.progress_area
= widgets
.get_widget('progress_area')
40 self
.comment
= widgets
.get_widget('comment')
42 widgets
.get_widget('stop').connect('clicked', lambda b
: policy
.handler
.abort_all_downloads())
44 self
.refresh_button
= widgets
.get_widget('refresh')
47 self
.browser
= InterfaceBrowser(policy
, widgets
)
49 prefs
= widgets
.get_widget('preferences')
50 self
.window
.action_area
.set_child_secondary(prefs
, True)
52 # Glade won't let me add this to the template!
54 run_button
= dialog
.MixedButton(_("_Download"), gtk
.STOCK_EXECUTE
, button
= gtk
.ToggleButton())
56 run_button
= dialog
.MixedButton(_("_Run"), gtk
.STOCK_EXECUTE
, button
= gtk
.ToggleButton())
57 self
.window
.add_action_widget(run_button
, gtk
.RESPONSE_OK
)
59 run_button
.set_flags(gtk
.CAN_DEFAULT
)
60 self
.run_button
= run_button
62 self
.window
.set_default_response(gtk
.RESPONSE_OK
)
63 self
.window
.default_widget
.grab_focus()
65 def response(dialog
, resp
):
66 if resp
in (gtk
.RESPONSE_CANCEL
, gtk
.RESPONSE_DELETE_EVENT
):
69 elif resp
== gtk
.RESPONSE_OK
:
70 if self
.cancel_download_and_run
:
71 self
.cancel_download_and_run
.trigger()
72 if run_button
.get_active():
73 self
.cancel_download_and_run
= tasks
.Blocker("cancel downloads")
74 self
.download_and_run(run_button
, self
.cancel_download_and_run
)
75 elif resp
== gtk
.RESPONSE_HELP
:
77 elif resp
== SHOW_PREFERENCES
:
79 preferences
.show_preferences(policy
)
80 self
.window
.connect('response', response
)
81 self
.window
.realize() # Make busy pointer work, even with --systray
89 def set_response_sensitive(self
, response
, sensitive
):
90 self
.window
.set_response_sensitive(response
, sensitive
)
93 def download_and_run(self
, run_button
, cancelled
):
95 downloaded
= self
.policy
.download_uncached_implementations()
98 # We need to wait until everything is downloaded...
99 blockers
= [downloaded
, cancelled
]
101 tasks
.check(blockers
)
103 if cancelled
.happened
:
106 if self
.policy
.get_uncached_implementations():
107 dialog
.alert(self
.window
, _('Not all downloads succeeded; cannot run program.'))
109 from zeroinstall
.injector
import selections
110 sels
= selections
.Selections(self
.policy
)
112 reply
= doc
.toxml('utf-8')
113 sys
.stdout
.write(('Length:%8x\n' % len(reply
)) + reply
)
114 self
.window
.destroy()
115 sys
.exit(0) # Success
118 except Exception, ex
:
119 run_button
.set_active(False)
120 self
.report_exception(ex
)
122 def update_download_status(self
):
123 """Called at regular intervals while there are downloads in progress,
124 and once at the end. Update the display."""
125 monitored_downloads
= self
.policy
.handler
.monitored_downloads
127 self
.browser
.update_download_status()
129 if not monitored_downloads
:
130 self
.progress_area
.hide()
131 self
.window
.window
.set_cursor(None)
134 if not self
.progress_area
.get_property('visible'):
135 self
.progress_area
.show()
136 self
.window
.window
.set_cursor(gtkutils
.get_busy_pointer())
139 done
= total
= self
.policy
.handler
.total_bytes_downloaded
# Completed downloads
140 n_downloads
= self
.policy
.handler
.n_completed_downloads
141 # Now add downloads in progress...
142 for x
in monitored_downloads
.values():
143 if x
.status
!= download
.download_fetching
: continue
147 so_far
= x
.get_bytes_downloaded_so_far()
148 total
+= x
.expected_size
or max(4096, so_far
) # Guess about 4K for feeds/icons
151 progress_text
= '%s / %s' % (pretty_size(done
), pretty_size(total
))
153 self
.progress
.set_text(_('Downloading one file (%s)') % progress_text
)
155 self
.progress
.set_text(_('Downloading %d files (%s)') % (n_downloads
, progress_text
))
157 if total
== 0 or (n_downloads
< 2 and not any_known
):
158 self
.progress
.pulse()
160 self
.progress
.set_fraction(float(done
) / total
)
162 def set_message(self
, message
):
164 self
.comment
.set_text(message
)
165 attrs
= pango
.AttrList()
166 attrs
.insert(pango
.AttrWeight(pango
.WEIGHT_BOLD
, end_index
= len(message
)))
167 self
.comment
.set_attributes(attrs
)
170 def use_systray_icon(self
):
172 self
.systray_icon
= gtk
.status_icon_new_from_icon_name("zeroinstall-zero2desktop")
173 except Exception, ex
:
174 info("No system tray support: %s", ex
)
176 root_iface
= iface_cache
.iface_cache
.get_interface(self
.policy
.root
)
177 self
.systray_icon
.set_tooltip('Checking for updates for %s' % root_iface
.get_name())
178 self
.systray_icon
.connect('activate', self
.remove_systray_icon
)
180 def remove_systray_icon(self
, i
= None):
181 assert self
.systray_icon
, i
183 self
.systray_icon
.set_visible(False)
184 self
.systray_icon
= None
186 def report_exception(self
, ex
):
187 if not isinstance(ex
, SafeException
):
189 traceback
.print_exc()
190 if self
.systray_icon
:
191 self
.systray_icon
.set_blinking(True)
192 self
.systray_icon
.set_tooltip(str(ex
) + '\n(click for details)')
194 dialog
.alert(self
.window
, str(ex
))
196 gui_help
= help_box
.HelpBox("Injector Help",
198 A program is made up of many different components, typically written by different \
199 groups of people. Each component is available in multiple versions. Zero Install is \
200 used when starting a program. Its job is to decide which implementation of each required \
203 Zero Install starts with the program you want to run (like 'The Gimp') and chooses an \
204 implementation (like 'The Gimp 2.2.0'). However, this implementation \
205 will in turn depend on other components, such as 'GTK' (which draws the menus \
206 and buttons). Thus, it must choose implementations of \
207 each dependency (each of which may require further components, and so on)."""),
209 ('List of components', """
210 The main window displays all these components, and the version of each chosen \
211 implementation. The top-most one represents the program you tried to run, and each direct \
212 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
213 downloaded, or '(cached)' if it is already on this computer.
215 If you are happy with the choices shown, click on the Download (or Run) button to \
216 download (and run) the program."""),
218 ('Choosing different versions', """
219 To control which implementations (versions) are chosen you can click on Preferences \
220 and adjust the network policy and the overall stability policy. These settings affect \
221 all programs run using Zero Install.
223 Alternatively, you can edit the policy of an individual component by clicking on the \
224 button at the end of its line in the table and choosing "Show Versions" from the menu. \
225 See that dialog's help text for more information.
228 ('Reporting bugs', """
229 To report a bug, right-click over the component which you think contains the problem \
230 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
231 choose the top one (i.e. the program itself). The program's author can reassign the \
232 bug if necessary, or switch to using a different version of the library.
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.