When downloading, animate the pointer too.
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / 0launch-gui / mainwindow.py
blob4d0f617a02b743458ffaf05de2870cd48c1efcd5
1 import gtk
2 from logging import warn
3 import os, sys
4 from zeroinstall import SafeException
5 from zeroinstall.support import tasks, pretty_size
6 from zeroinstall.injector import download
7 from iface_browser import InterfaceBrowser
8 import help_box
9 import dialog
11 tips = gtk.Tooltips()
13 SHOW_PREFERENCES = 0
15 class MainWindow:
16 progress = None
17 progress_area = None
18 busy_pointer = None
19 browser = None
20 window = None
21 cancel_download_and_run = None
22 policy = None
24 def __init__(self, policy, widgets, download_only):
25 self.policy = policy
27 policy.watchers.append(lambda: self.window.set_response_sensitive(gtk.RESPONSE_OK, policy.solver.ready))
29 self.window = widgets.get_widget('main')
30 self.window.set_default_size(gtk.gdk.screen_width() * 2 / 5, 300)
32 self.progress = widgets.get_widget('progress')
33 self.progress_area = widgets.get_widget('progress_area')
35 widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())
37 cache = widgets.get_widget('show_cache')
38 cache.connect('clicked',
39 lambda b: os.spawnlp(os.P_WAIT, sys.argv[0], sys.argv[0], '-c'))
41 self.refresh_button = widgets.get_widget('refresh')
43 # Tree view
44 self.browser = InterfaceBrowser(policy, widgets)
46 prefs = widgets.get_widget('preferences')
47 self.window.action_area.set_child_secondary(prefs, True)
49 # Glade won't let me add this to the template!
50 if download_only:
51 run_button = dialog.MixedButton("_Download", gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
52 else:
53 run_button = dialog.MixedButton("_Run", gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
54 self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
55 run_button.show_all()
56 run_button.set_flags(gtk.CAN_DEFAULT)
58 self.window.set_default_response(gtk.RESPONSE_OK)
59 self.window.default_widget.grab_focus()
61 def response(dialog, resp):
62 if resp in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
63 self.window.destroy()
64 sys.exit(1)
65 elif resp == gtk.RESPONSE_OK:
66 if self.cancel_download_and_run:
67 self.cancel_download_and_run.trigger()
68 if run_button.get_active():
69 self.cancel_download_and_run = tasks.Blocker("cancel downloads")
70 self.download_and_run(run_button, self.cancel_download_and_run)
71 elif resp == gtk.RESPONSE_HELP:
72 gui_help.display()
73 elif resp == SHOW_PREFERENCES:
74 import preferences
75 preferences.show_preferences(policy)
76 self.window.connect('response', response)
78 def destroy(self):
79 self.window.destroy()
81 def show(self):
82 self.window.show()
84 def set_response_sensitive(self, response, sensitive):
85 self.window.set_response_sensitive(response, sensitive)
87 @tasks.async
88 def download_and_run(self, run_button, cancelled):
89 try:
90 downloaded = self.policy.download_uncached_implementations()
92 if downloaded:
93 # We need to wait until everything is downloaded...
94 blockers = [downloaded, cancelled]
95 yield blockers
96 tasks.check(blockers)
98 if cancelled.happened:
99 return
101 if self.policy.get_uncached_implementations():
102 dialog.alert(self.window, 'Not all downloads succeeded; cannot run program.')
103 else:
104 from zeroinstall.injector import selections
105 sels = selections.Selections(self.policy)
106 doc = sels.toDOM()
107 reply = doc.toxml('utf-8')
108 sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
109 self.window.destroy()
110 sys.exit(0) # Success
111 except SafeException, ex:
112 run_button.set_active(False)
113 self.policy.handler.report_error(ex)
114 except SystemExit:
115 raise
116 except Exception, ex:
117 run_button.set_active(False)
118 import traceback
119 traceback.print_exc()
120 self.policy.handler.report_error(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)
132 return
134 if not self.progress_area.get_property('visible'):
135 self.progress_area.show()
136 if self.busy_pointer is None:
137 self.busy_pointer = dialog.get_busy_pointer(self.window.window)
138 self.window.window.set_cursor(self.busy_pointer)
140 any_known = False
141 done = total = self.policy.handler.total_bytes_downloaded # Completed downloads
142 n_downloads = self.policy.handler.n_completed_downloads
143 # Now add downloads in progress...
144 for x in monitored_downloads.values():
145 if x.status != download.download_fetching: continue
146 n_downloads += 1
147 if x.expected_size:
148 any_known = True
149 so_far = x.get_bytes_downloaded_so_far()
150 total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons
151 done += so_far
153 progress_text = '%s / %s' % (pretty_size(done), pretty_size(total))
154 if n_downloads == 1:
155 self.progress.set_text('Downloading one file (%s)' % progress_text)
156 else:
157 self.progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text))
159 if total == 0 or (n_downloads < 2 and not any_known):
160 self.progress.pulse()
161 else:
162 self.progress.set_fraction(float(done) / total)
164 gui_help = help_box.HelpBox("Injector Help",
165 ('Overview', """
166 A program is made up of many different components, typically written by different \
167 groups of people. Each component is available in multiple versions. The injector is \
168 used when starting a program. Its job is to decide which implementation of each required \
169 component to use.
171 An interface describes what a component does. The injector starts with \
172 the interface for the program you want to run (like 'The Gimp') and chooses an \
173 implementation (like 'The Gimp 2.2.0'). However, this implementation \
174 will in turn depend on other interfaces, such as 'GTK' (which draws the menus \
175 and buttons). Thus, the injector must choose implementations of \
176 each dependency (each of which may require further interfaces, and so on)."""),
178 ('List of interfaces', """
179 The main window displays all these interfaces, and the version of each chosen \
180 implementation. The top-most one represents the program you tried to run, and each direct \
181 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
182 downloaded, or '(cached)' if it is already on this computer.
184 If you are happy with the choices shown, click on the Download (or Run) button to \
185 download (and run) the program."""),
187 ('Choosing different versions', """
188 To control which implementations (versions) are chosen you can click on Preferences \
189 and adjust the network policy and the overall stability policy. These settings affect \
190 all programs run using Zero Install.
192 Alternatively, you can edit the policy of an individual interface by selecting it \
193 and clicking on the 'Interface Properties' button. \
194 See that dialog's help text for more information.
196 Right-click on an interface in the list for a menu.
197 """),
199 ('Reporting bugs', """
200 To report a bug, right-click over the interface which you think contains the problem \
201 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
202 choose the top one (i.e. the program itself). The program's author can reassign the \
203 bug if necessary, or switch to using a different version of the library.
204 """),
206 ('The cache', """
207 Each version of a program that is downloaded is stored in the Zero Install cache. This \
208 means that it won't need to be downloaded again each time you run the program. Click on \
209 the 'Show Cache' button to see what is currently in the cache, or to remove versions \
210 you no longer need to save disk space."""),