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