| Home | Trees | Indices | Help |
|
|---|
|
|
1 # This application is released under the GNU General Public License
2 # v3 (or, at your option, any later version). You can find the full
3 # text of the license under http://www.gnu.org/licenses/gpl.txt.
4 # By using, editing and/or distributing this software you agree to
5 # the terms and conditions of this license.
6 # Thank you for using free software!
7
8 # Options-system (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
9 #
10 # INFO:
11 # - a dynamic Options-system that allows very easy creation of
12 # objects with embedded configuration-system.
13 # NOTE: The Dialog is not very nice yet - it is not good OOP-practice
14 # because too big functions and bad class-layout ... but it works
15 # for now ... :)
16 #
17 # TODO:
18 # - option-widgets for all option-types (e.g. ListOptionWidget, ColorOptionWidget)
19 # - OptionGroup-class instead of (or behind) add_options_group
20 # - TimeOption, DateOption
21 # - FileOption needs filter/limit-attribute
22 # - allow options to disable/enable other options
23 # - support for EditableOptions-subclasses as options
24 # - separate OptionEditorWidget from Editor-Dialog
25 # - place ui-code into screenlets.options.ui-module
26 # - create own widgets for each Option-subclass
27 #
28
29 import screenlets
30 import utils
31
32 import os
33 import gtk, gobject
34 import xml.dom.minidom
35 from xml.dom.minidom import Node
36
37 # translation stuff
38 import gettext
39 gettext.textdomain('screenlets')
40 gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX + '/share/locale')
41
44
45 # -----------------------------------------------------------------------
46 # Option-classes and subclasses
47 # -----------------------------------------------------------------------
48
50 """An Option stores information about a certain object-attribute. It doesn't
51 carry information about the value or the object it belongs to - it is only a
52 one-way data-storage for describing how to handle attributes."""
53
54 __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
55 gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
56
57 - def __init__ (self, group, name, default, label, desc,
58 disabled=False, hidden=False, callback=None, protected=False):
59 """Creates a new Option with the given information."""
60 super(Option, self).__init__()
61 self.name = name
62 self.label = label
63 self.desc = desc
64 self.default = default
65 self.disabled = disabled
66 self.hidden = hidden
67 # for groups (TODO: OptionGroup)
68 self.group= group
69 # callback to be notified when this option changes
70 self.callback = callback
71 # real-time update?
72 self.realtime = True
73 # protected from get/set through service
74 self.protected = protected
75
77 """Callback - called when an option gets imported from a string.
78 This function MUST return the string-value converted to the required
79 type!"""
80 return strvalue.replace("\\n", "\n")
81
88
89
91 """An Option-subclass for string-values that contain filenames. Adds
92 a patterns-attribute that can contain a list of patterns to be shown
93 in the assigned file selection dialog. The show_pixmaps-attribute
94 can be set to True to make the filedialog show all image-types
95 supported by gtk.Pixmap. If the directory-attributue is true, the
96 dialog will ony allow directories."""
97
104
105
109
110
113
114
122
123
125 """An Option for values of type string."""
126
132
133
135 """An Option for values of type number (can be int or float)."""
136
137 - def __init__ (self, group, name, default, label, desc, min=-100000, max=100000,
138 increment=1, **keyword_args):
139 Option.__init__(self, group, name, default, label, desc, **keyword_args)
140 self.min = min
141 self.max = max
142 self.increment = increment
143
145 """Called when IntOption gets imported. Converts str to int."""
146 try:
147 if strvalue[0]=='-':
148 return int(strvalue[1:]) * -1
149 return int(strvalue)
150 except:
151 print "Error during on_import - option: %s." % self.name
152 return 0
153
154
156 """An Option for values of type float."""
157
160 IntOption.__init__(self, group, name, default, label, desc,
161 **keyword_args)
162 self.digits = digits
163
169
170
173
174
176 """An Option for colors. Stored as a list with 4 values (r, g, b, a)."""
177
179 """Import (r, g, b, a) from comma-separated string."""
180 # strip braces and spaces
181 strvalue = strvalue.lstrip('(')
182 strvalue = strvalue.rstrip(')')
183 strvalue = strvalue.strip()
184 # split value on commas
185 tmpval = strvalue.split(',')
186 outval = []
187 for f in tmpval:
188 # create list and again remove spaces
189 outval.append(float(f.strip()))
190 return outval
191
193 """Export r, g, b, a to comma-separated string."""
194 l = len(value)
195 outval = ''
196 for i in xrange(l):
197 outval += str(value[i])
198 if i < l-1:
199 outval += ','
200 return outval
201
202
204 """An Option-type for list of strings."""
205
207 """Import python-style list from a string (like [1, 2, 'test'])"""
208 lst = eval(strvalue)
209 return lst
210
214
215
216 import gnomekeyring
218 """An Option-type for username/password combos. Stores the password in
219 the gnome-keyring (if available) and only saves username and auth_token
220 through the screenlets-backend.
221 TODO:
222 - not create new token for any change (use "set" instead of "create" if
223 the given item already exists)
224 - use usual storage if no keyring is available but output warning
225 - on_delete-function for removing the data from keyring when the
226 Screenlet holding the option gets deleted"""
227
229 Option.__init__ (self, group, name, default, label, desc,
230 protected=True, **keyword_args)
231 # check for availability of keyring
232 if not gnomekeyring.is_available():
233 raise Exception('GnomeKeyring is not available!!') # TEMP!!!
234 # THIS IS A WORKAROUND FOR A BUG IN KEYRING (usually we would use
235 # gnomekeyring.get_default_keyring_sync() here):
236 # find first available keyring
237 self.keyring_list = gnomekeyring.list_keyring_names_sync()
238 if len(self.keyring_list) == 0:
239 raise Exception('No keyrings found. Please create one first!')
240 else:
241 # we prefer the default keyring
242 try:
243 self.keyring = gnomekeyring.get_default_keyring_sync()
244 except:
245 if "session" in self.keyring_list:
246 print "Warning: No default keyring found, using session keyring. Storage is not permanent!"
247 self.keyring = "session"
248 else:
249 print "Warning: Neither default nor session keyring found, assuming keyring %s!" % self.keyring_list[0]
250 self.keyring = self.keyring_list[0]
251
252
254 """Import account info from a string (like 'username:auth_token'),
255 retrieve the password from the storage and return a tuple containing
256 username and password."""
257 # split string into username/auth_token
258 #data = strvalue.split(':', 1)
259 (name, auth_token) = strvalue.split(':', 1)
260 if name and auth_token:
261 # read pass from storage
262 try:
263 pw = gnomekeyring.item_get_info_sync(self.keyring,
264 int(auth_token)).get_secret()
265 except Exception, ex:
266 print "ERROR: Unable to read password from keyring: %s" % ex
267 pw = ''
268 # return
269 return (name, pw)
270 else:
271 raise Exception('Illegal value in AccountOption.on_import.')
272
274 """Export the given tuple/list containing a username and a password. The
275 function stores the password in the gnomekeyring and returns a
276 string in form 'username:auth_token'."""
277 # store password in storage
278 attribs = dict(name=value[0])
279 auth_token = gnomekeyring.item_create_sync(self.keyring,
280 gnomekeyring.ITEM_GENERIC_SECRET, value[0], attribs, value[1], True)
281 # build value from username and auth_token
282 return value[0] + ':' + str(auth_token)
283
284 """#TEST:
285 o = AccountOption('None', 'pop3_account', ('',''), 'Username/Password', 'Enter username/password here ...')
286 # save option to keyring
287 exported_account = o.on_export(('RYX', 'mysecretpassword'))
288 print exported_account
289 # and read option back from keyring
290 print o.on_import(exported_account)
291
292
293 import sys
294 sys.exit(0)"""
295
298
299
300 # -----------------------------------------------------------------------
301 # EditableOptions-class and needed functions
302 # -----------------------------------------------------------------------
303
305 """Create an Option from an XML-node with option-metadata."""
306 #print "TODO OPTION: " + str(cn)
307 otype = node.getAttribute("type")
308 oname = node.getAttribute("name")
309 ohidden = node.getAttribute("hidden")
310 odefault = None
311 oinfo = ''
312 olabel = ''
313 omin = None
314 omax = None
315 oincrement = 1
316 ochoices = ''
317 odigits = None
318 if otype and oname:
319 # parse children of option-node and save all useful attributes
320 for attr in node.childNodes:
321 if attr.nodeType == Node.ELEMENT_NODE:
322 if attr.nodeName == 'label':
323 olabel = attr.firstChild.nodeValue
324 elif attr.nodeName == 'info':
325 oinfo = attr.firstChild.nodeValue
326 elif attr.nodeName == 'default':
327 odefault = attr.firstChild.nodeValue
328 elif attr.nodeName == 'min':
329 omin = attr.firstChild.nodeValue
330 elif attr.nodeName == 'max':
331 omax = attr.firstChild.nodeValue
332 elif attr.nodeName == 'increment':
333 oincrement = attr.firstChild.nodeValue
334 elif attr.nodeName == 'choices':
335 ochoices = attr.firstChild.nodeValue
336 elif attr.nodeName == 'digits':
337 odigits = attr.firstChild.nodeValue
338 # if we have all needed values, create the Option
339 if odefault:
340 # create correct classname here
341 cls = otype[0].upper() + otype.lower()[1:] + 'Option'
342 #print 'Create: ' +cls +' / ' + oname + ' ('+otype+')'
343 # and build new instance (we use on_import for setting default val)
344 clsobj = getattr(__import__(__name__), cls)
345 opt = clsobj(groupname, oname, None, olabel, oinfo)
346 opt.default = opt.on_import(odefault)
347 # set values to the correct types
348 if cls == 'IntOption':
349 if omin:
350 opt.min = int(omin)
351 if omax:
352 opt.max = int(omax)
353 if oincrement:
354 opt.increment = int(oincrement)
355 elif cls == 'FloatOption':
356 if odigits:
357 opt.digits = int(odigits)
358 if omin:
359 opt.min = float(omin)
360 if omax:
361 opt.max = float(omax)
362 if oincrement:
363 opt.increment = float(oincrement)
364 elif cls == 'StringOption':
365 if ochoices:
366 opt.choices = ochoices
367 return opt
368 return None
369
370
372 """The EditableOptions can be inherited from to allow objects to export
373 editable options for editing them with the OptionsEditor-class.
374 NOTE: This could use some improvement and is very poorly coded :) ..."""
375
377 self.__options__ = []
378 self.__options_groups__ = {}
379 # This is a workaround to remember the order of groups
380 self.__options_groups_ordered__ = []
381
383 """Add an editable option to this object. Editable Options can be edited
384 and configured using the OptionsDialog. The optional callback-arg can be
385 used to set a callback that gets notified when the option changes its
386 value."""
387 #print "Add option: "+option.name
388 # if option already editable (i.e. initialized), return
389 for o in self.__options__:
390 if o.name == option.name:
391 return False
392 self.__dict__[option.name] = option.default
393 # set auto-update (TEMPORARY?)
394 option.realtime = realtime
395 # add option to group (output error if group is undefined)
396 try:
397 self.__options_groups__[option.group]['options'].append(option)
398 except:
399 print "Options: Error - group %s not defined." % option.group
400 return False
401 # now add the option
402 self.__options__.append(option)
403 # if callback is set, add callback
404 if callback:
405 option.connect("option_changed", callback)
406 return True
407
408
410 """Add a new options-group to this Options-object"""
411 self.__options_groups__[name] = {'label':name,
412 'info':group_info, 'options':[]}
413 self.__options_groups_ordered__.append(name)
414 #print self.options_groups
415
417 """Disable the inputs for a certain Option."""
418 for o in self.__options__:
419 if o.name == name:
420 o.disabled = True
421 return True
422 return False
423
425 """Enable the inputs for a certain Option."""
426 for o in self.__options__:
427 if o.name == name:
428 o.disabled = False
429 return True
430 return False
431
433 """Returns all editable options within a list (without groups)
434 as key/value tuples."""
435 lst = []
436 for o in self.__options__:
437 lst.append((o.name, getattr(self, o.name)))
438 return lst
439
441 """Returns an option in this Options by it's name (or None).
442 TODO: this gives wrong results in childclasses ... maybe access
443 as class-attribute??"""
444 for o in self.__options__:
445 if o.name == name:
446 return o
447 return None
448
450 """Remove an option from this Options."""
451 for o in self.__options__:
452 if o.name == name:
453 del o
454 return True
455 return True
456
458 """This function creates options from an XML-file with option-metadata.
459 TODO: make this more reusable and place it into module (once the groups
460 are own objects)"""
461 # create xml document
462 try:
463 doc = xml.dom.minidom.parse(filename)
464 except:
465 raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
466 # get rootnode
467 root = doc.firstChild
468 if not root or root.nodeName != 'screenlet':
469 raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
470 # ok, let's check the nodes: this one should contain option-groups
471 groups = []
472 for node in root.childNodes:
473 # we only want element-nodes
474 if node.nodeType == Node.ELEMENT_NODE:
475 #print node
476 if node.nodeName != 'group' or not node.hasChildNodes():
477 # we only allow groups in the first level (groups need children)
478 raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
479 else:
480 # ok, create a new group and parse its elements
481 group = {}
482 group['name'] = node.getAttribute("name")
483 if not group['name']:
484 raise Exception('No name for group defined in "%s".' % filename)
485 group['info'] = ''
486 group['options'] = []
487 # check all children in group
488 for on in node.childNodes:
489 if on.nodeType == Node.ELEMENT_NODE:
490 if on.nodeName == 'info':
491 # info-node? set group-info
492 group['info'] = on.firstChild.nodeValue
493 elif on.nodeName == 'option':
494 # option node? parse option node
495 opt = create_option_from_node (on, group['name'])
496 # ok? add it to list
497 if opt:
498 group['options'].append(opt)
499 else:
500 raise Exception('Invalid option-node found in "%s".' % filename)
501
502 # create new group
503 if len(group['options']):
504 self.add_options_group(group['name'], group['info'])
505 for o in group['options']:
506 self.add_option(o)
507 # add group to list
508 #groups.append(group)
509
510 # -----------------------------------------------------------------------
511 # OptionsDialog and UI-classes
512 # -----------------------------------------------------------------------
513
515 """An editing dialog used for editing options of the ListOption-type."""
516
517 model = None
518 tree = None
519 buttonbox = None
520
521 # call gtk.Dialog.__init__
523 super(ListOptionDialog, self).__init__("Edit List",
524 flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
525 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
526 gtk.STOCK_OK, gtk.RESPONSE_OK))
527 # set size
528 self.resize(300, 370)
529 self.set_keep_above(True) # to avoid confusion
530 # init vars
531 self.model = gtk.ListStore(str)
532 # create UI
533 self.create_ui()
534
536 """Create the user-interface for this dialog."""
537 # create outer hbox (tree|buttons)
538 hbox = gtk.HBox()
539 hbox.set_border_width(10)
540 hbox.set_spacing(10)
541 # create tree
542 self.tree = gtk.TreeView(model=self.model)
543 self.tree.set_headers_visible(False)
544 self.tree.set_reorderable(True)
545 #self.tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
546 col = gtk.TreeViewColumn('')
547 cell = gtk.CellRendererText()
548 #cell.set_property('cell-background', 'cyan')
549 cell.set_property('foreground', 'black')
550 col.pack_start(cell, False)
551 col.set_attributes(cell, text=0)
552 self.tree.append_column(col)
553 self.tree.show()
554 hbox.pack_start(self.tree, True, True)
555 #sep = gtk.VSeparator()
556 #sep.show()
557 #hbox.add(sep)
558 # create buttons
559 self.buttonbox = bb = gtk.VButtonBox()
560 self.buttonbox.set_layout(gtk.BUTTONBOX_START)
561 b1 = gtk.Button(stock=gtk.STOCK_ADD)
562 b2 = gtk.Button(stock=gtk.STOCK_EDIT)
563 b3 = gtk.Button(stock=gtk.STOCK_REMOVE)
564 b1.connect('clicked', self.button_callback, 'add')
565 b2.connect('clicked', self.button_callback, 'edit')
566 b3.connect('clicked', self.button_callback, 'remove')
567 bb.add(b1)
568 bb.add(b2)
569 bb.add(b3)
570 self.buttonbox.show_all()
571 #hbox.add(self.buttonbox)
572 hbox.pack_end(self.buttonbox, False)
573 # add everything to outer hbox and show it
574 hbox.show()
575 self.vbox.add(hbox)
576
581
583 """Return the list that is currently being edited in this editor."""
584 lst = []
585 for i in self.model:
586 lst.append(i[0])
587 return lst
588
590 """Remove the currently selected item."""
591 sel = self.tree.get_selection()
592 if sel:
593 it = sel.get_selected()[1]
594 if it:
595 print self.model.get_value(it, 0)
596 self.model.remove(it)
597
599 """Show entry-dialog and return string."""
600 entry = gtk.Entry()
601 entry.set_text(default)
602 entry.show()
603 dlg = gtk.Dialog("Add/Edit Item", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
604 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
605 gtk.RESPONSE_OK))
606 dlg.set_keep_above(True)
607 dlg.vbox.add(entry)
608 resp = dlg.run()
609 ret = None
610 if resp == gtk.RESPONSE_OK:
611 ret = entry.get_text()
612 dlg.destroy()
613 return ret
614
632
633
634 # TEST
635 """dlg = ListOptionDialog()
636 dlg.set_list(['test1', 'afarew34s', 'fhjh23faj', 'yxcdfs58df', 'hsdf7jsdfh'])
637 dlg.run()
638 print "RESULT: " + str(dlg.get_list())
639 dlg.destroy()
640 import sys
641 sys.exit(1)"""
642 # /TEST
643
645 """A dynamic options-editor for editing Screenlets which are implementing
646 the EditableOptions-class."""
647
648 __shown_object = None
649
651 # call gtk.Dialog.__init__
652 super(OptionsDialog, self).__init__(
653 _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
654 gtk.DIALOG_NO_SEPARATOR,
655 buttons = (#gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_APPLY,
656 gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
657 # set size
658 self.resize(width, height)
659 self.set_keep_above(True) # to avoid confusion
660 self.set_border_width(10)
661 # create attribs
662 self.page_about = None
663 self.page_options = None
664 self.page_themes = None
665 self.vbox_editor = None
666 self.hbox_about = None
667 self.infotext = None
668 self.infoicon = None
669 # create theme-list
670 self.liststore = gtk.ListStore(object)
671 self.tree = gtk.TreeView(model=self.liststore)
672 # create/add outer notebook
673 self.main_notebook = gtk.Notebook()
674 self.main_notebook.show()
675 self.vbox.add(self.main_notebook)
676 # create/init notebook pages
677 self.create_about_page()
678 self.create_themes_page()
679 self.create_options_page()
680
681 # "public" functions
682
684 """Reset all entries for the currently shown object to their default
685 values (the values the object has when it is first created).
686 NOTE: This function resets ALL options, so BE CARFEUL!"""
687 if self.__shown_object:
688 for o in self.__shown_object.__options__:
689 # set default value
690 setattr(self.__shown_object, o.name, o.default)
691
693 """Update the "About"-page with the given information."""
694 # convert infotext (remove EOLs and TABs)
695 info = info.replace("\n", "")
696 info = info.replace("\t", " ")
697 # create markup
698 markup = '\n<b><span size="xx-large">' + name + '</span></b>'
699 if version:
700 markup += ' <span size="large"><b>' + version + '</b></span>'
701 markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
702 self.infotext.set_markup(markup)
703 # icon?
704 if icon:
705 # remove old icon
706 if self.infoicon:
707 self.infoicon.destroy()
708 # set new icon
709 self.infoicon = icon
710 self.infoicon.set_alignment(0.0, 0.10)
711 self.infoicon.show()
712 self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
713 else:
714 self.infoicon.hide()
715
717 """Update the OptionsEditor to show the options for the given Object.
718 The Object needs to be an EditableOptions-subclass.
719 NOTE: This needs heavy improvement and should use OptionGroups once
720 they exist"""
721 self.__shown_object = obj
722 # create notebook for groups
723 notebook = gtk.Notebook()
724 self.vbox_editor.add(notebook)
725 for group in obj.__options_groups_ordered__:
726 group_data = obj.__options_groups__[group]
727 # create box for tab-page
728 page = gtk.VBox()
729 page.set_border_width(10)
730 if group_data['info'] != '':
731 info = gtk.Label(group_data['info'])
732 info.show()
733 info.set_alignment(0, 0)
734 page.pack_start(info, 0, 0, 7)
735 sep = gtk.HSeparator()
736 sep.show()
737 #page.pack_start(sep, 0, 0, 5)
738 # create VBox for inputs
739 box = gtk.VBox()
740 box.show()
741 box.set_border_width(5)
742 # add box to page
743 page.add(box)
744 page.show()
745 # add new notebook-page
746 label = gtk.Label(group_data['label'])
747 label.show()
748 notebook.append_page(page, label)
749 # and create inputs
750 for option in group_data['options']:
751 if option.hidden == False:
752 val = getattr(obj, option.name)#obj.__dict__[option.name]
753 w = self.get_widget_for_option(option, val)
754 if w:
755 box.pack_start(w, 0, 0)
756 w.show()
757 notebook.show()
758 # show/hide themes tab, depending on whether the screenlet uses themes
759 if obj.uses_theme and obj.theme_name != '':
760 self.show_themes_for_screenlet(obj)
761 else:
762 self.page_themes.hide()
763
765 """Update the Themes-page to display the available themes for the
766 given Screenlet-object."""
767
768
769 dircontent = []
770 screenlets.utils.refresh_available_screenlet_paths()
771
772 for path in screenlets.SCREENLETS_PATH:
773 p = path + '/' + obj.get_short_name() + '/themes'
774 print p
775 #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
776 try:
777 dc = os.listdir(p)
778 for d in dc:
779 dircontent.append({'name':d, 'path':p+'/'})
780 except:
781 print "Path %s not found." % p
782
783 # list with found themes
784 found_themes = []
785
786 # check all themes in path
787 for elem in dircontent:
788 # load themes with the same name only once
789 if found_themes.count(elem['name']):
790 continue
791 found_themes.append(elem['name'])
792 # build full path of theme.conf
793 theme_conf = elem['path'] + elem['name'] + '/theme.conf'
794 # if dir contains a theme.conf
795 if os.access(theme_conf, os.F_OK):
796 # load it and create new list entry
797 ini = screenlets.utils.IniReader()
798 if ini.load(theme_conf):
799 # check for section
800 if ini.has_section('Theme'):
801 # get metainfo from theme
802 th_fullname = ini.get_option('name',
803 section='Theme')
804 th_info = ini.get_option('info',
805 section='Theme')
806 th_version = ini.get_option('version',
807 section='Theme')
808 th_author = ini.get_option('author',
809 section='Theme')
810 # create array from metainfo and add it to liststore
811 info = [elem['name'], th_fullname, th_info, th_author,
812 th_version]
813 self.liststore.append([info])
814 else:
815 # no theme section in theme.conf just add theme-name
816 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
817 else:
818 # no theme.conf in dir? just add theme-name
819 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
820 # is it the active theme?
821 if elem['name'] == obj.theme_name:
822 # select it in tree
823 print "active theme is: %s" % elem['name']
824 sel = self.tree.get_selection()
825 if sel:
826 it = self.liststore.get_iter_from_string(\
827 str(len(self.liststore)-1))
828 if it:
829 sel.select_iter(it)
830
831 # UI-creation
832
834 """Create the "About"-tab."""
835 self.page_about = gtk.HBox()
836 # create about box
837 self.hbox_about = gtk.HBox()
838 self.hbox_about.show()
839 self.page_about.add(self.hbox_about)
840 # create icon
841 self.infoicon = gtk.Image()
842 self.infoicon.show()
843 self.page_about.pack_start(self.infoicon, 0, 1, 10)
844 # create infotext
845 self.infotext = gtk.Label()
846 self.infotext.use_markup = True
847 self.infotext.set_line_wrap(True)
848 self.infotext.set_alignment(0.0, 0.0)
849 self.infotext.show()
850 self.page_about.pack_start(self.infotext, 1, 1, 5)
851 # add page
852 self.page_about.show()
853 self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
854
856 """Create the "Options"-tab."""
857 self.page_options = gtk.HBox()
858 # create vbox for options-editor
859 self.vbox_editor = gtk.VBox(spacing=3)
860 self.vbox_editor.set_border_width(5)
861 self.vbox_editor.show()
862 self.page_options.add(self.vbox_editor)
863 # show/add page
864 self.page_options.show()
865 self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
866
868 """Create the "Themes"-tab."""
869 self.page_themes = gtk.VBox(spacing=5)
870 self.page_themes.set_border_width(10)
871 # create info-text list
872 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.'))
873 txt.set_size_request(450, -1)
874 txt.set_line_wrap(True)
875 txt.set_alignment(0.0, 0.0)
876 txt.show()
877 self.page_themes.pack_start(txt, False, True)
878 # create theme-selector list
879 self.tree.set_headers_visible(False)
880 self.tree.connect('cursor-changed', self.__tree_cursor_changed)
881 self.tree.show()
882 col = gtk.TreeViewColumn('')
883 cell = gtk.CellRendererText()
884 col.pack_start(cell, True)
885 #cell.set_property('foreground', 'black')
886 col.set_cell_data_func(cell, self.__render_cell)
887 self.tree.append_column(col)
888 # wrap tree in scrollwin
889 sw = gtk.ScrolledWindow()
890 sw.set_shadow_type(gtk.SHADOW_IN)
891 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
892 sw.add(self.tree)
893 sw.show()
894 # add vbox and add tree/buttons
895 vbox = gtk.VBox()
896 vbox.pack_start(sw, True, True)
897 vbox.show()
898 # show/add page
899 self.page_themes.add(vbox)
900 self.page_themes.show()
901 self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
902
904 """Callback for rendering the cells in the theme-treeview."""
905 # get attributes-list from Treemodel
906 attrib = model.get_value(iter, 0)
907
908 # set colors depending on state
909 col = '555555'
910 name_uc = attrib[0][0].upper() + attrib[0][1:]
911 # create markup depending on info
912 if attrib[1] == '-' and attrib[2] == '-':
913 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
914 '</span></b> (' + _('no info available') + ')'
915 else:
916 if attrib[1] == None : attrib[1] = '-'
917 if attrib[2] == None : attrib[2] = '-'
918 if attrib[3] == None : attrib[3] = '-'
919 if attrib[4] == None : attrib[4] = '-'
920 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
921 '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
922 '">' + attrib[2].replace('\\n', '\n') + \
923 '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
924 # set markup
925 cell.set_property('markup', mu)
926
927 # UI-callbacks
928
930 """Callback for handling selection changes in the Themes-treeview."""
931 sel = self.tree.get_selection()
932 if sel:
933 s = sel.get_selected()
934 if s:
935 it = s[1]
936 if it:
937 attribs = self.liststore.get_value(it, 0)
938 if attribs and self.__shown_object:
939 #print attribs
940 # set theme in Screenlet (if not already active)
941 if self.__shown_object.theme_name != attribs[0]:
942 self.__shown_object.theme_name = attribs[0]
943
944 # option-widget creation (should be split in several classes)
945
947 """Return a gtk.*Widget with Label within a HBox for a given option.
948 NOTE: This is incredibly ugly, ideally all Option-subclasses should
949 have their own widgets - like StringOptionWidget, ColorOptionWidget,
950 ... and then be simply created dynamically"""
951 t = option.__class__
952 widget = None
953 if t == BoolOption:
954 widget = gtk.CheckButton()
955 widget.set_active(value)
956 widget.connect("toggled", self.options_callback, option)
957 elif t == StringOption:
958 if option.choices:
959 # if a list of values is defined, show combobox
960 widget = gtk.combo_box_new_text()
961 p = -1
962 i = 0
963 for s in option.choices:
964 widget.append_text(s)
965 if s==value:
966 p = i
967 i+=1
968 widget.set_active(p)
969 #widget.connect("changed", self.options_callback, option)
970 else:
971 widget = gtk.Entry()
972 widget.set_text(value)
973 # if it is a password, set text to be invisible
974 if option.password:
975 widget.set_visibility(False)
976 #widget.connect("key-press-event", self.options_callback, option)
977 widget.connect("changed", self.options_callback, option)
978 #widget.set_size_request(180, 28)
979 elif t == IntOption or t == FloatOption:
980 widget = gtk.SpinButton()
981 #widget.set_size_request(50, 22)
982 #widget.set_text(str(value))
983 if t == FloatOption:
984 widget.set_digits(option.digits)
985 widget.set_increments(option.increment, int(option.max/option.increment))
986 else:
987 widget.set_increments(option.increment, int(option.max/option.increment))
988 if option.min!=None and option.max!=None:
989 #print "Setting range for input to: %f, %f" % (option.min, option.max)
990 widget.set_range(option.min, option.max)
991 widget.set_value(value)
992 widget.connect("value-changed", self.options_callback, option)
993 elif t == ColorOption:
994 widget = gtk.ColorButton(gtk.gdk.Color(int(value[0]*65535), int(value[1]*65535), int(value[2]*65535)))
995 widget.set_use_alpha(True)
996 # print value
997 # print value[3]
998 widget.set_alpha(int(value[3]*65535))
999 widget.connect("color-set", self.options_callback, option)
1000 elif t == FontOption:
1001 widget = gtk.FontButton()
1002 widget.set_font_name(value)
1003 widget.connect("font-set", self.options_callback, option)
1004 elif t == FileOption:
1005 widget = gtk.FileChooserButton(_("Choose File"))
1006 widget.set_filename(value)
1007 widget.set_size_request(180, 28)
1008 widget.connect("selection-changed", self.options_callback, option)
1009 elif t == DirectoryOption:
1010 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1011 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
1012 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1013 widget = gtk.FileChooserButton(dlg)
1014 widget.set_title(_("Choose Directory"))
1015 widget.set_filename(value)
1016 widget.set_size_request(180, 28)
1017 widget.connect("selection-changed", self.options_callback, option)
1018 elif t == ImageOption:
1019 # create entry and button (entry is hidden)
1020 entry = gtk.Entry()
1021 entry.set_text(value)
1022 entry.set_editable(False)
1023 but = gtk.Button()
1024 # util to reload preview image
1025 def create_preview (filename):
1026 if filename and os.path.isfile(filename):
1027 pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
1028 if pb:
1029 img = gtk.Image()
1030 img.set_from_pixbuf(pb)
1031 return img
1032 img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
1033 gtk.ICON_SIZE_LARGE_TOOLBAR)
1034 img.set_size_request(64, 64)
1035 return img
1036 # create button
1037 def but_callback (widget):
1038 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1039 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
1040 dlg.set_title(_("Choose Image"))
1041 dlg.set_keep_above(True)
1042 dlg.set_filename(entry.get_text())
1043 flt = gtk.FileFilter()
1044 flt.add_pixbuf_formats()
1045 dlg.set_filter(flt)
1046 prev = gtk.Image()
1047 box = gtk.VBox()
1048 box.set_size_request(150, -1)
1049 box.add(prev)
1050 prev.show()
1051 # add preview widget to filechooser
1052 def preview_callback(widget):
1053 fname = dlg.get_preview_filename()
1054 if fname and os.path.isfile(fname):
1055 pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
1056 if pb:
1057 prev.set_from_pixbuf(pb)
1058 dlg.set_preview_widget_active(True)
1059 else:
1060 dlg.set_preview_widget_active(False)
1061 dlg.set_preview_widget_active(True)
1062 dlg.connect('selection-changed', preview_callback)
1063 dlg.set_preview_widget(box)
1064 # run
1065 response = dlg.run()
1066 if response == gtk.RESPONSE_OK:
1067 entry.set_text(dlg.get_filename())
1068 but.set_image(create_preview(dlg.get_filename()))
1069 self.options_callback(dlg, option)
1070 dlg.destroy()
1071 # load preview image
1072 but.set_image(create_preview(value))
1073 but.connect('clicked', but_callback)
1074 # create widget
1075 widget = gtk.HBox()
1076 widget.add(entry)
1077 widget.add(but)
1078 but.show()
1079 widget.show()
1080 # add tooltips
1081 #but.set_tooltip_text(_('Select Image ...'))
1082 but.set_tooltip_text(option.desc)
1083
1084 elif t == ListOption:
1085 entry= gtk.Entry()
1086 entry.set_editable(False)
1087 entry.set_text(str(value))
1088 entry.show()
1089 img = gtk.Image()
1090 img.set_from_stock(gtk.STOCK_EDIT, 1)
1091 but = gtk.Button()
1092 but.set_image(img)
1093 def open_listeditor(event):
1094 # open dialog
1095 dlg = ListOptionDialog()
1096 # read string from entry and import it through option-class
1097 # (this is needed to always have an up-to-date value)
1098 dlg.set_list(option.on_import(entry.get_text()))
1099 resp = dlg.run()
1100 if resp == gtk.RESPONSE_OK:
1101 # set text in entry
1102 entry.set_text(str(dlg.get_list()))
1103 # manually call the options-callback
1104 self.options_callback(dlg, option)
1105 dlg.destroy()
1106 but.show()
1107 but.connect("clicked", open_listeditor)
1108 but.set_tooltip_text(_('Open List-Editor ...'))
1109 entry.set_tooltip_text(option.desc)
1110 widget = gtk.HBox()
1111 widget.add(entry)
1112 widget.add(but)
1113 elif t == AccountOption:
1114 widget = gtk.HBox()
1115 vb = gtk.VBox()
1116 input_name = gtk.Entry()
1117 input_name.set_text(value[0])
1118 input_name.show()
1119 input_pass = gtk.Entry()
1120 input_pass.set_visibility(False) # password
1121 input_pass.set_text(value[1])
1122 input_pass.show()
1123 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1124 but.show()
1125 but.connect("clicked", self.apply_options_callback, option, widget)
1126 vb.add(input_name)
1127 vb.add(input_pass)
1128 vb.show()
1129 but.set_tooltip_text(_('Apply username/password ...'))
1130 input_name.set_tooltip_text(_('Enter username here ...'))
1131 input_pass.set_tooltip_text(_('Enter password here ...'))
1132 widget.add(vb)
1133 widget.add(but)
1134 elif t == TimeOption:
1135 widget = gtk.HBox()
1136 input_hour = gtk.SpinButton()#climb_rate=1.0)
1137 input_minute = gtk.SpinButton()
1138 input_second = gtk.SpinButton()
1139 input_hour.set_range(0, 23)
1140 input_hour.set_max_length(2)
1141 input_hour.set_increments(1, 1)
1142 input_hour.set_numeric(True)
1143 input_hour.set_value(value[0])
1144 input_minute.set_range(0, 59)
1145 input_minute.set_max_length(2)
1146 input_minute.set_increments(1, 1)
1147 input_minute.set_numeric(True)
1148 input_minute.set_value(value[1])
1149 input_second.set_range(0, 59)
1150 input_second.set_max_length(2)
1151 input_second.set_increments(1, 1)
1152 input_second.set_numeric(True)
1153 input_second.set_value(value[2])
1154 input_hour.connect('value-changed', self.options_callback, option)
1155 input_minute.connect('value-changed', self.options_callback, option)
1156 input_second.connect('value-changed', self.options_callback, option)
1157 input_hour.set_tooltip_text(option.desc)
1158 input_minute.set_tooltip_text(option.desc)
1159 input_second.set_tooltip_text(option.desc)
1160 widget.add(input_hour)
1161 widget.add(gtk.Label(':'))
1162 widget.add(input_minute)
1163 widget.add(gtk.Label(':'))
1164 widget.add(input_second)
1165 widget.add(gtk.Label('h'))
1166 widget.show_all()
1167 else:
1168 widget = gtk.Entry()
1169 print "unsupported type ''" % str(t)
1170 hbox = gtk.HBox()
1171 label = gtk.Label()
1172 label.set_alignment(0.0, 0.0)
1173 label.set_label(option.label)
1174 label.set_size_request(180, 28)
1175 label.show()
1176 hbox.pack_start(label, 0, 1)
1177 if widget:
1178 if option.disabled: # option disabled?
1179 widget.set_sensitive(False)
1180 label.set_sensitive(False)
1181 #label.set_mnemonic_widget(widget)
1182 widget.set_tooltip_text(option.desc)
1183 widget.show()
1184 # check if needs Apply-button
1185 if option.realtime == False:
1186 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1187 but.show()
1188 but.connect("clicked", self.apply_options_callback,
1189 option, widget)
1190 b = gtk.HBox()
1191 b.show()
1192 b.pack_start(widget, 0, 0)
1193 b.pack_start(but, 0, 0)
1194 hbox.pack_start(b, 0, 0)
1195 else:
1196 #hbox.pack_start(widget, -1, 1)
1197 hbox.pack_start(widget, 0, 0)
1198 return hbox
1199
1201 """Read an option's value from the widget and return it."""
1202 if not widget.window:
1203 return False
1204 # get type of option and read the widget's value
1205 val = None
1206 t = option.__class__
1207 if t == IntOption:
1208 val = int(widget.get_value())
1209 elif t == FloatOption:
1210 val = widget.get_value()
1211 elif t == StringOption:
1212 if option.choices:
1213 # if default is a list, handle combobox
1214 val = widget.get_active_text()
1215 else:
1216 val = widget.get_text()
1217 elif t == BoolOption:
1218 val = widget.get_active()
1219 elif t == ColorOption:
1220 col = widget.get_color()
1221 al = widget.get_alpha()
1222 val = (col.red/65535.0, col.green/65535.0,
1223 col.blue/65535.0, al/65535.0)
1224 elif t == FontOption:
1225 val = widget.get_font_name()
1226 elif t == FileOption or t == DirectoryOption or t == ImageOption:
1227 val = widget.get_filename()
1228 #print widget
1229 #elif t == ImageOption:
1230 # val = widget.get_text()
1231 elif t == ListOption:
1232 # the widget is a ListOptionDialog here
1233 val = widget.get_list()
1234 elif t == AccountOption:
1235 # the widget is a HBox containing a VBox containing two Entries
1236 # (ideally we should have a custom widget for the AccountOption)
1237 for c in widget.get_children():
1238 if c.__class__ == gtk.VBox:
1239 c2 = c.get_children()
1240 val = (c2[0].get_text(), c2[1].get_text())
1241 elif t == TimeOption:
1242 box = widget.get_parent()
1243 inputs = box.get_children()
1244 val = (int(inputs[0].get_value()), int(inputs[2].get_value()),
1245 int(inputs[4].get_value()))
1246 else:
1247 print "OptionsDialog: Unknown option type: %s" % str(t)
1248 return None
1249 # return the value
1250 return val
1251
1252 # option-widget event-handling
1253
1254 # TODO: custom callback/signal for each option?
1256 """Callback for handling changed-events on entries."""
1257 print "Changed: %s" % optionobj.name
1258 if self.__shown_object:
1259 # if the option is not real-time updated,
1260 if optionobj.realtime == False:
1261 return False
1262 # read option
1263 val = self.read_option_from_widget(widget, optionobj)
1264 if val != None:
1265 #print "SetOption: "+optionobj.name+"="+str(val)
1266 # set option
1267 setattr(self.__shown_object, optionobj.name, val)
1268 # notify option-object's on_changed-handler
1269 optionobj.emit("option_changed", optionobj)
1270 return False
1271
1273 """Callback for handling Apply-button presses."""
1274 if self.__shown_object:
1275 # read option
1276 val = self.read_option_from_widget(entry, optionobj)
1277 if val != None:
1278 #print "SetOption: "+optionobj.name+"="+str(val)
1279 # set option
1280 setattr(self.__shown_object, optionobj.name, val)
1281 # notify option-object's on_changed-handler
1282 optionobj.emit("option_changed", optionobj)
1283 return False
1284
1285
1286
1287 # ------ ONLY FOR TESTING ------------------:
1288 if __name__ == "__main__":
1289
1290 import os
1291
1292 # this is only for testing - should be a Screenlet
1294
1295 testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
1296 pop3_account = ('Username', '')
1297
1298 # TEST
1299 pin_x = 100
1300 pin_y = 6
1301 text_x = 19
1302 text_y = 35
1303 font_name = 'Sans 12'
1304 rgba_color = (0.0, 0.0, 1.0, 1.0)
1305 text_prefix = '<b>'
1306 text_suffix = '</b>'
1307 note_text = "" # hidden option because val has its own editing-dialog
1308 random_pin_pos = True
1309 opt1 = 'testval 1'
1310 opt2 = 'testval 2'
1311 filename2 = ''
1312 filename = ''
1313 dirname = ''
1314 font = 'Sans 12'
1315 color = (0.1, 0.5, 0.9, 0.9)
1316 name = 'a name'
1317 name2 = 'another name'
1318 combo_test = 'el2'
1319 flt = 0.5
1320 x = 10
1321 y = 25
1322 width = 30
1323 height = 50
1324 is_sticky = False
1325 is_widget = False
1326 time = (12, 32, 49) # a time-value (tuple with ints)
1327
1329 EditableOptions.__init__(self)
1330 # Add group
1331 self.add_options_group('General',
1332 'The general options for this Object ...')
1333 self.add_options_group('Window',
1334 'The Window-related options for this Object ...')
1335 self.add_options_group('Test', 'A Test-group ...')
1336 # Add editable options
1337 self.add_option(ListOption('Test', 'testlist', self.testlist,
1338 'ListOption-Test', 'Testing a ListOption-type ...'))
1339 self.add_option(StringOption('Window', 'name', 'TESTNAME',
1340 'Testname', 'The name/id of this Screenlet-instance ...'),
1341 realtime=False)
1342 self.add_option(AccountOption('Test', 'pop3_account',
1343 self.pop3_account, 'Username/Password',
1344 'Enter username/password here ...'))
1345 self.add_option(StringOption('Window', 'name2', 'TESTNAME2',
1346 'String2', 'Another string-test ...'))
1347 self.add_option(StringOption('Test', 'combo_test', "el1", 'Combo',
1348 'A StringOption displaying a drop-down-list with choices...',
1349 choices=['el1', 'el2', 'element 3']))
1350 self.add_option(FloatOption('General', 'flt', 30,
1351 'A Float', 'Testing a FLOAT-type ...',
1352 min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
1353 self.add_option(IntOption('General', 'x', 30,
1354 'X-Position', 'The X-position of this Screenlet ...',
1355 min=0, max=gtk.gdk.screen_width()))
1356 self.add_option(IntOption('General', 'y', 30,
1357 'Y-Position', 'The Y-position of this Screenlet ...',
1358 min=0, max=gtk.gdk.screen_height()))
1359 self.add_option(IntOption('Test', 'width', 300,
1360 'Width', 'The width of this Screenlet ...', min=100, max=1000))
1361 self.add_option(IntOption('Test', 'height', 150,
1362 'Height', 'The height of this Screenlet ...',
1363 min=100, max=1000))
1364 self.add_option(BoolOption('General', 'is_sticky', True,
1365 'Stick to Desktop', 'Show this Screenlet always ...'))
1366 self.add_option(BoolOption('General', 'is_widget', False,
1367 'Treat as Widget', 'Treat this Screenlet as a "Widget" ...'))
1368 self.add_option(FontOption('Test', 'font', 'Sans 14',
1369 'Font', 'The font for whatever ...'))
1370 self.add_option(ColorOption('Test', 'color', (1, 0.35, 0.35, 0.7),
1371 'Color', 'The color for whatever ...'))
1372 self.add_option(FileOption('Test', 'filename', os.environ['HOME'],
1373 'Filename-Test', 'Testing a FileOption-type ...',
1374 patterns=['*.py', '*.pyc']))
1375 self.add_option(ImageOption('Test', 'filename2', os.environ['HOME'],
1376 'Image-Test', 'Testing the ImageOption-type ...'))
1377 self.add_option(DirectoryOption('Test', 'dirname', os.environ['HOME'],
1378 'Directory-Test', 'Testing a FileOption-type ...'))
1379 self.add_option(TimeOption('Test','time', self.time,
1380 'TimeOption-Test', 'Testing a TimeOption-type ...'))
1381 # TEST
1382 self.disable_option('width')
1383 self.disable_option('height')
1384 # TEST: load options from file
1385 #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
1386
1390
1392 return self.__class__.__name__[:-6]
1393
1394
1395
1396 # this is only for testing - should be a Screenlet
1398
1399 uses_theme = True
1400 theme_name = 'test'
1401
1403 TestObject.__init__(self)
1404 self.add_option(StringOption('Test', 'anothertest', 'ksjhsjgd',
1405 'Another Test', 'An attribute in the subclass ...'))
1406 self.add_option(StringOption('Test', 'theme_name', self.theme_name,
1407 'Theme', 'The theme for this Screenelt ...',
1408 choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
1409
1410
1411 # TEST: load/save
1412 # TEST: option-editing
1413 to = TestChildObject()
1414 #print to.export_options_as_list()
1415 se = OptionsDialog(500, 380)#, treeview=True)
1416 #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
1417 img = gtk.Image()
1418 img.set_from_file('../share/screenlets/Notes/icon.svg')
1419 se.set_info('TestOptions',
1420 'A test for an extended options-dialog with embedded about-info.' +
1421 ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
1422 '<span color="red"> ONLY A TEST!</span>',
1423 '(c) RYX 2007', version='v0.0.1', icon=img)
1424 se.show_options_for_object(to)
1425 resp = se.run()
1426 if resp == gtk.RESPONSE_OK:
1427 print "OK"
1428 else:
1429 print "Cancelled."
1430 se.destroy()
1431 print to.export_options_as_list()
1432
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Feb 28 23:21:27 2011 | http://epydoc.sourceforge.net |