Merge lp:~doctormo/screenlets/options-rework into lp:screenlets

Proposed by Martin Owens
Status: Merged
Merged at revision: 632
Proposed branch: lp:~doctormo/screenlets/options-rework
Merge into: lp:screenlets
Diff against target: 3868 lines (+2019/-1579)
14 files modified
setup.py (+1/-1)
src/lib/__init__.py (+156/-146)
src/lib/options.py (+0/-1432)
src/lib/options/__init__.py (+202/-0)
src/lib/options/account_option.py (+143/-0)
src/lib/options/base.py (+624/-0)
src/lib/options/boolean_option.py (+54/-0)
src/lib/options/colour_option.py (+149/-0)
src/lib/options/file_option.py (+179/-0)
src/lib/options/font_option.py (+52/-0)
src/lib/options/list_option.py (+210/-0)
src/lib/options/number_option.py (+90/-0)
src/lib/options/string_option.py (+79/-0)
src/lib/options/time_option.py (+80/-0)
To merge this branch: bzr merge lp:~doctormo/screenlets/options-rework
Reviewer Review Type Date Requested Status
Screenlets Dev Team Pending
Review via email: mp+55179@code.launchpad.net

Description of the change

Reattempt to get merged in updated options. These are API compatible and offer a new colour array option.

To post a comment you must log in.
Revision history for this message
Märt Põder (boamaod) wrote :

It seems to function generally, but acts a bit differently from the old one. For example text option does not save multiline text (probably it needs to be escaped). If nobody is against it, I think the code should be merged if Martin is willing to work on the code and fix bugs when they appear...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'setup.py'
2--- setup.py 2010-11-10 23:53:53 +0000
3+++ setup.py 2011-03-28 16:19:24 +0000
4@@ -119,7 +119,7 @@
5 os.system (buildcmd % (dname, name, name, dname))
6 files_list.append ((destpath % dname, [mopath % (dname,name.replace('-'+dname,''))]))
7
8-PACKAGES = ['screenlets','screenlets.plugins']
9+PACKAGES = ['screenlets','screenlets.plugins','screenlets.options']
10
11 # ----------------------------
12 # Call setup()
13
14=== modified file 'src/lib/__init__.py'
15--- src/lib/__init__.py 2011-02-09 20:45:08 +0000
16+++ src/lib/__init__.py 2011-03-28 16:19:24 +0000
17@@ -136,15 +136,15 @@
18
19 DEBIAN = True
20 try:
21- subprocess.call(["dpkg"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
22+ subprocess.call(["dpkg"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
23 except OSError:
24- DEBIAN = False
25+ DEBIAN = False
26
27 UBUNTU = True
28 try:
29- subprocess.call(["apt-add-repository"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
30+ subprocess.call(["apt-add-repository"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
31 except OSError:
32- UBUNTU = False
33+ UBUNTU = False
34
35 #-------------------------------------------------------------------------------
36 # CLASSES
37@@ -318,8 +318,8 @@
38 """@DEPRECATED Moved to Screenlets class: Draws a circule"""
39 ctx.save()
40 ctx.translate(x, y)
41- ctx.arc(width/2,height/2,min(height,width)/2,0,2*math.pi)
42- if fill:ctx.fill()
43+ ctx.arc(width/2,height/2,min(height,width)/2,0,2*math.pi)
44+ if fill: ctx.fill()
45 else: ctx.stroke()
46 ctx.restore()
47
48@@ -328,7 +328,7 @@
49 ctx.save()
50 ctx.move_to(start_x, start_y)
51 ctx.set_line_width(line_width)
52- ctx.rel_line_to(end_x, end_y)
53+ ctx.rel_line_to(end_x, end_y)
54 if close : ctx.close_path()
55 if preserve: ctx.stroke_preserve()
56 else: ctx.stroke()
57@@ -348,30 +348,30 @@
58 ctx.save()
59 ctx.translate(x, y)
60 padding=0 # Padding from the edges of the window
61- rounded=rounded_angle # How round to make the edges 20 is ok
62- w = width
63+ rounded=rounded_angle # How round to make the edges 20 is ok
64+ w = width
65 h = height
66
67- # Move to top corner
68- ctx.move_to(0+padding+rounded, 0+padding)
69-
70- # Top right corner and round the edge
71- ctx.line_to(w-padding-rounded, 0+padding)
72- ctx.arc(w-padding-rounded, 0+padding+rounded, rounded, (math.pi/2 )+(math.pi) , 0)
73-
74- # Bottom right corner and round the edge
75- ctx.line_to(w-padding, h-padding-rounded)
76- ctx.arc(w-padding-rounded, h-padding-rounded, rounded, 0, math.pi/2)
77-
78- # Bottom left corner and round the edge.
79- ctx.line_to(0+padding+rounded, h-padding)
80- ctx.arc(0+padding+rounded, h-padding-rounded, rounded,math.pi/2, math.pi)
81-
82- # Top left corner and round the edge
83- ctx.line_to(0+padding, 0+padding+rounded)
84- ctx.arc(0+padding+rounded, 0+padding+rounded, rounded, math.pi, (math.pi/2 )+(math.pi))
85-
86- # Fill in the shape.
87+ # Move to top corner
88+ ctx.move_to(0+padding+rounded, 0+padding)
89+
90+ # Top right corner and round the edge
91+ ctx.line_to(w-padding-rounded, 0+padding)
92+ ctx.arc(w-padding-rounded, 0+padding+rounded, rounded, (math.pi/2 )+(math.pi) , 0)
93+
94+ # Bottom right corner and round the edge
95+ ctx.line_to(w-padding, h-padding-rounded)
96+ ctx.arc(w-padding-rounded, h-padding-rounded, rounded, 0, math.pi/2)
97+
98+ # Bottom left corner and round the edge.
99+ ctx.line_to(0+padding+rounded, h-padding)
100+ ctx.arc(0+padding+rounded, h-padding-rounded, rounded,math.pi/2, math.pi)
101+
102+ # Top left corner and round the edge
103+ ctx.line_to(0+padding, 0+padding+rounded)
104+ ctx.arc(0+padding+rounded, 0+padding+rounded, rounded, math.pi, (math.pi/2 )+(math.pi))
105+
106+ # Fill in the shape.
107 if fill:ctx.fill()
108 else: ctx.stroke()
109 ctx.restore()
110@@ -430,29 +430,29 @@
111 ctx.restore()
112
113 def show_notification (self,text):
114- """@DEPRECATED Moved to Screenlets class: Show notification window at current mouse position."""
115+ """@DEPRECATED Moved to Screenlets class: Show notification window at current mouse position."""
116 if self.notify == None:
117- self.notify = Notify()
118- self.notify.text = text
119- self.notify.show()
120+ self.notify = Notify()
121+ self.notify.text = text
122+ self.notify.show()
123
124 def hide_notification (self):
125- """@DEPRECATED Moved to Screenlets class: hide notification window"""
126+ """@DEPRECATED Moved to Screenlets class: hide notification window"""
127 if self.notify != None:
128 self.notify.hide()
129 self.notify = None
130
131 def show_tooltip (self,text,tooltipx,tooltipy):
132- """@DEPRECATED: Moved to Screenlets class: Show tooltip window at current mouse position."""
133+ """@DEPRECATED: Moved to Screenlets class: Show tooltip window at current mouse position."""
134 if self.tooltip == None:
135- self.tooltip = Tooltip(300, 400)
136- self.tooltip.text = text
137- self.tooltip.x = tooltipx
138- self.tooltip.y = tooltipy
139- self.tooltip.show()
140+ self.tooltip = Tooltip(300, 400)
141+ self.tooltip.text = text
142+ self.tooltip.x = tooltipx
143+ self.tooltip.y = tooltipy
144+ self.tooltip.show()
145
146 def hide_tooltip (self):
147- """@DEPRECATED Moved to Screenlets class: hide tooltip window"""
148+ """@DEPRECATED Moved to Screenlets class: hide tooltip window"""
149 if self.tooltip != None:
150 self.tooltip.hide()
151 self.tooltip = None
152@@ -742,70 +742,80 @@
153 else: self.draw_buttons = False
154 if uses_theme:
155 self.uses_theme = True
156- self.add_option(StringOption('Screenlet', 'theme_name',
157- 'default', '', '', hidden=True))
158+ self.add_option(StringOption('Screenlet', 'theme_name',
159+ default='default', hidden=True))
160 # create/add options
161- self.add_option(IntOption('Screenlet', 'x',
162- 0, _('X-Position'), _('The X-position of this Screenlet ...'),
163+ self.add_option(IntOption('Screenlet', 'x',
164+ default=0, label=_('X-Position'),
165+ desc=_('The X-position of this Screenlet ...'),
166 min=0, max=gtk.gdk.screen_width()))
167- self.add_option(IntOption('Screenlet', 'y',
168- 0, _('Y-Position'), _('The Y-position of this Screenlet ...'),
169+ self.add_option(IntOption('Screenlet', 'y',
170+ default=0, label=_('Y-Position'),
171+ desc=_('The Y-position of this Screenlet ...'),
172 min=0, max=gtk.gdk.screen_height()))
173- self.add_option(IntOption('Screenlet', 'width',
174- width, _('Width'), _('The width of this Screenlet ...'),
175- min=16, max=1000, hidden=True))
176- self.add_option(IntOption('Screenlet', 'height',
177- height, _('Height'), _('The height of this Screenlet ...'),
178- min=16, max=1000, hidden=True))
179- self.add_option(FloatOption('Screenlet', 'scale',
180- self.scale, _('Scale'), _('The scale-factor of this Screenlet ...'),
181+ self.add_option(IntOption('Screenlet', 'width',
182+ default=width, label=_('Width'),
183+ desc=_('The width of this Screenlet ...'),
184+ min=16, max=1000, hidden=True))
185+ self.add_option(IntOption('Screenlet', 'height',
186+ default=height, label=_('Height'),
187+ desc=_('The height of this Screenlet ...'),
188+ min=16, max=1000, hidden=True))
189+ self.add_option(FloatOption('Screenlet', 'scale',
190+ default=self.scale, label=_('Scale'),
191+ desc=_('The scale-factor of this Screenlet ...'),
192 min=0.1, max=10.0, digits=2, increment=0.1))
193- self.add_option(FloatOption('Screenlet', 'opacity',
194- self.opacity, _('Opacity'), _('The opacity of the Screenlet window ...'),
195+ self.add_option(FloatOption('Screenlet', 'opacity',
196+ default=self.opacity, label=_('Opacity'),
197+ desc=_('The opacity of the Screenlet window ...'),
198 min=0.1, max=1.0, digits=2, increment=0.1))
199- self.add_option(BoolOption('Screenlet', 'is_sticky',
200- is_sticky, _('Stick to Desktop'),
201- _('Show this Screenlet on all workspaces ...')))
202- self.add_option(BoolOption('Screenlet', 'is_widget',
203- is_widget, _('Treat as Widget'),
204- _('Treat this Screenlet as a "Widget" ...')))
205- self.add_option(BoolOption('Screenlet', 'is_dragged',
206- self.is_dragged, "Is the screenlet dragged","Is the screenlet dragged", hidden=True))
207- self.add_option(BoolOption('Screenlet', 'is_sizable',
208- is_sizable, "Can the screenlet be resized","is_sizable", hidden=True))
209- self.add_option(BoolOption('Screenlet', 'is_visible',
210- self.is_visible, "Usefull to use screenlets as gnome panel applets","is_visible", hidden=True))
211- self.add_option(BoolOption('Screenlet', 'lock_position',
212- self.lock_position, _('Lock position'),
213- _('Stop the screenlet from being moved...')))
214- self.add_option(BoolOption('Screenlet', 'keep_above',
215- self.keep_above, _('Keep above'),
216- _('Keep this Screenlet above other windows ...')))
217- self.add_option(BoolOption('Screenlet', 'keep_below',
218- self.keep_below, _('Keep below'),
219- _('Keep this Screenlet below other windows ...')))
220- self.add_option(BoolOption('Screenlet', 'draw_buttons',
221- self.draw_buttons, _('Draw button controls'),
222- _('Draw buttons in top right corner')))
223- self.add_option(BoolOption('Screenlet', 'skip_pager',
224- self.skip_pager, _('Skip Pager'),
225- _('Set this Screenlet to show/hide in pagers ...')))
226- self.add_option(BoolOption('Screenlet', 'skip_taskbar',
227- self.skip_pager, _('Skip Taskbar'),
228- _('Set this Screenlet to show/hide in taskbars ...')))
229- self.add_option(BoolOption('Screenlet', 'resize_on_scroll',
230- self.resize_on_scroll, _("Resize on mouse scroll"),"resize_on_scroll"))
231+ self.add_option(BoolOption('Screenlet', 'is_sticky',
232+ default=is_sticky, label=_('Stick to Desktop'),
233+ desc=_('Show this Screenlet on all workspaces ...')))
234+ self.add_option(BoolOption('Screenlet', 'is_widget',
235+ default=is_widget, label=_('Treat as Widget'),
236+ desc=_('Treat this Screenlet as a "Widget" ...')))
237+ self.add_option(BoolOption('Screenlet', 'is_dragged',
238+ default=self.is_dragged, label="Is the screenlet dragged",
239+ desc="Is the screenlet dragged", hidden=True))
240+ self.add_option(BoolOption('Screenlet', 'is_sizable',
241+ default=is_sizable, label="Can the screenlet be resized",
242+ desc="is_sizable", hidden=True))
243+ self.add_option(BoolOption('Screenlet', 'is_visible',
244+ default=self.is_visible, label="Usefull to use screenlets as gnome panel applets",
245+ desc="is_visible", hidden=True))
246+ self.add_option(BoolOption('Screenlet', 'lock_position',
247+ default=self.lock_position, label=_('Lock position'),
248+ desc=_('Stop the screenlet from being moved...')))
249+ self.add_option(BoolOption('Screenlet', 'keep_above',
250+ default=self.keep_above, label=_('Keep above'),
251+ desc=_('Keep this Screenlet above other windows ...')))
252+ self.add_option(BoolOption('Screenlet', 'keep_below',
253+ default=self.keep_below, label=_('Keep below'),
254+ desc=_('Keep this Screenlet below other windows ...')))
255+ self.add_option(BoolOption('Screenlet', 'draw_buttons',
256+ default=self.draw_buttons, label=_('Draw button controls'),
257+ desc=_('Draw buttons in top right corner')))
258+ self.add_option(BoolOption('Screenlet', 'skip_pager',
259+ default=self.skip_pager, label=_('Skip Pager'),
260+ desc=_('Set this Screenlet to show/hide in pagers ...')))
261+ self.add_option(BoolOption('Screenlet', 'skip_taskbar',
262+ default=self.skip_pager, label=_('Skip Taskbar'),
263+ desc=_('Set this Screenlet to show/hide in taskbars ...')))
264+ self.add_option(BoolOption('Screenlet', 'resize_on_scroll',
265+ default=self.resize_on_scroll, label=_("Resize on mouse scroll"),
266+ desc="resize_on_scroll"))
267 self.add_option(BoolOption('Screenlet', 'ignore_requirements',
268 self.ignore_requirements, _('Ignore requirements'),
269 _('Set this Screenlet to ignore/demand DEB requirements ...')))
270 if uses_theme:
271 self.ask_on_option_override = ask_on_option_override
272- self.add_option(BoolOption('Screenlet', 'allow_option_override',
273- self.allow_option_override, _('Allow overriding Options'),
274- _('Allow themes to override options in this screenlet ...')))
275- self.add_option(BoolOption('Screenlet', 'ask_on_option_override',
276- self.ask_on_option_override, _('Ask on Override'),
277- _('Show a confirmation-dialog when a theme wants to override ')+\
278+ self.add_option(BoolOption('Screenlet', 'allow_option_override',
279+ default=self.allow_option_override, label=_('Allow overriding Options'),
280+ desc=_('Allow themes to override options in this screenlet ...')))
281+ self.add_option(BoolOption('Screenlet', 'ask_on_option_override',
282+ default=self.ask_on_option_override, label=_('Ask on Override'),
283+ desc=_('Show a confirmation-dialog when a theme wants to override ')+\
284 _('the current options of this Screenlet ...')))
285 # disable width/height
286 self.disable_option('width')
287@@ -959,7 +969,7 @@
288 if self.window.window:
289 self.window.window.set_skip_taskbar_hint(bool(value))
290 # NOTE: This is the new recommended way of storing options in real-time
291- # (we access the backend through the session here)
292+ # (we access the backend through the session here)
293 if self.saving_enabled:
294 o = self.get_option_by_name(name)
295 if o != None:
296@@ -2084,35 +2094,35 @@
297
298
299 def show_notification (self,text):
300- """Show notification window at current mouse position."""
301+ """Show notification window at current mouse position."""
302 if self.notify == None:
303- self.notify = Notify()
304- self.notify.text = text
305- self.notify.show()
306+ self.notify = Notify()
307+ self.notify.text = text
308+ self.notify.show()
309
310 def hide_notification (self):
311- """hide notification window"""
312+ """hide notification window"""
313 if self.notify != None:
314 self.notify.hide()
315 self.notify = None
316
317 def show_tooltip (self,text,tooltipx,tooltipy):
318- """Show tooltip window at current mouse position."""
319+ """Show tooltip window at current mouse position."""
320 if self.tooltip == None:
321- self.tooltip = Tooltip(300, 400)
322- self.tooltip.text = text
323- self.tooltip.x = tooltipx
324- self.tooltip.y = tooltipy
325+ self.tooltip = Tooltip(300, 400)
326+ self.tooltip.text = text
327+ self.tooltip.x = tooltipx
328+ self.tooltip.y = tooltipy
329 self.tooltip.show()
330 else:
331- #self.tooltip = Tooltip(300, 400)
332- self.tooltip.text = text
333- self.tooltip.x = tooltipx
334- self.tooltip.y = tooltipy
335+ #self.tooltip = Tooltip(300, 400)
336+ self.tooltip.text = text
337+ self.tooltip.x = tooltipx
338+ self.tooltip.y = tooltipy
339 #self.tooltip.show()
340
341 def hide_tooltip (self):
342- """hide tooltip window"""
343+ """hide tooltip window"""
344 if self.tooltip != None:
345 self.tooltip.hide()
346 self.tooltip = None
347@@ -2204,21 +2214,21 @@
348 """A window that displays a text and serves as Tooltip (very basic yet)."""
349
350 # internals
351- __timeout = None
352-
353+ __timeout = None
354+
355 # attribs
356- text = ''
357- font_name = 'FreeSans 9'
358- width = 100
359- height = 20
360- x = 0
361- y = 0
362-
363+ text = ''
364+ font_name = 'FreeSans 9'
365+ width = 100
366+ height = 20
367+ x = 0
368+ y = 0
369+
370 def __init__ (self, width, height):
371 object.__init__(self)
372 # init
373- self.__dict__['width'] = width
374- self.__dict__['height'] = height
375+ self.__dict__['width'] = width
376+ self.__dict__['height'] = height
377 self.window = gtk.Window()
378 self.window.set_app_paintable(True)
379 self.window.set_size_request(width, height)
380@@ -2237,7 +2247,7 @@
381 pango.FontDescription(self.font_name))
382 #self.p_layout.set_width(-1)
383 self.p_layout.set_width(width * pango.SCALE - 6)
384-
385+
386 def __setattr__ (self, name, value):
387 self.__dict__[name] = value
388 if name in ('width', 'height', 'text'):
389@@ -2253,7 +2263,7 @@
390 self.window.move(int(value), int(self.y))
391 elif name == 'y':
392 self.window.move(int(self.x), int(value))
393-
394+
395 def show (self):
396 """Show the Tooltip window."""
397 self.cancel_show()
398@@ -2264,22 +2274,22 @@
399 """Show the Tooltip window after a given delay."""
400 self.cancel_show()
401 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
402-
403+
404 def hide (self):
405 """Hide the Tooltip window."""
406 self.cancel_show()
407 self.window.destroy()
408-
409+
410 def cancel_show (self):
411 """Cancel showing of the Tooltip."""
412 if self.__timeout:
413 gobject.source_remove(self.__timeout)
414 self.p_context = None
415 self.p_layout = None
416-
417+
418 def __show_timeout (self):
419 self.show()
420-
421+
422 def screen_changed (self, window, screen=None):
423 if screen == None:
424 screen = window.get_screen()
425@@ -2287,10 +2297,10 @@
426 if not map:
427 map = screen.get_rgb_colormap()
428 window.set_colormap(map)
429-
430+
431 def expose (self, widget, event):
432 ctx = self.window.window.cairo_create()
433- ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
434+ ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
435 # set a clip region for the expose event
436 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
437 ctx.clip()
438@@ -2317,17 +2327,17 @@
439 """A window that displays a text and serves as Notification (very basic yet)."""
440
441 # internals
442- __timeout = None
443-
444+ __timeout = None
445+
446 # attribs
447- text = ''
448- font_name = 'FreeSans 9'
449- width = 200
450- height = 100
451- x = 0
452- y = 0
453+ text = ''
454+ font_name = 'FreeSans 9'
455+ width = 200
456+ height = 100
457+ x = 0
458+ y = 0
459 gradient = cairo.LinearGradient(0, 100,0, 0)
460-
461+
462 def __init__ (self):
463 object.__init__(self)
464 # init
465@@ -2349,7 +2359,7 @@
466 pango.FontDescription(self.font_name))
467 #self.p_layout.set_width(-1)
468 self.p_layout.set_width(self.width * pango.SCALE - 6)
469-
470+
471 def __setattr__ (self, name, value):
472 self.__dict__[name] = value
473 if name in ('text'):
474@@ -2369,22 +2379,22 @@
475 """Show the Notify window after a given delay."""
476 self.cancel_show()
477 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
478-
479+
480 def hide (self):
481 """Hide the Notify window."""
482 self.cancel_show()
483 self.window.destroy()
484-
485+
486 def cancel_show (self):
487 """Cancel showing of the Notify."""
488 if self.__timeout:
489 gobject.source_remove(self.__timeout)
490 self.p_context = None
491 self.p_layout = None
492-
493+
494 def __show_timeout (self):
495 self.show()
496-
497+
498 def screen_changed (self, window, screen=None):
499 if screen == None:
500 screen = window.get_screen()
501@@ -2392,10 +2402,10 @@
502 if not map:
503 map = screen.get_rgb_colormap()
504 window.set_colormap(map)
505-
506+
507 def expose (self, widget, event):
508 ctx = self.window.window.cairo_create()
509- ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
510+ ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
511 # set a clip region for the expose event
512 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
513 ctx.clip()
514
515=== added directory 'src/lib/options'
516=== removed file 'src/lib/options.py'
517--- src/lib/options.py 2011-02-20 05:06:16 +0000
518+++ src/lib/options.py 1970-01-01 00:00:00 +0000
519@@ -1,1432 +0,0 @@
520-# This application is released under the GNU General Public License
521-# v3 (or, at your option, any later version). You can find the full
522-# text of the license under http://www.gnu.org/licenses/gpl.txt.
523-# By using, editing and/or distributing this software you agree to
524-# the terms and conditions of this license.
525-# Thank you for using free software!
526-
527-# Options-system (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
528-#
529-# INFO:
530-# - a dynamic Options-system that allows very easy creation of
531-# objects with embedded configuration-system.
532-# NOTE: The Dialog is not very nice yet - it is not good OOP-practice
533-# because too big functions and bad class-layout ... but it works
534-# for now ... :)
535-#
536-# TODO:
537-# - option-widgets for all option-types (e.g. ListOptionWidget, ColorOptionWidget)
538-# - OptionGroup-class instead of (or behind) add_options_group
539-# - TimeOption, DateOption
540-# - FileOption needs filter/limit-attribute
541-# - allow options to disable/enable other options
542-# - support for EditableOptions-subclasses as options
543-# - separate OptionEditorWidget from Editor-Dialog
544-# - place ui-code into screenlets.options.ui-module
545-# - create own widgets for each Option-subclass
546-#
547-
548-import screenlets
549-import utils
550-
551-import os
552-import gtk, gobject
553-import xml.dom.minidom
554-from xml.dom.minidom import Node
555-
556-# translation stuff
557-import gettext
558-gettext.textdomain('screenlets')
559-gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX + '/share/locale')
560-
561-def _(s):
562- return gettext.gettext(s)
563-
564-# -----------------------------------------------------------------------
565-# Option-classes and subclasses
566-# -----------------------------------------------------------------------
567-
568-class Option(gobject.GObject):
569- """An Option stores information about a certain object-attribute. It doesn't
570- carry information about the value or the object it belongs to - it is only a
571- one-way data-storage for describing how to handle attributes."""
572-
573- __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
574- gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
575-
576- def __init__ (self, group, name, default, label, desc,
577- disabled=False, hidden=False, callback=None, protected=False):
578- """Creates a new Option with the given information."""
579- super(Option, self).__init__()
580- self.name = name
581- self.label = label
582- self.desc = desc
583- self.default = default
584- self.disabled = disabled
585- self.hidden = hidden
586- # for groups (TODO: OptionGroup)
587- self.group= group
588- # callback to be notified when this option changes
589- self.callback = callback
590- # real-time update?
591- self.realtime = True
592- # protected from get/set through service
593- self.protected = protected
594-
595- def on_import (self, strvalue):
596- """Callback - called when an option gets imported from a string.
597- This function MUST return the string-value converted to the required
598- type!"""
599- return strvalue.replace("\\n", "\n")
600-
601- def on_export (self, value):
602- """Callback - called when an option gets exported to a string. The
603- value-argument needs to be converted to a string that can be imported
604- by the on_import-handler. This handler MUST return the value
605- converted to a string!"""
606- return str(value).replace("\n", "\\n")
607-
608-
609-class FileOption (Option):
610- """An Option-subclass for string-values that contain filenames. Adds
611- a patterns-attribute that can contain a list of patterns to be shown
612- in the assigned file selection dialog. The show_pixmaps-attribute
613- can be set to True to make the filedialog show all image-types
614- supported by gtk.Pixmap. If the directory-attributue is true, the
615- dialog will ony allow directories."""
616-
617- def __init__ (self, group, name, default, label, desc,
618- patterns=['*'], image=False, directory=False, **keyword_args):
619- Option.__init__(self, group, name, default,label, desc, **keyword_args)
620- self.patterns = patterns
621- self.image = image
622- self.directory = False
623-
624-
625-class ImageOption (Option):
626- """An Option-subclass for string-values that contain filenames of
627- image-files."""
628-
629-
630-class DirectoryOption (Option):
631- """An Option-subclass for filename-strings that contain directories."""
632-
633-
634-class BoolOption (Option):
635- """An Option for boolean values."""
636-
637- def on_import (self, strvalue):
638- if strvalue == "True":
639- return True
640- return False
641-
642-
643-class StringOption (Option):
644- """An Option for values of type string."""
645-
646- def __init__ (self, group, name, default, label, desc,
647- choices=None, password=False, **keyword_args):
648- Option.__init__(self, group, name, default,label, desc, **keyword_args)
649- self.choices = choices
650- self.password = password
651-
652-
653-class IntOption (Option):
654- """An Option for values of type number (can be int or float)."""
655-
656- def __init__ (self, group, name, default, label, desc, min=-100000, max=100000,
657- increment=1, **keyword_args):
658- Option.__init__(self, group, name, default, label, desc, **keyword_args)
659- self.min = min
660- self.max = max
661- self.increment = increment
662-
663- def on_import (self, strvalue):
664- """Called when IntOption gets imported. Converts str to int."""
665- try:
666- if strvalue[0]=='-':
667- return int(strvalue[1:]) * -1
668- return int(strvalue)
669- except:
670- print "Error during on_import - option: %s." % self.name
671- return 0
672-
673-
674-class FloatOption (IntOption):
675- """An Option for values of type float."""
676-
677- def __init__ (self, group, name, default, label, desc, digits=1,
678- **keyword_args):
679- IntOption.__init__(self, group, name, default, label, desc,
680- **keyword_args)
681- self.digits = digits
682-
683- def on_import (self, strvalue):
684- """Called when FloatOption gets imported. Converts str to float."""
685- if strvalue[0]=='-':
686- return float(strvalue[1:]) * -1.0
687- return float(strvalue)
688-
689-
690-class FontOption (Option):
691- """An Option for fonts (a simple StringOption)."""
692-
693-
694-class ColorOption (Option):
695- """An Option for colors. Stored as a list with 4 values (r, g, b, a)."""
696-
697- def on_import (self, strvalue):
698- """Import (r, g, b, a) from comma-separated string."""
699- # strip braces and spaces
700- strvalue = strvalue.lstrip('(')
701- strvalue = strvalue.rstrip(')')
702- strvalue = strvalue.strip()
703- # split value on commas
704- tmpval = strvalue.split(',')
705- outval = []
706- for f in tmpval:
707- # create list and again remove spaces
708- outval.append(float(f.strip()))
709- return outval
710-
711- def on_export (self, value):
712- """Export r, g, b, a to comma-separated string."""
713- l = len(value)
714- outval = ''
715- for i in xrange(l):
716- outval += str(value[i])
717- if i < l-1:
718- outval += ','
719- return outval
720-
721-
722-class ListOption (Option):
723- """An Option-type for list of strings."""
724-
725- def on_import (self, strvalue):
726- """Import python-style list from a string (like [1, 2, 'test'])"""
727- lst = eval(strvalue)
728- return lst
729-
730- def on_export (self, value):
731- """Export list as string."""
732- return str(value)
733-
734-
735-import gnomekeyring
736-class AccountOption (Option):
737- """An Option-type for username/password combos. Stores the password in
738- the gnome-keyring (if available) and only saves username and auth_token
739- through the screenlets-backend.
740- TODO:
741- - not create new token for any change (use "set" instead of "create" if
742- the given item already exists)
743- - use usual storage if no keyring is available but output warning
744- - on_delete-function for removing the data from keyring when the
745- Screenlet holding the option gets deleted"""
746-
747- def __init__ (self, group, name, default, label, desc, **keyword_args):
748- Option.__init__ (self, group, name, default, label, desc,
749- protected=True, **keyword_args)
750- # check for availability of keyring
751- if not gnomekeyring.is_available():
752- raise Exception('GnomeKeyring is not available!!') # TEMP!!!
753- # THIS IS A WORKAROUND FOR A BUG IN KEYRING (usually we would use
754- # gnomekeyring.get_default_keyring_sync() here):
755- # find first available keyring
756- self.keyring_list = gnomekeyring.list_keyring_names_sync()
757- if len(self.keyring_list) == 0:
758- raise Exception('No keyrings found. Please create one first!')
759- else:
760- # we prefer the default keyring
761- try:
762- self.keyring = gnomekeyring.get_default_keyring_sync()
763- except:
764- if "session" in self.keyring_list:
765- print "Warning: No default keyring found, using session keyring. Storage is not permanent!"
766- self.keyring = "session"
767- else:
768- print "Warning: Neither default nor session keyring found, assuming keyring %s!" % self.keyring_list[0]
769- self.keyring = self.keyring_list[0]
770-
771-
772- def on_import (self, strvalue):
773- """Import account info from a string (like 'username:auth_token'),
774- retrieve the password from the storage and return a tuple containing
775- username and password."""
776- # split string into username/auth_token
777- #data = strvalue.split(':', 1)
778- (name, auth_token) = strvalue.split(':', 1)
779- if name and auth_token:
780- # read pass from storage
781- try:
782- pw = gnomekeyring.item_get_info_sync(self.keyring,
783- int(auth_token)).get_secret()
784- except Exception, ex:
785- print "ERROR: Unable to read password from keyring: %s" % ex
786- pw = ''
787- # return
788- return (name, pw)
789- else:
790- raise Exception('Illegal value in AccountOption.on_import.')
791-
792- def on_export (self, value):
793- """Export the given tuple/list containing a username and a password. The
794- function stores the password in the gnomekeyring and returns a
795- string in form 'username:auth_token'."""
796- # store password in storage
797- attribs = dict(name=value[0])
798- auth_token = gnomekeyring.item_create_sync(self.keyring,
799- gnomekeyring.ITEM_GENERIC_SECRET, value[0], attribs, value[1], True)
800- # build value from username and auth_token
801- return value[0] + ':' + str(auth_token)
802-
803-"""#TEST:
804-o = AccountOption('None', 'pop3_account', ('',''), 'Username/Password', 'Enter username/password here ...')
805-# save option to keyring
806-exported_account = o.on_export(('RYX', 'mysecretpassword'))
807-print exported_account
808-# and read option back from keyring
809-print o.on_import(exported_account)
810-
811-
812-import sys
813-sys.exit(0)"""
814-
815-class TimeOption (ColorOption):
816- """An Option-subclass for string-values that contain dates."""
817-
818-
819-# -----------------------------------------------------------------------
820-# EditableOptions-class and needed functions
821-# -----------------------------------------------------------------------
822-
823-def create_option_from_node (node, groupname):
824- """Create an Option from an XML-node with option-metadata."""
825- #print "TODO OPTION: " + str(cn)
826- otype = node.getAttribute("type")
827- oname = node.getAttribute("name")
828- ohidden = node.getAttribute("hidden")
829- odefault = None
830- oinfo = ''
831- olabel = ''
832- omin = None
833- omax = None
834- oincrement = 1
835- ochoices = ''
836- odigits = None
837- if otype and oname:
838- # parse children of option-node and save all useful attributes
839- for attr in node.childNodes:
840- if attr.nodeType == Node.ELEMENT_NODE:
841- if attr.nodeName == 'label':
842- olabel = attr.firstChild.nodeValue
843- elif attr.nodeName == 'info':
844- oinfo = attr.firstChild.nodeValue
845- elif attr.nodeName == 'default':
846- odefault = attr.firstChild.nodeValue
847- elif attr.nodeName == 'min':
848- omin = attr.firstChild.nodeValue
849- elif attr.nodeName == 'max':
850- omax = attr.firstChild.nodeValue
851- elif attr.nodeName == 'increment':
852- oincrement = attr.firstChild.nodeValue
853- elif attr.nodeName == 'choices':
854- ochoices = attr.firstChild.nodeValue
855- elif attr.nodeName == 'digits':
856- odigits = attr.firstChild.nodeValue
857- # if we have all needed values, create the Option
858- if odefault:
859- # create correct classname here
860- cls = otype[0].upper() + otype.lower()[1:] + 'Option'
861- #print 'Create: ' +cls +' / ' + oname + ' ('+otype+')'
862- # and build new instance (we use on_import for setting default val)
863- clsobj = getattr(__import__(__name__), cls)
864- opt = clsobj(groupname, oname, None, olabel, oinfo)
865- opt.default = opt.on_import(odefault)
866- # set values to the correct types
867- if cls == 'IntOption':
868- if omin:
869- opt.min = int(omin)
870- if omax:
871- opt.max = int(omax)
872- if oincrement:
873- opt.increment = int(oincrement)
874- elif cls == 'FloatOption':
875- if odigits:
876- opt.digits = int(odigits)
877- if omin:
878- opt.min = float(omin)
879- if omax:
880- opt.max = float(omax)
881- if oincrement:
882- opt.increment = float(oincrement)
883- elif cls == 'StringOption':
884- if ochoices:
885- opt.choices = ochoices
886- return opt
887- return None
888-
889-
890-class EditableOptions(object):
891- """The EditableOptions can be inherited from to allow objects to export
892- editable options for editing them with the OptionsEditor-class.
893- NOTE: This could use some improvement and is very poorly coded :) ..."""
894-
895- def __init__ (self):
896- self.__options__ = []
897- self.__options_groups__ = {}
898- # This is a workaround to remember the order of groups
899- self.__options_groups_ordered__ = []
900-
901- def add_option (self, option, callback=None, realtime=True):
902- """Add an editable option to this object. Editable Options can be edited
903- and configured using the OptionsDialog. The optional callback-arg can be
904- used to set a callback that gets notified when the option changes its
905- value."""
906- #print "Add option: "+option.name
907- # if option already editable (i.e. initialized), return
908- for o in self.__options__:
909- if o.name == option.name:
910- return False
911- self.__dict__[option.name] = option.default
912- # set auto-update (TEMPORARY?)
913- option.realtime = realtime
914- # add option to group (output error if group is undefined)
915- try:
916- self.__options_groups__[option.group]['options'].append(option)
917- except:
918- print "Options: Error - group %s not defined." % option.group
919- return False
920- # now add the option
921- self.__options__.append(option)
922- # if callback is set, add callback
923- if callback:
924- option.connect("option_changed", callback)
925- return True
926-
927-
928- def add_options_group (self, name, group_info):
929- """Add a new options-group to this Options-object"""
930- self.__options_groups__[name] = {'label':name,
931- 'info':group_info, 'options':[]}
932- self.__options_groups_ordered__.append(name)
933- #print self.options_groups
934-
935- def disable_option (self, name):
936- """Disable the inputs for a certain Option."""
937- for o in self.__options__:
938- if o.name == name:
939- o.disabled = True
940- return True
941- return False
942-
943- def enable_option(self, name):
944- """Enable the inputs for a certain Option."""
945- for o in self.__options__:
946- if o.name == name:
947- o.disabled = False
948- return True
949- return False
950-
951- def export_options_as_list (self):
952- """Returns all editable options within a list (without groups)
953- as key/value tuples."""
954- lst = []
955- for o in self.__options__:
956- lst.append((o.name, getattr(self, o.name)))
957- return lst
958-
959- def get_option_by_name (self, name):
960- """Returns an option in this Options by it's name (or None).
961- TODO: this gives wrong results in childclasses ... maybe access
962- as class-attribute??"""
963- for o in self.__options__:
964- if o.name == name:
965- return o
966- return None
967-
968- def remove_option (self, name):
969- """Remove an option from this Options."""
970- for o in self.__options__:
971- if o.name == name:
972- del o
973- return True
974- return True
975-
976- def add_options_from_file (self, filename):
977- """This function creates options from an XML-file with option-metadata.
978- TODO: make this more reusable and place it into module (once the groups
979- are own objects)"""
980- # create xml document
981- try:
982- doc = xml.dom.minidom.parse(filename)
983- except:
984- raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
985- # get rootnode
986- root = doc.firstChild
987- if not root or root.nodeName != 'screenlet':
988- raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
989- # ok, let's check the nodes: this one should contain option-groups
990- groups = []
991- for node in root.childNodes:
992- # we only want element-nodes
993- if node.nodeType == Node.ELEMENT_NODE:
994- #print node
995- if node.nodeName != 'group' or not node.hasChildNodes():
996- # we only allow groups in the first level (groups need children)
997- raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
998- else:
999- # ok, create a new group and parse its elements
1000- group = {}
1001- group['name'] = node.getAttribute("name")
1002- if not group['name']:
1003- raise Exception('No name for group defined in "%s".' % filename)
1004- group['info'] = ''
1005- group['options'] = []
1006- # check all children in group
1007- for on in node.childNodes:
1008- if on.nodeType == Node.ELEMENT_NODE:
1009- if on.nodeName == 'info':
1010- # info-node? set group-info
1011- group['info'] = on.firstChild.nodeValue
1012- elif on.nodeName == 'option':
1013- # option node? parse option node
1014- opt = create_option_from_node (on, group['name'])
1015- # ok? add it to list
1016- if opt:
1017- group['options'].append(opt)
1018- else:
1019- raise Exception('Invalid option-node found in "%s".' % filename)
1020-
1021- # create new group
1022- if len(group['options']):
1023- self.add_options_group(group['name'], group['info'])
1024- for o in group['options']:
1025- self.add_option(o)
1026- # add group to list
1027- #groups.append(group)
1028-
1029-# -----------------------------------------------------------------------
1030-# OptionsDialog and UI-classes
1031-# -----------------------------------------------------------------------
1032-
1033-class ListOptionDialog (gtk.Dialog):
1034- """An editing dialog used for editing options of the ListOption-type."""
1035-
1036- model = None
1037- tree = None
1038- buttonbox = None
1039-
1040- # call gtk.Dialog.__init__
1041- def __init__ (self):
1042- super(ListOptionDialog, self).__init__("Edit List",
1043- flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
1044- buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1045- gtk.STOCK_OK, gtk.RESPONSE_OK))
1046- # set size
1047- self.resize(300, 370)
1048- self.set_keep_above(True) # to avoid confusion
1049- # init vars
1050- self.model = gtk.ListStore(str)
1051- # create UI
1052- self.create_ui()
1053-
1054- def create_ui (self):
1055- """Create the user-interface for this dialog."""
1056- # create outer hbox (tree|buttons)
1057- hbox = gtk.HBox()
1058- hbox.set_border_width(10)
1059- hbox.set_spacing(10)
1060- # create tree
1061- self.tree = gtk.TreeView(model=self.model)
1062- self.tree.set_headers_visible(False)
1063- self.tree.set_reorderable(True)
1064- #self.tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
1065- col = gtk.TreeViewColumn('')
1066- cell = gtk.CellRendererText()
1067- #cell.set_property('cell-background', 'cyan')
1068- cell.set_property('foreground', 'black')
1069- col.pack_start(cell, False)
1070- col.set_attributes(cell, text=0)
1071- self.tree.append_column(col)
1072- self.tree.show()
1073- hbox.pack_start(self.tree, True, True)
1074- #sep = gtk.VSeparator()
1075- #sep.show()
1076- #hbox.add(sep)
1077- # create buttons
1078- self.buttonbox = bb = gtk.VButtonBox()
1079- self.buttonbox.set_layout(gtk.BUTTONBOX_START)
1080- b1 = gtk.Button(stock=gtk.STOCK_ADD)
1081- b2 = gtk.Button(stock=gtk.STOCK_EDIT)
1082- b3 = gtk.Button(stock=gtk.STOCK_REMOVE)
1083- b1.connect('clicked', self.button_callback, 'add')
1084- b2.connect('clicked', self.button_callback, 'edit')
1085- b3.connect('clicked', self.button_callback, 'remove')
1086- bb.add(b1)
1087- bb.add(b2)
1088- bb.add(b3)
1089- self.buttonbox.show_all()
1090- #hbox.add(self.buttonbox)
1091- hbox.pack_end(self.buttonbox, False)
1092- # add everything to outer hbox and show it
1093- hbox.show()
1094- self.vbox.add(hbox)
1095-
1096- def set_list (self, lst):
1097- """Set the list to be edited in this editor."""
1098- for el in lst:
1099- self.model.append([el])
1100-
1101- def get_list (self):
1102- """Return the list that is currently being edited in this editor."""
1103- lst = []
1104- for i in self.model:
1105- lst.append(i[0])
1106- return lst
1107-
1108- def remove_selected_item (self):
1109- """Remove the currently selected item."""
1110- sel = self.tree.get_selection()
1111- if sel:
1112- it = sel.get_selected()[1]
1113- if it:
1114- print self.model.get_value(it, 0)
1115- self.model.remove(it)
1116-
1117- def entry_dialog (self, default = ''):
1118- """Show entry-dialog and return string."""
1119- entry = gtk.Entry()
1120- entry.set_text(default)
1121- entry.show()
1122- dlg = gtk.Dialog("Add/Edit Item", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
1123- buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
1124- gtk.RESPONSE_OK))
1125- dlg.set_keep_above(True)
1126- dlg.vbox.add(entry)
1127- resp = dlg.run()
1128- ret = None
1129- if resp == gtk.RESPONSE_OK:
1130- ret = entry.get_text()
1131- dlg.destroy()
1132- return ret
1133-
1134- def button_callback (self, widget, id):
1135- print "PRESS: %s" % id
1136- if id == 'remove':
1137- self.remove_selected_item()
1138- if id == 'add':
1139- new = self.entry_dialog()
1140- if new != None:
1141- self.model.append([new])
1142- if id == 'edit':
1143- sel = self.tree.get_selection()
1144- if sel:
1145- it = sel.get_selected()[1]
1146- if it:
1147- new = self.entry_dialog(self.model.get_value(it, 0))
1148- if new != None:
1149- #self.model.append([new])
1150- self.model.set_value(it, 0, new)
1151-
1152-
1153-# TEST
1154-"""dlg = ListOptionDialog()
1155-dlg.set_list(['test1', 'afarew34s', 'fhjh23faj', 'yxcdfs58df', 'hsdf7jsdfh'])
1156-dlg.run()
1157-print "RESULT: " + str(dlg.get_list())
1158-dlg.destroy()
1159-import sys
1160-sys.exit(1)"""
1161-# /TEST
1162-
1163-class OptionsDialog (gtk.Dialog):
1164- """A dynamic options-editor for editing Screenlets which are implementing
1165- the EditableOptions-class."""
1166-
1167- __shown_object = None
1168-
1169- def __init__ (self, width, height):
1170- # call gtk.Dialog.__init__
1171- super(OptionsDialog, self).__init__(
1172- _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
1173- gtk.DIALOG_NO_SEPARATOR,
1174- buttons = (#gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_APPLY,
1175- gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
1176- # set size
1177- self.resize(width, height)
1178- self.set_keep_above(True) # to avoid confusion
1179- self.set_border_width(10)
1180- # create attribs
1181- self.page_about = None
1182- self.page_options = None
1183- self.page_themes = None
1184- self.vbox_editor = None
1185- self.hbox_about = None
1186- self.infotext = None
1187- self.infoicon = None
1188- # create theme-list
1189- self.liststore = gtk.ListStore(object)
1190- self.tree = gtk.TreeView(model=self.liststore)
1191- # create/add outer notebook
1192- self.main_notebook = gtk.Notebook()
1193- self.main_notebook.show()
1194- self.vbox.add(self.main_notebook)
1195- # create/init notebook pages
1196- self.create_about_page()
1197- self.create_themes_page()
1198- self.create_options_page()
1199-
1200- # "public" functions
1201-
1202- def reset_to_defaults (self):
1203- """Reset all entries for the currently shown object to their default
1204- values (the values the object has when it is first created).
1205- NOTE: This function resets ALL options, so BE CARFEUL!"""
1206- if self.__shown_object:
1207- for o in self.__shown_object.__options__:
1208- # set default value
1209- setattr(self.__shown_object, o.name, o.default)
1210-
1211- def set_info (self, name, info, copyright='', version='', icon=None):
1212- """Update the "About"-page with the given information."""
1213- # convert infotext (remove EOLs and TABs)
1214- info = info.replace("\n", "")
1215- info = info.replace("\t", " ")
1216- # create markup
1217- markup = '\n<b><span size="xx-large">' + name + '</span></b>'
1218- if version:
1219- markup += ' <span size="large"><b>' + version + '</b></span>'
1220- markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
1221- self.infotext.set_markup(markup)
1222- # icon?
1223- if icon:
1224- # remove old icon
1225- if self.infoicon:
1226- self.infoicon.destroy()
1227- # set new icon
1228- self.infoicon = icon
1229- self.infoicon.set_alignment(0.0, 0.10)
1230- self.infoicon.show()
1231- self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
1232- else:
1233- self.infoicon.hide()
1234-
1235- def show_options_for_object (self, obj):
1236- """Update the OptionsEditor to show the options for the given Object.
1237- The Object needs to be an EditableOptions-subclass.
1238- NOTE: This needs heavy improvement and should use OptionGroups once
1239- they exist"""
1240- self.__shown_object = obj
1241- # create notebook for groups
1242- notebook = gtk.Notebook()
1243- self.vbox_editor.add(notebook)
1244- for group in obj.__options_groups_ordered__:
1245- group_data = obj.__options_groups__[group]
1246- # create box for tab-page
1247- page = gtk.VBox()
1248- page.set_border_width(10)
1249- if group_data['info'] != '':
1250- info = gtk.Label(group_data['info'])
1251- info.show()
1252- info.set_alignment(0, 0)
1253- page.pack_start(info, 0, 0, 7)
1254- sep = gtk.HSeparator()
1255- sep.show()
1256- #page.pack_start(sep, 0, 0, 5)
1257- # create VBox for inputs
1258- box = gtk.VBox()
1259- box.show()
1260- box.set_border_width(5)
1261- # add box to page
1262- page.add(box)
1263- page.show()
1264- # add new notebook-page
1265- label = gtk.Label(group_data['label'])
1266- label.show()
1267- notebook.append_page(page, label)
1268- # and create inputs
1269- for option in group_data['options']:
1270- if option.hidden == False:
1271- val = getattr(obj, option.name)#obj.__dict__[option.name]
1272- w = self.get_widget_for_option(option, val)
1273- if w:
1274- box.pack_start(w, 0, 0)
1275- w.show()
1276- notebook.show()
1277- # show/hide themes tab, depending on whether the screenlet uses themes
1278- if obj.uses_theme and obj.theme_name != '':
1279- self.show_themes_for_screenlet(obj)
1280- else:
1281- self.page_themes.hide()
1282-
1283- def show_themes_for_screenlet (self, obj):
1284- """Update the Themes-page to display the available themes for the
1285- given Screenlet-object."""
1286-
1287-
1288- dircontent = []
1289- screenlets.utils.refresh_available_screenlet_paths()
1290-
1291- for path in screenlets.SCREENLETS_PATH:
1292- p = path + '/' + obj.get_short_name() + '/themes'
1293- print p
1294- #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
1295- try:
1296- dc = os.listdir(p)
1297- for d in dc:
1298- dircontent.append({'name':d, 'path':p+'/'})
1299- except:
1300- print "Path %s not found." % p
1301-
1302- # list with found themes
1303- found_themes = []
1304-
1305- # check all themes in path
1306- for elem in dircontent:
1307- # load themes with the same name only once
1308- if found_themes.count(elem['name']):
1309- continue
1310- found_themes.append(elem['name'])
1311- # build full path of theme.conf
1312- theme_conf = elem['path'] + elem['name'] + '/theme.conf'
1313- # if dir contains a theme.conf
1314- if os.access(theme_conf, os.F_OK):
1315- # load it and create new list entry
1316- ini = screenlets.utils.IniReader()
1317- if ini.load(theme_conf):
1318- # check for section
1319- if ini.has_section('Theme'):
1320- # get metainfo from theme
1321- th_fullname = ini.get_option('name',
1322- section='Theme')
1323- th_info = ini.get_option('info',
1324- section='Theme')
1325- th_version = ini.get_option('version',
1326- section='Theme')
1327- th_author = ini.get_option('author',
1328- section='Theme')
1329- # create array from metainfo and add it to liststore
1330- info = [elem['name'], th_fullname, th_info, th_author,
1331- th_version]
1332- self.liststore.append([info])
1333- else:
1334- # no theme section in theme.conf just add theme-name
1335- self.liststore.append([[elem['name'], '-', '-', '-', '-']])
1336- else:
1337- # no theme.conf in dir? just add theme-name
1338- self.liststore.append([[elem['name'], '-', '-', '-', '-']])
1339- # is it the active theme?
1340- if elem['name'] == obj.theme_name:
1341- # select it in tree
1342- print "active theme is: %s" % elem['name']
1343- sel = self.tree.get_selection()
1344- if sel:
1345- it = self.liststore.get_iter_from_string(\
1346- str(len(self.liststore)-1))
1347- if it:
1348- sel.select_iter(it)
1349-
1350- # UI-creation
1351-
1352- def create_about_page (self):
1353- """Create the "About"-tab."""
1354- self.page_about = gtk.HBox()
1355- # create about box
1356- self.hbox_about = gtk.HBox()
1357- self.hbox_about.show()
1358- self.page_about.add(self.hbox_about)
1359- # create icon
1360- self.infoicon = gtk.Image()
1361- self.infoicon.show()
1362- self.page_about.pack_start(self.infoicon, 0, 1, 10)
1363- # create infotext
1364- self.infotext = gtk.Label()
1365- self.infotext.use_markup = True
1366- self.infotext.set_line_wrap(True)
1367- self.infotext.set_alignment(0.0, 0.0)
1368- self.infotext.show()
1369- self.page_about.pack_start(self.infotext, 1, 1, 5)
1370- # add page
1371- self.page_about.show()
1372- self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
1373-
1374- def create_options_page (self):
1375- """Create the "Options"-tab."""
1376- self.page_options = gtk.HBox()
1377- # create vbox for options-editor
1378- self.vbox_editor = gtk.VBox(spacing=3)
1379- self.vbox_editor.set_border_width(5)
1380- self.vbox_editor.show()
1381- self.page_options.add(self.vbox_editor)
1382- # show/add page
1383- self.page_options.show()
1384- self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
1385-
1386- def create_themes_page (self):
1387- """Create the "Themes"-tab."""
1388- self.page_themes = gtk.VBox(spacing=5)
1389- self.page_themes.set_border_width(10)
1390- # create info-text list
1391- 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.'))
1392- txt.set_size_request(450, -1)
1393- txt.set_line_wrap(True)
1394- txt.set_alignment(0.0, 0.0)
1395- txt.show()
1396- self.page_themes.pack_start(txt, False, True)
1397- # create theme-selector list
1398- self.tree.set_headers_visible(False)
1399- self.tree.connect('cursor-changed', self.__tree_cursor_changed)
1400- self.tree.show()
1401- col = gtk.TreeViewColumn('')
1402- cell = gtk.CellRendererText()
1403- col.pack_start(cell, True)
1404- #cell.set_property('foreground', 'black')
1405- col.set_cell_data_func(cell, self.__render_cell)
1406- self.tree.append_column(col)
1407- # wrap tree in scrollwin
1408- sw = gtk.ScrolledWindow()
1409- sw.set_shadow_type(gtk.SHADOW_IN)
1410- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1411- sw.add(self.tree)
1412- sw.show()
1413- # add vbox and add tree/buttons
1414- vbox = gtk.VBox()
1415- vbox.pack_start(sw, True, True)
1416- vbox.show()
1417- # show/add page
1418- self.page_themes.add(vbox)
1419- self.page_themes.show()
1420- self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
1421-
1422- def __render_cell(self, tvcolumn, cell, model, iter):
1423- """Callback for rendering the cells in the theme-treeview."""
1424- # get attributes-list from Treemodel
1425- attrib = model.get_value(iter, 0)
1426-
1427- # set colors depending on state
1428- col = '555555'
1429- name_uc = attrib[0][0].upper() + attrib[0][1:]
1430- # create markup depending on info
1431- if attrib[1] == '-' and attrib[2] == '-':
1432- mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
1433- '</span></b> (' + _('no info available') + ')'
1434- else:
1435- if attrib[1] == None : attrib[1] = '-'
1436- if attrib[2] == None : attrib[2] = '-'
1437- if attrib[3] == None : attrib[3] = '-'
1438- if attrib[4] == None : attrib[4] = '-'
1439- mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
1440- '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
1441- '">' + attrib[2].replace('\\n', '\n') + \
1442- '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
1443- # set markup
1444- cell.set_property('markup', mu)
1445-
1446- # UI-callbacks
1447-
1448- def __tree_cursor_changed (self, treeview):
1449- """Callback for handling selection changes in the Themes-treeview."""
1450- sel = self.tree.get_selection()
1451- if sel:
1452- s = sel.get_selected()
1453- if s:
1454- it = s[1]
1455- if it:
1456- attribs = self.liststore.get_value(it, 0)
1457- if attribs and self.__shown_object:
1458- #print attribs
1459- # set theme in Screenlet (if not already active)
1460- if self.__shown_object.theme_name != attribs[0]:
1461- self.__shown_object.theme_name = attribs[0]
1462-
1463- # option-widget creation (should be split in several classes)
1464-
1465- def get_widget_for_option (self, option, value=None):
1466- """Return a gtk.*Widget with Label within a HBox for a given option.
1467- NOTE: This is incredibly ugly, ideally all Option-subclasses should
1468- have their own widgets - like StringOptionWidget, ColorOptionWidget,
1469- ... and then be simply created dynamically"""
1470- t = option.__class__
1471- widget = None
1472- if t == BoolOption:
1473- widget = gtk.CheckButton()
1474- widget.set_active(value)
1475- widget.connect("toggled", self.options_callback, option)
1476- elif t == StringOption:
1477- if option.choices:
1478- # if a list of values is defined, show combobox
1479- widget = gtk.combo_box_new_text()
1480- p = -1
1481- i = 0
1482- for s in option.choices:
1483- widget.append_text(s)
1484- if s==value:
1485- p = i
1486- i+=1
1487- widget.set_active(p)
1488- #widget.connect("changed", self.options_callback, option)
1489- else:
1490- widget = gtk.Entry()
1491- widget.set_text(value)
1492- # if it is a password, set text to be invisible
1493- if option.password:
1494- widget.set_visibility(False)
1495- #widget.connect("key-press-event", self.options_callback, option)
1496- widget.connect("changed", self.options_callback, option)
1497- #widget.set_size_request(180, 28)
1498- elif t == IntOption or t == FloatOption:
1499- widget = gtk.SpinButton()
1500- #widget.set_size_request(50, 22)
1501- #widget.set_text(str(value))
1502- if t == FloatOption:
1503- widget.set_digits(option.digits)
1504- widget.set_increments(option.increment, int(option.max/option.increment))
1505- else:
1506- widget.set_increments(option.increment, int(option.max/option.increment))
1507- if option.min!=None and option.max!=None:
1508- #print "Setting range for input to: %f, %f" % (option.min, option.max)
1509- widget.set_range(option.min, option.max)
1510- widget.set_value(value)
1511- widget.connect("value-changed", self.options_callback, option)
1512- elif t == ColorOption:
1513- widget = gtk.ColorButton(gtk.gdk.Color(int(value[0]*65535), int(value[1]*65535), int(value[2]*65535)))
1514- widget.set_use_alpha(True)
1515-# print value
1516-# print value[3]
1517- widget.set_alpha(int(value[3]*65535))
1518- widget.connect("color-set", self.options_callback, option)
1519- elif t == FontOption:
1520- widget = gtk.FontButton()
1521- widget.set_font_name(value)
1522- widget.connect("font-set", self.options_callback, option)
1523- elif t == FileOption:
1524- widget = gtk.FileChooserButton(_("Choose File"))
1525- widget.set_filename(value)
1526- widget.set_size_request(180, 28)
1527- widget.connect("selection-changed", self.options_callback, option)
1528- elif t == DirectoryOption:
1529- dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1530- gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
1531- action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1532- widget = gtk.FileChooserButton(dlg)
1533- widget.set_title(_("Choose Directory"))
1534- widget.set_filename(value)
1535- widget.set_size_request(180, 28)
1536- widget.connect("selection-changed", self.options_callback, option)
1537- elif t == ImageOption:
1538- # create entry and button (entry is hidden)
1539- entry = gtk.Entry()
1540- entry.set_text(value)
1541- entry.set_editable(False)
1542- but = gtk.Button()
1543- # util to reload preview image
1544- def create_preview (filename):
1545- if filename and os.path.isfile(filename):
1546- pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
1547- if pb:
1548- img = gtk.Image()
1549- img.set_from_pixbuf(pb)
1550- return img
1551- img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
1552- gtk.ICON_SIZE_LARGE_TOOLBAR)
1553- img.set_size_request(64, 64)
1554- return img
1555- # create button
1556- def but_callback (widget):
1557- dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1558- gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
1559- dlg.set_title(_("Choose Image"))
1560- dlg.set_keep_above(True)
1561- dlg.set_filename(entry.get_text())
1562- flt = gtk.FileFilter()
1563- flt.add_pixbuf_formats()
1564- dlg.set_filter(flt)
1565- prev = gtk.Image()
1566- box = gtk.VBox()
1567- box.set_size_request(150, -1)
1568- box.add(prev)
1569- prev.show()
1570- # add preview widget to filechooser
1571- def preview_callback(widget):
1572- fname = dlg.get_preview_filename()
1573- if fname and os.path.isfile(fname):
1574- pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
1575- if pb:
1576- prev.set_from_pixbuf(pb)
1577- dlg.set_preview_widget_active(True)
1578- else:
1579- dlg.set_preview_widget_active(False)
1580- dlg.set_preview_widget_active(True)
1581- dlg.connect('selection-changed', preview_callback)
1582- dlg.set_preview_widget(box)
1583- # run
1584- response = dlg.run()
1585- if response == gtk.RESPONSE_OK:
1586- entry.set_text(dlg.get_filename())
1587- but.set_image(create_preview(dlg.get_filename()))
1588- self.options_callback(dlg, option)
1589- dlg.destroy()
1590- # load preview image
1591- but.set_image(create_preview(value))
1592- but.connect('clicked', but_callback)
1593- # create widget
1594- widget = gtk.HBox()
1595- widget.add(entry)
1596- widget.add(but)
1597- but.show()
1598- widget.show()
1599- # add tooltips
1600- #but.set_tooltip_text(_('Select Image ...'))
1601- but.set_tooltip_text(option.desc)
1602-
1603- elif t == ListOption:
1604- entry= gtk.Entry()
1605- entry.set_editable(False)
1606- entry.set_text(str(value))
1607- entry.show()
1608- img = gtk.Image()
1609- img.set_from_stock(gtk.STOCK_EDIT, 1)
1610- but = gtk.Button()
1611- but.set_image(img)
1612- def open_listeditor(event):
1613- # open dialog
1614- dlg = ListOptionDialog()
1615- # read string from entry and import it through option-class
1616- # (this is needed to always have an up-to-date value)
1617- dlg.set_list(option.on_import(entry.get_text()))
1618- resp = dlg.run()
1619- if resp == gtk.RESPONSE_OK:
1620- # set text in entry
1621- entry.set_text(str(dlg.get_list()))
1622- # manually call the options-callback
1623- self.options_callback(dlg, option)
1624- dlg.destroy()
1625- but.show()
1626- but.connect("clicked", open_listeditor)
1627- but.set_tooltip_text(_('Open List-Editor ...'))
1628- entry.set_tooltip_text(option.desc)
1629- widget = gtk.HBox()
1630- widget.add(entry)
1631- widget.add(but)
1632- elif t == AccountOption:
1633- widget = gtk.HBox()
1634- vb = gtk.VBox()
1635- input_name = gtk.Entry()
1636- input_name.set_text(value[0])
1637- input_name.show()
1638- input_pass = gtk.Entry()
1639- input_pass.set_visibility(False) # password
1640- input_pass.set_text(value[1])
1641- input_pass.show()
1642- but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1643- but.show()
1644- but.connect("clicked", self.apply_options_callback, option, widget)
1645- vb.add(input_name)
1646- vb.add(input_pass)
1647- vb.show()
1648- but.set_tooltip_text(_('Apply username/password ...'))
1649- input_name.set_tooltip_text(_('Enter username here ...'))
1650- input_pass.set_tooltip_text(_('Enter password here ...'))
1651- widget.add(vb)
1652- widget.add(but)
1653- elif t == TimeOption:
1654- widget = gtk.HBox()
1655- input_hour = gtk.SpinButton()#climb_rate=1.0)
1656- input_minute = gtk.SpinButton()
1657- input_second = gtk.SpinButton()
1658- input_hour.set_range(0, 23)
1659- input_hour.set_max_length(2)
1660- input_hour.set_increments(1, 1)
1661- input_hour.set_numeric(True)
1662- input_hour.set_value(value[0])
1663- input_minute.set_range(0, 59)
1664- input_minute.set_max_length(2)
1665- input_minute.set_increments(1, 1)
1666- input_minute.set_numeric(True)
1667- input_minute.set_value(value[1])
1668- input_second.set_range(0, 59)
1669- input_second.set_max_length(2)
1670- input_second.set_increments(1, 1)
1671- input_second.set_numeric(True)
1672- input_second.set_value(value[2])
1673- input_hour.connect('value-changed', self.options_callback, option)
1674- input_minute.connect('value-changed', self.options_callback, option)
1675- input_second.connect('value-changed', self.options_callback, option)
1676- input_hour.set_tooltip_text(option.desc)
1677- input_minute.set_tooltip_text(option.desc)
1678- input_second.set_tooltip_text(option.desc)
1679- widget.add(input_hour)
1680- widget.add(gtk.Label(':'))
1681- widget.add(input_minute)
1682- widget.add(gtk.Label(':'))
1683- widget.add(input_second)
1684- widget.add(gtk.Label('h'))
1685- widget.show_all()
1686- else:
1687- widget = gtk.Entry()
1688- print "unsupported type ''" % str(t)
1689- hbox = gtk.HBox()
1690- label = gtk.Label()
1691- label.set_alignment(0.0, 0.0)
1692- label.set_label(option.label)
1693- label.set_size_request(180, 28)
1694- label.show()
1695- hbox.pack_start(label, 0, 1)
1696- if widget:
1697- if option.disabled: # option disabled?
1698- widget.set_sensitive(False)
1699- label.set_sensitive(False)
1700- #label.set_mnemonic_widget(widget)
1701- widget.set_tooltip_text(option.desc)
1702- widget.show()
1703- # check if needs Apply-button
1704- if option.realtime == False:
1705- but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1706- but.show()
1707- but.connect("clicked", self.apply_options_callback,
1708- option, widget)
1709- b = gtk.HBox()
1710- b.show()
1711- b.pack_start(widget, 0, 0)
1712- b.pack_start(but, 0, 0)
1713- hbox.pack_start(b, 0, 0)
1714- else:
1715- #hbox.pack_start(widget, -1, 1)
1716- hbox.pack_start(widget, 0, 0)
1717- return hbox
1718-
1719- def read_option_from_widget (self, widget, option):
1720- """Read an option's value from the widget and return it."""
1721- if not widget.window:
1722- return False
1723- # get type of option and read the widget's value
1724- val = None
1725- t = option.__class__
1726- if t == IntOption:
1727- val = int(widget.get_value())
1728- elif t == FloatOption:
1729- val = widget.get_value()
1730- elif t == StringOption:
1731- if option.choices:
1732- # if default is a list, handle combobox
1733- val = widget.get_active_text()
1734- else:
1735- val = widget.get_text()
1736- elif t == BoolOption:
1737- val = widget.get_active()
1738- elif t == ColorOption:
1739- col = widget.get_color()
1740- al = widget.get_alpha()
1741- val = (col.red/65535.0, col.green/65535.0,
1742- col.blue/65535.0, al/65535.0)
1743- elif t == FontOption:
1744- val = widget.get_font_name()
1745- elif t == FileOption or t == DirectoryOption or t == ImageOption:
1746- val = widget.get_filename()
1747- #print widget
1748- #elif t == ImageOption:
1749- # val = widget.get_text()
1750- elif t == ListOption:
1751- # the widget is a ListOptionDialog here
1752- val = widget.get_list()
1753- elif t == AccountOption:
1754- # the widget is a HBox containing a VBox containing two Entries
1755- # (ideally we should have a custom widget for the AccountOption)
1756- for c in widget.get_children():
1757- if c.__class__ == gtk.VBox:
1758- c2 = c.get_children()
1759- val = (c2[0].get_text(), c2[1].get_text())
1760- elif t == TimeOption:
1761- box = widget.get_parent()
1762- inputs = box.get_children()
1763- val = (int(inputs[0].get_value()), int(inputs[2].get_value()),
1764- int(inputs[4].get_value()))
1765- else:
1766- print "OptionsDialog: Unknown option type: %s" % str(t)
1767- return None
1768- # return the value
1769- return val
1770-
1771- # option-widget event-handling
1772-
1773- # TODO: custom callback/signal for each option?
1774- def options_callback (self, widget, optionobj):
1775- """Callback for handling changed-events on entries."""
1776- print "Changed: %s" % optionobj.name
1777- if self.__shown_object:
1778- # if the option is not real-time updated,
1779- if optionobj.realtime == False:
1780- return False
1781- # read option
1782- val = self.read_option_from_widget(widget, optionobj)
1783- if val != None:
1784- #print "SetOption: "+optionobj.name+"="+str(val)
1785- # set option
1786- setattr(self.__shown_object, optionobj.name, val)
1787- # notify option-object's on_changed-handler
1788- optionobj.emit("option_changed", optionobj)
1789- return False
1790-
1791- def apply_options_callback (self, widget, optionobj, entry):
1792- """Callback for handling Apply-button presses."""
1793- if self.__shown_object:
1794- # read option
1795- val = self.read_option_from_widget(entry, optionobj)
1796- if val != None:
1797- #print "SetOption: "+optionobj.name+"="+str(val)
1798- # set option
1799- setattr(self.__shown_object, optionobj.name, val)
1800- # notify option-object's on_changed-handler
1801- optionobj.emit("option_changed", optionobj)
1802- return False
1803-
1804-
1805-
1806-# ------ ONLY FOR TESTING ------------------:
1807-if __name__ == "__main__":
1808-
1809- import os
1810-
1811- # this is only for testing - should be a Screenlet
1812- class TestObject (EditableOptions):
1813-
1814- testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
1815- pop3_account = ('Username', '')
1816-
1817- # TEST
1818- pin_x = 100
1819- pin_y = 6
1820- text_x = 19
1821- text_y = 35
1822- font_name = 'Sans 12'
1823- rgba_color = (0.0, 0.0, 1.0, 1.0)
1824- text_prefix = '<b>'
1825- text_suffix = '</b>'
1826- note_text = "" # hidden option because val has its own editing-dialog
1827- random_pin_pos = True
1828- opt1 = 'testval 1'
1829- opt2 = 'testval 2'
1830- filename2 = ''
1831- filename = ''
1832- dirname = ''
1833- font = 'Sans 12'
1834- color = (0.1, 0.5, 0.9, 0.9)
1835- name = 'a name'
1836- name2 = 'another name'
1837- combo_test = 'el2'
1838- flt = 0.5
1839- x = 10
1840- y = 25
1841- width = 30
1842- height = 50
1843- is_sticky = False
1844- is_widget = False
1845- time = (12, 32, 49) # a time-value (tuple with ints)
1846-
1847- def __init__ (self):
1848- EditableOptions.__init__(self)
1849- # Add group
1850- self.add_options_group('General',
1851- 'The general options for this Object ...')
1852- self.add_options_group('Window',
1853- 'The Window-related options for this Object ...')
1854- self.add_options_group('Test', 'A Test-group ...')
1855- # Add editable options
1856- self.add_option(ListOption('Test', 'testlist', self.testlist,
1857- 'ListOption-Test', 'Testing a ListOption-type ...'))
1858- self.add_option(StringOption('Window', 'name', 'TESTNAME',
1859- 'Testname', 'The name/id of this Screenlet-instance ...'),
1860- realtime=False)
1861- self.add_option(AccountOption('Test', 'pop3_account',
1862- self.pop3_account, 'Username/Password',
1863- 'Enter username/password here ...'))
1864- self.add_option(StringOption('Window', 'name2', 'TESTNAME2',
1865- 'String2', 'Another string-test ...'))
1866- self.add_option(StringOption('Test', 'combo_test', "el1", 'Combo',
1867- 'A StringOption displaying a drop-down-list with choices...',
1868- choices=['el1', 'el2', 'element 3']))
1869- self.add_option(FloatOption('General', 'flt', 30,
1870- 'A Float', 'Testing a FLOAT-type ...',
1871- min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
1872- self.add_option(IntOption('General', 'x', 30,
1873- 'X-Position', 'The X-position of this Screenlet ...',
1874- min=0, max=gtk.gdk.screen_width()))
1875- self.add_option(IntOption('General', 'y', 30,
1876- 'Y-Position', 'The Y-position of this Screenlet ...',
1877- min=0, max=gtk.gdk.screen_height()))
1878- self.add_option(IntOption('Test', 'width', 300,
1879- 'Width', 'The width of this Screenlet ...', min=100, max=1000))
1880- self.add_option(IntOption('Test', 'height', 150,
1881- 'Height', 'The height of this Screenlet ...',
1882- min=100, max=1000))
1883- self.add_option(BoolOption('General', 'is_sticky', True,
1884- 'Stick to Desktop', 'Show this Screenlet always ...'))
1885- self.add_option(BoolOption('General', 'is_widget', False,
1886- 'Treat as Widget', 'Treat this Screenlet as a "Widget" ...'))
1887- self.add_option(FontOption('Test', 'font', 'Sans 14',
1888- 'Font', 'The font for whatever ...'))
1889- self.add_option(ColorOption('Test', 'color', (1, 0.35, 0.35, 0.7),
1890- 'Color', 'The color for whatever ...'))
1891- self.add_option(FileOption('Test', 'filename', os.environ['HOME'],
1892- 'Filename-Test', 'Testing a FileOption-type ...',
1893- patterns=['*.py', '*.pyc']))
1894- self.add_option(ImageOption('Test', 'filename2', os.environ['HOME'],
1895- 'Image-Test', 'Testing the ImageOption-type ...'))
1896- self.add_option(DirectoryOption('Test', 'dirname', os.environ['HOME'],
1897- 'Directory-Test', 'Testing a FileOption-type ...'))
1898- self.add_option(TimeOption('Test','time', self.time,
1899- 'TimeOption-Test', 'Testing a TimeOption-type ...'))
1900- # TEST
1901- self.disable_option('width')
1902- self.disable_option('height')
1903- # TEST: load options from file
1904- #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
1905-
1906- def __setattr__(self, name, value):
1907- self.__dict__[name] = value
1908- print name + "=" + str(value)
1909-
1910- def get_short_name(self):
1911- return self.__class__.__name__[:-6]
1912-
1913-
1914-
1915- # this is only for testing - should be a Screenlet
1916- class TestChildObject (TestObject):
1917-
1918- uses_theme = True
1919- theme_name = 'test'
1920-
1921- def __init__ (self):
1922- TestObject.__init__(self)
1923- self.add_option(StringOption('Test', 'anothertest', 'ksjhsjgd',
1924- 'Another Test', 'An attribute in the subclass ...'))
1925- self.add_option(StringOption('Test', 'theme_name', self.theme_name,
1926- 'Theme', 'The theme for this Screenelt ...',
1927- choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
1928-
1929-
1930- # TEST: load/save
1931- # TEST: option-editing
1932- to = TestChildObject()
1933- #print to.export_options_as_list()
1934- se = OptionsDialog(500, 380)#, treeview=True)
1935- #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
1936- img = gtk.Image()
1937- img.set_from_file('../share/screenlets/Notes/icon.svg')
1938- se.set_info('TestOptions',
1939- 'A test for an extended options-dialog with embedded about-info.' +
1940- ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
1941- '<span color="red"> ONLY A TEST!</span>',
1942- '(c) RYX 2007', version='v0.0.1', icon=img)
1943- se.show_options_for_object(to)
1944- resp = se.run()
1945- if resp == gtk.RESPONSE_OK:
1946- print "OK"
1947- else:
1948- print "Cancelled."
1949- se.destroy()
1950- print to.export_options_as_list()
1951-
1952
1953=== added file 'src/lib/options/__init__.py'
1954--- src/lib/options/__init__.py 1970-01-01 00:00:00 +0000
1955+++ src/lib/options/__init__.py 2011-03-28 16:19:24 +0000
1956@@ -0,0 +1,202 @@
1957+# This application is released under the GNU General Public License
1958+# v3 (or, at your option, any later version). You can find the full
1959+# text of the license under http://www.gnu.org/licenses/gpl.txt.
1960+# By using, editing and/or distributing this software you agree to
1961+# the terms and conditions of this license.
1962+# Thank you for using free software!
1963+
1964+# Options-system (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
1965+# Heavily Refactored by Martin Owens (c) 2009
1966+#
1967+# INFO:
1968+# - a dynamic Options-system that allows very easy creation of
1969+# objects with embedded configuration-system.
1970+# NOTE: The Dialog is not very nice yet - it is not good OOP-practice
1971+# because too big functions and bad class-layout ... but it works
1972+# for now ... :)
1973+#
1974+# TODO:
1975+# - option-widgets for all option-types (e.g. ListOptionWidget, ColorOptionWidget)
1976+# - OptionGroup-class instead of (or behind) add_options_group
1977+# - TimeOption, DateOption
1978+# - FileOption needs filter/limit-attribute
1979+# - allow options to disable/enable other options
1980+# - support for EditableOptions-subclasses as options
1981+# - separate OptionEditorWidget from Editor-Dialog
1982+# - place ui-code into screenlets.options.ui-module
1983+# - create own widgets for each Option-subclass
1984+#
1985+
1986+import screenlets
1987+
1988+import os
1989+import gtk, gobject
1990+
1991+# translation stuff
1992+import gettext
1993+gettext.textdomain('screenlets')
1994+gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX + '/share/locale')
1995+
1996+def _(s):
1997+ return gettext.gettext(s)
1998+
1999+from boolean_option import BoolOption
2000+from string_option import StringOption
2001+from number_option import IntOption, FloatOption
2002+from list_option import ListOption
2003+from account_option import AccountOption
2004+from font_option import FontOption
2005+from file_option import FileOption, DirectoryOption, ImageOption
2006+from colour_option import ColorOption, ColorsOption
2007+from time_option import TimeOption
2008+from base import EditableOptions, OptionsDialog
2009+
2010+# ------ ONLY FOR TESTING ------------------:
2011+if __name__ == "__main__":
2012+
2013+ import os
2014+
2015+ # this is only for testing - should be a Screenlet
2016+ class TestObject (EditableOptions):
2017+
2018+ testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
2019+ pop3_account = ('Username', '')
2020+
2021+ # TEST
2022+ pin_x = 100
2023+ pin_y = 6
2024+ text_x = 19
2025+ text_y = 35
2026+ font_name = 'Sans 12'
2027+ rgba_color = (0.0, 0.0, 1.0, 1.0)
2028+ text_prefix = '<b>'
2029+ text_suffix = '</b>'
2030+ note_text = "" # hidden option because val has its own editing-dialog
2031+ random_pin_pos = True
2032+ opt1 = 'testval 1'
2033+ opt2 = 'testval 2'
2034+ filename2 = ''
2035+ filename = ''
2036+ dirname = ''
2037+ font = 'Sans 12'
2038+ color = (0.1, 0.5, 0.9, 0.9)
2039+ name = 'a name'
2040+ name2 = 'another name'
2041+ combo_test = 'el2'
2042+ flt = 0.5
2043+ x = 10
2044+ y = 25
2045+ width = 30
2046+ height = 50
2047+ is_sticky = False
2048+ is_widget = False
2049+ time = (12, 32, 49) # a time-value (tuple with ints)
2050+
2051+ def __init__ (self):
2052+ EditableOptions.__init__(self)
2053+ # Add group
2054+ self.add_options_group('General',
2055+ 'The general options for this Object ...')
2056+ self.add_options_group('Window',
2057+ 'The Window-related options for this Object ...')
2058+ self.add_options_group('Test', 'A Test-group ...')
2059+ # Add editable options
2060+ self.add_option(ListOption('Test', 'testlist', default=self.testlist,
2061+ label='ListOption-Test', desc='Testing a ListOption-type ...'))
2062+ self.add_option(StringOption('Window', 'name', default='TESTNAME',
2063+ label='Testname', desc='The name/id of this Screenlet-instance ...'),
2064+ realtime=False)
2065+ self.add_option(AccountOption('Test', 'pop3_account',
2066+ default=self.pop3_account, label='Username/Password',
2067+ desc='Enter username/password here ...'))
2068+ self.add_option(StringOption('Window', 'name2', default='TESTNAME2',
2069+ label='String2', desc='Another string-test ...'))
2070+ self.add_option(StringOption('Test', 'combo_test', default="el1",
2071+ label='Combo', desc='A StringOption for a drop-down-list.',
2072+ choices=['el1', 'el2', 'element 3']))
2073+ self.add_option(FloatOption('General', 'flt', default=30.4,
2074+ label='A Float', desc='Testing a FLOAT-type ...',
2075+ min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
2076+ self.add_option(IntOption('General', 'x', default=30,
2077+ label='X-Position', desc='The X-position of this Screenlet ...',
2078+ min=0, max=gtk.gdk.screen_width()))
2079+ self.add_option(IntOption('General', 'y', default=30,
2080+ label='Y-Position', desc='The Y-position of this Screenlet ...',
2081+ min=0, max=gtk.gdk.screen_height()))
2082+ self.add_option(IntOption('Test', 'width', default=300,
2083+ label='Width', desc='The width of this Screenlet ...',
2084+ min=100, max=1000, increment=12))
2085+ self.add_option(IntOption('Test', 'height', default=150,
2086+ label='Height', desc='The height of this Screenlet ...',
2087+ min=100, max=1000))
2088+ self.add_option(BoolOption('General', 'is_sticky', default=True,
2089+ label='Stick to Desktop', desc='Show this Screenlet always ...'))
2090+ self.add_option(BoolOption('General', 'is_widget', default=False,
2091+ label='Treat as Widget', desc='Treat this Screenlet as a "Widget" ...'))
2092+ self.add_option(FontOption('Test', 'font', default='Sans 14',
2093+ label='Font', desc='The font for whatever ...'))
2094+ self.add_option(ColorOption('Test', 'color', default=(1, 0.35, 0.35, 0.7),
2095+ label='Color', desc='The color for whatever ...'))
2096+ self.add_option(ColorsOption('Test', 'rainbows', default=[(1, 0.35, 0.35, 0.7), (0.1, 0.8, 0.2, 0.2), (1, 0.35, 0.6, 0.7)],
2097+ label='Multi-Colours', desc='The colors for whatever ...'))
2098+ self.add_option(ColorsOption('Test', 'rainbow2', default=(1, 0.35, 0.35, 0.7),
2099+ label='Colours-Up', desc='The colors for whatever ...'))
2100+ self.add_option(FileOption('Test', 'filename', default=os.environ['HOME'],
2101+ label='Filename-Test', desc='Testing a FileOption-type ...',
2102+ patterns=[ ( 'Python Files', ['*.py', '*.pyc'] ) ]))
2103+ self.add_option(ImageOption('Test', 'filename2', default=os.environ['HOME'],
2104+ label='Image-Test', desc='Testing the ImageOption-type ...'))
2105+ self.add_option(DirectoryOption('Test', 'dirname', default=os.environ['HOME'],
2106+ label='Directory-Test', desc='Testing a FileOption-type ...'))
2107+ self.add_option(TimeOption('Test','time', default=self.time,
2108+ label='TimeOption-Test', desc='Testing a TimeOption-type ...'))
2109+ # TEST
2110+ self.disable_option('width')
2111+ self.disable_option('height')
2112+ # TEST: load options from file
2113+ #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
2114+
2115+ def __setattr__(self, name, value):
2116+ self.__dict__[name] = value
2117+ print name + "=" + str(value)
2118+
2119+ def get_short_name(self):
2120+ return self.__class__.__name__[:-6]
2121+
2122+
2123+ # this is only for testing - should be a Screenlet
2124+ class TestChildObject (TestObject):
2125+
2126+ uses_theme = True
2127+ theme_name = 'test'
2128+
2129+ def __init__ (self):
2130+ TestObject.__init__(self)
2131+ self.add_option(StringOption('Test', 'anothertest', default='ksjhsjgd',
2132+ label='Another Test', desc='An attribute in the subclass ...'))
2133+ self.add_option(StringOption('Test', 'theme_name', default=self.theme_name,
2134+ label='Theme', desc='The theme for this Screenelt ...',
2135+ choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
2136+
2137+ # TEST: load/save
2138+ # TEST: option-editing
2139+ to = TestChildObject()
2140+ #print to.export_options_as_list()
2141+ se = OptionsDialog(500, 380)#, treeview=True)
2142+ #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
2143+ img = gtk.Image()
2144+ img.set_from_file('../share/screenlets/Notes/icon.svg')
2145+ se.set_info('TestOptions',
2146+ 'A test for an extended options-dialog with embedded about-info.' +
2147+ ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
2148+ '<span color="red"> ONLY A TEST!</span>',
2149+ '(c) RYX 2007', version='v0.0.1', icon=img)
2150+ se.show_options_for_object(to)
2151+ resp = se.run()
2152+ if resp == gtk.RESPONSE_OK:
2153+ print "OK"
2154+ else:
2155+ print "Cancelled."
2156+ se.destroy()
2157+ print to.export_options_as_list()
2158+
2159
2160=== added file 'src/lib/options/account_option.py'
2161--- src/lib/options/account_option.py 1970-01-01 00:00:00 +0000
2162+++ src/lib/options/account_option.py 2011-03-28 16:19:24 +0000
2163@@ -0,0 +1,143 @@
2164+#
2165+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
2166+#
2167+# This program is free software; you can redistribute it and/or modify
2168+# it under the terms of the GNU General Public License as published by
2169+# the Free Software Foundation; either version 3 of the License, or
2170+# (at your option) any later version.
2171+#
2172+# This program is distributed in the hope that it will be useful,
2173+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2174+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2175+# GNU General Public License for more details.
2176+#
2177+# You should have received a copy of the GNU General Public License
2178+# along with this program; if not, write to the Free Software
2179+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2180+#
2181+"""
2182+Account options, these classes will display a text box.
2183+"""
2184+
2185+import gtk
2186+import gnomekeyring
2187+
2188+from screenlets.options import _
2189+from base import Option
2190+
2191+class AccountOption(Option):
2192+ """
2193+ An Option-type for username/password combos. Stores the password in
2194+ the gnome-keyring (if available) and only saves username and auth_token
2195+ through the screenlets-backend.
2196+ TODO:
2197+ - not create new token for any change (use "set" instead of "create" if
2198+ the given item already exists)
2199+ - use usual storage if no keyring is available but output warning
2200+ - on_delete-function for removing the data from keyring when the
2201+ Screenlet holding the option gets deleted
2202+ """
2203+ protected = True
2204+
2205+ def __init__(self, group, name, *attr, **args):
2206+ super(AccountOption, self).__init__ (group, name, *attr, **args)
2207+ # check for availability of keyring
2208+ if not gnomekeyring.is_available():
2209+ raise Exception('GnomeKeyring is not available!!')
2210+ # THIS IS A WORKAROUND FOR A BUG IN KEYRING (usually we would use
2211+ # gnomekeyring.get_default_keyring_sync() here):
2212+ # find first available keyring
2213+ self.keyring_list = gnomekeyring.list_keyring_names_sync()
2214+ if len(self.keyring_list) == 0:
2215+ raise Exception('No keyrings found. Please create one first!')
2216+ else:
2217+ # we prefer the default keyring
2218+ try:
2219+ self.keyring = gnomekeyring.get_default_keyring_sync()
2220+ except:
2221+ if "session" in self.keyring_list:
2222+ print "Warning: No default keyring found, using session keyring. Storage is not permanent!"
2223+ self.keyring = "session"
2224+ else:
2225+ print "Warning: Neither default nor session keyring found, assuming keyring %s!" % self.keyring_list[0]
2226+ self.keyring = self.keyring_list[0]
2227+
2228+
2229+ def on_import(self, strvalue):
2230+ """Import account info from a string (like 'username:auth_token'),.
2231+ retrieve the password from the storage and return a tuple containing
2232+ username and password."""
2233+ # split string into username/auth_token
2234+ #data = strvalue.split(':', 1)
2235+ (name, auth_token) = strvalue.split(':', 1)
2236+ if name and auth_token:
2237+ # read pass from storage
2238+ try:
2239+ pw = gnomekeyring.item_get_info_sync(self.keyring,
2240+ int(auth_token)).get_secret()
2241+ except Exception, ex:
2242+ print "ERROR: Unable to read password from keyring: %s" % ex
2243+ pw = ''
2244+ # return
2245+ return (name, pw)
2246+ else:
2247+ raise Exception('Illegal value in AccountOption.on_import.')
2248+
2249+ def on_export(self, value):
2250+ """Export the given tuple/list containing a username and a password. The
2251+ function stores the password in the gnomekeyring and returns a
2252+ string in form 'username:auth_token'."""
2253+ # store password in storage
2254+ attribs = dict(name=value[0])
2255+ auth_token = gnomekeyring.item_create_sync(self.keyring,
2256+ gnomekeyring.ITEM_GENERIC_SECRET, value[0], attribs, value[1], True)
2257+ # build value from username and auth_token
2258+ return value[0] + ':' + str(auth_token)
2259+
2260+ def generate_widget(self, value):
2261+ """Generate a textbox for a account options"""
2262+ self.widget = gtk.HBox()
2263+ vb = gtk.VBox()
2264+ input_name = gtk.Entry()
2265+ input_name.set_text(value[0])
2266+ input_name.show()
2267+ input_pass = gtk.Entry()
2268+ input_pass.set_visibility(False) # password
2269+ input_pass.set_text(value[1])
2270+ input_pass.show()
2271+ but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
2272+ but.show()
2273+ but.connect("clicked", self.has_changed)
2274+ vb.add(input_name)
2275+ vb.add(input_pass)
2276+ vb.show()
2277+ but.set_tooltip_text(_('Apply username/password ...'))
2278+ input_name.set_tooltip_text(_('Enter username here ...'))
2279+ input_pass.set_tooltip_text(_('Enter password here ...'))
2280+ self.widget.add(vb)
2281+ self.widget.add(but)
2282+ return self.widget
2283+
2284+ def set_value(self, value):
2285+ """Set the account value as required."""
2286+ self.value = value
2287+
2288+ def has_changed(self, widget):
2289+ """Executed when the widget event kicks off."""
2290+ # the widget is a HBox containing a VBox containing two Entries
2291+ # (ideally we should have a custom widget for the AccountOption)
2292+ for c in self.widget.get_children():
2293+ if c.__class__ == gtk.VBox:
2294+ c2 = c.get_children()
2295+ self.value = (c2[0].get_text(), c2[1].get_text())
2296+ super(AccountOption, self).has_changed()
2297+
2298+"""#TEST:
2299+o = AccountOption('None', 'pop3_account', ('',''), 'Username/Password', 'Enter username/password here ...')
2300+# save option to keyring
2301+exported_account = o.on_export(('RYX', 'mysecretpassword'))
2302+print exported_account
2303+# and read option back from keyring
2304+print o.on_import(exported_account)
2305+"""
2306+
2307
2308=== added file 'src/lib/options/base.py'
2309--- src/lib/options/base.py 1970-01-01 00:00:00 +0000
2310+++ src/lib/options/base.py 2011-03-28 16:19:24 +0000
2311@@ -0,0 +1,624 @@
2312+#
2313+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
2314+#
2315+# This program is free software; you can redistribute it and/or modify
2316+# it under the terms of the GNU General Public License as published by
2317+# the Free Software Foundation; either version 3 of the License, or
2318+# (at your option) any later version.
2319+#
2320+# This program is distributed in the hope that it will be useful,
2321+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2322+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2323+# GNU General Public License for more details.
2324+#
2325+# You should have received a copy of the GNU General Public License
2326+# along with this program; if not, write to the Free Software
2327+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2328+#
2329+"""
2330+Base classes and basic mechanics for all screenlet options.
2331+"""
2332+
2333+import screenlets
2334+from screenlets.options import _
2335+
2336+import os
2337+import gtk, gobject
2338+import xml.dom.minidom
2339+from xml.dom.minidom import Node
2340+
2341+# -----------------------------------------------------------------------
2342+# Option-classes and subclasses
2343+# -----------------------------------------------------------------------
2344+
2345+class Option(gobject.GObject):
2346+ """An Option stores information about a certain object-attribute. It doesn't
2347+ carry information about the value or the object it belongs to - it is only a
2348+ one-way data-storage for describing how to handle attributes."""
2349+ __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
2350+ gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
2351+ default = None
2352+ label = None
2353+ desc = None
2354+ hidden = False
2355+ disabled = False
2356+ realtime = True
2357+ protected = False
2358+ widget = None
2359+
2360+ def __init__ (self, group, name, *attr, **args):
2361+ """Creates a new Option with the given information."""
2362+ super(Option, self).__init__()
2363+ self.name = name
2364+ self.group = group
2365+ # To maintain compatability, we parse out the 3 attributes and
2366+ # Move into known arguments.
2367+ if len(attr) == 3:
2368+ args.setdefault('default', attr[0])
2369+ args.setdefault('label', attr[1])
2370+ args.setdefault('desc', attr[2])
2371+ # This should allow any of the class options to be set on init.
2372+ for name in args.keys():
2373+ if hasattr(self, name):
2374+ # Replaces class variables (defaults) with object vars
2375+ setattr(self, name, args[name])
2376+
2377+ # XXX for groups (TODO: OptionGroup)
2378+ # XXX callback to be notified when this option changes
2379+ # XXX real-time update?
2380+ # XXX protected from get/set through service
2381+
2382+ def on_import(self, strvalue):
2383+ """Callback - called when an option gets imported from a string.
2384+ This function MUST return the string-value converted to the required
2385+ type!"""
2386+ return strvalue.replace("\\n", "\n")
2387+
2388+ def on_export(self, value):
2389+ """Callback - called when an option gets exported to a string. The
2390+ value-argument needs to be converted to a string that can be imported
2391+ by the on_import-handler. This handler MUST return the value
2392+ converted to a string!"""
2393+ return str(value).replace("\n", "\\n")
2394+
2395+ def generate_widget(self):
2396+ """This should generate all the required widgets for display."""
2397+ raise NotImplementedError, "Generating Widget should be done in child"
2398+
2399+ def set_value(self, value):
2400+ """Set the true/false value to the checkbox widget"""
2401+ raise NotImplementedError, "Can't update the widget and local value"
2402+
2403+ def has_changed(self):
2404+ """Executed when the widget event kicks off."""
2405+ return self.emit("option_changed", self)
2406+
2407+
2408+def create_option_from_node (node, groupname):
2409+ """Create an Option from an XML-node with option-metadata."""
2410+ #print "TODO OPTION: " + str(cn)
2411+ otype = node.getAttribute("type")
2412+ oname = node.getAttribute("name")
2413+ ohidden = node.getAttribute("hidden")
2414+ options = {
2415+ 'default' : None,
2416+ 'info' : '',
2417+ 'label' : '',
2418+ 'min' : None,
2419+ 'max' : None,
2420+ 'increment' : 1,
2421+ 'choices' : '',
2422+ 'digits' : None,
2423+ }
2424+ if otype and oname:
2425+ # parse children of option-node and save all useful attributes
2426+ for attr in node.childNodes:
2427+ if attr.nodeType == Node.ELEMENT_NODE and attr.nodeName in options.keys():
2428+ options[attr.nodeName] = attr.firstChild.nodeValue
2429+ # if we have all needed values, create the Option
2430+ if options['default']:
2431+ # create correct classname here
2432+ cls = otype[0].upper() + otype.lower()[1:] + 'Option'
2433+ #print 'Create: ' +cls +' / ' + oname + ' ('+otype+')'
2434+ # and build new instance (we use on_import for setting default val)
2435+ clsobj = getattr(__import__(__name__), cls)
2436+ opt = clsobj(groupname, oname, default=None,
2437+ label=options['label'], desc=options['info'])
2438+ opt.default = opt.on_import(options['default'])
2439+ # set values to the correct types
2440+ if cls == 'IntOption':
2441+ if options['min']:
2442+ opt.min = int(options['min'])
2443+ if options['max']:
2444+ opt.max = int(options['max'])
2445+ if options['increment']:
2446+ opt.increment = int(options['increment'])
2447+ elif cls == 'FloatOption':
2448+ if options['digits']:
2449+ opt.digits = int(options['digits'])
2450+ if options['min']:
2451+ opt.min = float(options['min'])
2452+ if options['min']:
2453+ opt.max = float(options['max'])
2454+ if options['increment']:
2455+ opt.increment = float(options['increment'])
2456+ elif cls == 'StringOption':
2457+ if options['choices']:
2458+ opt.choices = options['choices']
2459+ return opt
2460+ return None
2461+
2462+
2463+class EditableOptions(object):
2464+ """The EditableOptions can be inherited from to allow objects to export
2465+ editable options for editing them with the OptionsEditor-class.
2466+ NOTE: This could use some improvement and is very poorly coded :) ..."""
2467+
2468+ def __init__ (self):
2469+ self.__options__ = []
2470+ self.__options_groups__ = {}
2471+ # This is a workaround to remember the order of groups
2472+ self.__options_groups_ordered__ = []
2473+
2474+ def add_option (self, option, callback=None, realtime=True):
2475+ """Add an editable option to this object. Editable Options can be edited
2476+ and configured using the OptionsDialog. The optional callback-arg can be
2477+ used to set a callback that gets notified when the option changes its
2478+ value."""
2479+ #print "Add option: "+option.name
2480+ # if option already editable (i.e. initialized), return
2481+ for o in self.__options__:
2482+ if o.name == option.name:
2483+ return False
2484+ self.__dict__[option.name] = option.default
2485+ # set auto-update (TEMPORARY?)
2486+ option.realtime = realtime
2487+ # add option to group (output error if group is undefined)
2488+ try:
2489+ self.__options_groups__[option.group]['options'].append(option)
2490+ except:
2491+ print "Options: Error - group %s not defined." % option.group
2492+ return False
2493+ # now add the option
2494+ self.__options__.append(option)
2495+ # if callback is set, add callback
2496+ if callback:
2497+ option.connect("option_changed", callback)
2498+ option.connect("option_changed", self.callback_value_changed)
2499+ return True
2500+
2501+
2502+ def add_options_group (self, name, group_info):
2503+ """Add a new options-group to this Options-object"""
2504+ self.__options_groups__[name] = {'label':name,
2505+ 'info':group_info, 'options':[]}
2506+ self.__options_groups_ordered__.append(name)
2507+ #print self.options_groups
2508+
2509+ def disable_option(self, name):
2510+ """Disable the inputs for a certain Option."""
2511+ for o in self.__options__:
2512+ if o.name == name:
2513+ o.disabled = True
2514+ return True
2515+ return False
2516+
2517+ def enable_option(self, name):
2518+ """Enable the inputs for a certain Option."""
2519+ for o in self.__options__:
2520+ if o.name == name:
2521+ o.disabled = False
2522+ return True
2523+ return False
2524+
2525+ def export_options_as_list(self):
2526+ """Returns all editable options within a list (without groups)
2527+ as key/value tuples."""
2528+ lst = []
2529+ for o in self.__options__:
2530+ lst.append((o.name, o.on_export(getattr(self, o.name))))
2531+ return lst
2532+
2533+ def callback_value_changed(self, sender, option):
2534+ """Called when a widget has updated and this needs calling."""
2535+ if hasattr(self, option.name):
2536+ return setattr(self, option.name, option.value)
2537+ raise KeyError, "Callback tried to set an option that wasn't defined."
2538+
2539+ def get_option_by_name (self, name):
2540+ """Returns an option in this Options by it's name (or None).
2541+ TODO: this gives wrong results in childclasses ... maybe access
2542+ as class-attribute??"""
2543+ for o in self.__options__:
2544+ if o.name == name:
2545+ return o
2546+ return None
2547+
2548+ def remove_option (self, name):
2549+ """Remove an option from this Options."""
2550+ for o in self.__options__:
2551+ if o.name == name:
2552+ del o
2553+ return True
2554+ return True
2555+
2556+ def add_options_from_file (self, filename):
2557+ """This function creates options from an XML-file with option-metadata.
2558+ TODO: make this more reusable and place it into module (once the groups
2559+ are own objects)"""
2560+ # create xml document
2561+ try:
2562+ doc = xml.dom.minidom.parse(filename)
2563+ except:
2564+ raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
2565+ # get rootnode
2566+ root = doc.firstChild
2567+ if not root or root.nodeName != 'screenlet':
2568+ raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
2569+ # ok, let's check the nodes: this one should contain option-groups
2570+ groups = []
2571+ for node in root.childNodes:
2572+ # we only want element-nodes
2573+ if node.nodeType == Node.ELEMENT_NODE:
2574+ #print node
2575+ if node.nodeName != 'group' or not node.hasChildNodes():
2576+ # we only allow groups in the first level (groups need children)
2577+ raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
2578+ else:
2579+ # ok, create a new group and parse its elements
2580+ group = {}
2581+ group['name'] = node.getAttribute("name")
2582+ if not group['name']:
2583+ raise Exception('No name for group defined in "%s".' % filename)
2584+ group['info'] = ''
2585+ group['options'] = []
2586+ # check all children in group
2587+ for on in node.childNodes:
2588+ if on.nodeType == Node.ELEMENT_NODE:
2589+ if on.nodeName == 'info':
2590+ # info-node? set group-info
2591+ group['info'] = on.firstChild.nodeValue
2592+ elif on.nodeName == 'option':
2593+ # option node? parse option node
2594+ opt = create_option_from_node (on, group['name'])
2595+ # ok? add it to list
2596+ if opt:
2597+ group['options'].append(opt)
2598+ else:
2599+ raise Exception('Invalid option-node found in "%s".' % filename)
2600+
2601+ # create new group
2602+ if len(group['options']):
2603+ self.add_options_group(group['name'], group['info'])
2604+ for o in group['options']:
2605+ self.add_option(o)
2606+ # add group to list
2607+ #groups.append(group)
2608+
2609+
2610+class OptionsDialog(gtk.Dialog):
2611+ """A dynamic options-editor for editing Screenlets which are implementing
2612+ the EditableOptions-class."""
2613+
2614+ __shown_object = None
2615+
2616+ def __init__ (self, width, height):
2617+ # call gtk.Dialog.__init__
2618+ super(OptionsDialog, self).__init__(
2619+ _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
2620+ gtk.DIALOG_NO_SEPARATOR,
2621+ buttons = (#gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_APPLY,
2622+ gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
2623+ # set size
2624+ self.resize(width, height)
2625+ self.set_keep_above(True) # to avoid confusion
2626+ self.set_border_width(10)
2627+ # create attribs
2628+ self.page_about = None
2629+ self.page_options = None
2630+ self.page_themes = None
2631+ self.vbox_editor = None
2632+ self.hbox_about = None
2633+ self.infotext = None
2634+ self.infoicon = None
2635+ # create theme-list
2636+ self.liststore = gtk.ListStore(object)
2637+ self.tree = gtk.TreeView(model=self.liststore)
2638+ # create/add outer notebook
2639+ self.main_notebook = gtk.Notebook()
2640+ self.main_notebook.show()
2641+ self.vbox.add(self.main_notebook)
2642+ # create/init notebook pages
2643+ self.create_about_page()
2644+ self.create_themes_page()
2645+ self.create_options_page()
2646+
2647+ # "public" functions
2648+
2649+ def reset_to_defaults(self):
2650+ """Reset all entries for the currently shown object to their default
2651+ values (the values the object has when it is first created).
2652+ NOTE: This function resets ALL options, so BE CARFEUL!"""
2653+ if self.__shown_object:
2654+ for o in self.__shown_object.__options__:
2655+ # set default value
2656+ setattr(self.__shown_object, o.name, o.default)
2657+
2658+ def set_info(self, name, info, copyright='', version='', icon=None):
2659+ """Update the "About"-page with the given information."""
2660+ # convert infotext (remove EOLs and TABs)
2661+ info = info.replace("\n", "")
2662+ info = info.replace("\t", " ")
2663+ # create markup
2664+ markup = '\n<b><span size="xx-large">' + name + '</span></b>'
2665+ if version:
2666+ markup += ' <span size="large"><b>' + version + '</b></span>'
2667+ markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
2668+ self.infotext.set_markup(markup)
2669+ # icon?
2670+ if icon:
2671+ # remove old icon
2672+ if self.infoicon:
2673+ self.infoicon.destroy()
2674+ # set new icon
2675+ self.infoicon = icon
2676+ self.infoicon.set_alignment(0.0, 0.10)
2677+ self.infoicon.show()
2678+ self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
2679+ else:
2680+ self.infoicon.hide()
2681+
2682+ def show_options_for_object (self, obj):
2683+ """Update the OptionsEditor to show the options for the given Object.
2684+ The Object needs to be an EditableOptions-subclass.
2685+ NOTE: This needs heavy improvement and should use OptionGroups once
2686+ they exist"""
2687+ self.__shown_object = obj
2688+ # create notebook for groups
2689+ notebook = gtk.Notebook()
2690+ self.vbox_editor.add(notebook)
2691+ for group in obj.__options_groups_ordered__:
2692+ group_data = obj.__options_groups__[group]
2693+ # create box for tab-page
2694+ page = gtk.VBox()
2695+ page.set_border_width(10)
2696+ if group_data['info'] != '':
2697+ info = gtk.Label(group_data['info'])
2698+ info.show()
2699+ info.set_alignment(0, 0)
2700+ page.pack_start(info, 0, 0, 7)
2701+ sep = gtk.HSeparator()
2702+ sep.show()
2703+ #page.pack_start(sep, 0, 0, 5)
2704+ # create VBox for inputs
2705+ box = gtk.VBox()
2706+ box.show()
2707+ box.set_border_width(5)
2708+ # add box to page
2709+ page.add(box)
2710+ page.show()
2711+ # add new notebook-page
2712+ label = gtk.Label(group_data['label'])
2713+ label.show()
2714+ notebook.append_page(page, label)
2715+ # and create inputs
2716+ for option in group_data['options']:
2717+ if option.hidden == False:
2718+ val = getattr(obj, option.name)
2719+ w = self.generate_widget( option, val )
2720+ if w:
2721+ box.pack_start(w, 0, 0)
2722+ w.show()
2723+ notebook.show()
2724+ # show/hide themes tab, depending on whether the screenlet uses themes
2725+ if obj.uses_theme and obj.theme_name != '':
2726+ self.show_themes_for_screenlet(obj)
2727+ else:
2728+ self.page_themes.hide()
2729+
2730+ def generate_widget(self, option, value):
2731+ """Generate the required widgets and add the label."""
2732+ widget = option.generate_widget(value)
2733+ hbox = gtk.HBox()
2734+ label = gtk.Label()
2735+ label.set_alignment(0.0, 0.0)
2736+ label.set_label(option.label)
2737+ label.set_size_request(180, 28)
2738+ label.show()
2739+ hbox.pack_start(label, 0, 1)
2740+ if widget:
2741+ if option.disabled:
2742+ widget.set_sensitive(False)
2743+ label.set_sensitive(False)
2744+ widget.set_tooltip_text(option.desc)
2745+ widget.show()
2746+ # check if needs Apply-button
2747+ if option.realtime == False:
2748+ but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
2749+ but.show()
2750+ but.connect("clicked", option.has_changed)
2751+ b = gtk.HBox()
2752+ b.show()
2753+ b.pack_start(widget, 0, 0)
2754+ b.pack_start(but, 0, 0)
2755+ hbox.pack_start(b, 0, 0)
2756+ else:
2757+ hbox.pack_start(widget, 0, 0)
2758+ return hbox
2759+
2760+
2761+ def show_themes_for_screenlet (self, obj):
2762+ """Update the Themes-page to display the available themes for the
2763+ given Screenlet-object."""
2764+ dircontent = []
2765+ screenlets.utils.refresh_available_screenlet_paths()
2766+ # now check all paths for themes
2767+ for path in screenlets.SCREENLETS_PATH:
2768+ p = path + '/' + obj.get_short_name() + '/themes'
2769+ print p
2770+ #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
2771+ try:
2772+ dc = os.listdir(p)
2773+ for d in dc:
2774+ dircontent.append({'name':d, 'path':p+'/'})
2775+ except:
2776+ print "Path %s not found." % p
2777+ # list with found themes
2778+ found_themes = []
2779+ # check all themes in path
2780+ for elem in dircontent:
2781+ # load themes with the same name only once
2782+ if found_themes.count(elem['name']):
2783+ continue
2784+ found_themes.append(elem['name'])
2785+ # build full path of theme.conf
2786+ theme_conf = elem['path'] + elem['name'] + '/theme.conf'
2787+ # if dir contains a theme.conf
2788+ if os.access(theme_conf, os.F_OK):
2789+ # load it and create new list entry
2790+ ini = screenlets.utils.IniReader()
2791+ if ini.load(theme_conf):
2792+ # check for section
2793+ if ini.has_section('Theme'):
2794+ # get metainfo from theme
2795+ th_fullname = ini.get_option('name',
2796+ section='Theme')
2797+ th_info = ini.get_option('info',
2798+ section='Theme')
2799+ th_version = ini.get_option('version',
2800+ section='Theme')
2801+ th_author = ini.get_option('author',
2802+ section='Theme')
2803+ # create array from metainfo and add it to liststore
2804+ info = [elem['name'], th_fullname, th_info, th_author,
2805+ th_version]
2806+ self.liststore.append([info])
2807+ else:
2808+ # no theme section in theme.conf just add theme-name
2809+ self.liststore.append([[elem['name'], '-', '-', '-', '-']])
2810+ else:
2811+ # no theme.conf in dir? just add theme-name
2812+ self.liststore.append([[elem['name'], '-', '-', '-', '-']])
2813+ # is it the active theme?
2814+ if elem['name'] == obj.theme_name:
2815+ # select it in tree
2816+ print "active theme is: %s" % elem['name']
2817+ sel = self.tree.get_selection()
2818+ if sel:
2819+ it = self.liststore.get_iter_from_string(\
2820+ str(len(self.liststore)-1))
2821+ if it:
2822+ sel.select_iter(it)
2823+ # UI-creation
2824+
2825+ def create_about_page (self):
2826+ """Create the "About"-tab."""
2827+ self.page_about = gtk.HBox()
2828+ # create about box
2829+ self.hbox_about = gtk.HBox()
2830+ self.hbox_about.show()
2831+ self.page_about.add(self.hbox_about)
2832+ # create icon
2833+ self.infoicon = gtk.Image()
2834+ self.infoicon.show()
2835+ self.page_about.pack_start(self.infoicon, 0, 1, 10)
2836+ # create infotext
2837+ self.infotext = gtk.Label()
2838+ self.infotext.use_markup = True
2839+ self.infotext.set_line_wrap(True)
2840+ self.infotext.set_alignment(0.0, 0.0)
2841+ self.infotext.show()
2842+ self.page_about.pack_start(self.infotext, 1, 1, 5)
2843+ # add page
2844+ self.page_about.show()
2845+ self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
2846+
2847+ def create_options_page (self):
2848+ """Create the "Options"-tab."""
2849+ self.page_options = gtk.HBox()
2850+ # create vbox for options-editor
2851+ self.vbox_editor = gtk.VBox(spacing=3)
2852+ self.vbox_editor.set_border_width(5)
2853+ self.vbox_editor.show()
2854+ self.page_options.add(self.vbox_editor)
2855+ # show/add page
2856+ self.page_options.show()
2857+ self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
2858+
2859+ def create_themes_page (self):
2860+ """Create the "Themes"-tab."""
2861+ self.page_themes = gtk.VBox(spacing=5)
2862+ self.page_themes.set_border_width(10)
2863+ # create info-text list
2864+ 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.'))
2865+ txt.set_size_request(450, -1)
2866+ txt.set_line_wrap(True)
2867+ txt.set_alignment(0.0, 0.0)
2868+ txt.show()
2869+ self.page_themes.pack_start(txt, False, True)
2870+ # create theme-selector list
2871+ self.tree.set_headers_visible(False)
2872+ self.tree.connect('cursor-changed', self.__tree_cursor_changed)
2873+ self.tree.show()
2874+ col = gtk.TreeViewColumn('')
2875+ cell = gtk.CellRendererText()
2876+ col.pack_start(cell, True)
2877+ #cell.set_property('foreground', 'black')
2878+ col.set_cell_data_func(cell, self.__render_cell)
2879+ self.tree.append_column(col)
2880+ # wrap tree in scrollwin
2881+ sw = gtk.ScrolledWindow()
2882+ sw.set_shadow_type(gtk.SHADOW_IN)
2883+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2884+ sw.add(self.tree)
2885+ sw.show()
2886+ # add vbox and add tree/buttons
2887+ vbox = gtk.VBox()
2888+ vbox.pack_start(sw, True, True)
2889+ vbox.show()
2890+ # show/add page
2891+ self.page_themes.add(vbox)
2892+ self.page_themes.show()
2893+ self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
2894+
2895+ def __render_cell(self, tvcolumn, cell, model, iter):
2896+ """Callback for rendering the cells in the theme-treeview."""
2897+ # get attributes-list from Treemodel
2898+ attrib = model.get_value(iter, 0)
2899+
2900+ # set colors depending on state
2901+ col = '555555'
2902+ name_uc = attrib[0][0].upper() + attrib[0][1:]
2903+ # create markup depending on info
2904+ if attrib[1] == '-' and attrib[2] == '-':
2905+ mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
2906+ '</span></b> (' + _('no info available') + ')'
2907+ else:
2908+ if attrib[1] == None : attrib[1] = '-'
2909+ if attrib[2] == None : attrib[2] = '-'
2910+ if attrib[3] == None : attrib[3] = '-'
2911+ if attrib[4] == None : attrib[4] = '-'
2912+ mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
2913+ '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
2914+ '">' + attrib[2].replace('\\n', '\n') + \
2915+ '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
2916+ # set markup
2917+ cell.set_property('markup', mu)
2918+
2919+ # UI-callbacks
2920+
2921+ def __tree_cursor_changed (self, treeview):
2922+ """Callback for handling selection changes in the Themes-treeview."""
2923+ sel = self.tree.get_selection()
2924+ if sel:
2925+ s = sel.get_selected()
2926+ if s:
2927+ it = s[1]
2928+ if it:
2929+ attribs = self.liststore.get_value(it, 0)
2930+ if attribs and self.__shown_object:
2931+ #print attribs
2932+ # set theme in Screenlet (if not already active)
2933+ if self.__shown_object.theme_name != attribs[0]:
2934+ self.__shown_object.theme_name = attribs[0]
2935+
2936
2937=== added file 'src/lib/options/boolean_option.py'
2938--- src/lib/options/boolean_option.py 1970-01-01 00:00:00 +0000
2939+++ src/lib/options/boolean_option.py 2011-03-28 16:19:24 +0000
2940@@ -0,0 +1,54 @@
2941+#
2942+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
2943+#
2944+# This program is free software; you can redistribute it and/or modify
2945+# it under the terms of the GNU General Public License as published by
2946+# the Free Software Foundation; either version 3 of the License, or
2947+# (at your option) any later version.
2948+#
2949+# This program is distributed in the hope that it will be useful,
2950+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2951+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2952+# GNU General Public License for more details.
2953+#
2954+# You should have received a copy of the GNU General Public License
2955+# along with this program; if not, write to the Free Software
2956+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2957+#
2958+"""
2959+Boolean options, these classes will display a boolean
2960+Checkbox as required and control the formating of data.
2961+"""
2962+
2963+import gtk
2964+
2965+from screenlets.options import _
2966+from base import Option
2967+
2968+class BoolOption(Option):
2969+ """An Option for boolean values."""
2970+ def on_import(self, strvalue):
2971+ """When a boolean is imported from the config."""
2972+ return strvalue.lower() == "true"
2973+
2974+ def on_export(self, value):
2975+ """When a boolean is exported to the config."""
2976+ return str(value)
2977+
2978+ def generate_widget(self, value):
2979+ """Generate a checkbox for a boolean option."""
2980+ if not self.widget:
2981+ self.widget = gtk.CheckButton()
2982+ self.set_value(value)
2983+ self.widget.connect("toggled", self.has_changed)
2984+ return self.widget
2985+
2986+ def set_value(self, value):
2987+ """Set the true/false value to the checkbox widget"""
2988+ self.value = value
2989+ self.widget.set_active(self.value)
2990+
2991+ def has_changed(self, widget):
2992+ """Executed when the widget event kicks off."""
2993+ self.set_value( self.widget.get_active() )
2994+ super(BoolOption, self).has_changed()
2995
2996=== added file 'src/lib/options/colour_option.py'
2997--- src/lib/options/colour_option.py 1970-01-01 00:00:00 +0000
2998+++ src/lib/options/colour_option.py 2011-03-28 16:19:24 +0000
2999@@ -0,0 +1,149 @@
3000+#
3001+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3002+#
3003+# This program is free software; you can redistribute it and/or modify
3004+# it under the terms of the GNU General Public License as published by
3005+# the Free Software Foundation; either version 3 of the License, or
3006+# (at your option) any later version.
3007+#
3008+# This program is distributed in the hope that it will be useful,
3009+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3010+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3011+# GNU General Public License for more details.
3012+#
3013+# You should have received a copy of the GNU General Public License
3014+# along with this program; if not, write to the Free Software
3015+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3016+#
3017+"""
3018+Color options, these classes will display a text box.
3019+"""
3020+
3021+import gtk
3022+
3023+from screenlets.options import _
3024+from base import Option
3025+
3026+class ColorOption(Option):
3027+ """An Option for color options."""
3028+ def on_import(self, strvalue):
3029+ """Import (r, g, b, a) from comma-separated string."""
3030+ # strip braces and spaces
3031+ strvalue = strvalue.lstrip('(')
3032+ strvalue = strvalue.rstrip(')')
3033+ strvalue = strvalue.strip()
3034+ # split value on commas
3035+ tmpval = strvalue.split(',')
3036+ outval = []
3037+ for f in tmpval:
3038+ # create list and again remove spaces
3039+ outval.append(float(f.strip()))
3040+ return outval
3041+
3042+ def on_export(self, value):
3043+ """Export r, g, b, a to comma-separated string."""
3044+ l = len(value)
3045+ outval = ''
3046+ for i in xrange(l):
3047+ if type(value[i]) == float:
3048+ outval += "%0.5f" % value[i]
3049+ else:
3050+ outval += str(value[i])
3051+ if i < l-1:
3052+ outval += ','
3053+ return outval
3054+
3055+ def generate_widget(self, value):
3056+ """Generate a textbox for a color options"""
3057+ self.widget = self.get_box_from_colour( value )
3058+ self.set_value(value)
3059+ return self.widget
3060+
3061+ def set_value(self, value):
3062+ """Set the color value as required."""
3063+ self.value = value
3064+
3065+ def has_changed(self, widget):
3066+ """Executed when the widget event kicks off."""
3067+ self.value = self.get_colour_from_box(self.widget)
3068+ super(ColorOption, self).has_changed()
3069+
3070+ def get_box_from_colour(self, colour):
3071+ """Turn a colour array into a colour widget"""
3072+ result = gtk.ColorButton(gtk.gdk.Color(
3073+ int(colour[0]*65535), int(colour[1]*65535), int(colour[2]*65535)))
3074+ result.set_use_alpha(True)
3075+ result.set_alpha(int(colour[3]*65535))
3076+ result.connect("color-set", self.has_changed)
3077+ return result
3078+
3079+ def get_colour_from_box(self, widget):
3080+ """Turn a colour widget into a colour array"""
3081+ colour = widget.get_color()
3082+ return (
3083+ colour.red/65535.0,
3084+ colour.green/65535.0,
3085+ colour.blue/65535.0,
3086+ widget.get_alpha()/65535.0
3087+ )
3088+
3089+
3090+class ColorsOption(ColorOption):
3091+ """Allow a list of colours to be created"""
3092+ def on_import(self, value):
3093+ """Importing multiple colours"""
3094+ result = []
3095+ for col in value.split(';'):
3096+ if col:
3097+ result.append(super(ColorsOption, self).on_import(col))
3098+ return result
3099+
3100+ def on_export(self, value):
3101+ """Exporting multiple colours"""
3102+ result = ""
3103+ for col in value:
3104+ result += super(ColorsOption, self).on_export(col)+';'
3105+ return result
3106+
3107+ def generate_widget(self, value):
3108+ """Generate a textbox for a color options"""
3109+ self.widget = gtk.HBox()
3110+ if type(value[0]) in [int, float]:
3111+ value = [value]
3112+ for col in value:
3113+ self.add_colour_box(self.widget, col, False)
3114+
3115+ but = gtk.Button('Add', gtk.STOCK_ADD)
3116+ but.show()
3117+ but.connect("clicked", self.add_colour_box)
3118+ self.widget.pack_end(but)
3119+
3120+ self.set_value(value)
3121+ return self.widget
3122+
3123+ def del_colour_box(self, widget, event):
3124+ """Remove a colour box from the array when right clicked"""
3125+ if event.button == 3:
3126+ if len(self.widget.get_children()) > 2:
3127+ self.widget.remove(widget)
3128+ self.has_changed(widget)
3129+
3130+ def add_colour_box(self, widget, col=None, update=True):
3131+ """Add a new box for colours"""
3132+ if not col:
3133+ col = self.value[-1]
3134+ new_box = self.get_box_from_colour( col )
3135+ new_box.connect("button_press_event", self.del_colour_box)
3136+ self.widget.pack_start(new_box, padding=1)
3137+ new_box.show()
3138+ if update:
3139+ self.has_changed(widget)
3140+
3141+ def has_changed(self, widget):
3142+ """The colour widgets have changed!"""
3143+ self.value = []
3144+ for c in self.widget.get_children():
3145+ if type(c) == gtk.ColorButton:
3146+ self.value.append(self.get_colour_from_box( c ))
3147+ super(ColorOption, self).has_changed()
3148+
3149
3150=== added file 'src/lib/options/file_option.py'
3151--- src/lib/options/file_option.py 1970-01-01 00:00:00 +0000
3152+++ src/lib/options/file_option.py 2011-03-28 16:19:24 +0000
3153@@ -0,0 +1,179 @@
3154+#
3155+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3156+#
3157+# This program is free software; you can redistribute it and/or modify
3158+# it under the terms of the GNU General Public License as published by
3159+# the Free Software Foundation; either version 3 of the License, or
3160+# (at your option) any later version.
3161+#
3162+# This program is distributed in the hope that it will be useful,
3163+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3164+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3165+# GNU General Public License for more details.
3166+#
3167+# You should have received a copy of the GNU General Public License
3168+# along with this program; if not, write to the Free Software
3169+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3170+#
3171+"""
3172+File options, these classes will display a file select widget.
3173+"""
3174+
3175+import gtk
3176+import os
3177+
3178+from screenlets.options import _
3179+from base import Option
3180+
3181+class FileOption(Option):
3182+ """
3183+ An Option-subclass for string-values that contain filenames. Adds
3184+ a patterns-attribute that can contain a list of patterns to be shown
3185+ in the assigned file selection dialog. The show_pixmaps-attribute
3186+ can be set to True to make the filedialog show all image-types.
3187+ supported by gtk.Pixmap. If the directory-attributue is true, the
3188+ dialog will ony allow directories.
3189+
3190+ XXX - Some of this doen't yet work, unknown reason.
3191+ """
3192+ patterns = [ ( 'All Files', ['*'] ) ]
3193+ image = False
3194+ _gtk_file_mode = gtk.FILE_CHOOSER_ACTION_OPEN
3195+ _opener_title = _("Choose File")
3196+
3197+ def on_import(self, strvalue):
3198+ """When a file is imported from the config."""
3199+ return strvalue
3200+
3201+ def on_export(self, value):
3202+ """When a file is exported to the config."""
3203+ return str(value)
3204+
3205+ def generate_widget(self, value):
3206+ """Generate a special widget for file options"""
3207+ dlg = self.generate_file_opener()
3208+ self.widget = gtk.FileChooserButton(dlg)
3209+ self.widget.set_title(self._opener_title)
3210+ self.widget.set_size_request(180, 28)
3211+ self.set_value(value)
3212+ self.widget.connect("selection-changed", self.has_changed)
3213+ return self.widget
3214+
3215+ def generate_file_opener(self):
3216+ """Generate a file opener widget"""
3217+ dlg = gtk.FileChooserDialog(action=self._gtk_file_mode,
3218+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
3219+ gtk.STOCK_OPEN, gtk.RESPONSE_OK),
3220+ )
3221+ dlg.set_keep_above(True)
3222+ self.set_filters(dlg)
3223+ return dlg
3224+
3225+ def set_filters(self, dlg):
3226+ """Add file filters to the dialog widget"""
3227+ if self.patterns:
3228+ for filter in self.patterns:
3229+ fil = gtk.FileFilter()
3230+ fil.set_name("%s (%s)" % (filter[0], ','.join(filter[1])))
3231+ for pattern in filter[1]:
3232+ fil.add_pattern(pattern)
3233+ dlg.add_filter(fil)
3234+
3235+ def set_value(self, value):
3236+ """Set the file value as required."""
3237+ self.widget.set_filename(value)
3238+ self.value = value
3239+
3240+ def has_changed(self, widget):
3241+ """Executed when the widget event kicks off."""
3242+ self.value = self.widget.get_filename()
3243+ super(FileOption, self).has_changed()
3244+
3245+
3246+class DirectoryOption(FileOption):
3247+ """Directory is based on file widgets"""
3248+ _gtk_file_mode = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
3249+ _opener_title = _("Choose Directory")
3250+
3251+
3252+class ImageOption(FileOption):
3253+ """Image is based on file widgets"""
3254+ _opener_title = _("Choose Image")
3255+
3256+ def set_filters(self, dlg):
3257+ """Add the standard pixbug formats"""
3258+ flt = gtk.FileFilter()
3259+ flt.add_pixbuf_formats()
3260+ dlg.set_filter(flt)
3261+
3262+ def generate_widget(self, value):
3263+ """Crazy image opener widget generation."""
3264+ # create entry and button (entry is hidden)
3265+ self._entry = gtk.Entry()
3266+ self._entry.set_text(value)
3267+ self._entry.set_editable(False)
3268+ but = gtk.Button()
3269+ # load preview image
3270+ but.set_image(self.create_preview(value))
3271+ but.connect('clicked', self.but_callback)
3272+ # create widget
3273+ self.widget = gtk.HBox()
3274+ self.widget.add(self._entry)
3275+ self.widget.add(but)
3276+ but.show()
3277+ self.widget.show()
3278+ # add tooltips
3279+ but.set_tooltip_text(_('Select Image ...'))
3280+ but.set_tooltip_text(self.desc)
3281+ return self.widget
3282+
3283+ def create_preview(self, filename):
3284+ """Utililty method to reload preview image"""
3285+ if filename and os.path.isfile(filename):
3286+ pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
3287+ if pb:
3288+ img = gtk.Image()
3289+ img.set_from_pixbuf(pb)
3290+ return img
3291+ img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
3292+ gtk.ICON_SIZE_LARGE_TOOLBAR)
3293+ img.set_size_request(64, 64)
3294+ return img
3295+
3296+ def but_callback(self, widget):
3297+ """Create button"""
3298+ dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
3299+ gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
3300+ dlg.set_keep_above(True)
3301+ dlg.set_filename(self._entry.get_text())
3302+ prev = gtk.Image()
3303+ box = gtk.VBox()
3304+ box.set_size_request(150, -1)
3305+ box.add(prev)
3306+ prev.show()
3307+ dlg.set_preview_widget_active(True)
3308+ dlg.connect('selection-changed', self.preview_callback, dlg, prev)
3309+ dlg.set_preview_widget(box)
3310+ response = dlg.run()
3311+ if response == gtk.RESPONSE_OK:
3312+ self._entry.set_text(dlg.get_filename())
3313+ widget.set_image(self.create_preview(dlg.get_filename()))
3314+ self.has_changed(self.widget)
3315+ dlg.destroy()
3316+
3317+ def preview_callback(self, widget, dlg, prev):
3318+ """add preview widget to filechooser"""
3319+ fname = dlg.get_preview_filename()
3320+ if fname and os.path.isfile(fname):
3321+ pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
3322+ if pb:
3323+ prev.set_from_pixbuf(pb)
3324+ dlg.set_preview_widget_active(True)
3325+ else:
3326+ dlg.set_preview_widget_active(False)
3327+
3328+ def has_changed(self, widget):
3329+ """Executed when the widget event kicks off."""
3330+ self.value = self._entry.get_text()
3331+ super(FileOption, self).has_changed()
3332+
3333
3334=== added file 'src/lib/options/font_option.py'
3335--- src/lib/options/font_option.py 1970-01-01 00:00:00 +0000
3336+++ src/lib/options/font_option.py 2011-03-28 16:19:24 +0000
3337@@ -0,0 +1,52 @@
3338+#
3339+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3340+#
3341+# This program is free software; you can redistribute it and/or modify
3342+# it under the terms of the GNU General Public License as published by
3343+# the Free Software Foundation; either version 3 of the License, or
3344+# (at your option) any later version.
3345+#
3346+# This program is distributed in the hope that it will be useful,
3347+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3348+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3349+# GNU General Public License for more details.
3350+#
3351+# You should have received a copy of the GNU General Public License
3352+# along with this program; if not, write to the Free Software
3353+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3354+#
3355+"""
3356+Font options, these classes will display a text box.
3357+"""
3358+
3359+import gtk
3360+
3361+from screenlets.options import _
3362+from base import Option
3363+
3364+class FontOption(Option):
3365+ """An class for font options."""
3366+ def on_import(self, strvalue):
3367+ """When a font is imported from the config."""
3368+ return strvalue
3369+
3370+ def on_export(self, value):
3371+ """When a font is exported to the config."""
3372+ return str(value)
3373+
3374+ def generate_widget(self, value):
3375+ """Generate a special widget for font options"""
3376+ self.widget = gtk.FontButton()
3377+ self.set_value(value)
3378+ self.widget.connect("font-set", self.has_changed)
3379+ return self.widget
3380+
3381+ def set_value(self, value):
3382+ """Set the font value as required."""
3383+ self.widget.set_font_name(value)
3384+ self.value = value
3385+
3386+ def has_changed(self, widget):
3387+ """Executed when the widget event kicks off."""
3388+ self.value = widget.get_font_name()
3389+ super(FontOption, self).has_changed()
3390
3391=== added file 'src/lib/options/list_option.py'
3392--- src/lib/options/list_option.py 1970-01-01 00:00:00 +0000
3393+++ src/lib/options/list_option.py 2011-03-28 16:19:24 +0000
3394@@ -0,0 +1,210 @@
3395+#
3396+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3397+#
3398+# This program is free software; you can redistribute it and/or modify
3399+# it under the terms of the GNU General Public License as published by
3400+# the Free Software Foundation; either version 3 of the License, or
3401+# (at your option) any later version.
3402+#
3403+# This program is distributed in the hope that it will be useful,
3404+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3405+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3406+# GNU General Public License for more details.
3407+#
3408+# You should have received a copy of the GNU General Public License
3409+# along with this program; if not, write to the Free Software
3410+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3411+#
3412+"""
3413+List options, these classes will display all sorts of crazy shit.
3414+"""
3415+
3416+import gtk
3417+
3418+from screenlets.options import _
3419+from base import Option
3420+
3421+class ListOption(Option):
3422+ """An Option for string options."""
3423+ def on_import(self, strvalue):
3424+ """When a list is imported from the config."""
3425+ return eval(strvalue)
3426+
3427+ def on_export(self, value):
3428+ """When a list is exported to the config."""
3429+ return str(value)
3430+
3431+ def generate_widget(self, value):
3432+ """Generate some widgets for a list."""
3433+ self._entry = gtk.Entry()
3434+ self._entry.set_editable(False)
3435+ self.set_value(value)
3436+ self._entry.show()
3437+ img = gtk.Image()
3438+ img.set_from_stock(gtk.STOCK_EDIT, 1)
3439+ but = gtk.Button()
3440+ but.set_image(img)
3441+ but.show()
3442+ but.connect("clicked", self.open_listeditor)
3443+ but.set_tooltip_text(_('Open List-Editor ...'))
3444+ self._entry.set_tooltip_text(self.desc)
3445+ self.widget = gtk.HBox()
3446+ self.widget.add(self._entry)
3447+ self.widget.add(but)
3448+ return self.widget
3449+
3450+ def open_listeditor(self, event):
3451+ # open dialog
3452+ dlg = ListOptionDialog()
3453+ # read string from entry and import it through option-class
3454+ # (this is needed to always have an up-to-date value)
3455+ dlg.set_list(self.on_import(self._entry.get_text()))
3456+ resp = dlg.run()
3457+ if resp == gtk.RESPONSE_OK:
3458+ # set text in entry
3459+ self._entry.set_text(str(dlg.get_list()))
3460+ # manually call the options-callback
3461+ self.has_changed(dlg)
3462+ dlg.destroy()
3463+
3464+ def set_value(self, value):
3465+ """Set the list string value as required."""
3466+ self._entry.set_text(str(value))
3467+ self.value = value
3468+
3469+ def has_changed(self, widget):
3470+ """Executed when the widget event kicks off."""
3471+ self.value = widget.get_list()
3472+ super(ListOption, self).has_changed()
3473+
3474+
3475+class ListOptionDialog(gtk.Dialog):
3476+ """An editing dialog used for editing options of the ListOption-type."""
3477+ model = None
3478+ tree = None
3479+ buttonbox = None
3480+
3481+ # call gtk.Dialog.__init__
3482+ def __init__ (self):
3483+ super(ListOptionDialog, self).__init__(
3484+ "Edit List",
3485+ flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
3486+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
3487+ gtk.STOCK_OK, gtk.RESPONSE_OK)
3488+ )
3489+ # set size
3490+ self.resize(300, 370)
3491+ self.set_keep_above(True)
3492+ # init vars
3493+ self.model = gtk.ListStore(str)
3494+ # create UI
3495+ self.create_ui()
3496+
3497+ def create_ui (self):
3498+ """Create the user-interface for this dialog."""
3499+ # create outer hbox (tree|buttons)
3500+ hbox = gtk.HBox()
3501+ hbox.set_border_width(10)
3502+ hbox.set_spacing(10)
3503+ # create tree
3504+ self.tree = gtk.TreeView(model=self.model)
3505+ self.tree.set_headers_visible(False)
3506+ self.tree.set_reorderable(True)
3507+ #self.tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
3508+ col = gtk.TreeViewColumn('')
3509+ cell = gtk.CellRendererText()
3510+ #cell.set_property('cell-background', 'cyan')
3511+ cell.set_property('foreground', 'black')
3512+ col.pack_start(cell, False)
3513+ col.set_attributes(cell, text=0)
3514+ self.tree.append_column(col)
3515+ self.tree.show()
3516+ hbox.pack_start(self.tree, True, True)
3517+ #sep = gtk.VSeparator()
3518+ #sep.show()
3519+ #hbox.add(sep)
3520+ # create buttons
3521+ self.buttonbox = bb = gtk.VButtonBox()
3522+ self.buttonbox.set_layout(gtk.BUTTONBOX_START)
3523+ b1 = gtk.Button(stock=gtk.STOCK_ADD)
3524+ b2 = gtk.Button(stock=gtk.STOCK_EDIT)
3525+ b3 = gtk.Button(stock=gtk.STOCK_REMOVE)
3526+ b1.connect('clicked', self.button_callback, 'add')
3527+ b2.connect('clicked', self.button_callback, 'edit')
3528+ b3.connect('clicked', self.button_callback, 'remove')
3529+ bb.add(b1)
3530+ bb.add(b2)
3531+ bb.add(b3)
3532+ self.buttonbox.show_all()
3533+ #hbox.add(self.buttonbox)
3534+ hbox.pack_end(self.buttonbox, False)
3535+ # add everything to outer hbox and show it
3536+ hbox.show()
3537+ self.vbox.add(hbox)
3538+
3539+ def set_list (self, lst):
3540+ """Set the list to be edited in this editor."""
3541+ for el in lst:
3542+ self.model.append([el])
3543+
3544+ def get_list (self):
3545+ """Return the list that is currently being edited in this editor."""
3546+ lst = []
3547+ for i in self.model:
3548+ lst.append(i[0])
3549+ return lst
3550+
3551+ def remove_selected_item (self):
3552+ """Remove the currently selected item."""
3553+ sel = self.tree.get_selection()
3554+ if sel:
3555+ it = sel.get_selected()[1]
3556+ if it:
3557+ print self.model.get_value(it, 0)
3558+ self.model.remove(it)
3559+
3560+ def entry_dialog (self, default = ''):
3561+ """Show entry-dialog and return string."""
3562+ entry = gtk.Entry()
3563+ entry.set_text(default)
3564+ entry.show()
3565+ dlg = gtk.Dialog("Add/Edit Item", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
3566+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
3567+ gtk.RESPONSE_OK))
3568+ dlg.set_keep_above(True)
3569+ dlg.vbox.add(entry)
3570+ resp = dlg.run()
3571+ ret = None
3572+ if resp == gtk.RESPONSE_OK:
3573+ ret = entry.get_text()
3574+ dlg.destroy()
3575+ return ret
3576+
3577+ def button_callback (self, widget, id):
3578+ print "PRESS: %s" % id
3579+ if id == 'remove':
3580+ self.remove_selected_item()
3581+ if id == 'add':
3582+ new = self.entry_dialog()
3583+ if new != None:
3584+ self.model.append([new])
3585+ if id == 'edit':
3586+ sel = self.tree.get_selection()
3587+ if sel:
3588+ it = sel.get_selected()[1]
3589+ if it:
3590+ new = self.entry_dialog(self.model.get_value(it, 0))
3591+ if new != None:
3592+ #self.model.append([new])
3593+ self.model.set_value(it, 0, new)
3594+
3595+# TEST>-
3596+"""dlg = ListOptionDialog()
3597+dlg.set_list(['test1', 'afarew34s', 'fhjh23faj', 'yxcdfs58df', 'hsdf7jsdfh'])
3598+dlg.run()
3599+print "RESULT: " + str(dlg.get_list())
3600+dlg.destroy()
3601+import sys
3602+sys.exit(1)"""
3603+# /TEST
3604+
3605
3606=== added file 'src/lib/options/number_option.py'
3607--- src/lib/options/number_option.py 1970-01-01 00:00:00 +0000
3608+++ src/lib/options/number_option.py 2011-03-28 16:19:24 +0000
3609@@ -0,0 +1,90 @@
3610+#
3611+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3612+#
3613+# This program is free software; you can redistribute it and/or modify
3614+# it under the terms of the GNU General Public License as published by
3615+# the Free Software Foundation; either version 3 of the License, or
3616+# (at your option) any later version.
3617+#
3618+# This program is distributed in the hope that it will be useful,
3619+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3620+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3621+# GNU General Public License for more details.
3622+#
3623+# You should have received a copy of the GNU General Public License
3624+# along with this program; if not, write to the Free Software
3625+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3626+#
3627+"""
3628+Integer and Float options, these classes will display a spin box.
3629+"""
3630+
3631+import gtk
3632+
3633+from screenlets.options import _
3634+from base import Option
3635+
3636+class IntOption(Option):
3637+ """An Option for integer options."""
3638+ min = -100000
3639+ max = 100000
3640+ increment = 1
3641+
3642+ def on_import(self, strvalue):
3643+ """When a integer is imported from the config."""
3644+ try:
3645+ if strvalue[0]=='-':
3646+ return int(strvalue[1:]) * -1
3647+ return int(strvalue)
3648+ except:
3649+ sys.stderr.write(_("Error during on_import - option: %s.\n") % self.name)
3650+ return 0
3651+
3652+ return int(strvalue)
3653+
3654+ def on_export(self, value):
3655+ """When a string is exported to the config."""
3656+ return str(value)
3657+
3658+ def generate_widget(self, value):
3659+ """Generate a spin button for integer options"""
3660+ self.widget = gtk.SpinButton()
3661+ self.widget.set_increments(self.increment, int(self.max / self.increment))
3662+ if self.min != None and self.max != None:
3663+ self.widget.set_range(self.min, self.max)
3664+ self.set_value(value)
3665+ self.widget.connect("value-changed", self.has_changed)
3666+ return self.widget
3667+
3668+ def set_value(self, value):
3669+ """Set the int value, including the value of the widget."""
3670+ self.value = value
3671+ self.widget.set_value(value)
3672+
3673+ def has_changed(self, widget):
3674+ """Executed when the widget event kicks off."""
3675+ self.value = int(widget.get_value())
3676+ super(IntOption, self).has_changed()
3677+
3678+
3679+class FloatOption(IntOption):
3680+ """An option for float numbers."""
3681+ digits = 0
3682+
3683+ def on_import (self, strvalue):
3684+ """Called when FloatOption gets imported. Converts str to float."""
3685+ if strvalue[0]=='-':
3686+ return float(strvalue[1:]) * -1.0
3687+ return float(strvalue)
3688+
3689+ def generate_widget(self, value):
3690+ """Do the same as int but add the number of ditgits"""
3691+ super(FloatOption, self).generate_widget(value)
3692+ self.widget.set_digits(self.digits)
3693+ return self.widget
3694+
3695+ def has_changed(self, widget):
3696+ """Executed when the widget event kicks off."""
3697+ self.value = float(widget.get_value())
3698+ super(IntOption, self).has_changed()
3699+
3700
3701=== added file 'src/lib/options/string_option.py'
3702--- src/lib/options/string_option.py 1970-01-01 00:00:00 +0000
3703+++ src/lib/options/string_option.py 2011-03-28 16:19:24 +0000
3704@@ -0,0 +1,79 @@
3705+#
3706+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3707+#
3708+# This program is free software; you can redistribute it and/or modify
3709+# it under the terms of the GNU General Public License as published by
3710+# the Free Software Foundation; either version 3 of the License, or
3711+# (at your option) any later version.
3712+#
3713+# This program is distributed in the hope that it will be useful,
3714+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3715+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3716+# GNU General Public License for more details.
3717+#
3718+# You should have received a copy of the GNU General Public License
3719+# along with this program; if not, write to the Free Software
3720+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3721+#
3722+"""
3723+String options, these classes will display a text box.
3724+"""
3725+
3726+import gtk
3727+
3728+from screenlets.options import _
3729+from base import Option
3730+
3731+class StringOption(Option):
3732+ """An Option for string options."""
3733+ choices = None
3734+ password = False
3735+
3736+ def on_import(self, strvalue):
3737+ """When a string is imported from the config."""
3738+ return strvalue
3739+
3740+ def on_export(self, value):
3741+ """When a string is exported to the config."""
3742+ return str(value)
3743+
3744+ def generate_widget(self, value):
3745+ """Generate a textbox for a string options"""
3746+ if self.choices:
3747+ # if a list of values is defined, show combobox
3748+ self.widget = gtk.combo_box_new_text()
3749+ p = -1
3750+ i = 0
3751+ for s in self.choices:
3752+ self.widget.append_text(s)
3753+ if s==value:
3754+ p = i
3755+ i+=1
3756+ self.widget.set_active(p)
3757+ else:
3758+ self.widget = gtk.Entry()
3759+ # if it is a password, set text to be invisible
3760+ if self.password:
3761+ self.widget.set_visibility(False)
3762+
3763+ self.set_value(value)
3764+ self.widget.connect("changed", self.has_changed)
3765+ #self.widget.set_size_request(180, 28)
3766+ return self.widget
3767+
3768+ def set_value(self, value):
3769+ """Set the string value as required."""
3770+ self.value = value
3771+ if self.choices:
3772+ # TODO self.widget.set_active(p)
3773+ pass
3774+ else:
3775+ self.widget.set_text(value)
3776+
3777+ def has_changed(self, widget):
3778+ """Executed when the widget event kicks off."""
3779+ if self.choices:
3780+ self.set_value( widget.get_active_text() )
3781+ else:
3782+ self.set_value( widget.get_text() )
3783+ super(StringOption, self).has_changed()
3784
3785=== added file 'src/lib/options/time_option.py'
3786--- src/lib/options/time_option.py 1970-01-01 00:00:00 +0000
3787+++ src/lib/options/time_option.py 2011-03-28 16:19:24 +0000
3788@@ -0,0 +1,80 @@
3789+#
3790+# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3791+#
3792+# This program is free software; you can redistribute it and/or modify
3793+# it under the terms of the GNU General Public License as published by
3794+# the Free Software Foundation; either version 3 of the License, or
3795+# (at your option) any later version.
3796+#
3797+# This program is distributed in the hope that it will be useful,
3798+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3799+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3800+# GNU General Public License for more details.
3801+#
3802+# You should have received a copy of the GNU General Public License
3803+# along with this program; if not, write to the Free Software
3804+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3805+#
3806+"""
3807+Time options, these classes will display a text box.
3808+"""
3809+
3810+import gtk
3811+
3812+from screenlets.options import _
3813+from colour_option import ColorOption
3814+
3815+class TimeOption(ColorOption):
3816+ """An class for time options."""
3817+
3818+ def generate_widget(self, value):
3819+ """Generate a textbox for a time options"""
3820+ self.widget = gtk.HBox()
3821+ input_hour = gtk.SpinButton()
3822+ input_minute = gtk.SpinButton()
3823+ input_second = gtk.SpinButton()
3824+ input_hour.set_range(0, 23)
3825+ input_hour.set_max_length(2)
3826+ input_hour.set_increments(1, 1)
3827+ input_hour.set_numeric(True)
3828+ input_hour.set_value(value[0])
3829+ input_minute.set_range(0, 59)
3830+ input_minute.set_max_length(2)
3831+ input_minute.set_increments(1, 1)
3832+ input_minute.set_numeric(True)
3833+ input_minute.set_value(value[1])
3834+ input_second.set_range(0, 59)
3835+ input_second.set_max_length(2)
3836+ input_second.set_increments(1, 1)
3837+ input_second.set_numeric(True)
3838+ input_second.set_value(value[2])
3839+ input_hour.connect('value-changed', self.has_changed)
3840+ input_minute.connect('value-changed', self.has_changed)
3841+ input_second.connect('value-changed', self.has_changed)
3842+ input_hour.set_tooltip_text(self.desc)
3843+ input_minute.set_tooltip_text(self.desc)
3844+ input_second.set_tooltip_text(self.desc)
3845+ self.widget.add(input_hour)
3846+ self.widget.add(gtk.Label(':'))
3847+ self.widget.add(input_minute)
3848+ self.widget.add(gtk.Label(':'))
3849+ self.widget.add(input_second)
3850+ self.widget.add(gtk.Label('h'))
3851+ self.widget.show_all()
3852+ return self.widget
3853+
3854+ def set_value(self, value):
3855+ """Set the time value as required."""
3856+ self.value = value
3857+
3858+ def has_changed(self, widget):
3859+ """Executed when the widget event kicks off."""
3860+ box = widget.get_parent()
3861+ inputs = box.get_children()
3862+ self.value = (
3863+ int(inputs[0].get_value()),
3864+ int(inputs[2].get_value()),
3865+ int(inputs[4].get_value()),
3866+ )
3867+ super(ColorOption, self).has_changed()
3868+

Subscribers

People subscribed via source and target branches

to status/vote changes: