Added --systray option to the GUI
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / 0launch-gui / mainwindow.py
blob8aed2cdb750de6e68e8745550bee288336307d8b
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 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
12 import dialog
13 from zeroinstall.gtkui import gtkutils
14 from zeroinstall.gtkui import help_box
16 tips = gtk.Tooltips()
18 SHOW_PREFERENCES = 0
20 class MainWindow:
21 progress = None
22 progress_area = None
23 browser = None
24 window = None
25 cancel_download_and_run = None
26 policy = None
27 comment = None
28 systray_icon = None
30 def __init__(self, policy, widgets, download_only):
31 self.policy = policy
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')
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)
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):
67 self.window.destroy()
68 sys.exit(1)
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:
76 gui_help.display()
77 elif resp == SHOW_PREFERENCES:
78 import preferences
79 preferences.show_preferences(policy)
80 self.window.connect('response', response)
81 self.window.realize() # Make busy pointer work, even with --systray
83 def destroy(self):
84 self.window.destroy()
86 def show(self):
87 self.window.show()
89 def set_response_sensitive(self, response, sensitive):
90 self.window.set_response_sensitive(response, sensitive)
92 @tasks.async
93 def download_and_run(self, run_button, cancelled):
94 try:
95 downloaded = self.policy.download_uncached_implementations()
97 if downloaded:
98 # We need to wait until everything is downloaded...
99 blockers = [downloaded, cancelled]
100 yield blockers
101 tasks.check(blockers)
103 if cancelled.happened:
104 return
106 if self.policy.get_uncached_implementations():
107 dialog.alert(self.window, _('Not all downloads succeeded; cannot run program.'))
108 else:
109 from zeroinstall.injector import selections
110 sels = selections.Selections(self.policy)
111 doc = sels.toDOM()
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
116 except SystemExit:
117 raise
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)
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 def use_systray_icon(self):
171 try:
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)
175 else:
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
182 self.show()
183 self.systray_icon.set_visible(False)
184 self.systray_icon = None
186 def report_exception(self, ex):
187 if not isinstance(ex, SafeException):
188 import traceback
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)')
193 else:
194 dialog.alert(self.window, str(ex))
196 gui_help = help_box.HelpBox("Injector Help",
197 ('Overview', """
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 \
201 component to use.
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.
226 """),
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.
233 """),
235 ('The cache', """
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.
239 """),