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