Added some missing copyright headers.
[zeroinstall.git] / zeroinstall / 0launch-gui / mainwindow.py
blobd6e7455e448d3b3b94440ebe4f184bb38302569c
1 # Copyright (C) 2008, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import gtk
5 from logging import warn
6 import os, sys
7 from zeroinstall import SafeException
8 from zeroinstall.support import tasks, pretty_size
9 from zeroinstall.injector import download
10 from iface_browser import InterfaceBrowser
11 import help_box
12 import dialog
14 tips = gtk.Tooltips()
16 SHOW_PREFERENCES = 0
18 class MainWindow:
19 progress = None
20 progress_area = None
21 busy_pointer = None
22 browser = None
23 window = None
24 cancel_download_and_run = None
25 policy = None
27 def __init__(self, policy, widgets, download_only):
28 self.policy = policy
30 policy.watchers.append(lambda: self.window.set_response_sensitive(gtk.RESPONSE_OK, policy.solver.ready))
32 self.window = widgets.get_widget('main')
33 self.window.set_default_size(gtk.gdk.screen_width() * 2 / 5, 300)
35 self.progress = widgets.get_widget('progress')
36 self.progress_area = widgets.get_widget('progress_area')
38 widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())
40 cache = widgets.get_widget('show_cache')
41 cache.connect('clicked',
42 lambda b: os.spawnlp(os.P_WAIT, sys.argv[0], sys.argv[0], '-c'))
44 self.refresh_button = widgets.get_widget('refresh')
46 # Tree view
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!
53 if download_only:
54 run_button = dialog.MixedButton("_Download", gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
55 else:
56 run_button = dialog.MixedButton("_Run", gtk.STOCK_EXECUTE, button = gtk.ToggleButton())
57 self.window.add_action_widget(run_button, gtk.RESPONSE_OK)
58 run_button.show_all()
59 run_button.set_flags(gtk.CAN_DEFAULT)
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)
81 def destroy(self):
82 self.window.destroy()
84 def show(self):
85 self.window.show()
87 def set_response_sensitive(self, response, sensitive):
88 self.window.set_response_sensitive(response, sensitive)
90 @tasks.async
91 def download_and_run(self, run_button, cancelled):
92 try:
93 downloaded = self.policy.download_uncached_implementations()
95 if downloaded:
96 # We need to wait until everything is downloaded...
97 blockers = [downloaded, cancelled]
98 yield blockers
99 tasks.check(blockers)
101 if cancelled.happened:
102 return
104 if self.policy.get_uncached_implementations():
105 dialog.alert(self.window, 'Not all downloads succeeded; cannot run program.')
106 else:
107 from zeroinstall.injector import selections
108 sels = selections.Selections(self.policy)
109 doc = sels.toDOM()
110 reply = doc.toxml('utf-8')
111 sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
112 self.window.destroy()
113 sys.exit(0) # Success
114 except SafeException, ex:
115 run_button.set_active(False)
116 self.policy.handler.report_error(ex)
117 except SystemExit:
118 raise
119 except Exception, ex:
120 run_button.set_active(False)
121 import traceback
122 traceback.print_exc()
123 self.policy.handler.report_error(ex)
125 def update_download_status(self):
126 """Called at regular intervals while there are downloads in progress,
127 and once at the end. Update the display."""
128 monitored_downloads = self.policy.handler.monitored_downloads
130 self.browser.update_download_status()
132 if not monitored_downloads:
133 self.progress_area.hide()
134 self.window.window.set_cursor(None)
135 return
137 if not self.progress_area.get_property('visible'):
138 self.progress_area.show()
139 if self.busy_pointer is None:
140 self.busy_pointer = dialog.get_busy_pointer(self.window.window)
141 self.window.window.set_cursor(self.busy_pointer)
143 any_known = False
144 done = total = self.policy.handler.total_bytes_downloaded # Completed downloads
145 n_downloads = self.policy.handler.n_completed_downloads
146 # Now add downloads in progress...
147 for x in monitored_downloads.values():
148 if x.status != download.download_fetching: continue
149 n_downloads += 1
150 if x.expected_size:
151 any_known = True
152 so_far = x.get_bytes_downloaded_so_far()
153 total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons
154 done += so_far
156 progress_text = '%s / %s' % (pretty_size(done), pretty_size(total))
157 if n_downloads == 1:
158 self.progress.set_text('Downloading one file (%s)' % progress_text)
159 else:
160 self.progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text))
162 if total == 0 or (n_downloads < 2 and not any_known):
163 self.progress.pulse()
164 else:
165 self.progress.set_fraction(float(done) / total)
167 gui_help = help_box.HelpBox("Injector Help",
168 ('Overview', """
169 A program is made up of many different components, typically written by different \
170 groups of people. Each component is available in multiple versions. Zero Install is \
171 used when starting a program. Its job is to decide which implementation of each required \
172 component to use.
174 An interface describes what a component does. The injector starts with \
175 the interface for the program you want to run (like 'The Gimp') and chooses an \
176 implementation (like 'The Gimp 2.2.0'). However, this implementation \
177 will in turn depend on other interfaces, such as 'GTK' (which draws the menus \
178 and buttons). Thus, it must choose implementations of \
179 each dependency (each of which may require further interfaces, and so on)."""),
181 ('List of interfaces', """
182 The main window displays all these interfaces, and the version of each chosen \
183 implementation. The top-most one represents the program you tried to run, and each direct \
184 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
185 downloaded, or '(cached)' if it is already on this computer.
187 If you are happy with the choices shown, click on the Download (or Run) button to \
188 download (and run) the program."""),
190 ('Choosing different versions', """
191 To control which implementations (versions) are chosen you can click on Preferences \
192 and adjust the network policy and the overall stability policy. These settings affect \
193 all programs run using Zero Install.
195 Alternatively, you can edit the policy of an individual interface by clicking on the \
196 button at the end of its line in the table and choosing "Show Versions" from the menu. \
197 See that dialog's help text for more information.
198 """),
200 ('Reporting bugs', """
201 To report a bug, right-click over the interface which you think contains the problem \
202 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
203 choose the top one (i.e. the program itself). The program's author can reassign the \
204 bug if necessary, or switch to using a different version of the library.
205 """),
207 ('The cache', """
208 Each version of a program that is downloaded is stored in the Zero Install cache. This \
209 means that it won't need to be downloaded again each time you run the program. Click on \
210 the 'Show Cache' button to see what is currently in the cache, or to remove versions \
211 you no longer need to save disk space."""),