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