Update year to 2009 in various places
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / 0launch-gui / mainwindow.py
blob8bfd8077694cb1e47a184ede8947b6440bb3f5ac
1 # Copyright (C) 2009, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import gtk
5 import sys
6 from zeroinstall import SafeException
7 from zeroinstall.support import tasks, pretty_size
8 from zeroinstall.injector import download
9 from iface_browser import InterfaceBrowser
10 import dialog
11 from zeroinstall.gtkui import gtkutils
12 from zeroinstall.gtkui import help_box
14 tips = gtk.Tooltips()
16 SHOW_PREFERENCES = 0
18 class MainWindow:
19 progress = None
20 progress_area = None
21 browser = None
22 window = None
23 cancel_download_and_run = None
24 policy = None
25 comment = 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')
37 self.comment = widgets.get_widget('comment')
39 widgets.get_widget('stop').connect('clicked', lambda b: policy.handler.abort_all_downloads())
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 self.window.window.set_cursor(gtkutils.get_busy_pointer())
138 any_known = False
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
144 n_downloads += 1
145 if x.expected_size:
146 any_known = True
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
149 done += so_far
151 progress_text = '%s / %s' % (pretty_size(done), pretty_size(total))
152 if n_downloads == 1:
153 self.progress.set_text(_('Downloading one file (%s)') % progress_text)
154 else:
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()
159 else:
160 self.progress.set_fraction(float(done) / total)
162 def set_message(self, message):
163 import pango
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)
168 self.comment.show()
170 gui_help = help_box.HelpBox("Injector Help",
171 ('Overview', """
172 A program is made up of many different components, typically written by different \
173 groups of people. Each component is available in multiple versions. Zero Install is \
174 used when starting a program. Its job is to decide which implementation of each required \
175 component to use.
177 Zero Install starts with the program you want to run (like 'The Gimp') and chooses an \
178 implementation (like 'The Gimp 2.2.0'). However, this implementation \
179 will in turn depend on other components, such as 'GTK' (which draws the menus \
180 and buttons). Thus, it must choose implementations of \
181 each dependency (each of which may require further components, and so on)."""),
183 ('List of components', """
184 The main window displays all these components, and the version of each chosen \
185 implementation. The top-most one represents the program you tried to run, and each direct \
186 child is a dependency. The 'Fetch' column shows the amount of data that needs to be \
187 downloaded, or '(cached)' if it is already on this computer.
189 If you are happy with the choices shown, click on the Download (or Run) button to \
190 download (and run) the program."""),
192 ('Choosing different versions', """
193 To control which implementations (versions) are chosen you can click on Preferences \
194 and adjust the network policy and the overall stability policy. These settings affect \
195 all programs run using Zero Install.
197 Alternatively, you can edit the policy of an individual component by clicking on the \
198 button at the end of its line in the table and choosing "Show Versions" from the menu. \
199 See that dialog's help text for more information.
200 """),
202 ('Reporting bugs', """
203 To report a bug, right-click over the component which you think contains the problem \
204 and choose 'Report a Bug...' from the menu. If you don't know which one is the cause, \
205 choose the top one (i.e. the program itself). The program's author can reassign the \
206 bug if necessary, or switch to using a different version of the library.
207 """),
209 ('The cache', """
210 Each version of a program that is downloaded is stored in the Zero Install cache. This \
211 means that it won't need to be downloaded again each time you run the program. The \
212 "0store manage" command can be used to view the cache.
213 """),