Moved into a sub-dir, so that a svn checkout has the same structure as
[rox-lib/lack.git] / ROX-Lib2 / python / rox / mime_handler.py
blob2f7a5e168e1f91192d7afb592f16a211d1b8da48
1 """This module allows applications to set themselves as the default handler for
2 a particular MIME type. This is generally not a good thing to do, because it
3 annoys users if programs fight over the defaults."""
5 import os
7 import rox
8 from rox import _, mime, choices, basedir
10 SITE='rox.sourceforge.net'
12 _TNAME = 0
13 _COMMENT = 1
14 _CURRENT = 2
15 _INSTALL = 3
16 _ICON = 4
17 _UNINSTALL = 5
18 _IS_OURS = 6 # Hidden column
20 def load_path(site, dir, leaf):
21 path=None
22 try:
23 path=basedir.load_first_config(site, dir, leaf)
24 if not path:
25 path=choices.load(dir, leaf)
26 except:
27 pass
28 return path
30 def save_path(site, dir, leaf, create=1):
31 filer=basedir.load_first_config(SITE, 'ROX-Filer')
33 if filer and os.path.isdir(filer):
34 path=basedir.save_config_path(site, dir)
35 path=os.path.join(path, leaf)
36 else:
37 path=choices.save(dir, leaf, create)
39 return path
41 class InstallList(rox.Dialog):
42 """Dialog to select installation of MIME type handlers"""
43 def __init__(self, application, itype, dir, types, info=None, check=True,
44 site=SITE):
45 """Create the install list dialog.
46 application - path to application to install
47 itype - string describing the type of action to install
48 dir - directory in Choices to store links in
49 types - list of MIME types
50 info - optional message to display below list
51 check - if true (the default), check for existing entries"""
52 rox.Dialog.__init__(self, title=_('Install %s') % itype,
53 buttons=(rox.g.STOCK_CANCEL, rox.g.RESPONSE_CLOSE,
54 rox.g.STOCK_OK, rox.g.RESPONSE_ACCEPT))
56 self.itype=itype
57 self.dir=dir
58 self.site=site
59 self.types=types
60 self.app=application
61 self.aname=os.path.basename(application)
62 self.check=check
64 vbox=self.vbox
66 swin = rox.g.ScrolledWindow()
67 swin.set_size_request(-1, 160)
68 swin.set_border_width(4)
69 swin.set_policy(rox.g.POLICY_NEVER, rox.g.POLICY_ALWAYS)
70 swin.set_shadow_type(rox.g.SHADOW_IN)
71 vbox.pack_start(swin, True, True, 0)
73 self.model = rox.g.ListStore(str, str, str, int, rox.g.gdk.Pixbuf,
74 int, int)
75 view = rox.g.TreeView(self.model)
76 self.view = view
77 swin.add(view)
78 view.set_search_column(1)
80 cell = rox.g.CellRendererPixbuf()
81 column = rox.g.TreeViewColumn('', cell, pixbuf = _ICON)
82 view.append_column(column)
84 cell = rox.g.CellRendererText()
85 column = rox.g.TreeViewColumn(_('Type'), cell, text = _TNAME)
86 view.append_column(column)
87 column.set_sort_column_id(_TNAME)
89 cell = rox.g.CellRendererText()
90 column = rox.g.TreeViewColumn(_('Name'), cell, text = _COMMENT)
91 view.append_column(column)
92 column.set_sort_column_id(_COMMENT)
94 if check:
95 cell = rox.g.CellRendererText()
96 column = rox.g.TreeViewColumn(_('Current'), cell, text = _CURRENT)
97 view.append_column(column)
98 column.set_sort_column_id(_CURRENT)
100 cell = rox.g.CellRendererToggle()
101 cell.set_property('activatable', True)
102 cell.connect('toggled', self.install_toggled, self.model)
103 column = rox.g.TreeViewColumn(_('Install?'), cell, active = _INSTALL)
104 view.append_column(column)
105 column.set_sort_column_id(_INSTALL)
107 cell = rox.g.CellRendererToggle()
108 cell.connect('toggled', self.uninstall_toggled, self.model)
109 column = rox.g.TreeViewColumn(_('Uninstall?'), cell, active = _UNINSTALL,
110 activatable= _IS_OURS)
111 view.append_column(column)
112 column.set_sort_column_id(_UNINSTALL)
114 view.get_selection().set_mode(rox.g.SELECTION_NONE)
116 if info:
117 hbox=rox.g.HBox(spacing=4)
118 img=rox.g.image_new_from_stock(rox.g.STOCK_DIALOG_INFO,
119 rox.g.ICON_SIZE_DIALOG)
120 hbox.pack_start(img)
122 lbl=rox.g.Label(info)
123 lbl.set_line_wrap(True)
124 hbox.pack_start(lbl)
126 vbox.pack_start(hbox)
128 vbox.show_all()
130 self.load_types()
132 def install_toggled(self, cell, path, model):
133 """Handle the CellRedererToggle stuff for the install column"""
134 if type(path) == str:
135 # Does this vary by pygtk version?
136 titer=model.iter_nth_child(None, int(path))
137 else:
138 titer=model.get_iter(path)
139 model.set_value(titer, _INSTALL, not cell.get_active())
140 if not cell.get_active():
141 model.set_value(titer, _UNINSTALL, 0)
143 def uninstall_toggled(self, cell, path, model):
144 """Handle the CellRedererToggle stuff for the uninstall column"""
145 if type(path) == str:
146 # Does this vary by pygtk version?
147 titer=model.iter_nth_child(None, int(path))
148 else:
149 titer=model.get_iter(path)
150 avail=model.get_value(titer, _IS_OURS)
151 if avail:
152 model.set_value(titer, _UNINSTALL, not cell.get_active())
153 if not cell.get_active():
154 model.set_value(titer, _INSTALL, 0)
155 else:
156 model.set_value(titer, _UNINSTALL, 0)
158 def load_types(self):
159 """Load list of types into window"""
160 self.model.clear()
162 for tname in self.types:
163 mime_type=mime.lookup(tname)
164 if self.check:
165 old=load_path(self.site, self.dir,
166 '%s_%s' %
167 (mime_type.media, mime_type.subtype))
168 if old and os.path.islink(old):
169 old=os.readlink(old)
170 oname=os.path.basename(old)
171 elif old:
172 oname='script'
173 else:
174 oname=''
176 if old==self.app:
177 dinstall=False
178 can_un=True
179 else:
180 dinstall=True
181 can_un=False
182 else:
183 dinstall=True
184 can_un=False
185 oname=''
187 icon=mime_type.get_icon(mime.ICON_SIZE_SMALL)
189 titer=self.model.append()
190 self.model.set(titer, _TNAME, tname,
191 _COMMENT, mime_type.get_comment(),
192 _INSTALL, dinstall,
193 _UNINSTALL, False, _IS_OURS, can_un)
194 if self.check:
195 self.model.set(titer, _CURRENT, oname)
196 if icon:
197 self.model.set(titer, _ICON, icon)
200 def get_active(self):
201 """Return list of types selected for installing"""
202 titer=self.model.get_iter_first()
203 active=[]
204 while titer:
205 if self.model.get_value(titer, _INSTALL):
206 active.append(self.model.get_value(titer, _TNAME))
207 titer=self.model.iter_next(titer)
209 return active
211 def get_uninstall(self):
212 """Return list of types selected for uninstalling"""
213 titer=self.model.get_iter_first()
214 uninstall=[]
215 while titer:
216 if self.model.get_value(titer, _UNINSTALL) and self.model.get_value(titer, _IS_OURS):
217 uninstall.append(self.model.get_value(titer, _TNAME))
218 titer=self.model.iter_next(titer)
220 return uninstall
222 def _run_by_injector():
223 """Internal function."""
224 try:
225 from zeroinstall.injector import basedir
226 for d in basedir.xdg_cache_dirs:
227 if rox.app_dir.find(d)==0:
228 # Applicaion is in a cache dir
229 return True
230 elif rox._roxlib_dir.find(d)==0:
231 # ROX-Lib is in a cache dir, we are probably being run by the
232 # injector
233 return True
235 except:
236 pass
237 return False
239 def _install_at(path, app_dir, injint):
240 """Internal function. Set one type."""
241 tmp=path+'.tmp%d' % os.getpid()
242 if injint and _run_by_injector(app_dir):
243 f=file(tmp, 'w')
244 f.write('#!/bin/sh\n')
245 f.write('0launch -c "%s" "$@"\n' % injint)
246 f.close()
247 os.chmod(tmp, 0755)
248 else:
249 os.symlink(app_dir, tmp)
251 if os.access(path, os.F_OK):
252 os.remove(path)
253 os.rename(tmp, path)
255 def _install_type_handler(types, dir, desc, application=None, overwrite=True,
256 info=None, injint=None):
257 """Internal function. Does the work of setting MIME-types or MIME-thumb"""
258 if len(types)<1:
259 return
261 if not application:
262 application=rox.app_dir
263 if application[0]!='/':
264 application=os.path.abspath(application)
266 win=InstallList(application, desc, dir, types, info)
268 if win.run()!=int(rox.g.RESPONSE_ACCEPT):
269 win.destroy()
270 return
272 try:
273 types=win.get_active()
275 for tname in types:
276 mime_type = mime.lookup(tname)
278 sname=save_path(SITE, dir,
279 '%s_%s' % (mime_type.media, mime_type.subtype))
280 _install_at(sname, application, injint)
282 types=win.get_uninstall()
284 for tname in types:
285 mime_type = mime.lookup(tname)
287 sname=save_path(SITE, dir,
288 '%s_%s' % (mime_type.media, mime_type.subtype))
289 os.remove(sname)
290 finally:
291 win.destroy()
293 run_action_msg=_("""Run actions can be changed by selecting a file of the appropriate type in the Filer and selecting the menu option 'Set Run Action...'""")
294 def install_run_action(types, application=None, overwrite=True, injint=None):
295 """Install application as the run action for 1 or more types.
296 application should be the full path to the AppDir.
297 If application is None then it is the running program which will
298 be installed. If overwrite is False then existing run actions will
299 not be changed. The user is asked to confirm the setting for each
300 type."""
301 _install_type_handler(types, "MIME-types", _("run action"),
302 application, overwrite, run_action_msg,
303 injint)
305 def install_thumbnailer(types, application=None, overwrite=True, injint=None):
306 """Install application as the thumbnail handler for 1 or more types.
307 application should be the full path to the AppDir.
308 If application is None then it is the running program which will
309 be installed. If overwrite is False then existing thumbnailerss will
310 not be changed. The user is asked to confirm the setting for each
311 type."""
312 _install_type_handler(types, "MIME-thumb", _("thumbnail handler"),
313 application, overwrite, _("""Thumbnail handlers provide support for creating thumbnail images of types of file. The filer can generate thumbnails for most types of image (JPEG, PNG, etc.) but relies on helper applications for the others."""),
314 injint)
316 def install_send_to_types(types, application=None, injint=None):
317 """Install application in the SendTo menu for 1 or more types.
318 application should be the full path to the AppDir.
319 If application is None then it is the running program which will
320 be installed. The user is asked to confirm the setting for each
321 type."""
322 if len(types)<1:
323 return
325 if not application:
326 application=rox.app_dir
327 if application[0]!='/':
328 application=os.path.abspath(application)
330 win=InstallList(application, _('type handler'), 'SendTo', types,
331 _("""The application can handle files of these types. Click on OK to add it to the SendTo menu for the type of file, and also the customized File menu."""),
332 check=False)
334 if win.run()!=int(rox.g.RESPONSE_ACCEPT):
335 win.destroy()
336 return
338 types=win.get_active()
340 for tname in types:
341 mime_type=mime.lookup(tname)
343 sname=save_path(SITE, 'SendTo/.%s_%s' % (mime_type.media,
344 mime_type.subtype),
345 win.aname)
346 _install_at(sname, application, injint)
348 types=win.get_uninstall()
350 for tname in types:
351 mime_type=mime.lookup(tname)
353 sname=save_path(SITE, 'SendTo/.%s_%s' % (mime_type.media,
354 mime_type.subtype),
355 win.aname)
356 os.remove(sname)
358 win.destroy()
360 def install_from_appinfo(appdir = rox.app_dir, injint=None):
361 """Read the AppInfo file from the AppDir and perform the installations
362 indicated. The elements to use are <CanThumbnail> and <CanRun>, each containing
363 a number of <MimeType type='...'/> elements.
364 appdir - Path to application (defaults to current app)
365 injint - Zero install injector interface, or None if none
367 import rox.AppInfo
369 app_info_path = os.path.join(appdir, 'AppInfo.xml')
370 ainfo = rox.AppInfo.AppInfo(app_info_path)
372 can_run = ainfo.getCanRun()
373 can_thumbnail = ainfo.getCanThumbnail()
374 if can_run or can_thumbnail:
375 install_run_action(can_run, appdir, injint)
376 install_thumbnailer(can_thumbnail, appdir, injint)
377 install_send_to_types(can_run, appdir, injint)
378 else:
379 raise Exception('Internal error: No actions found in %s. '
380 'Check your namespaces!' % app_info_path)