Updated 0desktop for GTK 3
[zeroinstall/solver.git] / zeroinstall / gtkui / addbox.py
blobd058b8182330e0d28ae1389ac48d3d636cc01c30
1 """A GTK dialog which lets the user add a new application to their desktop."""
3 # Copyright (C) 2009, Thomas Leonard
4 # See the README file for details, or visit http://0install.net.
6 from zeroinstall import _, gobject
7 import os, sys
8 import gtk
10 from zeroinstall import SafeException
11 from zeroinstall.injector import model
12 from zeroinstall.injector.namespaces import XMLNS_IFACE
13 from zeroinstall.injector.iface_cache import iface_cache
15 _URI_LIST = 0
16 _UTF_16 = 1
18 _RESPONSE_PREV = 0
19 _RESPONSE_NEXT = 1
21 def N_(message): return message
23 categories = [
24 N_('AudioVideo'),
25 N_('Audio'),
26 N_('Video'),
27 N_('Development'),
28 N_('Education'),
29 N_('Game'),
30 N_('Graphics'),
31 N_('Network'),
32 N_('Office'),
33 N_('Settings'),
34 N_('System'),
35 N_('Utility'),
38 class AddBox:
39 """A dialog box which prompts the user to choose the program to be added."""
40 def __init__(self, interface_uri = None):
41 builderfile = os.path.join(os.path.dirname(__file__), 'desktop.ui')
43 builder = gtk.Builder()
44 builder.set_translation_domain('zero-install')
45 builder.add_from_file(builderfile)
46 self.window = builder.get_object('main')
47 self.set_keep_above(True)
49 def set_uri_ok(uri):
50 text = uri.get_text()
51 self.window.set_response_sensitive(_RESPONSE_NEXT, bool(text))
53 uri = builder.get_object('interface_uri')
54 about = builder.get_object('about')
55 icon_widget = builder.get_object('icon')
56 category = builder.get_object('category')
57 dialog_next = builder.get_object('dialog_next')
58 dialog_ok = builder.get_object('dialog_ok')
60 if interface_uri:
61 uri.set_text(interface_uri)
63 uri.connect('changed', set_uri_ok)
64 set_uri_ok(uri)
66 for c in categories:
67 category.append_text(_(c))
68 category.set_active(11)
70 def uri_dropped(eb, drag_context, x, y, selection_data, info, timestamp):
71 uris = selection_data.get_uris()
72 if uris:
73 assert len(uris) == 1, uris
74 data, = uris
75 else:
76 if info == _UTF_16:
77 import codecs
78 data = codecs.getdecoder('utf16')(selection_data.get_data())[0]
79 data = data.split('\n', 1)[0].strip()
80 else:
81 data = selection_data.get_text().split('\n', 1)[0].strip()
82 if self._sanity_check(data):
83 uri.set_text(data)
84 drag_context.finish(True, False, timestamp)
85 self.window.response(_RESPONSE_NEXT)
86 return True
87 if sys.version_info[0] < 3:
88 def TargetEntry(*args): return args
89 else:
90 TargetEntry = gtk.TargetEntry.new
91 self.window.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_HIGHLIGHT,
92 [TargetEntry('text/uri-list', 0, _URI_LIST),
93 TargetEntry('text/x-moz-url', 0, _UTF_16)],
94 gtk.gdk.ACTION_COPY)
95 self.window.drag_dest_add_uri_targets() # Needed for GTK 3
96 self.window.connect('drag-data-received', uri_dropped)
98 nb = builder.get_object('notebook1')
100 def update_details_page():
101 iface = iface_cache.get_interface(model.canonical_iface_uri(uri.get_text()))
102 about.set_text('%s - %s' % (iface.get_name(), iface.summary))
103 icon_path = iface_cache.get_icon_path(iface)
104 from zeroinstall.gtkui import icon
105 icon_pixbuf = icon.load_icon(icon_path)
106 if icon_pixbuf:
107 icon_widget.set_from_pixbuf(icon_pixbuf)
109 feed_category = None
110 for meta in iface.get_metadata(XMLNS_IFACE, 'category'):
111 feed_category = meta.content
112 break
113 if feed_category:
114 i = 0
115 for row in categories:
116 if row.lower() == feed_category.lower():
117 category.set_active(i)
118 break
119 i += 1
120 self.window.set_response_sensitive(_RESPONSE_PREV, True)
122 def finish():
123 from . import xdgutils
124 iface = iface_cache.get_interface(model.canonical_iface_uri(uri.get_text()))
126 try:
127 icon_path = iface_cache.get_icon_path(iface)
128 xdgutils.add_to_menu(iface, icon_path, categories[category.get_active()])
129 except SafeException as ex:
130 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, str(ex))
131 box.run()
132 box.destroy()
133 else:
134 self.window.destroy()
136 def response(box, resp):
137 if resp == _RESPONSE_NEXT:
138 iface = uri.get_text()
139 if not self._sanity_check(iface):
140 return
141 self.window.set_sensitive(False)
142 self.set_keep_above(False)
143 import subprocess
144 child = subprocess.Popen(['0launch',
145 '--gui', '--download-only',
146 '--', iface],
147 stdout = subprocess.PIPE,
148 stderr = subprocess.STDOUT)
149 errors = ['']
150 def output_ready(src, cond):
151 got = os.read(src.fileno(), 100)
152 if got:
153 errors[0] += got
154 else:
155 status = child.wait()
156 self.window.set_sensitive(True)
157 self.set_keep_above(True)
158 if status == 0:
159 update_details_page()
160 nb.next_page()
161 dialog_next.set_property('visible', False)
162 dialog_ok.set_property('visible', True)
163 dialog_ok.grab_focus()
164 else:
165 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
166 _('Failed to run 0launch.\n') + errors[0])
167 box.run()
168 box.destroy()
169 return False
170 return True
171 gobject.io_add_watch(child.stdout,
172 gobject.IO_IN | gobject.IO_HUP,
173 output_ready)
174 elif resp == gtk.RESPONSE_OK:
175 finish()
176 elif resp == _RESPONSE_PREV:
177 dialog_next.set_property('visible', True)
178 dialog_ok.set_property('visible', False)
179 dialog_next.grab_focus()
180 nb.prev_page()
181 self.window.set_response_sensitive(_RESPONSE_PREV, False)
182 else:
183 box.destroy()
184 self.window.connect('response', response)
186 if len(sys.argv) > 1:
187 self.window.response(_RESPONSE_NEXT)
189 def set_keep_above(self, above):
190 if hasattr(self.window, 'set_keep_above'):
191 # This isn't very nice, but GNOME defaults to
192 # click-to-raise and in that mode drag-and-drop
193 # is useless without this...
194 self.window.set_keep_above(above)
196 def _sanity_check(self, uri):
197 if uri.endswith('.tar.bz2') or \
198 uri.endswith('.tar.gz') or \
199 uri.endswith('.exe') or \
200 uri.endswith('.rpm') or \
201 uri.endswith('.deb') or \
202 uri.endswith('.tgz'):
203 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
204 _("This URI (%s) looks like an archive, not a Zero Install feed. Make sure you're using the feed link!") % uri)
205 box.run()
206 box.destroy()
207 return False
208 return True