| 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
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 # crete tooltips-object
681 self.tooltips = gtk.Tooltips()
682
683 # "public" functions
684
686 """Reset all entries for the currently shown object to their default
687 values (the values the object has when it is first created).
688 NOTE: This function resets ALL options, so BE CARFEUL!"""
689 if self.__shown_object:
690 for o in self.__shown_object.__options__:
691 # set default value
692 setattr(self.__shown_object, o.name, o.default)
693
695 """Update the "About"-page with the given information."""
696 # convert infotext (remove EOLs and TABs)
697 info = info.replace("\n", "")
698 info = info.replace("\t", " ")
699 # create markup
700 markup = '\n<b><span size="xx-large">' + name + '</span></b>'
701 if version:
702 markup += ' <span size="large"><b>' + version + '</b></span>'
703 markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
704 self.infotext.set_markup(markup)
705 # icon?
706 if icon:
707 # remove old icon
708 if self.infoicon:
709 self.infoicon.destroy()
710 # set new icon
711 self.infoicon = icon
712 self.infoicon.set_alignment(0.0, 0.10)
713 self.infoicon.show()
714 self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
715 else:
716 self.infoicon.hide()
717
719 """Update the OptionsEditor to show the options for the given Object.
720 The Object needs to be an EditableOptions-subclass.
721 NOTE: This needs heavy improvement and should use OptionGroups once
722 they exist"""
723 self.__shown_object = obj
724 # create notebook for groups
725 notebook = gtk.Notebook()
726 self.vbox_editor.add(notebook)
727 for group in obj.__options_groups_ordered__:
728 group_data = obj.__options_groups__[group]
729 # create box for tab-page
730 page = gtk.VBox()
731 page.set_border_width(10)
732 if group_data['info'] != '':
733 info = gtk.Label(group_data['info'])
734 info.show()
735 info.set_alignment(0, 0)
736 page.pack_start(info, 0, 0, 7)
737 sep = gtk.HSeparator()
738 sep.show()
739 #page.pack_start(sep, 0, 0, 5)
740 # create VBox for inputs
741 box = gtk.VBox()
742 box.show()
743 box.set_border_width(5)
744 # add box to page
745 page.add(box)
746 page.show()
747 # add new notebook-page
748 label = gtk.Label(group_data['label'])
749 label.show()
750 notebook.append_page(page, label)
751 # and create inputs
752 for option in group_data['options']:
753 if option.hidden == False:
754 val = getattr(obj, option.name)#obj.__dict__[option.name]
755 w = self.get_widget_for_option(option, val)
756 if w:
757 box.pack_start(w, 0, 0)
758 w.show()
759 notebook.show()
760 # show/hide themes tab, depending on whether the screenlet uses themes
761 if obj.uses_theme and obj.theme_name != '':
762 self.show_themes_for_screenlet(obj)
763 else:
764 self.page_themes.hide()
765
767 """Update the Themes-page to display the available themes for the
768 given Screenlet-object."""
769
770
771 dircontent = []
772 screenlets.utils.refresh_available_screenlet_paths()
773
774 for path in screenlets.SCREENLETS_PATH:
775 p = path + '/' + obj.get_short_name() + '/themes'
776 print p
777 #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
778 try:
779 dc = os.listdir(p)
780 for d in dc:
781 dircontent.append({'name':d, 'path':p+'/'})
782 except:
783 print "Path %s not found." % p
784
785 # list with found themes
786 found_themes = []
787
788 # check all themes in path
789 for elem in dircontent:
790 # load themes with the same name only once
791 if found_themes.count(elem['name']):
792 continue
793 found_themes.append(elem['name'])
794 # build full path of theme.conf
795 theme_conf = elem['path'] + elem['name'] + '/theme.conf'
796 # if dir contains a theme.conf
797 if os.access(theme_conf, os.F_OK):
798 # load it and create new list entry
799 ini = screenlets.utils.IniReader()
800 if ini.load(theme_conf):
801 # check for section
802 if ini.has_section('Theme'):
803 # get metainfo from theme
804 th_fullname = ini.get_option('name',
805 section='Theme')
806 th_info = ini.get_option('info',
807 section='Theme')
808 th_version = ini.get_option('version',
809 section='Theme')
810 th_author = ini.get_option('author',
811 section='Theme')
812 # create array from metainfo and add it to liststore
813 info = [elem['name'], th_fullname, th_info, th_author,
814 th_version]
815 self.liststore.append([info])
816 else:
817 # no theme section in theme.conf just add theme-name
818 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
819 else:
820 # no theme.conf in dir? just add theme-name
821 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
822 # is it the active theme?
823 if elem['name'] == obj.theme_name:
824 # select it in tree
825 print "active theme is: %s" % elem['name']
826 sel = self.tree.get_selection()
827 if sel:
828 it = self.liststore.get_iter_from_string(\
829 str(len(self.liststore)-1))
830 if it:
831 sel.select_iter(it)
832
833 # UI-creation
834
836 """Create the "About"-tab."""
837 self.page_about = gtk.HBox()
838 # create about box
839 self.hbox_about = gtk.HBox()
840 self.hbox_about.show()
841 self.page_about.add(self.hbox_about)
842 # create icon
843 self.infoicon = gtk.Image()
844 self.infoicon.show()
845 self.page_about.pack_start(self.infoicon, 0, 1, 10)
846 # create infotext
847 self.infotext = gtk.Label()
848 self.infotext.use_markup = True
849 self.infotext.set_line_wrap(True)
850 self.infotext.set_alignment(0.0, 0.0)
851 self.infotext.show()
852 self.page_about.pack_start(self.infotext, 1, 1, 5)
853 # add page
854 self.page_about.show()
855 self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
856
858 """Create the "Options"-tab."""
859 self.page_options = gtk.HBox()
860 # create vbox for options-editor
861 self.vbox_editor = gtk.VBox(spacing=3)
862 self.vbox_editor.set_border_width(5)
863 self.vbox_editor.show()
864 self.page_options.add(self.vbox_editor)
865 # show/add page
866 self.page_options.show()
867 self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
868
870 """Create the "Themes"-tab."""
871 self.page_themes = gtk.VBox(spacing=5)
872 self.page_themes.set_border_width(10)
873 # create info-text list
874 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.'))
875 txt.set_size_request(450, -1)
876 txt.set_line_wrap(True)
877 txt.set_alignment(0.0, 0.0)
878 txt.show()
879 self.page_themes.pack_start(txt, False, True)
880 # create theme-selector list
881 self.tree.set_headers_visible(False)
882 self.tree.connect('cursor-changed', self.__tree_cursor_changed)
883 self.tree.show()
884 col = gtk.TreeViewColumn('')
885 cell = gtk.CellRendererText()
886 col.pack_start(cell, True)
887 #cell.set_property('foreground', 'black')
888 col.set_cell_data_func(cell, self.__render_cell)
889 self.tree.append_column(col)
890 # wrap tree in scrollwin
891 sw = gtk.ScrolledWindow()
892 sw.set_shadow_type(gtk.SHADOW_IN)
893 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
894 sw.add(self.tree)
895 sw.show()
896 # add vbox and add tree/buttons
897 vbox = gtk.VBox()
898 vbox.pack_start(sw, True, True)
899 vbox.show()
900 # show/add page
901 self.page_themes.add(vbox)
902 self.page_themes.show()
903 self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
904
906 """Callback for rendering the cells in the theme-treeview."""
907 # get attributes-list from Treemodel
908 attrib = model.get_value(iter, 0)
909
910 # set colors depending on state
911 col = '555555'
912 name_uc = attrib[0][0].upper() + attrib[0][1:]
913 # create markup depending on info
914 if attrib[1] == '-' and attrib[2] == '-':
915 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
916 '</span></b> (' + _('no info available') + ')'
917 else:
918 if attrib[1] == None : attrib[1] = '-'
919 if attrib[2] == None : attrib[2] = '-'
920 if attrib[3] == None : attrib[3] = '-'
921 if attrib[4] == None : attrib[4] = '-'
922 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
923 '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
924 '">' + attrib[2].replace('\\n', '\n') + \
925 '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
926 # set markup
927 cell.set_property('markup', mu)
928
929 # UI-callbacks
930
932 """Callback for handling selection changes in the Themes-treeview."""
933 sel = self.tree.get_selection()
934 if sel:
935 s = sel.get_selected()
936 if s:
937 it = s[1]
938 if it:
939 attribs = self.liststore.get_value(it, 0)
940 if attribs and self.__shown_object:
941 #print attribs
942 # set theme in Screenlet (if not already active)
943 if self.__shown_object.theme_name != attribs[0]:
944 self.__shown_object.theme_name = attribs[0]
945
946 # option-widget creation (should be split in several classes)
947
949 """Return a gtk.*Widget with Label within a HBox for a given option.
950 NOTE: This is incredibly ugly, ideally all Option-subclasses should
951 have their own widgets - like StringOptionWidget, ColorOptionWidget,
952 ... and then be simply created dynamically"""
953 t = option.__class__
954 widget = None
955 if t == BoolOption:
956 widget = gtk.CheckButton()
957 widget.set_active(value)
958 widget.connect("toggled", self.options_callback, option)
959 elif t == StringOption:
960 if option.choices:
961 # if a list of values is defined, show combobox
962 widget = gtk.combo_box_new_text()
963 p = -1
964 i = 0
965 for s in option.choices:
966 widget.append_text(s)
967 if s==value:
968 p = i
969 i+=1
970 widget.set_active(p)
971 #widget.connect("changed", self.options_callback, option)
972 else:
973 widget = gtk.Entry()
974 widget.set_text(value)
975 # if it is a password, set text to be invisible
976 if option.password:
977 widget.set_visibility(False)
978 #widget.connect("key-press-event", self.options_callback, option)
979 widget.connect("changed", self.options_callback, option)
980 #widget.set_size_request(180, 28)
981 elif t == IntOption or t == FloatOption:
982 widget = gtk.SpinButton()
983 #widget.set_size_request(50, 22)
984 #widget.set_text(str(value))
985 if t == FloatOption:
986 widget.set_digits(option.digits)
987 widget.set_increments(option.increment, int(option.max/option.increment))
988 else:
989 widget.set_increments(option.increment, int(option.max/option.increment))
990 if option.min!=None and option.max!=None:
991 #print "Setting range for input to: %f, %f" % (option.min, option.max)
992 widget.set_range(option.min, option.max)
993 widget.set_value(value)
994 widget.connect("value-changed", self.options_callback, option)
995 elif t == ColorOption:
996 widget = gtk.ColorButton(gtk.gdk.Color(int(value[0]*65535), int(value[1]*65535), int(value[2]*65535)))
997 widget.set_use_alpha(True)
998 # print value
999 # print value[3]
1000 widget.set_alpha(int(value[3]*65535))
1001 widget.connect("color-set", self.options_callback, option)
1002 elif t == FontOption:
1003 widget = gtk.FontButton()
1004 widget.set_font_name(value)
1005 widget.connect("font-set", self.options_callback, option)
1006 elif t == FileOption:
1007 widget = gtk.FileChooserButton(_("Choose File"))
1008 widget.set_filename(value)
1009 widget.set_size_request(180, 28)
1010 widget.connect("selection-changed", self.options_callback, option)
1011 elif t == DirectoryOption:
1012 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1013 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
1014 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1015 widget = gtk.FileChooserButton(dlg)
1016 widget.set_title(_("Choose Directory"))
1017 widget.set_filename(value)
1018 widget.set_size_request(180, 28)
1019 widget.connect("selection-changed", self.options_callback, option)
1020 elif t == ImageOption:
1021 # create entry and button (entry is hidden)
1022 entry = gtk.Entry()
1023 entry.set_text(value)
1024 entry.set_editable(False)
1025 but = gtk.Button()
1026 # util to reload preview image
1027 def create_preview (filename):
1028 if filename and os.path.isfile(filename):
1029 pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
1030 if pb:
1031 img = gtk.Image()
1032 img.set_from_pixbuf(pb)
1033 return img
1034 img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
1035 gtk.ICON_SIZE_LARGE_TOOLBAR)
1036 img.set_size_request(64, 64)
1037 return img
1038 # create button
1039 def but_callback (widget):
1040 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1041 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
1042 dlg.set_title(_("Choose Image"))
1043 dlg.set_keep_above(True)
1044 dlg.set_filename(entry.get_text())
1045 flt = gtk.FileFilter()
1046 flt.add_pixbuf_formats()
1047 dlg.set_filter(flt)
1048 prev = gtk.Image()
1049 box = gtk.VBox()
1050 box.set_size_request(150, -1)
1051 box.add(prev)
1052 prev.show()
1053 # add preview widget to filechooser
1054 def preview_callback(widget):
1055 fname = dlg.get_preview_filename()
1056 if fname and os.path.isfile(fname):
1057 pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
1058 if pb:
1059 prev.set_from_pixbuf(pb)
1060 dlg.set_preview_widget_active(True)
1061 else:
1062 dlg.set_preview_widget_active(False)
1063 dlg.set_preview_widget_active(True)
1064 dlg.connect('selection-changed', preview_callback)
1065 dlg.set_preview_widget(box)
1066 # run
1067 response = dlg.run()
1068 if response == gtk.RESPONSE_OK:
1069 entry.set_text(dlg.get_filename())
1070 but.set_image(create_preview(dlg.get_filename()))
1071 self.options_callback(dlg, option)
1072 dlg.destroy()
1073 # load preview image
1074 but.set_image(create_preview(value))
1075 but.connect('clicked', but_callback)
1076 # create widget
1077 widget = gtk.HBox()
1078 widget.add(entry)
1079 widget.add(but)
1080 but.show()
1081 widget.show()
1082 # add tooltips
1083 #self.tooltips.set_tip(but, 'Select Image ...')
1084 self.tooltips.set_tip(but, option.desc)
1085 elif t == ListOption:
1086 entry= gtk.Entry()
1087 entry.set_editable(False)
1088 entry.set_text(str(value))
1089 entry.show()
1090 img = gtk.Image()
1091 img.set_from_stock(gtk.STOCK_EDIT, 1)
1092 but = gtk.Button()
1093 but.set_image(img)
1094 def open_listeditor(event):
1095 # open dialog
1096 dlg = ListOptionDialog()
1097 # read string from entry and import it through option-class
1098 # (this is needed to always have an up-to-date value)
1099 dlg.set_list(option.on_import(entry.get_text()))
1100 resp = dlg.run()
1101 if resp == gtk.RESPONSE_OK:
1102 # set text in entry
1103 entry.set_text(str(dlg.get_list()))
1104 # manually call the options-callback
1105 self.options_callback(dlg, option)
1106 dlg.destroy()
1107 but.show()
1108 but.connect("clicked", open_listeditor)
1109 self.tooltips.set_tip(but, _('Open List-Editor ...'))
1110 self.tooltips.set_tip(entry, option.desc)
1111 widget = gtk.HBox()
1112 widget.add(entry)
1113 widget.add(but)
1114 elif t == AccountOption:
1115 widget = gtk.HBox()
1116 vb = gtk.VBox()
1117 input_name = gtk.Entry()
1118 input_name.set_text(value[0])
1119 input_name.show()
1120 input_pass = gtk.Entry()
1121 input_pass.set_visibility(False) # password
1122 input_pass.set_text(value[1])
1123 input_pass.show()
1124 but = gtk.Button('Apply', gtk.STOCK_APPLY)
1125 but.show()
1126 but.connect("clicked", self.apply_options_callback, option, widget)
1127 vb.add(input_name)
1128 vb.add(input_pass)
1129 vb.show()
1130 self.tooltips.set_tip(but, _('Apply username/password ...'))
1131 self.tooltips.set_tip(input_name, _('Enter username here ...'))
1132 self.tooltips.set_tip(input_pass, _('Enter password here ...'))
1133 widget.add(vb)
1134 widget.add(but)
1135 elif t == TimeOption:
1136 widget = gtk.HBox()
1137 input_hour = gtk.SpinButton()#climb_rate=1.0)
1138 input_minute = gtk.SpinButton()
1139 input_second = gtk.SpinButton()
1140 input_hour.set_range(0, 23)
1141 input_hour.set_max_length(2)
1142 input_hour.set_increments(1, 1)
1143 input_hour.set_numeric(True)
1144 input_hour.set_value(value[0])
1145 input_minute.set_range(0, 59)
1146 input_minute.set_max_length(2)
1147 input_minute.set_increments(1, 1)
1148 input_minute.set_numeric(True)
1149 input_minute.set_value(value[1])
1150 input_second.set_range(0, 59)
1151 input_second.set_max_length(2)
1152 input_second.set_increments(1, 1)
1153 input_second.set_numeric(True)
1154 input_second.set_value(value[2])
1155 input_hour.connect('value-changed', self.options_callback, option)
1156 input_minute.connect('value-changed', self.options_callback, option)
1157 input_second.connect('value-changed', self.options_callback, option)
1158 self.tooltips.set_tip(input_hour, option.desc)
1159 self.tooltips.set_tip(input_minute, option.desc)
1160 self.tooltips.set_tip(input_second, option.desc)
1161 widget.add(input_hour)
1162 widget.add(gtk.Label(':'))
1163 widget.add(input_minute)
1164 widget.add(gtk.Label(':'))
1165 widget.add(input_second)
1166 widget.add(gtk.Label('h'))
1167 widget.show_all()
1168 else:
1169 widget = gtk.Entry()
1170 print "unsupported type ''" % str(t)
1171 hbox = gtk.HBox()
1172 label = gtk.Label()
1173 label.set_alignment(0.0, 0.0)
1174 label.set_label(option.label)
1175 label.set_size_request(180, 28)
1176 label.show()
1177 hbox.pack_start(label, 0, 1)
1178 if widget:
1179 if option.disabled: # option disabled?
1180 widget.set_sensitive(False)
1181 label.set_sensitive(False)
1182 #label.set_mnemonic_widget(widget)
1183 self.tooltips.set_tip(widget, option.desc)
1184 widget.show()
1185 # check if needs Apply-button
1186 if option.realtime == False:
1187 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1188 but.show()
1189 but.connect("clicked", self.apply_options_callback,
1190 option, widget)
1191 b = gtk.HBox()
1192 b.show()
1193 b.pack_start(widget, 0, 0)
1194 b.pack_start(but, 0, 0)
1195 hbox.pack_start(b, 0, 0)
1196 else:
1197 #hbox.pack_start(widget, -1, 1)
1198 hbox.pack_start(widget, 0, 0)
1199 return hbox
1200
1202 """Read an option's value from the widget and return it."""
1203 if not widget.window:
1204 return False
1205 # get type of option and read the widget's value
1206 val = None
1207 t = option.__class__
1208 if t == IntOption:
1209 val = int(widget.get_value())
1210 elif t == FloatOption:
1211 val = widget.get_value()
1212 elif t == StringOption:
1213 if option.choices:
1214 # if default is a list, handle combobox
1215 val = widget.get_active_text()
1216 else:
1217 val = widget.get_text()
1218 elif t == BoolOption:
1219 val = widget.get_active()
1220 elif t == ColorOption:
1221 col = widget.get_color()
1222 al = widget.get_alpha()
1223 val = (col.red/65535.0, col.green/65535.0,
1224 col.blue/65535.0, al/65535.0)
1225 elif t == FontOption:
1226 val = widget.get_font_name()
1227 elif t == FileOption or t == DirectoryOption or t == ImageOption:
1228 val = widget.get_filename()
1229 #print widget
1230 #elif t == ImageOption:
1231 # val = widget.get_text()
1232 elif t == ListOption:
1233 # the widget is a ListOptionDialog here
1234 val = widget.get_list()
1235 elif t == AccountOption:
1236 # the widget is a HBox containing a VBox containing two Entries
1237 # (ideally we should have a custom widget for the AccountOption)
1238 for c in widget.get_children():
1239 if c.__class__ == gtk.VBox:
1240 c2 = c.get_children()
1241 val = (c2[0].get_text(), c2[1].get_text())
1242 elif t == TimeOption:
1243 box = widget.get_parent()
1244 inputs = box.get_children()
1245 val = (int(inputs[0].get_value()), int(inputs[2].get_value()),
1246 int(inputs[4].get_value()))
1247 else:
1248 print "OptionsDialog: Unknown option type: %s" % str(t)
1249 return None
1250 # return the value
1251 return val
1252
1253 # option-widget event-handling
1254
1255 # TODO: custom callback/signal for each option?
1257 """Callback for handling changed-events on entries."""
1258 print "Changed: %s" % optionobj.name
1259 if self.__shown_object:
1260 # if the option is not real-time updated,
1261 if optionobj.realtime == False:
1262 return False
1263 # read option
1264 val = self.read_option_from_widget(widget, optionobj)
1265 if val != None:
1266 #print "SetOption: "+optionobj.name+"="+str(val)
1267 # set option
1268 setattr(self.__shown_object, optionobj.name, val)
1269 # notify option-object's on_changed-handler
1270 optionobj.emit("option_changed", optionobj)
1271 return False
1272
1274 """Callback for handling Apply-button presses."""
1275 if self.__shown_object:
1276 # read option
1277 val = self.read_option_from_widget(entry, optionobj)
1278 if val != None:
1279 #print "SetOption: "+optionobj.name+"="+str(val)
1280 # set option
1281 setattr(self.__shown_object, optionobj.name, val)
1282 # notify option-object's on_changed-handler
1283 optionobj.emit("option_changed", optionobj)
1284 return False
1285
1286
1287
1288 # ------ ONLY FOR TESTING ------------------:
1289 if __name__ == "__main__":
1290
1291 import os
1292
1293 # this is only for testing - should be a Screenlet
1295
1296 testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
1297 pop3_account = ('Username', '')
1298
1299 # TEST
1300 pin_x = 100
1301 pin_y = 6
1302 text_x = 19
1303 text_y = 35
1304 font_name = 'Sans 12'
1305 rgba_color = (0.0, 0.0, 1.0, 1.0)
1306 text_prefix = '<b>'
1307 text_suffix = '</b>'
1308 note_text = "" # hidden option because val has its own editing-dialog
1309 random_pin_pos = True
1310 opt1 = 'testval 1'
1311 opt2 = 'testval 2'
1312 filename2 = ''
1313 filename = ''
1314 dirname = ''
1315 font = 'Sans 12'
1316 color = (0.1, 0.5, 0.9, 0.9)
1317 name = 'a name'
1318 name2 = 'another name'
1319 combo_test = 'el2'
1320 flt = 0.5
1321 x = 10
1322 y = 25
1323 width = 30
1324 height = 50
1325 is_sticky = False
1326 is_widget = False
1327 time = (12, 32, 49) # a time-value (tuple with ints)
1328
1330 EditableOptions.__init__(self)
1331 # Add group
1332 self.add_options_group('General',
1333 'The general options for this Object ...')
1334 self.add_options_group('Window',
1335 'The Window-related options for this Object ...')
1336 self.add_options_group('Test', 'A Test-group ...')
1337 # Add editable options
1338 self.add_option(ListOption('Test', 'testlist', self.testlist,
1339 'ListOption-Test', 'Testing a ListOption-type ...'))
1340 self.add_option(StringOption('Window', 'name', 'TESTNAME',
1341 'Testname', 'The name/id of this Screenlet-instance ...'),
1342 realtime=False)
1343 self.add_option(AccountOption('Test', 'pop3_account',
1344 self.pop3_account, 'Username/Password',
1345 'Enter username/password here ...'))
1346 self.add_option(StringOption('Window', 'name2', 'TESTNAME2',
1347 'String2', 'Another string-test ...'))
1348 self.add_option(StringOption('Test', 'combo_test', "el1", 'Combo',
1349 'A StringOption displaying a drop-down-list with choices...',
1350 choices=['el1', 'el2', 'element 3']))
1351 self.add_option(FloatOption('General', 'flt', 30,
1352 'A Float', 'Testing a FLOAT-type ...',
1353 min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
1354 self.add_option(IntOption('General', 'x', 30,
1355 'X-Position', 'The X-position of this Screenlet ...',
1356 min=0, max=gtk.gdk.screen_width()))
1357 self.add_option(IntOption('General', 'y', 30,
1358 'Y-Position', 'The Y-position of this Screenlet ...',
1359 min=0, max=gtk.gdk.screen_height()))
1360 self.add_option(IntOption('Test', 'width', 300,
1361 'Width', 'The width of this Screenlet ...', min=100, max=1000))
1362 self.add_option(IntOption('Test', 'height', 150,
1363 'Height', 'The height of this Screenlet ...',
1364 min=100, max=1000))
1365 self.add_option(BoolOption('General', 'is_sticky', True,
1366 'Stick to Desktop', 'Show this Screenlet always ...'))
1367 self.add_option(BoolOption('General', 'is_widget', False,
1368 'Treat as Widget', 'Treat this Screenlet as a "Widget" ...'))
1369 self.add_option(FontOption('Test', 'font', 'Sans 14',
1370 'Font', 'The font for whatever ...'))
1371 self.add_option(ColorOption('Test', 'color', (1, 0.35, 0.35, 0.7),
1372 'Color', 'The color for whatever ...'))
1373 self.add_option(FileOption('Test', 'filename', os.environ['HOME'],
1374 'Filename-Test', 'Testing a FileOption-type ...',
1375 patterns=['*.py', '*.pyc']))
1376 self.add_option(ImageOption('Test', 'filename2', os.environ['HOME'],
1377 'Image-Test', 'Testing the ImageOption-type ...'))
1378 self.add_option(DirectoryOption('Test', 'dirname', os.environ['HOME'],
1379 'Directory-Test', 'Testing a FileOption-type ...'))
1380 self.add_option(TimeOption('Test','time', self.time,
1381 'TimeOption-Test', 'Testing a TimeOption-type ...'))
1382 # TEST
1383 self.disable_option('width')
1384 self.disable_option('height')
1385 # TEST: load options from file
1386 #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
1387
1391
1393 return self.__class__.__name__[:-6]
1394
1395
1396
1397 # this is only for testing - should be a Screenlet
1399
1400 uses_theme = True
1401 theme_name = 'test'
1402
1404 TestObject.__init__(self)
1405 self.add_option(StringOption('Test', 'anothertest', 'ksjhsjgd',
1406 'Another Test', 'An attribute in the subclass ...'))
1407 self.add_option(StringOption('Test', 'theme_name', self.theme_name,
1408 'Theme', 'The theme for this Screenelt ...',
1409 choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
1410
1411
1412 # TEST: load/save
1413 # TEST: option-editing
1414 to = TestChildObject()
1415 #print to.export_options_as_list()
1416 se = OptionsDialog(500, 380)#, treeview=True)
1417 #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
1418 img = gtk.Image()
1419 img.set_from_file('../share/screenlets/Notes/icon.svg')
1420 se.set_info('TestOptions',
1421 'A test for an extended options-dialog with embedded about-info.' +
1422 ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
1423 '<span color="red"> ONLY A TEST!</span>',
1424 '(c) RYX 2007', version='v0.0.1', icon=img)
1425 se.show_options_for_object(to)
1426 resp = se.run()
1427 if resp == gtk.RESPONSE_OK:
1428 print "OK"
1429 else:
1430 print "Cancelled."
1431 se.destroy()
1432 print to.export_options_as_list()
1433
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Dec 4 19:47:07 2010 | http://epydoc.sourceforge.net |