1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """
19 Base classes and basic mechanics for all screenlet options.
20 """
21
22 import screenlets
23 from screenlets.options import _
24
25 import os
26 import gtk, gobject
27 import xml.dom.minidom
28 from xml.dom.minidom import Node
29
30
31
32
33
34 OPT_ATTRS = [ 'default', 'label', 'desc', 'choices' ]
35
37 """An Option stores information about a certain object-attribute. It doesn't
38 carry information about the value or the object it belongs to - it is only a
39 one-way data-storage for describing how to handle attributes."""
40 __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
41 gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
42 default = None
43 label = None
44 desc = None
45 hidden = False
46 disabled = False
47 realtime = True
48 protected = False
49 widget = None
50
51 - def __init__ (self, group, name, *attr, **args):
52 """Creates a new Option with the given information."""
53 super(Option, self).__init__()
54 if name == None:
55 raise ValueError("Option widget %s must have name." % str(type(self)) )
56 self.name = name
57 self.group = group
58
59
60 for i in range(len(attr)):
61 args.setdefault(OPT_ATTRS[i], attr[i])
62
63 for name in args.keys():
64 if hasattr(self, name):
65
66 setattr(self, name, args[name])
67
68
69
70
71
72
74 """Callback - called when an option gets imported from a string.
75 This function MUST return the string-value converted to the required
76 type!"""
77 return strvalue.replace("\\n", "\n")
78
80 """Callback - called when an option gets exported to a string. The
81 value-argument needs to be converted to a string that can be imported
82 by the on_import-handler. This handler MUST return the value
83 converted to a string!"""
84 return str(value).replace("\n", "\\n")
85
89
91 """Set the true/false value to the checkbox widget"""
92 raise NotImplementedError, "Can't update the widget and local value"
93
95 """Executed when the widget event kicks off."""
96 return self.emit("option_changed", self)
97
98
100 """Create an Option from an XML-node with option-metadata."""
101
102 otype = node.getAttribute("type")
103 oname = node.getAttribute("name")
104 ohidden = node.getAttribute("hidden")
105 options = {
106 'default' : None,
107 'info' : '',
108 'label' : '',
109 'min' : None,
110 'max' : None,
111 'increment' : 1,
112 'choices' : '',
113 'digits' : None,
114 }
115 if otype and oname:
116
117 for attr in node.childNodes:
118 if attr.nodeType == Node.ELEMENT_NODE and attr.nodeName in options.keys():
119 options[attr.nodeName] = attr.firstChild.nodeValue
120
121 if options['default']:
122
123 cls = otype[0].upper() + otype.lower()[1:] + 'Option'
124
125
126 clsobj = getattr(__import__(__name__), cls)
127 opt = clsobj(groupname, oname, default=None,
128 label=options['label'], desc=options['info'])
129 opt.default = opt.on_import(options['default'])
130
131 if cls == 'IntOption':
132 if options['min']:
133 opt.min = int(options['min'])
134 if options['max']:
135 opt.max = int(options['max'])
136 if options['increment']:
137 opt.increment = int(options['increment'])
138 elif cls == 'FloatOption':
139 if options['digits']:
140 opt.digits = int(options['digits'])
141 if options['min']:
142 opt.min = float(options['min'])
143 if options['min']:
144 opt.max = float(options['max'])
145 if options['increment']:
146 opt.increment = float(options['increment'])
147 elif cls == 'StringOption':
148 if options['choices']:
149 opt.choices = options['choices']
150 return opt
151 return None
152
153
155 """The EditableOptions can be inherited from to allow objects to export
156 editable options for editing them with the OptionsEditor-class.
157 NOTE: This could use some improvement and is very poorly coded :) ..."""
158
160 self.__options__ = []
161 self.__options_groups__ = {}
162
163 self.__options_groups_ordered__ = []
164
165 - def add_option (self, option, callback=None, realtime=True):
166 """Add an editable option to this object. Editable Options can be edited
167 and configured using the OptionsDialog. The optional callback-arg can be
168 used to set a callback that gets notified when the option changes its
169 value."""
170
171
172 for o in self.__options__:
173 if o.name == option.name:
174 return False
175 self.__dict__[option.name] = option.default
176
177 option.realtime = realtime
178
179 try:
180 self.__options_groups__[option.group]['options'].append(option)
181 except:
182 print "Options: Error - group %s not defined." % option.group
183 return False
184
185 self.__options__.append(option)
186
187 if callback:
188 option.connect("option_changed", callback)
189 option.connect("option_changed", self.callback_value_changed)
190 return True
191
192
194 """Add a new options-group to this Options-object"""
195 self.__options_groups__[name] = {'label':name,
196 'info':group_info, 'options':[]}
197 self.__options_groups_ordered__.append(name)
198
199
201 """Disable the inputs for a certain Option."""
202 for o in self.__options__:
203 if o.name == name:
204 o.disabled = True
205 return True
206 return False
207
209 """Enable the inputs for a certain Option."""
210 for o in self.__options__:
211 if o.name == name:
212 o.disabled = False
213 return True
214 return False
215
217 """Returns all editable options within a list (without groups)
218 as key/value tuples."""
219 lst = []
220 for o in self.__options__:
221 lst.append((o.name, o.on_export(getattr(self, o.name))))
222 return lst
223
225 """Called when a widget has updated and this needs calling."""
226 if hasattr(self, option.name):
227 return setattr(self, option.name, option.value)
228 raise KeyError, "Callback tried to set an option that wasn't defined."
229
231 """Returns an option in this Options by it's name (or None).
232 TODO: this gives wrong results in childclasses ... maybe access
233 as class-attribute??"""
234 for o in self.__options__:
235 if o.name == name:
236 return o
237 return None
238
240 """Remove an option from this Options."""
241 for o in self.__options__:
242 if o.name == name:
243 del o
244 return True
245 return True
246
248 """This function creates options from an XML-file with option-metadata.
249 TODO: make this more reusable and place it into module (once the groups
250 are own objects)"""
251
252 try:
253 doc = xml.dom.minidom.parse(filename)
254 except:
255 raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
256
257 root = doc.firstChild
258 if not root or root.nodeName != 'screenlet':
259 raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
260
261 groups = []
262 for node in root.childNodes:
263
264 if node.nodeType == Node.ELEMENT_NODE:
265
266 if node.nodeName != 'group' or not node.hasChildNodes():
267
268 raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
269 else:
270
271 group = {}
272 group['name'] = node.getAttribute("name")
273 if not group['name']:
274 raise Exception('No name for group defined in "%s".' % filename)
275 group['info'] = ''
276 group['options'] = []
277
278 for on in node.childNodes:
279 if on.nodeType == Node.ELEMENT_NODE:
280 if on.nodeName == 'info':
281
282 group['info'] = on.firstChild.nodeValue
283 elif on.nodeName == 'option':
284
285 opt = create_option_from_node (on, group['name'])
286
287 if opt:
288 group['options'].append(opt)
289 else:
290 raise Exception('Invalid option-node found in "%s".' % filename)
291
292
293 if len(group['options']):
294 self.add_options_group(group['name'], group['info'])
295 for o in group['options']:
296 self.add_option(o)
297
298
299
300
302 """A dynamic options-editor for editing Screenlets which are implementing
303 the EditableOptions-class."""
304
305 __shown_object = None
306
308
309 super(OptionsDialog, self).__init__(
310 _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
311 gtk.DIALOG_NO_SEPARATOR,
312 buttons = (
313 gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
314
315 self.resize(width, height)
316 self.set_keep_above(True)
317 self.set_border_width(10)
318
319 self.page_about = None
320 self.page_options = None
321 self.page_themes = None
322 self.vbox_editor = None
323 self.hbox_about = None
324 self.infotext = None
325 self.infoicon = None
326
327 self.liststore = gtk.ListStore(object)
328 self.tree = gtk.TreeView(model=self.liststore)
329
330 self.main_notebook = gtk.Notebook()
331 self.main_notebook.show()
332 self.vbox.add(self.main_notebook)
333
334 self.create_about_page()
335 self.create_themes_page()
336 self.create_options_page()
337
338
339
341 """Reset all entries for the currently shown object to their default
342 values (the values the object has when it is first created).
343 NOTE: This function resets ALL options, so BE CARFEUL!"""
344 if self.__shown_object:
345 for o in self.__shown_object.__options__:
346
347 setattr(self.__shown_object, o.name, o.default)
348
349 - def set_info(self, name, info, copyright='', version='', icon=None):
350 """Update the "About"-page with the given information."""
351
352 info = info.replace("\n", "")
353 info = info.replace("\t", " ")
354
355 markup = '\n<b><span size="xx-large">' + name + '</span></b>'
356 if version:
357 markup += ' <span size="large"><b>' + version + '</b></span>'
358 markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
359 self.infotext.set_markup(markup)
360
361 if icon:
362
363 if self.infoicon:
364 self.infoicon.destroy()
365
366 self.infoicon = icon
367 self.infoicon.set_alignment(0.0, 0.10)
368 self.infoicon.show()
369 self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
370 else:
371 self.infoicon.hide()
372
374 """Update the OptionsEditor to show the options for the given Object.
375 The Object needs to be an EditableOptions-subclass.
376 NOTE: This needs heavy improvement and should use OptionGroups once
377 they exist"""
378 self.__shown_object = obj
379
380 notebook = gtk.Notebook()
381 self.vbox_editor.add(notebook)
382 for group in obj.__options_groups_ordered__:
383 group_data = obj.__options_groups__[group]
384
385 page = gtk.VBox()
386 page.set_border_width(10)
387 if group_data['info'] != '':
388 info = gtk.Label(group_data['info'])
389 info.show()
390 info.set_alignment(0, 0)
391 page.pack_start(info, 0, 0, 7)
392 sep = gtk.HSeparator()
393 sep.show()
394
395
396 box = gtk.VBox()
397 box.show()
398 box.set_border_width(5)
399
400 page.add(box)
401 page.show()
402
403 label = gtk.Label(group_data['label'])
404 label.show()
405 notebook.append_page(page, label)
406
407 for option in group_data['options']:
408 if option.hidden == False:
409 name = getattr(obj, option.name)
410 if name == None:
411 raise ValueError("Option %s has no name" % str(type(obj)))
412 w = self.generate_widget( option, name )
413 if w:
414 box.pack_start(w, 0, 0)
415 w.show()
416 notebook.show()
417
418 if obj.uses_theme and obj.theme_name != '':
419 self.show_themes_for_screenlet(obj)
420 else:
421 self.page_themes.hide()
422
452
453
455 """Update the Themes-page to display the available themes for the
456 given Screenlet-object."""
457 dircontent = []
458 screenlets.utils.refresh_available_screenlet_paths()
459
460 for path in screenlets.SCREENLETS_PATH:
461 p = path + '/' + obj.get_short_name() + '/themes'
462 print p
463
464 try:
465 dc = os.listdir(p)
466 for d in dc:
467 dircontent.append({'name':d, 'path':p+'/'})
468 except:
469 print "Path %s not found." % p
470
471 found_themes = []
472
473 for elem in dircontent:
474
475 if found_themes.count(elem['name']):
476 continue
477 found_themes.append(elem['name'])
478
479 theme_conf = elem['path'] + elem['name'] + '/theme.conf'
480
481 if os.access(theme_conf, os.F_OK):
482
483 ini = screenlets.utils.IniReader()
484 if ini.load(theme_conf):
485
486 if ini.has_section('Theme'):
487
488 th_fullname = ini.get_option('name',
489 section='Theme')
490 th_info = ini.get_option('info',
491 section='Theme')
492 th_version = ini.get_option('version',
493 section='Theme')
494 th_author = ini.get_option('author',
495 section='Theme')
496
497 info = [elem['name'], th_fullname, th_info, th_author,
498 th_version]
499 self.liststore.append([info])
500 else:
501
502 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
503 else:
504
505 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
506
507 if elem['name'] == obj.theme_name:
508
509 print "active theme is: %s" % elem['name']
510 sel = self.tree.get_selection()
511 if sel:
512 it = self.liststore.get_iter_from_string(\
513 str(len(self.liststore)-1))
514 if it:
515 sel.select_iter(it)
516
517
519 """Create the "About"-tab."""
520 self.page_about = gtk.HBox()
521
522 self.hbox_about = gtk.HBox()
523 self.hbox_about.show()
524 self.page_about.add(self.hbox_about)
525
526 self.infoicon = gtk.Image()
527 self.infoicon.show()
528 self.page_about.pack_start(self.infoicon, 0, 1, 10)
529
530 self.infotext = gtk.Label()
531 self.infotext.use_markup = True
532 self.infotext.set_line_wrap(True)
533 self.infotext.set_alignment(0.0, 0.0)
534 self.infotext.show()
535 self.page_about.pack_start(self.infotext, 1, 1, 5)
536
537 self.page_about.show()
538 self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
539
541 """Create the "Options"-tab."""
542 self.page_options = gtk.HBox()
543
544 self.vbox_editor = gtk.VBox(spacing=3)
545 self.vbox_editor.set_border_width(5)
546 self.vbox_editor.show()
547 self.page_options.add(self.vbox_editor)
548
549 self.page_options.show()
550 self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
551
553 """Create the "Themes"-tab."""
554 self.page_themes = gtk.VBox(spacing=5)
555 self.page_themes.set_border_width(10)
556
557 txt = gtk.Label(_('Themes allow you to easily switch the appearance of your Screenlets. On this page you find a list of all available themes for this Screenlet.'))
558 txt.set_size_request(450, -1)
559 txt.set_line_wrap(True)
560 txt.set_alignment(0.0, 0.0)
561 txt.show()
562 self.page_themes.pack_start(txt, False, True)
563
564 self.tree.set_headers_visible(False)
565 self.tree.connect('cursor-changed', self.__tree_cursor_changed)
566 self.tree.show()
567 col = gtk.TreeViewColumn('')
568 cell = gtk.CellRendererText()
569 col.pack_start(cell, True)
570
571 col.set_cell_data_func(cell, self.__render_cell)
572 self.tree.append_column(col)
573
574 sw = gtk.ScrolledWindow()
575 sw.set_shadow_type(gtk.SHADOW_IN)
576 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
577 sw.add(self.tree)
578 sw.show()
579
580 vbox = gtk.VBox()
581 vbox.pack_start(sw, True, True)
582 vbox.show()
583
584 self.page_themes.add(vbox)
585 self.page_themes.show()
586 self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
587
589 """Callback for rendering the cells in the theme-treeview."""
590
591 attrib = model.get_value(iter, 0)
592
593
594 col = '555555'
595 name_uc = attrib[0][0].upper() + attrib[0][1:]
596
597 if attrib[1] == '-' and attrib[2] == '-':
598 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
599 '</span></b> (' + _('no info available') + ')'
600 else:
601 if attrib[1] == None : attrib[1] = '-'
602 if attrib[2] == None : attrib[2] = '-'
603 if attrib[3] == None : attrib[3] = '-'
604 if attrib[4] == None : attrib[4] = '-'
605 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
606 '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
607 '">' + attrib[2].replace('\\n', '\n') + \
608 '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
609
610 cell.set_property('markup', mu)
611
612
613
615 """Callback for handling selection changes in the Themes-treeview."""
616 sel = self.tree.get_selection()
617 if sel:
618 s = sel.get_selected()
619 if s:
620 it = s[1]
621 if it:
622 attribs = self.liststore.get_value(it, 0)
623 if attribs and self.__shown_object:
624
625
626 if self.__shown_object.theme_name != attribs[0]:
627 self.__shown_object.theme_name = attribs[0]
628