Allow "" as a valid value in a OptionsBox menu (reported by Guido Schimmels).
[rox-lib.git] / python / rox / mime_handler.py
blob2808d2ea7f188185ae77425147e7c9fde9d27f50
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
10 _TNAME = 0
11 _COMMENT = 1
12 _CURRENT = 2
13 _INSTALL = 3
14 _ICON = 4
15 _UNINSTALL = 5
16 _IS_OURS = 6 # Hidden column
18 class InstallList(rox.Dialog):
19 """Dialog to select installation of MIME type handlers"""
20 def __init__(self, application, itype, dir, types, info=None, check=True):
21 """Create the install list dialog.
22 application - path to application to install
23 itype - string describing the type of action to install
24 dir - directory in Choices to store links in
25 types - list of MIME types
26 info - optional message to display below list
27 check - if true (the default), check for existing entries"""
28 rox.Dialog.__init__(self, title='Install %s' % itype,
29 buttons=(rox.g.STOCK_CANCEL, rox.g.RESPONSE_CLOSE,
30 rox.g.STOCK_OK, rox.g.RESPONSE_ACCEPT))
32 self.itype=itype
33 self.dir=dir
34 self.types=types
35 self.app=application
36 self.aname=os.path.basename(application)
37 self.check=check
39 vbox=self.vbox
41 swin = rox.g.ScrolledWindow()
42 swin.set_size_request(-1, 160)
43 swin.set_border_width(4)
44 swin.set_policy(rox.g.POLICY_NEVER, rox.g.POLICY_ALWAYS)
45 swin.set_shadow_type(rox.g.SHADOW_IN)
46 vbox.pack_start(swin, True, True, 0)
48 self.model = rox.g.ListStore(str, str, str, int, rox.g.gdk.Pixbuf,
49 int, int)
50 view = rox.g.TreeView(self.model)
51 self.view = view
52 swin.add(view)
53 view.set_search_column(1)
55 cell = rox.g.CellRendererPixbuf()
56 column = rox.g.TreeViewColumn('', cell, pixbuf = _ICON)
57 view.append_column(column)
59 cell = rox.g.CellRendererText()
60 column = rox.g.TreeViewColumn('Type', cell, text = _TNAME)
61 view.append_column(column)
62 column.set_sort_column_id(_TNAME)
64 cell = rox.g.CellRendererText()
65 column = rox.g.TreeViewColumn('Name', cell, text = _COMMENT)
66 view.append_column(column)
67 column.set_sort_column_id(_COMMENT)
69 if check:
70 cell = rox.g.CellRendererText()
71 column = rox.g.TreeViewColumn('Current', cell, text = _CURRENT)
72 view.append_column(column)
73 column.set_sort_column_id(_CURRENT)
75 cell = rox.g.CellRendererToggle()
76 cell.set_property('activatable', True)
77 cell.connect('toggled', self.install_toggled, self.model)
78 column = rox.g.TreeViewColumn('Install?', cell, active = _INSTALL)
79 view.append_column(column)
80 column.set_sort_column_id(_INSTALL)
82 cell = rox.g.CellRendererToggle()
83 cell.connect('toggled', self.uninstall_toggled, self.model)
84 column = rox.g.TreeViewColumn('Uninstall?', cell, active = _UNINSTALL,
85 activatable= _IS_OURS)
86 view.append_column(column)
87 column.set_sort_column_id(_UNINSTALL)
89 view.get_selection().set_mode(rox.g.SELECTION_NONE)
91 if info:
92 hbox=rox.g.HBox(spacing=4)
93 img=rox.g.image_new_from_stock(rox.g.STOCK_DIALOG_INFO,
94 rox.g.ICON_SIZE_DIALOG)
95 hbox.pack_start(img)
97 lbl=rox.g.Label(info)
98 lbl.set_line_wrap(True)
99 hbox.pack_start(lbl)
101 vbox.pack_start(hbox)
103 vbox.show_all()
105 self.load_types()
107 def install_toggled(self, cell, path, model):
108 """Handle the CellRedererToggle stuff for the install column"""
109 if type(path) == str:
110 # Does this vary by pygtk version?
111 titer=model.iter_nth_child(None, int(path))
112 else:
113 titer=model.get_iter(path)
114 model.set_value(titer, _INSTALL, not cell.get_active())
115 if not cell.get_active():
116 model.set_value(titer, _UNINSTALL, 0)
118 def uninstall_toggled(self, cell, path, model):
119 """Handle the CellRedererToggle stuff for the uninstall column"""
120 if type(path) == str:
121 # Does this vary by pygtk version?
122 titer=model.iter_nth_child(None, int(path))
123 else:
124 titer=model.get_iter(path)
125 avail=model.get_value(titer, _IS_OURS)
126 if avail:
127 model.set_value(titer, _UNINSTALL, not cell.get_active())
128 if not cell.get_active():
129 model.set_value(titer, _INSTALL, 0)
130 else:
131 model.set_value(titer, _UNINSTALL, 0)
133 def load_types(self):
134 """Load list of types into window"""
135 self.model.clear()
137 for tname in self.types:
138 mime_type=mime.lookup(tname)
139 if self.check:
140 old=choices.load(self.dir, '%s_%s' %
141 (mime_type.media, mime_type.subtype))
142 if old and os.path.islink(old):
143 old=os.readlink(old)
144 oname=os.path.basename(old)
145 elif old:
146 oname='script'
147 else:
148 oname=''
150 if old==self.app:
151 dinstall=False
152 can_un=True
153 else:
154 dinstall=True
155 can_un=False
156 else:
157 dinstall=True
158 can_un=False
159 oname=''
161 icon=mime_type.get_icon(mime.ICON_SIZE_SMALL)
163 titer=self.model.append()
164 self.model.set(titer, _TNAME, tname,
165 _COMMENT, mime_type.get_comment(),
166 _INSTALL, dinstall,
167 _UNINSTALL, False, _IS_OURS, can_un)
168 if self.check:
169 self.model.set(titer, _CURRENT, oname)
170 if icon:
171 self.model.set(titer, _ICON, icon)
174 def get_active(self):
175 """Return list of types selected for installing"""
176 titer=self.model.get_iter_first()
177 active=[]
178 while titer:
179 if self.model.get_value(titer, _INSTALL):
180 active.append(self.model.get_value(titer, _TNAME))
181 titer=self.model.iter_next(titer)
183 return active
185 def get_uninstall(self):
186 """Return list of types selected for uninstalling"""
187 titer=self.model.get_iter_first()
188 uninstall=[]
189 while titer:
190 if self.model.get_value(titer, _UNINSTALL) and self.model.get_value(titer, _IS_OURS):
191 uninstall.append(self.model.get_value(titer, _TNAME))
192 titer=self.model.iter_next(titer)
194 return uninstall
197 def _install_type_handler(types, dir, desc, application=None, overwrite=True,
198 info=None):
199 """Internal function. Does the work of setting MIME-types or MIME-thumb"""
200 if len(types)<1:
201 return
203 if not application:
204 application=rox.app_dir
205 if application[0]!='/':
206 application=os.path.abspath(application)
208 win=InstallList(application, desc, dir, types, info)
210 if win.run()!=rox.g.RESPONSE_ACCEPT:
211 win.destroy()
212 return
214 try:
215 types=win.get_active()
217 for tname in types:
218 mime_type = mime.lookup(tname)
220 sname=choices.save(dir,
221 '%s_%s' % (mime_type.media, mime_type.subtype))
222 os.symlink(application, sname+'.tmp')
223 os.rename(sname+'.tmp', sname)
225 types=win.get_uninstall()
227 for tname in types:
228 mime_type = mime.lookup(tname)
230 sname=choices.save(dir,
231 '%s_%s' % (mime_type.media, mime_type.subtype))
232 os.remove(sname)
233 finally:
234 win.destroy()
236 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...'""")
237 def install_run_action(types, application=None, overwrite=True):
238 """Install application as the run action for 1 or more types.
239 application should be the full path to the AppDir.
240 If application is None then it is the running program which will
241 be installed. If overwrite is False then existing run actions will
242 not be changed. The user is asked to confirm the setting for each
243 type."""
244 _install_type_handler(types, "MIME-types", _("run action"),
245 application, overwrite, run_action_msg)
247 def install_thumbnailer(types, application=None, overwrite=True):
248 """Install application as the thumbnail handler for 1 or more types.
249 application should be the full path to the AppDir.
250 If application is None then it is the running program which will
251 be installed. If overwrite is False then existing thumbnailerss will
252 not be changed. The user is asked to confirm the setting for each
253 type."""
254 _install_type_handler(types, "MIME-thumb", _("thumbnail handler"),
255 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."""))
257 def install_send_to_types(types, application=None):
258 """Install application in the SendTo menu for 1 or more types.
259 application should be the full path to the AppDir.
260 If application is None then it is the running program which will
261 be installed. The user is asked to confirm the setting for each
262 type."""
263 if len(types)<1:
264 return
266 if not application:
267 application=rox.app_dir
268 if application[0]!='/':
269 application=os.path.abspath(application)
271 win=InstallList(application, _('type handler'), 'SendTo', types,
272 _("""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."""),
273 check=False)
275 if win.run()!=rox.g.RESPONSE_ACCEPT:
276 win.destroy()
277 return
279 types=win.get_active()
281 for tname in types:
282 mime_type=mime.lookup(tname)
284 sname=choices.save('SendTo/.%s_%s' % (mime_type.media,
285 mime_type.subtype),
286 win.aname)
287 os.symlink(application, sname+'.tmp')
288 os.rename(sname+'.tmp', sname)
290 types=win.get_uninstall()
292 for tname in types:
293 mime_type=mime.lookup(tname)
295 sname=choices.save('SendTo/.%s_%s' % (mime_type.media,
296 mime_type.subtype),
297 win.aname)
298 os.remove(sname)
300 win.destroy()
302 def install_from_appinfo(appdir = rox.app_dir):
303 """Read the AppInfo file from the AppDir and perform the installations
304 indicated. The elements to use are <CanThumbnail> and <CanRun>, each containing
305 a number of <MimeType type='...'/> elements.
306 appdir - Path to application (defaults to current app)
308 import rox.AppInfo
310 app_info_path = os.path.join(appdir, 'AppInfo.xml')
311 ainfo = rox.AppInfo.AppInfo(app_info_path)
313 can_run = ainfo.getCanRun()
314 can_thumbnail = ainfo.getCanThumbnail()
315 if can_run or can_thumbnail:
316 install_run_action(can_run, appdir)
317 install_thumbnailer(can_thumbnail, appdir)
318 install_send_to_types(can_run, appdir)
319 else:
320 raise Exception('Internal error: No actions found in %s. '
321 'Check your namespaces!' % app_info_path)