~ubuntu-branches/ubuntu/karmic/zeroinstall-injector/karmic

« back to all changes in this revision

Viewing changes to zeroinstall/0launch-gui/properties.py

  • Committer: Bazaar Package Importer
  • Author(s): Thomas Leonard
  • Date: 2007-01-23 21:50:46 UTC
  • Revision ID: james.westby@ubuntu.com-20070123215046-3ya2x81i99m5ya8r
Tags: upstream-0.25
ImportĀ upstreamĀ versionĀ 0.25

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from zeroinstall.injector.model import *
 
2
from zeroinstall.injector.iface_cache import iface_cache
 
3
from zeroinstall.injector import writer, namespaces, gpg
 
4
import gtk, sys, os
 
5
import sets     # Note: for Python 2.3; frozenset is only in Python 2.4
 
6
 
 
7
import help_box
 
8
from dialog import Dialog
 
9
from gui import policy
 
10
from impl_list import ImplementationList
 
11
import time
 
12
import dialog
 
13
import compile
 
14
 
 
15
_dialogs = {}   # Interface -> Properties
 
16
 
 
17
tips = gtk.Tooltips()
 
18
 
 
19
# Response codes
 
20
COMPILE = 2
 
21
 
 
22
def enumerate(items):
 
23
        x = 0
 
24
        for i in items:
 
25
                yield x, i
 
26
                x += 1
 
27
 
 
28
def format_para(para):
 
29
        lines = [l.strip() for l in para.split('\n')]
 
30
        return ' '.join(lines)
 
31
 
 
32
def open_in_browser(link):
 
33
        browser = os.environ.get('BROWSER', 'firefox')
 
34
        child = os.fork()
 
35
        if child == 0:
 
36
                # We are the child
 
37
                try:
 
38
                        os.spawnlp(os.P_NOWAIT, browser, browser, link)
 
39
                        os._exit(0)
 
40
                except Exception, ex:
 
41
                        print >>sys.stderr, "Error", ex
 
42
                        os._exit(1)
 
43
        os.waitpid(child, 0)
 
44
 
 
45
class Description(gtk.ScrolledWindow):
 
46
        def __init__(self):
 
47
                gtk.ScrolledWindow.__init__(self, None, None)
 
48
                self.set_shadow_type(gtk.SHADOW_IN)
 
49
                self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
 
50
                description = gtk.TextView()
 
51
                description.set_left_margin(4)
 
52
                description.set_right_margin(4)
 
53
                description.set_wrap_mode(gtk.WRAP_WORD)
 
54
                description.set_editable(False)
 
55
                description.set_cursor_visible(False)
 
56
                description.connect('button-press-event', self.button_press)
 
57
                self.add(description)
 
58
 
 
59
                self.buffer = description.get_buffer()
 
60
                self.heading_style = self.buffer.create_tag(underline = True, scale = 1.2)
 
61
                self.link_style = self.buffer.create_tag(underline = True, foreground = 'blue')
 
62
                description.set_size_request(-1, 100)
 
63
        
 
64
        def button_press(self, tv, bev):
 
65
                if bev.type == gtk.gdk.BUTTON_PRESS and bev.button == 1:
 
66
                        x, y = tv.window_to_buffer_coords(tv.get_window_type(bev.window),
 
67
                                                          int(bev.x), int(bev.y))
 
68
                        itr = tv.get_iter_at_location(x, y)
 
69
                        if itr and self.link_style in itr.get_tags():
 
70
                                if not itr.begins_tag(self.link_style):
 
71
                                        itr.backward_to_tag_toggle(self.link_style)
 
72
                                end = itr.copy()
 
73
                                end.forward_to_tag_toggle(self.link_style)
 
74
                                target = itr.get_text(end).strip()
 
75
                                open_in_browser(target)
 
76
        
 
77
        def set_details(self, interface):
 
78
                buffer = self.buffer
 
79
                heading_style = self.heading_style
 
80
 
 
81
                buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
 
82
 
 
83
                iter = buffer.get_start_iter()
 
84
 
 
85
                buffer.insert_with_tags(iter,
 
86
                        '%s ' % interface.get_name(), heading_style)
 
87
                buffer.insert(iter, '(%s)' % interface.summary)
 
88
 
 
89
                buffer.insert(iter, '\n%s\n' % interface.uri)
 
90
 
 
91
                # (converts to local time)
 
92
                if interface.last_modified:
 
93
                        buffer.insert(iter, '\nLast upstream change: %s' % time.ctime(interface.last_modified))
 
94
 
 
95
                if interface.last_checked:
 
96
                        buffer.insert(iter, '\nLast checked: %s' % time.ctime(interface.last_checked))
 
97
 
 
98
                buffer.insert_with_tags(iter, '\n\nDescription\n', heading_style)
 
99
 
 
100
                paragraphs = [format_para(p) for p in (interface.description or "-").split('\n\n')]
 
101
 
 
102
                buffer.insert(iter, '\n\n'.join(paragraphs))
 
103
                buffer.insert(iter, '\n')
 
104
 
 
105
                if hasattr(interface, 'get_metadata'):
 
106
                        need_gap = True
 
107
                        for x in interface.get_metadata(namespaces.XMLNS_IFACE, 'homepage'):
 
108
                                if need_gap:
 
109
                                        buffer.insert(iter, '\n')
 
110
                                        need_gap = False
 
111
                                buffer.insert(iter, 'Homepage: ')
 
112
                                buffer.insert_with_tags(iter, '%s\n' % x.content, self.link_style)
 
113
 
 
114
                if hasattr(iface_cache, 'get_cached_signatures'):
 
115
                        buffer.insert_with_tags(iter, '\nSignatures\n', heading_style)
 
116
                        sigs = iface_cache.get_cached_signatures(interface.uri)
 
117
                        if sigs:
 
118
                                for sig in sigs:
 
119
                                        if isinstance(sig, gpg.ValidSig):
 
120
                                                name = '<unknown>'
 
121
                                                if hasattr(sig, 'get_details'):
 
122
                                                        details = sig.get_details()
 
123
                                                        for item in details:
 
124
                                                                if item[0] in ('pub', 'uid') and len(item) > 9:
 
125
                                                                        name = item[9]
 
126
                                                                        break
 
127
                                                buffer.insert_with_tags(iter, 'Valid signature by "%s"\n- Dated: %s\n- Fingerprint: %s\n' %
 
128
                                                                (name, time.ctime(sig.get_timestamp()), sig.fingerprint))
 
129
                                                if not sig.is_trusted():
 
130
                                                        if interface.uri.startswith('/'):
 
131
                                                                buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list\n')
 
132
                                                        else:
 
133
                                                                buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list (either you removed it, or '
 
134
                                                                                                'you trust one of the other signatures)\n')
 
135
                                        else:
 
136
                                                buffer.insert_with_tags(iter, '%s\n' % sig)
 
137
                        else:
 
138
                                buffer.insert_with_tags(iter, 'No signature information (old style interface or out-of-date cache)\n')
 
139
 
 
140
 
 
141
class Feeds(gtk.VPaned):
 
142
        URI = 0
 
143
        ARCH = 1
 
144
        USED = 2
 
145
 
 
146
        def __init__(self, interface):
 
147
                gtk.VPaned.__init__(self)
 
148
                self.set_border_width(4)
 
149
                self.interface = interface
 
150
 
 
151
                hbox = gtk.HBox(False, 4)
 
152
                self.pack1(hbox, False, False)
 
153
 
 
154
                self.model = gtk.ListStore(str, str, bool)
 
155
 
 
156
                self.lines = self.build_model()
 
157
                for line in self.lines:
 
158
                        self.model.append(line)
 
159
 
 
160
                self.swin = gtk.ScrolledWindow()
 
161
                self.swin.set_shadow_type(gtk.SHADOW_IN)
 
162
                self.swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
163
                hbox.pack_start(self.swin, True, True, 0)
 
164
 
 
165
                buttons_vbox = gtk.VButtonBox()
 
166
                buttons_vbox.set_layout(gtk.BUTTONBOX_START)
 
167
                buttons_vbox.set_spacing(4)
 
168
 
 
169
                add_remote_feed_button = dialog.MixedButton(_('Add Remote Feed...'), gtk.STOCK_ADD, 0.0)
 
170
                add_remote_feed_button.connect('clicked',
 
171
                        lambda b: add_remote_feed(self.get_toplevel(), interface))
 
172
                buttons_vbox.add(add_remote_feed_button)
 
173
 
 
174
                add_local_feed_button = dialog.MixedButton(_('Add Local Feed...'), gtk.STOCK_ADD, 0.0)
 
175
                add_local_feed_button.connect('clicked', lambda b: add_local_feed(interface))
 
176
                tips.set_tip(add_local_feed_button,
 
177
                        _('If you have another implementation of this interface (e.g., a '
 
178
                          'CVS checkout), you can add it to the list by registering the XML '
 
179
                          'feed file that came with it.'))
 
180
                buttons_vbox.add(add_local_feed_button)
 
181
 
 
182
                self.remove_feed_button = dialog.MixedButton(_('Remove Feed'), gtk.STOCK_REMOVE, 0.0)
 
183
                def remove_feed(button):
 
184
                        model, iter = self.tv.get_selection().get_selected()
 
185
                        feed_uri = model[iter][Feeds.URI]
 
186
                        for x in interface.feeds:
 
187
                                if x.uri == feed_uri:
 
188
                                        if x.user_override:
 
189
                                                interface.feeds.remove(x)
 
190
                                                writer.save_interface(interface)
 
191
                                                policy.recalculate()
 
192
                                                return
 
193
                                        else:
 
194
                                                dialog.alert(self.get_toplevel(),
 
195
                                                        _("Can't remove '%s' as you didn't add it.") % feed_uri)
 
196
                                                return
 
197
                        raise Exception("Missing feed '%s'!" % feed_uri)
 
198
                self.remove_feed_button.connect('clicked', remove_feed)
 
199
                buttons_vbox.add(self.remove_feed_button)
 
200
 
 
201
                hbox.pack_start(buttons_vbox, False, True, 0)
 
202
 
 
203
                self.tv = gtk.TreeView(self.model)
 
204
                text = gtk.CellRendererText()
 
205
                self.tv.append_column(gtk.TreeViewColumn('Source', text, text = Feeds.URI, sensitive = Feeds.USED))
 
206
                self.tv.append_column(gtk.TreeViewColumn('Arch', text, text = Feeds.ARCH, sensitive = Feeds.USED))
 
207
                self.swin.add(self.tv)
 
208
 
 
209
                self.description = Description()
 
210
                self.add2(self.description)
 
211
 
 
212
                sel = self.tv.get_selection()
 
213
                sel.set_mode(gtk.SELECTION_BROWSE)
 
214
                sel.connect('changed', self.sel_changed)
 
215
                sel.select_path((0,))
 
216
        
 
217
        def build_model(self):
 
218
                usable_feeds = sets.ImmutableSet(policy.usable_feeds(self.interface))
 
219
                unusable_feeds = sets.ImmutableSet(self.interface.feeds) - usable_feeds
 
220
 
 
221
                out = [[self.interface.uri, None, True]]
 
222
 
 
223
                if self.interface.feeds:
 
224
                        for feed in usable_feeds:
 
225
                                out.append([feed.uri, feed.arch, True])
 
226
                        for feed in unusable_feeds:
 
227
                                out.append([feed.uri, feed.arch, False])
 
228
                return out
 
229
 
 
230
        def sel_changed(self, sel):
 
231
                model, iter = sel.get_selected()
 
232
                if not iter: return     # build in progress
 
233
                iface = model[iter][Feeds.URI]
 
234
                self.remove_feed_button.set_sensitive(iface != self.interface.uri)
 
235
                self.description.set_details(policy.get_interface(iface))
 
236
        
 
237
        def updated(self):
 
238
                new_lines = self.build_model()
 
239
                if new_lines != self.lines:
 
240
                        self.lines = new_lines
 
241
                        self.model.clear()
 
242
                        for line in self.lines:
 
243
                                self.model.append(line)
 
244
                        self.tv.get_selection().select_path((0,))
 
245
                else:
 
246
                        self.sel_changed(self.tv.get_selection())
 
247
 
 
248
class Properties(Dialog):
 
249
        interface = None
 
250
        use_list = None
 
251
 
 
252
        def __init__(self, interface):
 
253
                Dialog.__init__(self)
 
254
                self.interface = interface
 
255
                self.set_title('Interface ' + interface.get_name())
 
256
                self.set_default_size(-1,
 
257
                                      gtk.gdk.screen_height() / 3)
 
258
 
 
259
                self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
 
260
                self.compile_button = self.add_mixed_button(_('Compile'),
 
261
                                                        gtk.STOCK_CONVERT, COMPILE)
 
262
                self.compile_button.connect('clicked', lambda b: compile.compile(interface))
 
263
                self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CANCEL)
 
264
                self.set_default_response(gtk.RESPONSE_CANCEL)
 
265
 
 
266
                def response(dialog, resp):
 
267
                        if resp == gtk.RESPONSE_CANCEL:
 
268
                                self.destroy()
 
269
                        #elif resp == 1:
 
270
                        #       policy.begin_iface_download(interface, True)
 
271
                        elif resp == gtk.RESPONSE_HELP:
 
272
                                properties_help.display()
 
273
                self.connect('response', response)
 
274
 
 
275
                notebook = gtk.Notebook()
 
276
                self.vbox.pack_start(notebook, True, True, 0)
 
277
 
 
278
                feeds = Feeds(interface)
 
279
                notebook.append_page(feeds, gtk.Label(_('Feeds')))
 
280
                notebook.append_page(self.build_versions_column(interface), gtk.Label(_('Versions')))
 
281
 
 
282
                self.update_list()
 
283
                notebook.show_all()
 
284
 
 
285
                feeds.tv.grab_focus()
 
286
 
 
287
                def updated():
 
288
                        self.update_list()
 
289
                        feeds.updated()
 
290
                        self.shade_compile()
 
291
                self.connect('destroy', lambda s: policy.watchers.remove(updated))
 
292
                policy.watchers.append(updated)
 
293
                self.shade_compile()
 
294
        
 
295
        def shade_compile(self):
 
296
                # Note: we don't want to actually fetch the source interfaces at
 
297
                # this point, so we check whether:
 
298
                # - We have a feed of type 'src' (not fetched), or
 
299
                # - We have a source implementation in a regular feed
 
300
                have_src = False
 
301
                for f in self.interface.feeds:
 
302
                        if f.machine == 'src':
 
303
                                have_src = True
 
304
                                break
 
305
                if have_src is False:
 
306
                        # Don't have any src feeds. Do we have a source implementation
 
307
                        # as part of a regular feed?
 
308
                        impls = self.interface.implementations.values()
 
309
                        for f in policy.usable_feeds(self.interface):
 
310
                                try:
 
311
                                        feed_iface = policy.get_interface(f.uri)
 
312
                                        if feed_iface.implementations:
 
313
                                                impls.extend(feed_iface.implementations.values())
 
314
                                except NeedDownload:
 
315
                                        pass    # OK, will get called again later
 
316
                                except Exception, ex:
 
317
                                        warn("Failed to load feed '%s': %s", f.uri, str(ex))
 
318
                        for x in impls:
 
319
                                if x.machine == 'src':
 
320
                                        have_src = True
 
321
                                        break
 
322
                self.compile_button.set_sensitive(have_src)
 
323
        
 
324
        def update_list(self):
 
325
                impls = policy.get_ranked_implementations(self.interface)
 
326
                self.use_list.set_items(impls)
 
327
 
 
328
        def build_versions_column(self, interface):
 
329
                assert self.use_list is None
 
330
 
 
331
                vbox = gtk.VBox(False, 2)
 
332
                vbox.set_border_width(4)
 
333
 
 
334
                hbox = gtk.HBox(False, 2)
 
335
                vbox.pack_start(hbox, False, True, 2)
 
336
 
 
337
                eb = gtk.EventBox()
 
338
                stability = gtk.combo_box_new_text()
 
339
                eb.add(stability)
 
340
                stability.append_text('Use default setting')
 
341
                stability.set_active(0)
 
342
                for i, x in enumerate((stable, testing, developer)):
 
343
                        stability.append_text(str(x).capitalize())
 
344
                        if x is interface.stability_policy:
 
345
                                stability.set_active(i + 1)
 
346
                hbox.pack_start(gtk.Label('Preferred stability:'), False, True, 2)
 
347
                hbox.pack_start(eb, False, True, 0)
 
348
                def set_stability_policy(combo):
 
349
                        i = stability.get_active()
 
350
                        if i == 0:
 
351
                                new_stability = None
 
352
                        else:
 
353
                                name = stability.get_model()[i][0].lower()
 
354
                                new_stability = stability_levels[name]
 
355
                        interface.set_stability_policy(new_stability)
 
356
                        writer.save_interface(interface)
 
357
                        policy.recalculate()
 
358
                stability.connect('changed', set_stability_policy)
 
359
                tips.set_tip(eb, _('Implementations at this stability level or higher '
 
360
                                'will be used in preference to others. You can use this '
 
361
                                'to override the global "Help test new versions" setting '
 
362
                                'just for this interface.'))
 
363
 
 
364
                self.use_list = ImplementationList(interface)
 
365
                vbox.pack_start(self.use_list, True, True, 2)
 
366
 
 
367
                return vbox
 
368
        
 
369
def add_remote_feed(parent, interface):
 
370
        d = gtk.MessageDialog(parent, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL,
 
371
                _('Enter the URL of the new source of implementations of this interface:'))
 
372
        d.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
 
373
        d.set_default_response(gtk.RESPONSE_OK)
 
374
        entry = gtk.Entry()
 
375
 
 
376
        align = gtk.VBox(False, 0)
 
377
        align.set_border_width(4)
 
378
        align.add(entry)
 
379
        d.vbox.pack_start(align)
 
380
        entry.set_activates_default(True)
 
381
 
 
382
        entry.set_text('')
 
383
 
 
384
        d.vbox.show_all()
 
385
 
 
386
        error_label = gtk.Label('')
 
387
        error_label.set_padding(4, 4)
 
388
        align.pack_start(error_label)
 
389
 
 
390
        def error(message):
 
391
                if message:
 
392
                        error_label.set_text(message)
 
393
                        error_label.show()
 
394
                else:
 
395
                        error_label.hide()
 
396
 
 
397
        def download_done(iface):
 
398
                d.set_sensitive(True)
 
399
                if not iface.name:
 
400
                        error('Failed to read interface')
 
401
                        return
 
402
                if not iface.feed_for:
 
403
                        error("Interface '%s' is not a feed." % iface.get_name())
 
404
                elif interface.uri not in iface.feed_for:
 
405
                        error("Interface is not a feed for '%s'.\nOnly for:\n%s" %
 
406
                                (interface.uri, '\n'.join(iface.feed_for)))
 
407
                elif iface.uri in [f.uri for f in interface.feeds]:
 
408
                        error("Feed from '%s' has already been added!" % iface.uri)
 
409
                else:
 
410
                        interface.feeds.append(Feed(iface.uri, arch = None, user_override = True))
 
411
                        writer.save_interface(interface)
 
412
                        d.destroy()
 
413
                        policy.recalculate()
 
414
 
 
415
        def response(d, resp):
 
416
                error(None)
 
417
                if resp == gtk.RESPONSE_OK:
 
418
                        try:
 
419
                                url = entry.get_text()
 
420
                                if not url:
 
421
                                        raise SafeException(_('Enter a URL'))
 
422
                                iface = policy.get_interface(url)
 
423
                                policy.begin_iface_download(iface) # Force a refresh
 
424
                                d.set_sensitive(False)
 
425
                                policy.add_dl_callback(url, lambda: download_done(iface))
 
426
                        except SafeException, ex:
 
427
                                error(str(ex))
 
428
                else:
 
429
                        d.destroy()
 
430
                        return
 
431
        d.connect('response', response)
 
432
        d.show()
 
433
 
 
434
def add_local_feed(interface):
 
435
        sel = gtk.FileSelection(_('Select XML feed file'))
 
436
        sel.set_has_separator(False)
 
437
        def ok(b):
 
438
                from xml.dom import minidom
 
439
                from zeroinstall.injector import reader
 
440
                feed = sel.get_filename()
 
441
                try:
 
442
                        if hasattr(policy, 'get_feed_targets'):
 
443
                                feed_targets = policy.get_feed_targets(feed)
 
444
                                if interface not in feed_targets:
 
445
                                        raise Exception("Not a valid feed for '%s'; this is a feed for:\n%s" %
 
446
                                                        (interface.uri,
 
447
                                                        '\n'.join([f.uri for f in feed_targets])))
 
448
                                if interface.get_feed(feed):
 
449
                                        dialog.alert(None, 'This feed is already registered.')
 
450
                                else:
 
451
                                        interface.feeds.append(Feed(feed, user_override = True, arch = None))
 
452
                        else:
 
453
                                doc = minidom.parse(feed)
 
454
                                uri = doc.documentElement.getAttribute('uri')
 
455
                                if not uri:
 
456
                                        raise Exception("Missing uri attribute in interface file '%s'" % feed)
 
457
                                if uri != interface.uri:
 
458
                                        raise Exception("Feed is for interface '%s', not '%s'" %
 
459
                                                        (uri, interface.uri))
 
460
                                if feed in interface.feeds:
 
461
                                        raise Exception("Feed is already registered")
 
462
                                interface.feeds.append(feed)
 
463
                        writer.save_interface(interface)
 
464
                        sel.destroy()
 
465
                        reader.update_from_cache(interface)
 
466
                        policy.recalculate()
 
467
                except Exception, ex:
 
468
                        dialog.alert(None, "Error in feed file '%s':\n\n%s" % (feed, str(ex)))
 
469
                
 
470
        sel.ok_button.connect('clicked', ok)
 
471
        sel.cancel_button.connect('clicked', lambda b: sel.destroy())
 
472
        sel.show()
 
473
        
 
474
def edit(interface):
 
475
        assert isinstance(interface, Interface)
 
476
        if interface in _dialogs:
 
477
                _dialogs[interface].destroy()
 
478
        _dialogs[interface] = Properties(interface)
 
479
        _dialogs[interface].show()
 
480
 
 
481
properties_help = help_box.HelpBox("Injector Properties Help",
 
482
('Interface properties', """
 
483
This window displays information about an interface. There are two tabs at the top: \
 
484
Feeds shows the places where the injector looks for implementations of the interface, while \
 
485
Versions shows the list of implementations found (from all feeds) in order of preference."""),
 
486
 
 
487
('The Feeds tab', """
 
488
At the top is a list of feeds. By default, the injector uses the full name of the interface \
 
489
as the default feed location (so if you ask it to run the program "http://foo/bar.xml" then it will \
 
490
by default get the list of versions by downloading "http://foo/bar.xml".
 
491
 
 
492
You can add and remove feeds using the buttons on the right. The main feed may also add \
 
493
some extra feeds itself. If you've checked out a developer version of a program, you can use \
 
494
the 'Add Local Feed...' button to let the injector know about it, for example.
 
495
 
 
496
Below the list of feeds is a box describing the selected one:
 
497
 
 
498
- At the top is its short name.
 
499
- Below that is the address (a URL or filename).
 
500
- 'Last upstream change' shows the version of the cached copy of the interface file.
 
501
- 'Last checked' is the last time a fresh copy of the upstream interface file was \
 
502
downloaded.
 
503
- Then there is a longer description of the interface."""),
 
504
 
 
505
('The Versions tab', """
 
506
This tab shows a list of all known implementations of the interface, from all the feeds. \
 
507
The columns have the following meanings:
 
508
 
 
509
Version gives the version number. High-numbered versions are considered to be \
 
510
better than low-numbered ones.
 
511
 
 
512
Released gives the date this entry was added to the feed.
 
513
 
 
514
Stability is 'stable' if the implementation is believed to be stable, 'buggy' if \
 
515
it is known to contain serious bugs, and 'testing' if its stability is not yet \
 
516
known. This information is normally supplied and updated by the author of the \
 
517
software, but you can override their rating (overridden values are shown in upper-case). \
 
518
You can also use the special level 'preferred'.
 
519
 
 
520
C(ached) indicates whether the implementation is already stored on your computer. \
 
521
In off-line mode, only cached implementations are considered for use.
 
522
 
 
523
Arch indicates what kind of computer system the implementation is for, or 'any' \
 
524
if it works with all types of system.
 
525
"""),
 
526
('Sort order', """
 
527
The implementations are listed in the injector's currently preferred order (the one \
 
528
at the top will actually be used). Usable implementations all come before unusable \
 
529
ones.
 
530
 
 
531
Unusable ones are those for incompatible \
 
532
architectures, those marked as 'buggy', versions explicitly marked as incompatible with \
 
533
another interface you are using and, in off-line mode, uncached implementations. Unusable \
 
534
implementations are shown crossed out.
 
535
 
 
536
For the usable implementations, the order is as follows:
 
537
 
 
538
- Preferred implementations come first.
 
539
 
 
540
- Then, if network use is set to 'Minimal', cached implementations come before \
 
541
non-cached.
 
542
 
 
543
- Then, implementations at or above the selected stability level come before all others.
 
544
 
 
545
- Then, higher-numbered versions come before low-numbered ones.
 
546
 
 
547
- Then cached come before non-cached (for 'Full' network use mode).
 
548
"""),
 
549
 
 
550
('Compiling', """
 
551
If there is no binary available for your system then you may be able to compile one from \
 
552
source by clicking on the Compile button. If no source is available, the Compile button will \
 
553
be shown shaded.
 
554
"""))