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
=== modified file 'setup.py'
--- setup.py 2010-11-10 23:53:53 +0000
+++ setup.py 2011-03-28 16:19:24 +0000
@@ -119,7 +119,7 @@
119 os.system (buildcmd % (dname, name, name, dname))119 os.system (buildcmd % (dname, name, name, dname))
120 files_list.append ((destpath % dname, [mopath % (dname,name.replace('-'+dname,''))]))120 files_list.append ((destpath % dname, [mopath % (dname,name.replace('-'+dname,''))]))
121 121
122PACKAGES = ['screenlets','screenlets.plugins']122PACKAGES = ['screenlets','screenlets.plugins','screenlets.options']
123123
124# ----------------------------124# ----------------------------
125# Call setup()125# Call setup()
126126
=== modified file 'src/lib/__init__.py'
--- src/lib/__init__.py 2011-02-09 20:45:08 +0000
+++ src/lib/__init__.py 2011-03-28 16:19:24 +0000
@@ -136,15 +136,15 @@
136136
137DEBIAN = True137DEBIAN = True
138try:138try:
139 subprocess.call(["dpkg"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)139 subprocess.call(["dpkg"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
140except OSError:140except OSError:
141 DEBIAN = False141 DEBIAN = False
142142
143UBUNTU = True143UBUNTU = True
144try:144try:
145 subprocess.call(["apt-add-repository"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)145 subprocess.call(["apt-add-repository"], stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
146except OSError:146except OSError:
147 UBUNTU = False147 UBUNTU = False
148148
149#-------------------------------------------------------------------------------149#-------------------------------------------------------------------------------
150# CLASSES150# CLASSES
@@ -318,8 +318,8 @@
318 """@DEPRECATED Moved to Screenlets class: Draws a circule"""318 """@DEPRECATED Moved to Screenlets class: Draws a circule"""
319 ctx.save()319 ctx.save()
320 ctx.translate(x, y)320 ctx.translate(x, y)
321 ctx.arc(width/2,height/2,min(height,width)/2,0,2*math.pi)321 ctx.arc(width/2,height/2,min(height,width)/2,0,2*math.pi)
322 if fill:ctx.fill()322 if fill: ctx.fill()
323 else: ctx.stroke()323 else: ctx.stroke()
324 ctx.restore()324 ctx.restore()
325325
@@ -328,7 +328,7 @@
328 ctx.save()328 ctx.save()
329 ctx.move_to(start_x, start_y)329 ctx.move_to(start_x, start_y)
330 ctx.set_line_width(line_width)330 ctx.set_line_width(line_width)
331 ctx.rel_line_to(end_x, end_y)331 ctx.rel_line_to(end_x, end_y)
332 if close : ctx.close_path()332 if close : ctx.close_path()
333 if preserve: ctx.stroke_preserve()333 if preserve: ctx.stroke_preserve()
334 else: ctx.stroke()334 else: ctx.stroke()
@@ -348,30 +348,30 @@
348 ctx.save()348 ctx.save()
349 ctx.translate(x, y)349 ctx.translate(x, y)
350 padding=0 # Padding from the edges of the window350 padding=0 # Padding from the edges of the window
351 rounded=rounded_angle # How round to make the edges 20 is ok351 rounded=rounded_angle # How round to make the edges 20 is ok
352 w = width352 w = width
353 h = height353 h = height
354354
355 # Move to top corner355 # Move to top corner
356 ctx.move_to(0+padding+rounded, 0+padding)356 ctx.move_to(0+padding+rounded, 0+padding)
357 357
358 # Top right corner and round the edge358 # Top right corner and round the edge
359 ctx.line_to(w-padding-rounded, 0+padding)359 ctx.line_to(w-padding-rounded, 0+padding)
360 ctx.arc(w-padding-rounded, 0+padding+rounded, rounded, (math.pi/2 )+(math.pi) , 0)360 ctx.arc(w-padding-rounded, 0+padding+rounded, rounded, (math.pi/2 )+(math.pi) , 0)
361 361
362 # Bottom right corner and round the edge362 # Bottom right corner and round the edge
363 ctx.line_to(w-padding, h-padding-rounded)363 ctx.line_to(w-padding, h-padding-rounded)
364 ctx.arc(w-padding-rounded, h-padding-rounded, rounded, 0, math.pi/2)364 ctx.arc(w-padding-rounded, h-padding-rounded, rounded, 0, math.pi/2)
365 365
366 # Bottom left corner and round the edge.366 # Bottom left corner and round the edge.
367 ctx.line_to(0+padding+rounded, h-padding)367 ctx.line_to(0+padding+rounded, h-padding)
368 ctx.arc(0+padding+rounded, h-padding-rounded, rounded,math.pi/2, math.pi)368 ctx.arc(0+padding+rounded, h-padding-rounded, rounded,math.pi/2, math.pi)
369 369
370 # Top left corner and round the edge370 # Top left corner and round the edge
371 ctx.line_to(0+padding, 0+padding+rounded)371 ctx.line_to(0+padding, 0+padding+rounded)
372 ctx.arc(0+padding+rounded, 0+padding+rounded, rounded, math.pi, (math.pi/2 )+(math.pi))372 ctx.arc(0+padding+rounded, 0+padding+rounded, rounded, math.pi, (math.pi/2 )+(math.pi))
373 373
374 # Fill in the shape.374 # Fill in the shape.
375 if fill:ctx.fill()375 if fill:ctx.fill()
376 else: ctx.stroke()376 else: ctx.stroke()
377 ctx.restore()377 ctx.restore()
@@ -430,29 +430,29 @@
430 ctx.restore()430 ctx.restore()
431431
432 def show_notification (self,text):432 def show_notification (self,text):
433 """@DEPRECATED Moved to Screenlets class: Show notification window at current mouse position."""433 """@DEPRECATED Moved to Screenlets class: Show notification window at current mouse position."""
434 if self.notify == None:434 if self.notify == None:
435 self.notify = Notify()435 self.notify = Notify()
436 self.notify.text = text436 self.notify.text = text
437 self.notify.show()437 self.notify.show()
438438
439 def hide_notification (self):439 def hide_notification (self):
440 """@DEPRECATED Moved to Screenlets class: hide notification window"""440 """@DEPRECATED Moved to Screenlets class: hide notification window"""
441 if self.notify != None:441 if self.notify != None:
442 self.notify.hide()442 self.notify.hide()
443 self.notify = None443 self.notify = None
444444
445 def show_tooltip (self,text,tooltipx,tooltipy):445 def show_tooltip (self,text,tooltipx,tooltipy):
446 """@DEPRECATED: Moved to Screenlets class: Show tooltip window at current mouse position."""446 """@DEPRECATED: Moved to Screenlets class: Show tooltip window at current mouse position."""
447 if self.tooltip == None:447 if self.tooltip == None:
448 self.tooltip = Tooltip(300, 400)448 self.tooltip = Tooltip(300, 400)
449 self.tooltip.text = text449 self.tooltip.text = text
450 self.tooltip.x = tooltipx450 self.tooltip.x = tooltipx
451 self.tooltip.y = tooltipy451 self.tooltip.y = tooltipy
452 self.tooltip.show()452 self.tooltip.show()
453453
454 def hide_tooltip (self):454 def hide_tooltip (self):
455 """@DEPRECATED Moved to Screenlets class: hide tooltip window"""455 """@DEPRECATED Moved to Screenlets class: hide tooltip window"""
456 if self.tooltip != None:456 if self.tooltip != None:
457 self.tooltip.hide()457 self.tooltip.hide()
458 self.tooltip = None 458 self.tooltip = None
@@ -742,70 +742,80 @@
742 else: self.draw_buttons = False742 else: self.draw_buttons = False
743 if uses_theme:743 if uses_theme:
744 self.uses_theme = True744 self.uses_theme = True
745 self.add_option(StringOption('Screenlet', 'theme_name', 745 self.add_option(StringOption('Screenlet', 'theme_name',
746 'default', '', '', hidden=True))746 default='default', hidden=True))
747 # create/add options747 # create/add options
748 self.add_option(IntOption('Screenlet', 'x', 748 self.add_option(IntOption('Screenlet', 'x',
749 0, _('X-Position'), _('The X-position of this Screenlet ...'), 749 default=0, label=_('X-Position'),
750 desc=_('The X-position of this Screenlet ...'),
750 min=0, max=gtk.gdk.screen_width()))751 min=0, max=gtk.gdk.screen_width()))
751 self.add_option(IntOption('Screenlet', 'y', 752 self.add_option(IntOption('Screenlet', 'y',
752 0, _('Y-Position'), _('The Y-position of this Screenlet ...'), 753 default=0, label=_('Y-Position'),
754 desc=_('The Y-position of this Screenlet ...'),
753 min=0, max=gtk.gdk.screen_height()))755 min=0, max=gtk.gdk.screen_height()))
754 self.add_option(IntOption('Screenlet', 'width', 756 self.add_option(IntOption('Screenlet', 'width',
755 width, _('Width'), _('The width of this Screenlet ...'), 757 default=width, label=_('Width'),
756 min=16, max=1000, hidden=True))758 desc=_('The width of this Screenlet ...'),
757 self.add_option(IntOption('Screenlet', 'height', 759 min=16, max=1000, hidden=True))
758 height, _('Height'), _('The height of this Screenlet ...'), 760 self.add_option(IntOption('Screenlet', 'height',
759 min=16, max=1000, hidden=True))761 default=height, label=_('Height'),
760 self.add_option(FloatOption('Screenlet', 'scale', 762 desc=_('The height of this Screenlet ...'),
761 self.scale, _('Scale'), _('The scale-factor of this Screenlet ...'), 763 min=16, max=1000, hidden=True))
764 self.add_option(FloatOption('Screenlet', 'scale',
765 default=self.scale, label=_('Scale'),
766 desc=_('The scale-factor of this Screenlet ...'),
762 min=0.1, max=10.0, digits=2, increment=0.1))767 min=0.1, max=10.0, digits=2, increment=0.1))
763 self.add_option(FloatOption('Screenlet', 'opacity', 768 self.add_option(FloatOption('Screenlet', 'opacity',
764 self.opacity, _('Opacity'), _('The opacity of the Screenlet window ...'), 769 default=self.opacity, label=_('Opacity'),
770 desc=_('The opacity of the Screenlet window ...'),
765 min=0.1, max=1.0, digits=2, increment=0.1))771 min=0.1, max=1.0, digits=2, increment=0.1))
766 self.add_option(BoolOption('Screenlet', 'is_sticky', 772 self.add_option(BoolOption('Screenlet', 'is_sticky',
767 is_sticky, _('Stick to Desktop'), 773 default=is_sticky, label=_('Stick to Desktop'),
768 _('Show this Screenlet on all workspaces ...')))774 desc=_('Show this Screenlet on all workspaces ...')))
769 self.add_option(BoolOption('Screenlet', 'is_widget', 775 self.add_option(BoolOption('Screenlet', 'is_widget',
770 is_widget, _('Treat as Widget'), 776 default=is_widget, label=_('Treat as Widget'),
771 _('Treat this Screenlet as a "Widget" ...')))777 desc=_('Treat this Screenlet as a "Widget" ...')))
772 self.add_option(BoolOption('Screenlet', 'is_dragged', 778 self.add_option(BoolOption('Screenlet', 'is_dragged',
773 self.is_dragged, "Is the screenlet dragged","Is the screenlet dragged", hidden=True))779 default=self.is_dragged, label="Is the screenlet dragged",
774 self.add_option(BoolOption('Screenlet', 'is_sizable', 780 desc="Is the screenlet dragged", hidden=True))
775 is_sizable, "Can the screenlet be resized","is_sizable", hidden=True))781 self.add_option(BoolOption('Screenlet', 'is_sizable',
776 self.add_option(BoolOption('Screenlet', 'is_visible', 782 default=is_sizable, label="Can the screenlet be resized",
777 self.is_visible, "Usefull to use screenlets as gnome panel applets","is_visible", hidden=True))783 desc="is_sizable", hidden=True))
778 self.add_option(BoolOption('Screenlet', 'lock_position', 784 self.add_option(BoolOption('Screenlet', 'is_visible',
779 self.lock_position, _('Lock position'), 785 default=self.is_visible, label="Usefull to use screenlets as gnome panel applets",
780 _('Stop the screenlet from being moved...')))786 desc="is_visible", hidden=True))
781 self.add_option(BoolOption('Screenlet', 'keep_above', 787 self.add_option(BoolOption('Screenlet', 'lock_position',
782 self.keep_above, _('Keep above'), 788 default=self.lock_position, label=_('Lock position'),
783 _('Keep this Screenlet above other windows ...')))789 desc=_('Stop the screenlet from being moved...')))
784 self.add_option(BoolOption('Screenlet', 'keep_below', 790 self.add_option(BoolOption('Screenlet', 'keep_above',
785 self.keep_below, _('Keep below'), 791 default=self.keep_above, label=_('Keep above'),
786 _('Keep this Screenlet below other windows ...')))792 desc=_('Keep this Screenlet above other windows ...')))
787 self.add_option(BoolOption('Screenlet', 'draw_buttons', 793 self.add_option(BoolOption('Screenlet', 'keep_below',
788 self.draw_buttons, _('Draw button controls'), 794 default=self.keep_below, label=_('Keep below'),
789 _('Draw buttons in top right corner')))795 desc=_('Keep this Screenlet below other windows ...')))
790 self.add_option(BoolOption('Screenlet', 'skip_pager', 796 self.add_option(BoolOption('Screenlet', 'draw_buttons',
791 self.skip_pager, _('Skip Pager'), 797 default=self.draw_buttons, label=_('Draw button controls'),
792 _('Set this Screenlet to show/hide in pagers ...')))798 desc=_('Draw buttons in top right corner')))
793 self.add_option(BoolOption('Screenlet', 'skip_taskbar', 799 self.add_option(BoolOption('Screenlet', 'skip_pager',
794 self.skip_pager, _('Skip Taskbar'), 800 default=self.skip_pager, label=_('Skip Pager'),
795 _('Set this Screenlet to show/hide in taskbars ...')))801 desc=_('Set this Screenlet to show/hide in pagers ...')))
796 self.add_option(BoolOption('Screenlet', 'resize_on_scroll', 802 self.add_option(BoolOption('Screenlet', 'skip_taskbar',
797 self.resize_on_scroll, _("Resize on mouse scroll"),"resize_on_scroll"))803 default=self.skip_pager, label=_('Skip Taskbar'),
804 desc=_('Set this Screenlet to show/hide in taskbars ...')))
805 self.add_option(BoolOption('Screenlet', 'resize_on_scroll',
806 default=self.resize_on_scroll, label=_("Resize on mouse scroll"),
807 desc="resize_on_scroll"))
798 self.add_option(BoolOption('Screenlet', 'ignore_requirements', 808 self.add_option(BoolOption('Screenlet', 'ignore_requirements',
799 self.ignore_requirements, _('Ignore requirements'), 809 self.ignore_requirements, _('Ignore requirements'),
800 _('Set this Screenlet to ignore/demand DEB requirements ...')))810 _('Set this Screenlet to ignore/demand DEB requirements ...')))
801 if uses_theme:811 if uses_theme:
802 self.ask_on_option_override = ask_on_option_override812 self.ask_on_option_override = ask_on_option_override
803 self.add_option(BoolOption('Screenlet', 'allow_option_override', 813 self.add_option(BoolOption('Screenlet', 'allow_option_override',
804 self.allow_option_override, _('Allow overriding Options'), 814 default=self.allow_option_override, label=_('Allow overriding Options'),
805 _('Allow themes to override options in this screenlet ...')))815 desc=_('Allow themes to override options in this screenlet ...')))
806 self.add_option(BoolOption('Screenlet', 'ask_on_option_override', 816 self.add_option(BoolOption('Screenlet', 'ask_on_option_override',
807 self.ask_on_option_override, _('Ask on Override'), 817 default=self.ask_on_option_override, label=_('Ask on Override'),
808 _('Show a confirmation-dialog when a theme wants to override ')+\818 desc=_('Show a confirmation-dialog when a theme wants to override ')+\
809 _('the current options of this Screenlet ...')))819 _('the current options of this Screenlet ...')))
810 # disable width/height820 # disable width/height
811 self.disable_option('width')821 self.disable_option('width')
@@ -959,7 +969,7 @@
959 if self.window.window:969 if self.window.window:
960 self.window.window.set_skip_taskbar_hint(bool(value))970 self.window.window.set_skip_taskbar_hint(bool(value))
961 # NOTE: This is the new recommended way of storing options in real-time971 # NOTE: This is the new recommended way of storing options in real-time
962 # (we access the backend through the session here)972 # (we access the backend through the session here)
963 if self.saving_enabled:973 if self.saving_enabled:
964 o = self.get_option_by_name(name)974 o = self.get_option_by_name(name)
965 if o != None:975 if o != None:
@@ -2084,35 +2094,35 @@
20842094
20852095
2086 def show_notification (self,text):2096 def show_notification (self,text):
2087 """Show notification window at current mouse position."""2097 """Show notification window at current mouse position."""
2088 if self.notify == None:2098 if self.notify == None:
2089 self.notify = Notify()2099 self.notify = Notify()
2090 self.notify.text = text2100 self.notify.text = text
2091 self.notify.show()2101 self.notify.show()
20922102
2093 def hide_notification (self):2103 def hide_notification (self):
2094 """hide notification window"""2104 """hide notification window"""
2095 if self.notify != None:2105 if self.notify != None:
2096 self.notify.hide()2106 self.notify.hide()
2097 self.notify = None2107 self.notify = None
20982108
2099 def show_tooltip (self,text,tooltipx,tooltipy):2109 def show_tooltip (self,text,tooltipx,tooltipy):
2100 """Show tooltip window at current mouse position."""2110 """Show tooltip window at current mouse position."""
2101 if self.tooltip == None:2111 if self.tooltip == None:
2102 self.tooltip = Tooltip(300, 400)2112 self.tooltip = Tooltip(300, 400)
2103 self.tooltip.text = text2113 self.tooltip.text = text
2104 self.tooltip.x = tooltipx2114 self.tooltip.x = tooltipx
2105 self.tooltip.y = tooltipy2115 self.tooltip.y = tooltipy
2106 self.tooltip.show()2116 self.tooltip.show()
2107 else:2117 else:
2108 #self.tooltip = Tooltip(300, 400)2118 #self.tooltip = Tooltip(300, 400)
2109 self.tooltip.text = text2119 self.tooltip.text = text
2110 self.tooltip.x = tooltipx2120 self.tooltip.x = tooltipx
2111 self.tooltip.y = tooltipy2121 self.tooltip.y = tooltipy
2112 #self.tooltip.show()2122 #self.tooltip.show()
21132123
2114 def hide_tooltip (self):2124 def hide_tooltip (self):
2115 """hide tooltip window"""2125 """hide tooltip window"""
2116 if self.tooltip != None:2126 if self.tooltip != None:
2117 self.tooltip.hide()2127 self.tooltip.hide()
2118 self.tooltip = None 2128 self.tooltip = None
@@ -2204,21 +2214,21 @@
2204 """A window that displays a text and serves as Tooltip (very basic yet)."""2214 """A window that displays a text and serves as Tooltip (very basic yet)."""
2205 2215
2206 # internals2216 # internals
2207 __timeout = None2217 __timeout = None
2208 2218
2209 # attribs2219 # attribs
2210 text = ''2220 text = ''
2211 font_name = 'FreeSans 9'2221 font_name = 'FreeSans 9'
2212 width = 1002222 width = 100
2213 height = 202223 height = 20
2214 x = 02224 x = 0
2215 y = 02225 y = 0
2216 2226
2217 def __init__ (self, width, height):2227 def __init__ (self, width, height):
2218 object.__init__(self)2228 object.__init__(self)
2219 # init2229 # init
2220 self.__dict__['width'] = width2230 self.__dict__['width'] = width
2221 self.__dict__['height'] = height2231 self.__dict__['height'] = height
2222 self.window = gtk.Window()2232 self.window = gtk.Window()
2223 self.window.set_app_paintable(True)2233 self.window.set_app_paintable(True)
2224 self.window.set_size_request(width, height)2234 self.window.set_size_request(width, height)
@@ -2237,7 +2247,7 @@
2237 pango.FontDescription(self.font_name))2247 pango.FontDescription(self.font_name))
2238 #self.p_layout.set_width(-1)2248 #self.p_layout.set_width(-1)
2239 self.p_layout.set_width(width * pango.SCALE - 6)2249 self.p_layout.set_width(width * pango.SCALE - 6)
2240 2250
2241 def __setattr__ (self, name, value):2251 def __setattr__ (self, name, value):
2242 self.__dict__[name] = value2252 self.__dict__[name] = value
2243 if name in ('width', 'height', 'text'):2253 if name in ('width', 'height', 'text'):
@@ -2253,7 +2263,7 @@
2253 self.window.move(int(value), int(self.y))2263 self.window.move(int(value), int(self.y))
2254 elif name == 'y':2264 elif name == 'y':
2255 self.window.move(int(self.x), int(value))2265 self.window.move(int(self.x), int(value))
2256 2266
2257 def show (self):2267 def show (self):
2258 """Show the Tooltip window."""2268 """Show the Tooltip window."""
2259 self.cancel_show()2269 self.cancel_show()
@@ -2264,22 +2274,22 @@
2264 """Show the Tooltip window after a given delay."""2274 """Show the Tooltip window after a given delay."""
2265 self.cancel_show()2275 self.cancel_show()
2266 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)2276 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
2267 2277
2268 def hide (self):2278 def hide (self):
2269 """Hide the Tooltip window."""2279 """Hide the Tooltip window."""
2270 self.cancel_show()2280 self.cancel_show()
2271 self.window.destroy()2281 self.window.destroy()
2272 2282
2273 def cancel_show (self):2283 def cancel_show (self):
2274 """Cancel showing of the Tooltip."""2284 """Cancel showing of the Tooltip."""
2275 if self.__timeout:2285 if self.__timeout:
2276 gobject.source_remove(self.__timeout)2286 gobject.source_remove(self.__timeout)
2277 self.p_context = None2287 self.p_context = None
2278 self.p_layout = None2288 self.p_layout = None
2279 2289
2280 def __show_timeout (self):2290 def __show_timeout (self):
2281 self.show()2291 self.show()
2282 2292
2283 def screen_changed (self, window, screen=None):2293 def screen_changed (self, window, screen=None):
2284 if screen == None:2294 if screen == None:
2285 screen = window.get_screen()2295 screen = window.get_screen()
@@ -2287,10 +2297,10 @@
2287 if not map:2297 if not map:
2288 map = screen.get_rgb_colormap()2298 map = screen.get_rgb_colormap()
2289 window.set_colormap(map)2299 window.set_colormap(map)
2290 2300
2291 def expose (self, widget, event):2301 def expose (self, widget, event):
2292 ctx = self.window.window.cairo_create()2302 ctx = self.window.window.cairo_create()
2293 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?2303 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
2294 # set a clip region for the expose event2304 # set a clip region for the expose event
2295 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)2305 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
2296 ctx.clip()2306 ctx.clip()
@@ -2317,17 +2327,17 @@
2317 """A window that displays a text and serves as Notification (very basic yet)."""2327 """A window that displays a text and serves as Notification (very basic yet)."""
2318 2328
2319 # internals2329 # internals
2320 __timeout = None2330 __timeout = None
2321 2331
2322 # attribs2332 # attribs
2323 text = ''2333 text = ''
2324 font_name = 'FreeSans 9'2334 font_name = 'FreeSans 9'
2325 width = 2002335 width = 200
2326 height = 1002336 height = 100
2327 x = 02337 x = 0
2328 y = 02338 y = 0
2329 gradient = cairo.LinearGradient(0, 100,0, 0)2339 gradient = cairo.LinearGradient(0, 100,0, 0)
2330 2340
2331 def __init__ (self):2341 def __init__ (self):
2332 object.__init__(self)2342 object.__init__(self)
2333 # init2343 # init
@@ -2349,7 +2359,7 @@
2349 pango.FontDescription(self.font_name))2359 pango.FontDescription(self.font_name))
2350 #self.p_layout.set_width(-1)2360 #self.p_layout.set_width(-1)
2351 self.p_layout.set_width(self.width * pango.SCALE - 6)2361 self.p_layout.set_width(self.width * pango.SCALE - 6)
2352 2362
2353 def __setattr__ (self, name, value):2363 def __setattr__ (self, name, value):
2354 self.__dict__[name] = value2364 self.__dict__[name] = value
2355 if name in ('text'):2365 if name in ('text'):
@@ -2369,22 +2379,22 @@
2369 """Show the Notify window after a given delay."""2379 """Show the Notify window after a given delay."""
2370 self.cancel_show()2380 self.cancel_show()
2371 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)2381 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
2372 2382
2373 def hide (self):2383 def hide (self):
2374 """Hide the Notify window."""2384 """Hide the Notify window."""
2375 self.cancel_show()2385 self.cancel_show()
2376 self.window.destroy()2386 self.window.destroy()
2377 2387
2378 def cancel_show (self):2388 def cancel_show (self):
2379 """Cancel showing of the Notify."""2389 """Cancel showing of the Notify."""
2380 if self.__timeout:2390 if self.__timeout:
2381 gobject.source_remove(self.__timeout)2391 gobject.source_remove(self.__timeout)
2382 self.p_context = None2392 self.p_context = None
2383 self.p_layout = None2393 self.p_layout = None
2384 2394
2385 def __show_timeout (self):2395 def __show_timeout (self):
2386 self.show()2396 self.show()
2387 2397
2388 def screen_changed (self, window, screen=None):2398 def screen_changed (self, window, screen=None):
2389 if screen == None:2399 if screen == None:
2390 screen = window.get_screen()2400 screen = window.get_screen()
@@ -2392,10 +2402,10 @@
2392 if not map:2402 if not map:
2393 map = screen.get_rgb_colormap()2403 map = screen.get_rgb_colormap()
2394 window.set_colormap(map)2404 window.set_colormap(map)
2395 2405
2396 def expose (self, widget, event):2406 def expose (self, widget, event):
2397 ctx = self.window.window.cairo_create()2407 ctx = self.window.window.cairo_create()
2398 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?2408 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ?
2399 # set a clip region for the expose event2409 # set a clip region for the expose event
2400 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)2410 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height)
2401 ctx.clip()2411 ctx.clip()
24022412
=== added directory 'src/lib/options'
=== removed file 'src/lib/options.py'
--- src/lib/options.py 2011-02-20 05:06:16 +0000
+++ src/lib/options.py 1970-01-01 00:00:00 +0000
@@ -1,1432 +0,0 @@
1# This application is released under the GNU General Public License
2# v3 (or, at your option, any later version). You can find the full
3# text of the license under http://www.gnu.org/licenses/gpl.txt.
4# By using, editing and/or distributing this software you agree to
5# the terms and conditions of this license.
6# Thank you for using free software!
7
8# Options-system (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
9#
10# INFO:
11# - a dynamic Options-system that allows very easy creation of
12# objects with embedded configuration-system.
13# NOTE: The Dialog is not very nice yet - it is not good OOP-practice
14# because too big functions and bad class-layout ... but it works
15# for now ... :)
16#
17# TODO:
18# - option-widgets for all option-types (e.g. ListOptionWidget, ColorOptionWidget)
19# - OptionGroup-class instead of (or behind) add_options_group
20# - TimeOption, DateOption
21# - FileOption needs filter/limit-attribute
22# - allow options to disable/enable other options
23# - support for EditableOptions-subclasses as options
24# - separate OptionEditorWidget from Editor-Dialog
25# - place ui-code into screenlets.options.ui-module
26# - create own widgets for each Option-subclass
27#
28
29import screenlets
30import utils
31
32import os
33import gtk, gobject
34import xml.dom.minidom
35from xml.dom.minidom import Node
36
37# translation stuff
38import gettext
39gettext.textdomain('screenlets')
40gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX + '/share/locale')
41
42def _(s):
43 return gettext.gettext(s)
44
45# -----------------------------------------------------------------------
46# Option-classes and subclasses
47# -----------------------------------------------------------------------
48
49class Option(gobject.GObject):
50 """An Option stores information about a certain object-attribute. It doesn't
51 carry information about the value or the object it belongs to - it is only a
52 one-way data-storage for describing how to handle attributes."""
53
54 __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
55 gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
56
57 def __init__ (self, group, name, default, label, desc,
58 disabled=False, hidden=False, callback=None, protected=False):
59 """Creates a new Option with the given information."""
60 super(Option, self).__init__()
61 self.name = name
62 self.label = label
63 self.desc = desc
64 self.default = default
65 self.disabled = disabled
66 self.hidden = hidden
67 # for groups (TODO: OptionGroup)
68 self.group= group
69 # callback to be notified when this option changes
70 self.callback = callback
71 # real-time update?
72 self.realtime = True
73 # protected from get/set through service
74 self.protected = protected
75
76 def on_import (self, strvalue):
77 """Callback - called when an option gets imported from a string.
78 This function MUST return the string-value converted to the required
79 type!"""
80 return strvalue.replace("\\n", "\n")
81
82 def on_export (self, value):
83 """Callback - called when an option gets exported to a string. The
84 value-argument needs to be converted to a string that can be imported
85 by the on_import-handler. This handler MUST return the value
86 converted to a string!"""
87 return str(value).replace("\n", "\\n")
88
89
90class FileOption (Option):
91 """An Option-subclass for string-values that contain filenames. Adds
92 a patterns-attribute that can contain a list of patterns to be shown
93 in the assigned file selection dialog. The show_pixmaps-attribute
94 can be set to True to make the filedialog show all image-types
95 supported by gtk.Pixmap. If the directory-attributue is true, the
96 dialog will ony allow directories."""
97
98 def __init__ (self, group, name, default, label, desc,
99 patterns=['*'], image=False, directory=False, **keyword_args):
100 Option.__init__(self, group, name, default,label, desc, **keyword_args)
101 self.patterns = patterns
102 self.image = image
103 self.directory = False
104
105
106class ImageOption (Option):
107 """An Option-subclass for string-values that contain filenames of
108 image-files."""
109
110
111class DirectoryOption (Option):
112 """An Option-subclass for filename-strings that contain directories."""
113
114
115class BoolOption (Option):
116 """An Option for boolean values."""
117
118 def on_import (self, strvalue):
119 if strvalue == "True":
120 return True
121 return False
122
123
124class StringOption (Option):
125 """An Option for values of type string."""
126
127 def __init__ (self, group, name, default, label, desc,
128 choices=None, password=False, **keyword_args):
129 Option.__init__(self, group, name, default,label, desc, **keyword_args)
130 self.choices = choices
131 self.password = password
132
133
134class IntOption (Option):
135 """An Option for values of type number (can be int or float)."""
136
137 def __init__ (self, group, name, default, label, desc, min=-100000, max=100000,
138 increment=1, **keyword_args):
139 Option.__init__(self, group, name, default, label, desc, **keyword_args)
140 self.min = min
141 self.max = max
142 self.increment = increment
143
144 def on_import (self, strvalue):
145 """Called when IntOption gets imported. Converts str to int."""
146 try:
147 if strvalue[0]=='-':
148 return int(strvalue[1:]) * -1
149 return int(strvalue)
150 except:
151 print "Error during on_import - option: %s." % self.name
152 return 0
153
154
155class FloatOption (IntOption):
156 """An Option for values of type float."""
157
158 def __init__ (self, group, name, default, label, desc, digits=1,
159 **keyword_args):
160 IntOption.__init__(self, group, name, default, label, desc,
161 **keyword_args)
162 self.digits = digits
163
164 def on_import (self, strvalue):
165 """Called when FloatOption gets imported. Converts str to float."""
166 if strvalue[0]=='-':
167 return float(strvalue[1:]) * -1.0
168 return float(strvalue)
169
170
171class FontOption (Option):
172 """An Option for fonts (a simple StringOption)."""
173
174
175class ColorOption (Option):
176 """An Option for colors. Stored as a list with 4 values (r, g, b, a)."""
177
178 def on_import (self, strvalue):
179 """Import (r, g, b, a) from comma-separated string."""
180 # strip braces and spaces
181 strvalue = strvalue.lstrip('(')
182 strvalue = strvalue.rstrip(')')
183 strvalue = strvalue.strip()
184 # split value on commas
185 tmpval = strvalue.split(',')
186 outval = []
187 for f in tmpval:
188 # create list and again remove spaces
189 outval.append(float(f.strip()))
190 return outval
191
192 def on_export (self, value):
193 """Export r, g, b, a to comma-separated string."""
194 l = len(value)
195 outval = ''
196 for i in xrange(l):
197 outval += str(value[i])
198 if i < l-1:
199 outval += ','
200 return outval
201
202
203class ListOption (Option):
204 """An Option-type for list of strings."""
205
206 def on_import (self, strvalue):
207 """Import python-style list from a string (like [1, 2, 'test'])"""
208 lst = eval(strvalue)
209 return lst
210
211 def on_export (self, value):
212 """Export list as string."""
213 return str(value)
214
215
216import gnomekeyring
217class AccountOption (Option):
218 """An Option-type for username/password combos. Stores the password in
219 the gnome-keyring (if available) and only saves username and auth_token
220 through the screenlets-backend.
221 TODO:
222 - not create new token for any change (use "set" instead of "create" if
223 the given item already exists)
224 - use usual storage if no keyring is available but output warning
225 - on_delete-function for removing the data from keyring when the
226 Screenlet holding the option gets deleted"""
227
228 def __init__ (self, group, name, default, label, desc, **keyword_args):
229 Option.__init__ (self, group, name, default, label, desc,
230 protected=True, **keyword_args)
231 # check for availability of keyring
232 if not gnomekeyring.is_available():
233 raise Exception('GnomeKeyring is not available!!') # TEMP!!!
234 # THIS IS A WORKAROUND FOR A BUG IN KEYRING (usually we would use
235 # gnomekeyring.get_default_keyring_sync() here):
236 # find first available keyring
237 self.keyring_list = gnomekeyring.list_keyring_names_sync()
238 if len(self.keyring_list) == 0:
239 raise Exception('No keyrings found. Please create one first!')
240 else:
241 # we prefer the default keyring
242 try:
243 self.keyring = gnomekeyring.get_default_keyring_sync()
244 except:
245 if "session" in self.keyring_list:
246 print "Warning: No default keyring found, using session keyring. Storage is not permanent!"
247 self.keyring = "session"
248 else:
249 print "Warning: Neither default nor session keyring found, assuming keyring %s!" % self.keyring_list[0]
250 self.keyring = self.keyring_list[0]
251
252
253 def on_import (self, strvalue):
254 """Import account info from a string (like 'username:auth_token'),
255 retrieve the password from the storage and return a tuple containing
256 username and password."""
257 # split string into username/auth_token
258 #data = strvalue.split(':', 1)
259 (name, auth_token) = strvalue.split(':', 1)
260 if name and auth_token:
261 # read pass from storage
262 try:
263 pw = gnomekeyring.item_get_info_sync(self.keyring,
264 int(auth_token)).get_secret()
265 except Exception, ex:
266 print "ERROR: Unable to read password from keyring: %s" % ex
267 pw = ''
268 # return
269 return (name, pw)
270 else:
271 raise Exception('Illegal value in AccountOption.on_import.')
272
273 def on_export (self, value):
274 """Export the given tuple/list containing a username and a password. The
275 function stores the password in the gnomekeyring and returns a
276 string in form 'username:auth_token'."""
277 # store password in storage
278 attribs = dict(name=value[0])
279 auth_token = gnomekeyring.item_create_sync(self.keyring,
280 gnomekeyring.ITEM_GENERIC_SECRET, value[0], attribs, value[1], True)
281 # build value from username and auth_token
282 return value[0] + ':' + str(auth_token)
283
284"""#TEST:
285o = AccountOption('None', 'pop3_account', ('',''), 'Username/Password', 'Enter username/password here ...')
286# save option to keyring
287exported_account = o.on_export(('RYX', 'mysecretpassword'))
288print exported_account
289# and read option back from keyring
290print o.on_import(exported_account)
291
292
293import sys
294sys.exit(0)"""
295
296class TimeOption (ColorOption):
297 """An Option-subclass for string-values that contain dates."""
298
299
300# -----------------------------------------------------------------------
301# EditableOptions-class and needed functions
302# -----------------------------------------------------------------------
303
304def create_option_from_node (node, groupname):
305 """Create an Option from an XML-node with option-metadata."""
306 #print "TODO OPTION: " + str(cn)
307 otype = node.getAttribute("type")
308 oname = node.getAttribute("name")
309 ohidden = node.getAttribute("hidden")
310 odefault = None
311 oinfo = ''
312 olabel = ''
313 omin = None
314 omax = None
315 oincrement = 1
316 ochoices = ''
317 odigits = None
318 if otype and oname:
319 # parse children of option-node and save all useful attributes
320 for attr in node.childNodes:
321 if attr.nodeType == Node.ELEMENT_NODE:
322 if attr.nodeName == 'label':
323 olabel = attr.firstChild.nodeValue
324 elif attr.nodeName == 'info':
325 oinfo = attr.firstChild.nodeValue
326 elif attr.nodeName == 'default':
327 odefault = attr.firstChild.nodeValue
328 elif attr.nodeName == 'min':
329 omin = attr.firstChild.nodeValue
330 elif attr.nodeName == 'max':
331 omax = attr.firstChild.nodeValue
332 elif attr.nodeName == 'increment':
333 oincrement = attr.firstChild.nodeValue
334 elif attr.nodeName == 'choices':
335 ochoices = attr.firstChild.nodeValue
336 elif attr.nodeName == 'digits':
337 odigits = attr.firstChild.nodeValue
338 # if we have all needed values, create the Option
339 if odefault:
340 # create correct classname here
341 cls = otype[0].upper() + otype.lower()[1:] + 'Option'
342 #print 'Create: ' +cls +' / ' + oname + ' ('+otype+')'
343 # and build new instance (we use on_import for setting default val)
344 clsobj = getattr(__import__(__name__), cls)
345 opt = clsobj(groupname, oname, None, olabel, oinfo)
346 opt.default = opt.on_import(odefault)
347 # set values to the correct types
348 if cls == 'IntOption':
349 if omin:
350 opt.min = int(omin)
351 if omax:
352 opt.max = int(omax)
353 if oincrement:
354 opt.increment = int(oincrement)
355 elif cls == 'FloatOption':
356 if odigits:
357 opt.digits = int(odigits)
358 if omin:
359 opt.min = float(omin)
360 if omax:
361 opt.max = float(omax)
362 if oincrement:
363 opt.increment = float(oincrement)
364 elif cls == 'StringOption':
365 if ochoices:
366 opt.choices = ochoices
367 return opt
368 return None
369
370
371class EditableOptions(object):
372 """The EditableOptions can be inherited from to allow objects to export
373 editable options for editing them with the OptionsEditor-class.
374 NOTE: This could use some improvement and is very poorly coded :) ..."""
375
376 def __init__ (self):
377 self.__options__ = []
378 self.__options_groups__ = {}
379 # This is a workaround to remember the order of groups
380 self.__options_groups_ordered__ = []
381
382 def add_option (self, option, callback=None, realtime=True):
383 """Add an editable option to this object. Editable Options can be edited
384 and configured using the OptionsDialog. The optional callback-arg can be
385 used to set a callback that gets notified when the option changes its
386 value."""
387 #print "Add option: "+option.name
388 # if option already editable (i.e. initialized), return
389 for o in self.__options__:
390 if o.name == option.name:
391 return False
392 self.__dict__[option.name] = option.default
393 # set auto-update (TEMPORARY?)
394 option.realtime = realtime
395 # add option to group (output error if group is undefined)
396 try:
397 self.__options_groups__[option.group]['options'].append(option)
398 except:
399 print "Options: Error - group %s not defined." % option.group
400 return False
401 # now add the option
402 self.__options__.append(option)
403 # if callback is set, add callback
404 if callback:
405 option.connect("option_changed", callback)
406 return True
407
408
409 def add_options_group (self, name, group_info):
410 """Add a new options-group to this Options-object"""
411 self.__options_groups__[name] = {'label':name,
412 'info':group_info, 'options':[]}
413 self.__options_groups_ordered__.append(name)
414 #print self.options_groups
415
416 def disable_option (self, name):
417 """Disable the inputs for a certain Option."""
418 for o in self.__options__:
419 if o.name == name:
420 o.disabled = True
421 return True
422 return False
423
424 def enable_option(self, name):
425 """Enable the inputs for a certain Option."""
426 for o in self.__options__:
427 if o.name == name:
428 o.disabled = False
429 return True
430 return False
431
432 def export_options_as_list (self):
433 """Returns all editable options within a list (without groups)
434 as key/value tuples."""
435 lst = []
436 for o in self.__options__:
437 lst.append((o.name, getattr(self, o.name)))
438 return lst
439
440 def get_option_by_name (self, name):
441 """Returns an option in this Options by it's name (or None).
442 TODO: this gives wrong results in childclasses ... maybe access
443 as class-attribute??"""
444 for o in self.__options__:
445 if o.name == name:
446 return o
447 return None
448
449 def remove_option (self, name):
450 """Remove an option from this Options."""
451 for o in self.__options__:
452 if o.name == name:
453 del o
454 return True
455 return True
456
457 def add_options_from_file (self, filename):
458 """This function creates options from an XML-file with option-metadata.
459 TODO: make this more reusable and place it into module (once the groups
460 are own objects)"""
461 # create xml document
462 try:
463 doc = xml.dom.minidom.parse(filename)
464 except:
465 raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
466 # get rootnode
467 root = doc.firstChild
468 if not root or root.nodeName != 'screenlet':
469 raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
470 # ok, let's check the nodes: this one should contain option-groups
471 groups = []
472 for node in root.childNodes:
473 # we only want element-nodes
474 if node.nodeType == Node.ELEMENT_NODE:
475 #print node
476 if node.nodeName != 'group' or not node.hasChildNodes():
477 # we only allow groups in the first level (groups need children)
478 raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
479 else:
480 # ok, create a new group and parse its elements
481 group = {}
482 group['name'] = node.getAttribute("name")
483 if not group['name']:
484 raise Exception('No name for group defined in "%s".' % filename)
485 group['info'] = ''
486 group['options'] = []
487 # check all children in group
488 for on in node.childNodes:
489 if on.nodeType == Node.ELEMENT_NODE:
490 if on.nodeName == 'info':
491 # info-node? set group-info
492 group['info'] = on.firstChild.nodeValue
493 elif on.nodeName == 'option':
494 # option node? parse option node
495 opt = create_option_from_node (on, group['name'])
496 # ok? add it to list
497 if opt:
498 group['options'].append(opt)
499 else:
500 raise Exception('Invalid option-node found in "%s".' % filename)
501
502 # create new group
503 if len(group['options']):
504 self.add_options_group(group['name'], group['info'])
505 for o in group['options']:
506 self.add_option(o)
507 # add group to list
508 #groups.append(group)
509
510# -----------------------------------------------------------------------
511# OptionsDialog and UI-classes
512# -----------------------------------------------------------------------
513
514class ListOptionDialog (gtk.Dialog):
515 """An editing dialog used for editing options of the ListOption-type."""
516
517 model = None
518 tree = None
519 buttonbox = None
520
521 # call gtk.Dialog.__init__
522 def __init__ (self):
523 super(ListOptionDialog, self).__init__("Edit List",
524 flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
525 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
526 gtk.STOCK_OK, gtk.RESPONSE_OK))
527 # set size
528 self.resize(300, 370)
529 self.set_keep_above(True) # to avoid confusion
530 # init vars
531 self.model = gtk.ListStore(str)
532 # create UI
533 self.create_ui()
534
535 def create_ui (self):
536 """Create the user-interface for this dialog."""
537 # create outer hbox (tree|buttons)
538 hbox = gtk.HBox()
539 hbox.set_border_width(10)
540 hbox.set_spacing(10)
541 # create tree
542 self.tree = gtk.TreeView(model=self.model)
543 self.tree.set_headers_visible(False)
544 self.tree.set_reorderable(True)
545 #self.tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
546 col = gtk.TreeViewColumn('')
547 cell = gtk.CellRendererText()
548 #cell.set_property('cell-background', 'cyan')
549 cell.set_property('foreground', 'black')
550 col.pack_start(cell, False)
551 col.set_attributes(cell, text=0)
552 self.tree.append_column(col)
553 self.tree.show()
554 hbox.pack_start(self.tree, True, True)
555 #sep = gtk.VSeparator()
556 #sep.show()
557 #hbox.add(sep)
558 # create buttons
559 self.buttonbox = bb = gtk.VButtonBox()
560 self.buttonbox.set_layout(gtk.BUTTONBOX_START)
561 b1 = gtk.Button(stock=gtk.STOCK_ADD)
562 b2 = gtk.Button(stock=gtk.STOCK_EDIT)
563 b3 = gtk.Button(stock=gtk.STOCK_REMOVE)
564 b1.connect('clicked', self.button_callback, 'add')
565 b2.connect('clicked', self.button_callback, 'edit')
566 b3.connect('clicked', self.button_callback, 'remove')
567 bb.add(b1)
568 bb.add(b2)
569 bb.add(b3)
570 self.buttonbox.show_all()
571 #hbox.add(self.buttonbox)
572 hbox.pack_end(self.buttonbox, False)
573 # add everything to outer hbox and show it
574 hbox.show()
575 self.vbox.add(hbox)
576
577 def set_list (self, lst):
578 """Set the list to be edited in this editor."""
579 for el in lst:
580 self.model.append([el])
581
582 def get_list (self):
583 """Return the list that is currently being edited in this editor."""
584 lst = []
585 for i in self.model:
586 lst.append(i[0])
587 return lst
588
589 def remove_selected_item (self):
590 """Remove the currently selected item."""
591 sel = self.tree.get_selection()
592 if sel:
593 it = sel.get_selected()[1]
594 if it:
595 print self.model.get_value(it, 0)
596 self.model.remove(it)
597
598 def entry_dialog (self, default = ''):
599 """Show entry-dialog and return string."""
600 entry = gtk.Entry()
601 entry.set_text(default)
602 entry.show()
603 dlg = gtk.Dialog("Add/Edit Item", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
604 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
605 gtk.RESPONSE_OK))
606 dlg.set_keep_above(True)
607 dlg.vbox.add(entry)
608 resp = dlg.run()
609 ret = None
610 if resp == gtk.RESPONSE_OK:
611 ret = entry.get_text()
612 dlg.destroy()
613 return ret
614
615 def button_callback (self, widget, id):
616 print "PRESS: %s" % id
617 if id == 'remove':
618 self.remove_selected_item()
619 if id == 'add':
620 new = self.entry_dialog()
621 if new != None:
622 self.model.append([new])
623 if id == 'edit':
624 sel = self.tree.get_selection()
625 if sel:
626 it = sel.get_selected()[1]
627 if it:
628 new = self.entry_dialog(self.model.get_value(it, 0))
629 if new != None:
630 #self.model.append([new])
631 self.model.set_value(it, 0, new)
632
633
634# TEST
635"""dlg = ListOptionDialog()
636dlg.set_list(['test1', 'afarew34s', 'fhjh23faj', 'yxcdfs58df', 'hsdf7jsdfh'])
637dlg.run()
638print "RESULT: " + str(dlg.get_list())
639dlg.destroy()
640import sys
641sys.exit(1)"""
642# /TEST
643
644class OptionsDialog (gtk.Dialog):
645 """A dynamic options-editor for editing Screenlets which are implementing
646 the EditableOptions-class."""
647
648 __shown_object = None
649
650 def __init__ (self, width, height):
651 # call gtk.Dialog.__init__
652 super(OptionsDialog, self).__init__(
653 _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
654 gtk.DIALOG_NO_SEPARATOR,
655 buttons = (#gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_APPLY,
656 gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
657 # set size
658 self.resize(width, height)
659 self.set_keep_above(True) # to avoid confusion
660 self.set_border_width(10)
661 # create attribs
662 self.page_about = None
663 self.page_options = None
664 self.page_themes = None
665 self.vbox_editor = None
666 self.hbox_about = None
667 self.infotext = None
668 self.infoicon = None
669 # create theme-list
670 self.liststore = gtk.ListStore(object)
671 self.tree = gtk.TreeView(model=self.liststore)
672 # create/add outer notebook
673 self.main_notebook = gtk.Notebook()
674 self.main_notebook.show()
675 self.vbox.add(self.main_notebook)
676 # create/init notebook pages
677 self.create_about_page()
678 self.create_themes_page()
679 self.create_options_page()
680
681 # "public" functions
682
683 def reset_to_defaults (self):
684 """Reset all entries for the currently shown object to their default
685 values (the values the object has when it is first created).
686 NOTE: This function resets ALL options, so BE CARFEUL!"""
687 if self.__shown_object:
688 for o in self.__shown_object.__options__:
689 # set default value
690 setattr(self.__shown_object, o.name, o.default)
691
692 def set_info (self, name, info, copyright='', version='', icon=None):
693 """Update the "About"-page with the given information."""
694 # convert infotext (remove EOLs and TABs)
695 info = info.replace("\n", "")
696 info = info.replace("\t", " ")
697 # create markup
698 markup = '\n<b><span size="xx-large">' + name + '</span></b>'
699 if version:
700 markup += ' <span size="large"><b>' + version + '</b></span>'
701 markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
702 self.infotext.set_markup(markup)
703 # icon?
704 if icon:
705 # remove old icon
706 if self.infoicon:
707 self.infoicon.destroy()
708 # set new icon
709 self.infoicon = icon
710 self.infoicon.set_alignment(0.0, 0.10)
711 self.infoicon.show()
712 self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
713 else:
714 self.infoicon.hide()
715
716 def show_options_for_object (self, obj):
717 """Update the OptionsEditor to show the options for the given Object.
718 The Object needs to be an EditableOptions-subclass.
719 NOTE: This needs heavy improvement and should use OptionGroups once
720 they exist"""
721 self.__shown_object = obj
722 # create notebook for groups
723 notebook = gtk.Notebook()
724 self.vbox_editor.add(notebook)
725 for group in obj.__options_groups_ordered__:
726 group_data = obj.__options_groups__[group]
727 # create box for tab-page
728 page = gtk.VBox()
729 page.set_border_width(10)
730 if group_data['info'] != '':
731 info = gtk.Label(group_data['info'])
732 info.show()
733 info.set_alignment(0, 0)
734 page.pack_start(info, 0, 0, 7)
735 sep = gtk.HSeparator()
736 sep.show()
737 #page.pack_start(sep, 0, 0, 5)
738 # create VBox for inputs
739 box = gtk.VBox()
740 box.show()
741 box.set_border_width(5)
742 # add box to page
743 page.add(box)
744 page.show()
745 # add new notebook-page
746 label = gtk.Label(group_data['label'])
747 label.show()
748 notebook.append_page(page, label)
749 # and create inputs
750 for option in group_data['options']:
751 if option.hidden == False:
752 val = getattr(obj, option.name)#obj.__dict__[option.name]
753 w = self.get_widget_for_option(option, val)
754 if w:
755 box.pack_start(w, 0, 0)
756 w.show()
757 notebook.show()
758 # show/hide themes tab, depending on whether the screenlet uses themes
759 if obj.uses_theme and obj.theme_name != '':
760 self.show_themes_for_screenlet(obj)
761 else:
762 self.page_themes.hide()
763
764 def show_themes_for_screenlet (self, obj):
765 """Update the Themes-page to display the available themes for the
766 given Screenlet-object."""
767
768
769 dircontent = []
770 screenlets.utils.refresh_available_screenlet_paths()
771
772 for path in screenlets.SCREENLETS_PATH:
773 p = path + '/' + obj.get_short_name() + '/themes'
774 print p
775 #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
776 try:
777 dc = os.listdir(p)
778 for d in dc:
779 dircontent.append({'name':d, 'path':p+'/'})
780 except:
781 print "Path %s not found." % p
782
783 # list with found themes
784 found_themes = []
785
786 # check all themes in path
787 for elem in dircontent:
788 # load themes with the same name only once
789 if found_themes.count(elem['name']):
790 continue
791 found_themes.append(elem['name'])
792 # build full path of theme.conf
793 theme_conf = elem['path'] + elem['name'] + '/theme.conf'
794 # if dir contains a theme.conf
795 if os.access(theme_conf, os.F_OK):
796 # load it and create new list entry
797 ini = screenlets.utils.IniReader()
798 if ini.load(theme_conf):
799 # check for section
800 if ini.has_section('Theme'):
801 # get metainfo from theme
802 th_fullname = ini.get_option('name',
803 section='Theme')
804 th_info = ini.get_option('info',
805 section='Theme')
806 th_version = ini.get_option('version',
807 section='Theme')
808 th_author = ini.get_option('author',
809 section='Theme')
810 # create array from metainfo and add it to liststore
811 info = [elem['name'], th_fullname, th_info, th_author,
812 th_version]
813 self.liststore.append([info])
814 else:
815 # no theme section in theme.conf just add theme-name
816 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
817 else:
818 # no theme.conf in dir? just add theme-name
819 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
820 # is it the active theme?
821 if elem['name'] == obj.theme_name:
822 # select it in tree
823 print "active theme is: %s" % elem['name']
824 sel = self.tree.get_selection()
825 if sel:
826 it = self.liststore.get_iter_from_string(\
827 str(len(self.liststore)-1))
828 if it:
829 sel.select_iter(it)
830
831 # UI-creation
832
833 def create_about_page (self):
834 """Create the "About"-tab."""
835 self.page_about = gtk.HBox()
836 # create about box
837 self.hbox_about = gtk.HBox()
838 self.hbox_about.show()
839 self.page_about.add(self.hbox_about)
840 # create icon
841 self.infoicon = gtk.Image()
842 self.infoicon.show()
843 self.page_about.pack_start(self.infoicon, 0, 1, 10)
844 # create infotext
845 self.infotext = gtk.Label()
846 self.infotext.use_markup = True
847 self.infotext.set_line_wrap(True)
848 self.infotext.set_alignment(0.0, 0.0)
849 self.infotext.show()
850 self.page_about.pack_start(self.infotext, 1, 1, 5)
851 # add page
852 self.page_about.show()
853 self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
854
855 def create_options_page (self):
856 """Create the "Options"-tab."""
857 self.page_options = gtk.HBox()
858 # create vbox for options-editor
859 self.vbox_editor = gtk.VBox(spacing=3)
860 self.vbox_editor.set_border_width(5)
861 self.vbox_editor.show()
862 self.page_options.add(self.vbox_editor)
863 # show/add page
864 self.page_options.show()
865 self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
866
867 def create_themes_page (self):
868 """Create the "Themes"-tab."""
869 self.page_themes = gtk.VBox(spacing=5)
870 self.page_themes.set_border_width(10)
871 # create info-text list
872 txt = gtk.Label(_('Themes allow you to easily switch the appearance of your Screenlets. On this page you find a list of all available themes for this Screenlet.'))
873 txt.set_size_request(450, -1)
874 txt.set_line_wrap(True)
875 txt.set_alignment(0.0, 0.0)
876 txt.show()
877 self.page_themes.pack_start(txt, False, True)
878 # create theme-selector list
879 self.tree.set_headers_visible(False)
880 self.tree.connect('cursor-changed', self.__tree_cursor_changed)
881 self.tree.show()
882 col = gtk.TreeViewColumn('')
883 cell = gtk.CellRendererText()
884 col.pack_start(cell, True)
885 #cell.set_property('foreground', 'black')
886 col.set_cell_data_func(cell, self.__render_cell)
887 self.tree.append_column(col)
888 # wrap tree in scrollwin
889 sw = gtk.ScrolledWindow()
890 sw.set_shadow_type(gtk.SHADOW_IN)
891 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
892 sw.add(self.tree)
893 sw.show()
894 # add vbox and add tree/buttons
895 vbox = gtk.VBox()
896 vbox.pack_start(sw, True, True)
897 vbox.show()
898 # show/add page
899 self.page_themes.add(vbox)
900 self.page_themes.show()
901 self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
902
903 def __render_cell(self, tvcolumn, cell, model, iter):
904 """Callback for rendering the cells in the theme-treeview."""
905 # get attributes-list from Treemodel
906 attrib = model.get_value(iter, 0)
907
908 # set colors depending on state
909 col = '555555'
910 name_uc = attrib[0][0].upper() + attrib[0][1:]
911 # create markup depending on info
912 if attrib[1] == '-' and attrib[2] == '-':
913 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
914 '</span></b> (' + _('no info available') + ')'
915 else:
916 if attrib[1] == None : attrib[1] = '-'
917 if attrib[2] == None : attrib[2] = '-'
918 if attrib[3] == None : attrib[3] = '-'
919 if attrib[4] == None : attrib[4] = '-'
920 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
921 '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
922 '">' + attrib[2].replace('\\n', '\n') + \
923 '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
924 # set markup
925 cell.set_property('markup', mu)
926
927 # UI-callbacks
928
929 def __tree_cursor_changed (self, treeview):
930 """Callback for handling selection changes in the Themes-treeview."""
931 sel = self.tree.get_selection()
932 if sel:
933 s = sel.get_selected()
934 if s:
935 it = s[1]
936 if it:
937 attribs = self.liststore.get_value(it, 0)
938 if attribs and self.__shown_object:
939 #print attribs
940 # set theme in Screenlet (if not already active)
941 if self.__shown_object.theme_name != attribs[0]:
942 self.__shown_object.theme_name = attribs[0]
943
944 # option-widget creation (should be split in several classes)
945
946 def get_widget_for_option (self, option, value=None):
947 """Return a gtk.*Widget with Label within a HBox for a given option.
948 NOTE: This is incredibly ugly, ideally all Option-subclasses should
949 have their own widgets - like StringOptionWidget, ColorOptionWidget,
950 ... and then be simply created dynamically"""
951 t = option.__class__
952 widget = None
953 if t == BoolOption:
954 widget = gtk.CheckButton()
955 widget.set_active(value)
956 widget.connect("toggled", self.options_callback, option)
957 elif t == StringOption:
958 if option.choices:
959 # if a list of values is defined, show combobox
960 widget = gtk.combo_box_new_text()
961 p = -1
962 i = 0
963 for s in option.choices:
964 widget.append_text(s)
965 if s==value:
966 p = i
967 i+=1
968 widget.set_active(p)
969 #widget.connect("changed", self.options_callback, option)
970 else:
971 widget = gtk.Entry()
972 widget.set_text(value)
973 # if it is a password, set text to be invisible
974 if option.password:
975 widget.set_visibility(False)
976 #widget.connect("key-press-event", self.options_callback, option)
977 widget.connect("changed", self.options_callback, option)
978 #widget.set_size_request(180, 28)
979 elif t == IntOption or t == FloatOption:
980 widget = gtk.SpinButton()
981 #widget.set_size_request(50, 22)
982 #widget.set_text(str(value))
983 if t == FloatOption:
984 widget.set_digits(option.digits)
985 widget.set_increments(option.increment, int(option.max/option.increment))
986 else:
987 widget.set_increments(option.increment, int(option.max/option.increment))
988 if option.min!=None and option.max!=None:
989 #print "Setting range for input to: %f, %f" % (option.min, option.max)
990 widget.set_range(option.min, option.max)
991 widget.set_value(value)
992 widget.connect("value-changed", self.options_callback, option)
993 elif t == ColorOption:
994 widget = gtk.ColorButton(gtk.gdk.Color(int(value[0]*65535), int(value[1]*65535), int(value[2]*65535)))
995 widget.set_use_alpha(True)
996# print value
997# print value[3]
998 widget.set_alpha(int(value[3]*65535))
999 widget.connect("color-set", self.options_callback, option)
1000 elif t == FontOption:
1001 widget = gtk.FontButton()
1002 widget.set_font_name(value)
1003 widget.connect("font-set", self.options_callback, option)
1004 elif t == FileOption:
1005 widget = gtk.FileChooserButton(_("Choose File"))
1006 widget.set_filename(value)
1007 widget.set_size_request(180, 28)
1008 widget.connect("selection-changed", self.options_callback, option)
1009 elif t == DirectoryOption:
1010 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1011 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
1012 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1013 widget = gtk.FileChooserButton(dlg)
1014 widget.set_title(_("Choose Directory"))
1015 widget.set_filename(value)
1016 widget.set_size_request(180, 28)
1017 widget.connect("selection-changed", self.options_callback, option)
1018 elif t == ImageOption:
1019 # create entry and button (entry is hidden)
1020 entry = gtk.Entry()
1021 entry.set_text(value)
1022 entry.set_editable(False)
1023 but = gtk.Button()
1024 # util to reload preview image
1025 def create_preview (filename):
1026 if filename and os.path.isfile(filename):
1027 pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
1028 if pb:
1029 img = gtk.Image()
1030 img.set_from_pixbuf(pb)
1031 return img
1032 img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
1033 gtk.ICON_SIZE_LARGE_TOOLBAR)
1034 img.set_size_request(64, 64)
1035 return img
1036 # create button
1037 def but_callback (widget):
1038 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
1039 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
1040 dlg.set_title(_("Choose Image"))
1041 dlg.set_keep_above(True)
1042 dlg.set_filename(entry.get_text())
1043 flt = gtk.FileFilter()
1044 flt.add_pixbuf_formats()
1045 dlg.set_filter(flt)
1046 prev = gtk.Image()
1047 box = gtk.VBox()
1048 box.set_size_request(150, -1)
1049 box.add(prev)
1050 prev.show()
1051 # add preview widget to filechooser
1052 def preview_callback(widget):
1053 fname = dlg.get_preview_filename()
1054 if fname and os.path.isfile(fname):
1055 pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
1056 if pb:
1057 prev.set_from_pixbuf(pb)
1058 dlg.set_preview_widget_active(True)
1059 else:
1060 dlg.set_preview_widget_active(False)
1061 dlg.set_preview_widget_active(True)
1062 dlg.connect('selection-changed', preview_callback)
1063 dlg.set_preview_widget(box)
1064 # run
1065 response = dlg.run()
1066 if response == gtk.RESPONSE_OK:
1067 entry.set_text(dlg.get_filename())
1068 but.set_image(create_preview(dlg.get_filename()))
1069 self.options_callback(dlg, option)
1070 dlg.destroy()
1071 # load preview image
1072 but.set_image(create_preview(value))
1073 but.connect('clicked', but_callback)
1074 # create widget
1075 widget = gtk.HBox()
1076 widget.add(entry)
1077 widget.add(but)
1078 but.show()
1079 widget.show()
1080 # add tooltips
1081 #but.set_tooltip_text(_('Select Image ...'))
1082 but.set_tooltip_text(option.desc)
1083
1084 elif t == ListOption:
1085 entry= gtk.Entry()
1086 entry.set_editable(False)
1087 entry.set_text(str(value))
1088 entry.show()
1089 img = gtk.Image()
1090 img.set_from_stock(gtk.STOCK_EDIT, 1)
1091 but = gtk.Button()
1092 but.set_image(img)
1093 def open_listeditor(event):
1094 # open dialog
1095 dlg = ListOptionDialog()
1096 # read string from entry and import it through option-class
1097 # (this is needed to always have an up-to-date value)
1098 dlg.set_list(option.on_import(entry.get_text()))
1099 resp = dlg.run()
1100 if resp == gtk.RESPONSE_OK:
1101 # set text in entry
1102 entry.set_text(str(dlg.get_list()))
1103 # manually call the options-callback
1104 self.options_callback(dlg, option)
1105 dlg.destroy()
1106 but.show()
1107 but.connect("clicked", open_listeditor)
1108 but.set_tooltip_text(_('Open List-Editor ...'))
1109 entry.set_tooltip_text(option.desc)
1110 widget = gtk.HBox()
1111 widget.add(entry)
1112 widget.add(but)
1113 elif t == AccountOption:
1114 widget = gtk.HBox()
1115 vb = gtk.VBox()
1116 input_name = gtk.Entry()
1117 input_name.set_text(value[0])
1118 input_name.show()
1119 input_pass = gtk.Entry()
1120 input_pass.set_visibility(False) # password
1121 input_pass.set_text(value[1])
1122 input_pass.show()
1123 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1124 but.show()
1125 but.connect("clicked", self.apply_options_callback, option, widget)
1126 vb.add(input_name)
1127 vb.add(input_pass)
1128 vb.show()
1129 but.set_tooltip_text(_('Apply username/password ...'))
1130 input_name.set_tooltip_text(_('Enter username here ...'))
1131 input_pass.set_tooltip_text(_('Enter password here ...'))
1132 widget.add(vb)
1133 widget.add(but)
1134 elif t == TimeOption:
1135 widget = gtk.HBox()
1136 input_hour = gtk.SpinButton()#climb_rate=1.0)
1137 input_minute = gtk.SpinButton()
1138 input_second = gtk.SpinButton()
1139 input_hour.set_range(0, 23)
1140 input_hour.set_max_length(2)
1141 input_hour.set_increments(1, 1)
1142 input_hour.set_numeric(True)
1143 input_hour.set_value(value[0])
1144 input_minute.set_range(0, 59)
1145 input_minute.set_max_length(2)
1146 input_minute.set_increments(1, 1)
1147 input_minute.set_numeric(True)
1148 input_minute.set_value(value[1])
1149 input_second.set_range(0, 59)
1150 input_second.set_max_length(2)
1151 input_second.set_increments(1, 1)
1152 input_second.set_numeric(True)
1153 input_second.set_value(value[2])
1154 input_hour.connect('value-changed', self.options_callback, option)
1155 input_minute.connect('value-changed', self.options_callback, option)
1156 input_second.connect('value-changed', self.options_callback, option)
1157 input_hour.set_tooltip_text(option.desc)
1158 input_minute.set_tooltip_text(option.desc)
1159 input_second.set_tooltip_text(option.desc)
1160 widget.add(input_hour)
1161 widget.add(gtk.Label(':'))
1162 widget.add(input_minute)
1163 widget.add(gtk.Label(':'))
1164 widget.add(input_second)
1165 widget.add(gtk.Label('h'))
1166 widget.show_all()
1167 else:
1168 widget = gtk.Entry()
1169 print "unsupported type ''" % str(t)
1170 hbox = gtk.HBox()
1171 label = gtk.Label()
1172 label.set_alignment(0.0, 0.0)
1173 label.set_label(option.label)
1174 label.set_size_request(180, 28)
1175 label.show()
1176 hbox.pack_start(label, 0, 1)
1177 if widget:
1178 if option.disabled: # option disabled?
1179 widget.set_sensitive(False)
1180 label.set_sensitive(False)
1181 #label.set_mnemonic_widget(widget)
1182 widget.set_tooltip_text(option.desc)
1183 widget.show()
1184 # check if needs Apply-button
1185 if option.realtime == False:
1186 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
1187 but.show()
1188 but.connect("clicked", self.apply_options_callback,
1189 option, widget)
1190 b = gtk.HBox()
1191 b.show()
1192 b.pack_start(widget, 0, 0)
1193 b.pack_start(but, 0, 0)
1194 hbox.pack_start(b, 0, 0)
1195 else:
1196 #hbox.pack_start(widget, -1, 1)
1197 hbox.pack_start(widget, 0, 0)
1198 return hbox
1199
1200 def read_option_from_widget (self, widget, option):
1201 """Read an option's value from the widget and return it."""
1202 if not widget.window:
1203 return False
1204 # get type of option and read the widget's value
1205 val = None
1206 t = option.__class__
1207 if t == IntOption:
1208 val = int(widget.get_value())
1209 elif t == FloatOption:
1210 val = widget.get_value()
1211 elif t == StringOption:
1212 if option.choices:
1213 # if default is a list, handle combobox
1214 val = widget.get_active_text()
1215 else:
1216 val = widget.get_text()
1217 elif t == BoolOption:
1218 val = widget.get_active()
1219 elif t == ColorOption:
1220 col = widget.get_color()
1221 al = widget.get_alpha()
1222 val = (col.red/65535.0, col.green/65535.0,
1223 col.blue/65535.0, al/65535.0)
1224 elif t == FontOption:
1225 val = widget.get_font_name()
1226 elif t == FileOption or t == DirectoryOption or t == ImageOption:
1227 val = widget.get_filename()
1228 #print widget
1229 #elif t == ImageOption:
1230 # val = widget.get_text()
1231 elif t == ListOption:
1232 # the widget is a ListOptionDialog here
1233 val = widget.get_list()
1234 elif t == AccountOption:
1235 # the widget is a HBox containing a VBox containing two Entries
1236 # (ideally we should have a custom widget for the AccountOption)
1237 for c in widget.get_children():
1238 if c.__class__ == gtk.VBox:
1239 c2 = c.get_children()
1240 val = (c2[0].get_text(), c2[1].get_text())
1241 elif t == TimeOption:
1242 box = widget.get_parent()
1243 inputs = box.get_children()
1244 val = (int(inputs[0].get_value()), int(inputs[2].get_value()),
1245 int(inputs[4].get_value()))
1246 else:
1247 print "OptionsDialog: Unknown option type: %s" % str(t)
1248 return None
1249 # return the value
1250 return val
1251
1252 # option-widget event-handling
1253
1254 # TODO: custom callback/signal for each option?
1255 def options_callback (self, widget, optionobj):
1256 """Callback for handling changed-events on entries."""
1257 print "Changed: %s" % optionobj.name
1258 if self.__shown_object:
1259 # if the option is not real-time updated,
1260 if optionobj.realtime == False:
1261 return False
1262 # read option
1263 val = self.read_option_from_widget(widget, optionobj)
1264 if val != None:
1265 #print "SetOption: "+optionobj.name+"="+str(val)
1266 # set option
1267 setattr(self.__shown_object, optionobj.name, val)
1268 # notify option-object's on_changed-handler
1269 optionobj.emit("option_changed", optionobj)
1270 return False
1271
1272 def apply_options_callback (self, widget, optionobj, entry):
1273 """Callback for handling Apply-button presses."""
1274 if self.__shown_object:
1275 # read option
1276 val = self.read_option_from_widget(entry, optionobj)
1277 if val != None:
1278 #print "SetOption: "+optionobj.name+"="+str(val)
1279 # set option
1280 setattr(self.__shown_object, optionobj.name, val)
1281 # notify option-object's on_changed-handler
1282 optionobj.emit("option_changed", optionobj)
1283 return False
1284
1285
1286
1287# ------ ONLY FOR TESTING ------------------:
1288if __name__ == "__main__":
1289
1290 import os
1291
1292 # this is only for testing - should be a Screenlet
1293 class TestObject (EditableOptions):
1294
1295 testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
1296 pop3_account = ('Username', '')
1297
1298 # TEST
1299 pin_x = 100
1300 pin_y = 6
1301 text_x = 19
1302 text_y = 35
1303 font_name = 'Sans 12'
1304 rgba_color = (0.0, 0.0, 1.0, 1.0)
1305 text_prefix = '<b>'
1306 text_suffix = '</b>'
1307 note_text = "" # hidden option because val has its own editing-dialog
1308 random_pin_pos = True
1309 opt1 = 'testval 1'
1310 opt2 = 'testval 2'
1311 filename2 = ''
1312 filename = ''
1313 dirname = ''
1314 font = 'Sans 12'
1315 color = (0.1, 0.5, 0.9, 0.9)
1316 name = 'a name'
1317 name2 = 'another name'
1318 combo_test = 'el2'
1319 flt = 0.5
1320 x = 10
1321 y = 25
1322 width = 30
1323 height = 50
1324 is_sticky = False
1325 is_widget = False
1326 time = (12, 32, 49) # a time-value (tuple with ints)
1327
1328 def __init__ (self):
1329 EditableOptions.__init__(self)
1330 # Add group
1331 self.add_options_group('General',
1332 'The general options for this Object ...')
1333 self.add_options_group('Window',
1334 'The Window-related options for this Object ...')
1335 self.add_options_group('Test', 'A Test-group ...')
1336 # Add editable options
1337 self.add_option(ListOption('Test', 'testlist', self.testlist,
1338 'ListOption-Test', 'Testing a ListOption-type ...'))
1339 self.add_option(StringOption('Window', 'name', 'TESTNAME',
1340 'Testname', 'The name/id of this Screenlet-instance ...'),
1341 realtime=False)
1342 self.add_option(AccountOption('Test', 'pop3_account',
1343 self.pop3_account, 'Username/Password',
1344 'Enter username/password here ...'))
1345 self.add_option(StringOption('Window', 'name2', 'TESTNAME2',
1346 'String2', 'Another string-test ...'))
1347 self.add_option(StringOption('Test', 'combo_test', "el1", 'Combo',
1348 'A StringOption displaying a drop-down-list with choices...',
1349 choices=['el1', 'el2', 'element 3']))
1350 self.add_option(FloatOption('General', 'flt', 30,
1351 'A Float', 'Testing a FLOAT-type ...',
1352 min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
1353 self.add_option(IntOption('General', 'x', 30,
1354 'X-Position', 'The X-position of this Screenlet ...',
1355 min=0, max=gtk.gdk.screen_width()))
1356 self.add_option(IntOption('General', 'y', 30,
1357 'Y-Position', 'The Y-position of this Screenlet ...',
1358 min=0, max=gtk.gdk.screen_height()))
1359 self.add_option(IntOption('Test', 'width', 300,
1360 'Width', 'The width of this Screenlet ...', min=100, max=1000))
1361 self.add_option(IntOption('Test', 'height', 150,
1362 'Height', 'The height of this Screenlet ...',
1363 min=100, max=1000))
1364 self.add_option(BoolOption('General', 'is_sticky', True,
1365 'Stick to Desktop', 'Show this Screenlet always ...'))
1366 self.add_option(BoolOption('General', 'is_widget', False,
1367 'Treat as Widget', 'Treat this Screenlet as a "Widget" ...'))
1368 self.add_option(FontOption('Test', 'font', 'Sans 14',
1369 'Font', 'The font for whatever ...'))
1370 self.add_option(ColorOption('Test', 'color', (1, 0.35, 0.35, 0.7),
1371 'Color', 'The color for whatever ...'))
1372 self.add_option(FileOption('Test', 'filename', os.environ['HOME'],
1373 'Filename-Test', 'Testing a FileOption-type ...',
1374 patterns=['*.py', '*.pyc']))
1375 self.add_option(ImageOption('Test', 'filename2', os.environ['HOME'],
1376 'Image-Test', 'Testing the ImageOption-type ...'))
1377 self.add_option(DirectoryOption('Test', 'dirname', os.environ['HOME'],
1378 'Directory-Test', 'Testing a FileOption-type ...'))
1379 self.add_option(TimeOption('Test','time', self.time,
1380 'TimeOption-Test', 'Testing a TimeOption-type ...'))
1381 # TEST
1382 self.disable_option('width')
1383 self.disable_option('height')
1384 # TEST: load options from file
1385 #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
1386
1387 def __setattr__(self, name, value):
1388 self.__dict__[name] = value
1389 print name + "=" + str(value)
1390
1391 def get_short_name(self):
1392 return self.__class__.__name__[:-6]
1393
1394
1395
1396 # this is only for testing - should be a Screenlet
1397 class TestChildObject (TestObject):
1398
1399 uses_theme = True
1400 theme_name = 'test'
1401
1402 def __init__ (self):
1403 TestObject.__init__(self)
1404 self.add_option(StringOption('Test', 'anothertest', 'ksjhsjgd',
1405 'Another Test', 'An attribute in the subclass ...'))
1406 self.add_option(StringOption('Test', 'theme_name', self.theme_name,
1407 'Theme', 'The theme for this Screenelt ...',
1408 choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
1409
1410
1411 # TEST: load/save
1412 # TEST: option-editing
1413 to = TestChildObject()
1414 #print to.export_options_as_list()
1415 se = OptionsDialog(500, 380)#, treeview=True)
1416 #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
1417 img = gtk.Image()
1418 img.set_from_file('../share/screenlets/Notes/icon.svg')
1419 se.set_info('TestOptions',
1420 'A test for an extended options-dialog with embedded about-info.' +
1421 ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
1422 '<span color="red"> ONLY A TEST!</span>',
1423 '(c) RYX 2007', version='v0.0.1', icon=img)
1424 se.show_options_for_object(to)
1425 resp = se.run()
1426 if resp == gtk.RESPONSE_OK:
1427 print "OK"
1428 else:
1429 print "Cancelled."
1430 se.destroy()
1431 print to.export_options_as_list()
1432
14330
=== added file 'src/lib/options/__init__.py'
--- src/lib/options/__init__.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/__init__.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,202 @@
1# This application is released under the GNU General Public License
2# v3 (or, at your option, any later version). You can find the full
3# text of the license under http://www.gnu.org/licenses/gpl.txt.
4# By using, editing and/or distributing this software you agree to
5# the terms and conditions of this license.
6# Thank you for using free software!
7
8# Options-system (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
9# Heavily Refactored by Martin Owens (c) 2009
10#
11# INFO:
12# - a dynamic Options-system that allows very easy creation of
13# objects with embedded configuration-system.
14# NOTE: The Dialog is not very nice yet - it is not good OOP-practice
15# because too big functions and bad class-layout ... but it works
16# for now ... :)
17#
18# TODO:
19# - option-widgets for all option-types (e.g. ListOptionWidget, ColorOptionWidget)
20# - OptionGroup-class instead of (or behind) add_options_group
21# - TimeOption, DateOption
22# - FileOption needs filter/limit-attribute
23# - allow options to disable/enable other options
24# - support for EditableOptions-subclasses as options
25# - separate OptionEditorWidget from Editor-Dialog
26# - place ui-code into screenlets.options.ui-module
27# - create own widgets for each Option-subclass
28#
29
30import screenlets
31
32import os
33import gtk, gobject
34
35# translation stuff
36import gettext
37gettext.textdomain('screenlets')
38gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX + '/share/locale')
39
40def _(s):
41 return gettext.gettext(s)
42
43from boolean_option import BoolOption
44from string_option import StringOption
45from number_option import IntOption, FloatOption
46from list_option import ListOption
47from account_option import AccountOption
48from font_option import FontOption
49from file_option import FileOption, DirectoryOption, ImageOption
50from colour_option import ColorOption, ColorsOption
51from time_option import TimeOption
52from base import EditableOptions, OptionsDialog
53
54# ------ ONLY FOR TESTING ------------------:
55if __name__ == "__main__":
56
57 import os
58
59 # this is only for testing - should be a Screenlet
60 class TestObject (EditableOptions):
61
62 testlist = ['test1', 'test2', 3, 5, 'Noch ein Test']
63 pop3_account = ('Username', '')
64
65 # TEST
66 pin_x = 100
67 pin_y = 6
68 text_x = 19
69 text_y = 35
70 font_name = 'Sans 12'
71 rgba_color = (0.0, 0.0, 1.0, 1.0)
72 text_prefix = '<b>'
73 text_suffix = '</b>'
74 note_text = "" # hidden option because val has its own editing-dialog
75 random_pin_pos = True
76 opt1 = 'testval 1'
77 opt2 = 'testval 2'
78 filename2 = ''
79 filename = ''
80 dirname = ''
81 font = 'Sans 12'
82 color = (0.1, 0.5, 0.9, 0.9)
83 name = 'a name'
84 name2 = 'another name'
85 combo_test = 'el2'
86 flt = 0.5
87 x = 10
88 y = 25
89 width = 30
90 height = 50
91 is_sticky = False
92 is_widget = False
93 time = (12, 32, 49) # a time-value (tuple with ints)
94
95 def __init__ (self):
96 EditableOptions.__init__(self)
97 # Add group
98 self.add_options_group('General',
99 'The general options for this Object ...')
100 self.add_options_group('Window',
101 'The Window-related options for this Object ...')
102 self.add_options_group('Test', 'A Test-group ...')
103 # Add editable options
104 self.add_option(ListOption('Test', 'testlist', default=self.testlist,
105 label='ListOption-Test', desc='Testing a ListOption-type ...'))
106 self.add_option(StringOption('Window', 'name', default='TESTNAME',
107 label='Testname', desc='The name/id of this Screenlet-instance ...'),
108 realtime=False)
109 self.add_option(AccountOption('Test', 'pop3_account',
110 default=self.pop3_account, label='Username/Password',
111 desc='Enter username/password here ...'))
112 self.add_option(StringOption('Window', 'name2', default='TESTNAME2',
113 label='String2', desc='Another string-test ...'))
114 self.add_option(StringOption('Test', 'combo_test', default="el1",
115 label='Combo', desc='A StringOption for a drop-down-list.',
116 choices=['el1', 'el2', 'element 3']))
117 self.add_option(FloatOption('General', 'flt', default=30.4,
118 label='A Float', desc='Testing a FLOAT-type ...',
119 min=0, max=gtk.gdk.screen_width(), increment=0.01, digits=4))
120 self.add_option(IntOption('General', 'x', default=30,
121 label='X-Position', desc='The X-position of this Screenlet ...',
122 min=0, max=gtk.gdk.screen_width()))
123 self.add_option(IntOption('General', 'y', default=30,
124 label='Y-Position', desc='The Y-position of this Screenlet ...',
125 min=0, max=gtk.gdk.screen_height()))
126 self.add_option(IntOption('Test', 'width', default=300,
127 label='Width', desc='The width of this Screenlet ...',
128 min=100, max=1000, increment=12))
129 self.add_option(IntOption('Test', 'height', default=150,
130 label='Height', desc='The height of this Screenlet ...',
131 min=100, max=1000))
132 self.add_option(BoolOption('General', 'is_sticky', default=True,
133 label='Stick to Desktop', desc='Show this Screenlet always ...'))
134 self.add_option(BoolOption('General', 'is_widget', default=False,
135 label='Treat as Widget', desc='Treat this Screenlet as a "Widget" ...'))
136 self.add_option(FontOption('Test', 'font', default='Sans 14',
137 label='Font', desc='The font for whatever ...'))
138 self.add_option(ColorOption('Test', 'color', default=(1, 0.35, 0.35, 0.7),
139 label='Color', desc='The color for whatever ...'))
140 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)],
141 label='Multi-Colours', desc='The colors for whatever ...'))
142 self.add_option(ColorsOption('Test', 'rainbow2', default=(1, 0.35, 0.35, 0.7),
143 label='Colours-Up', desc='The colors for whatever ...'))
144 self.add_option(FileOption('Test', 'filename', default=os.environ['HOME'],
145 label='Filename-Test', desc='Testing a FileOption-type ...',
146 patterns=[ ( 'Python Files', ['*.py', '*.pyc'] ) ]))
147 self.add_option(ImageOption('Test', 'filename2', default=os.environ['HOME'],
148 label='Image-Test', desc='Testing the ImageOption-type ...'))
149 self.add_option(DirectoryOption('Test', 'dirname', default=os.environ['HOME'],
150 label='Directory-Test', desc='Testing a FileOption-type ...'))
151 self.add_option(TimeOption('Test','time', default=self.time,
152 label='TimeOption-Test', desc='Testing a TimeOption-type ...'))
153 # TEST
154 self.disable_option('width')
155 self.disable_option('height')
156 # TEST: load options from file
157 #self.add_options_from_file('/home/ryx/Desktop/python/screenlets/screenlets-0.0.9/src/share/screenlets/Notes/options.xml')
158
159 def __setattr__(self, name, value):
160 self.__dict__[name] = value
161 print name + "=" + str(value)
162
163 def get_short_name(self):
164 return self.__class__.__name__[:-6]
165
166
167 # this is only for testing - should be a Screenlet
168 class TestChildObject (TestObject):
169
170 uses_theme = True
171 theme_name = 'test'
172
173 def __init__ (self):
174 TestObject.__init__(self)
175 self.add_option(StringOption('Test', 'anothertest', default='ksjhsjgd',
176 label='Another Test', desc='An attribute in the subclass ...'))
177 self.add_option(StringOption('Test', 'theme_name', default=self.theme_name,
178 label='Theme', desc='The theme for this Screenelt ...',
179 choices=['test1', 'test2', 'mytheme', 'blue', 'test']))
180
181 # TEST: load/save
182 # TEST: option-editing
183 to = TestChildObject()
184 #print to.export_options_as_list()
185 se = OptionsDialog(500, 380)#, treeview=True)
186 #img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, 5)
187 img = gtk.Image()
188 img.set_from_file('../share/screenlets/Notes/icon.svg')
189 se.set_info('TestOptions',
190 'A test for an extended options-dialog with embedded about-info.' +
191 ' Can be used for the Screenlets to have all in one ...\nNOTE:' +
192 '<span color="red"> ONLY A TEST!</span>',
193 '(c) RYX 2007', version='v0.0.1', icon=img)
194 se.show_options_for_object(to)
195 resp = se.run()
196 if resp == gtk.RESPONSE_OK:
197 print "OK"
198 else:
199 print "Cancelled."
200 se.destroy()
201 print to.export_options_as_list()
202
0203
=== added file 'src/lib/options/account_option.py'
--- src/lib/options/account_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/account_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,143 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Account options, these classes will display a text box.
20"""
21
22import gtk
23import gnomekeyring
24
25from screenlets.options import _
26from base import Option
27
28class AccountOption(Option):
29 """
30 An Option-type for username/password combos. Stores the password in
31 the gnome-keyring (if available) and only saves username and auth_token
32 through the screenlets-backend.
33 TODO:
34 - not create new token for any change (use "set" instead of "create" if
35 the given item already exists)
36 - use usual storage if no keyring is available but output warning
37 - on_delete-function for removing the data from keyring when the
38 Screenlet holding the option gets deleted
39 """
40 protected = True
41
42 def __init__(self, group, name, *attr, **args):
43 super(AccountOption, self).__init__ (group, name, *attr, **args)
44 # check for availability of keyring
45 if not gnomekeyring.is_available():
46 raise Exception('GnomeKeyring is not available!!')
47 # THIS IS A WORKAROUND FOR A BUG IN KEYRING (usually we would use
48 # gnomekeyring.get_default_keyring_sync() here):
49 # find first available keyring
50 self.keyring_list = gnomekeyring.list_keyring_names_sync()
51 if len(self.keyring_list) == 0:
52 raise Exception('No keyrings found. Please create one first!')
53 else:
54 # we prefer the default keyring
55 try:
56 self.keyring = gnomekeyring.get_default_keyring_sync()
57 except:
58 if "session" in self.keyring_list:
59 print "Warning: No default keyring found, using session keyring. Storage is not permanent!"
60 self.keyring = "session"
61 else:
62 print "Warning: Neither default nor session keyring found, assuming keyring %s!" % self.keyring_list[0]
63 self.keyring = self.keyring_list[0]
64
65
66 def on_import(self, strvalue):
67 """Import account info from a string (like 'username:auth_token'),.
68 retrieve the password from the storage and return a tuple containing
69 username and password."""
70 # split string into username/auth_token
71 #data = strvalue.split(':', 1)
72 (name, auth_token) = strvalue.split(':', 1)
73 if name and auth_token:
74 # read pass from storage
75 try:
76 pw = gnomekeyring.item_get_info_sync(self.keyring,
77 int(auth_token)).get_secret()
78 except Exception, ex:
79 print "ERROR: Unable to read password from keyring: %s" % ex
80 pw = ''
81 # return
82 return (name, pw)
83 else:
84 raise Exception('Illegal value in AccountOption.on_import.')
85
86 def on_export(self, value):
87 """Export the given tuple/list containing a username and a password. The
88 function stores the password in the gnomekeyring and returns a
89 string in form 'username:auth_token'."""
90 # store password in storage
91 attribs = dict(name=value[0])
92 auth_token = gnomekeyring.item_create_sync(self.keyring,
93 gnomekeyring.ITEM_GENERIC_SECRET, value[0], attribs, value[1], True)
94 # build value from username and auth_token
95 return value[0] + ':' + str(auth_token)
96
97 def generate_widget(self, value):
98 """Generate a textbox for a account options"""
99 self.widget = gtk.HBox()
100 vb = gtk.VBox()
101 input_name = gtk.Entry()
102 input_name.set_text(value[0])
103 input_name.show()
104 input_pass = gtk.Entry()
105 input_pass.set_visibility(False) # password
106 input_pass.set_text(value[1])
107 input_pass.show()
108 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
109 but.show()
110 but.connect("clicked", self.has_changed)
111 vb.add(input_name)
112 vb.add(input_pass)
113 vb.show()
114 but.set_tooltip_text(_('Apply username/password ...'))
115 input_name.set_tooltip_text(_('Enter username here ...'))
116 input_pass.set_tooltip_text(_('Enter password here ...'))
117 self.widget.add(vb)
118 self.widget.add(but)
119 return self.widget
120
121 def set_value(self, value):
122 """Set the account value as required."""
123 self.value = value
124
125 def has_changed(self, widget):
126 """Executed when the widget event kicks off."""
127 # the widget is a HBox containing a VBox containing two Entries
128 # (ideally we should have a custom widget for the AccountOption)
129 for c in self.widget.get_children():
130 if c.__class__ == gtk.VBox:
131 c2 = c.get_children()
132 self.value = (c2[0].get_text(), c2[1].get_text())
133 super(AccountOption, self).has_changed()
134
135"""#TEST:
136o = AccountOption('None', 'pop3_account', ('',''), 'Username/Password', 'Enter username/password here ...')
137# save option to keyring
138exported_account = o.on_export(('RYX', 'mysecretpassword'))
139print exported_account
140# and read option back from keyring
141print o.on_import(exported_account)
142"""
143
0144
=== added file 'src/lib/options/base.py'
--- src/lib/options/base.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/base.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,624 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Base classes and basic mechanics for all screenlet options.
20"""
21
22import screenlets
23from screenlets.options import _
24
25import os
26import gtk, gobject
27import xml.dom.minidom
28from xml.dom.minidom import Node
29
30# -----------------------------------------------------------------------
31# Option-classes and subclasses
32# -----------------------------------------------------------------------
33
34class Option(gobject.GObject):
35 """An Option stores information about a certain object-attribute. It doesn't
36 carry information about the value or the object it belongs to - it is only a
37 one-way data-storage for describing how to handle attributes."""
38 __gsignals__ = dict(option_changed=(gobject.SIGNAL_RUN_FIRST,
39 gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)))
40 default = None
41 label = None
42 desc = None
43 hidden = False
44 disabled = False
45 realtime = True
46 protected = False
47 widget = None
48
49 def __init__ (self, group, name, *attr, **args):
50 """Creates a new Option with the given information."""
51 super(Option, self).__init__()
52 self.name = name
53 self.group = group
54 # To maintain compatability, we parse out the 3 attributes and
55 # Move into known arguments.
56 if len(attr) == 3:
57 args.setdefault('default', attr[0])
58 args.setdefault('label', attr[1])
59 args.setdefault('desc', attr[2])
60 # This should allow any of the class options to be set on init.
61 for name in args.keys():
62 if hasattr(self, name):
63 # Replaces class variables (defaults) with object vars
64 setattr(self, name, args[name])
65
66 # XXX for groups (TODO: OptionGroup)
67 # XXX callback to be notified when this option changes
68 # XXX real-time update?
69 # XXX protected from get/set through service
70
71 def on_import(self, strvalue):
72 """Callback - called when an option gets imported from a string.
73 This function MUST return the string-value converted to the required
74 type!"""
75 return strvalue.replace("\\n", "\n")
76
77 def on_export(self, value):
78 """Callback - called when an option gets exported to a string. The
79 value-argument needs to be converted to a string that can be imported
80 by the on_import-handler. This handler MUST return the value
81 converted to a string!"""
82 return str(value).replace("\n", "\\n")
83
84 def generate_widget(self):
85 """This should generate all the required widgets for display."""
86 raise NotImplementedError, "Generating Widget should be done in child"
87
88 def set_value(self, value):
89 """Set the true/false value to the checkbox widget"""
90 raise NotImplementedError, "Can't update the widget and local value"
91
92 def has_changed(self):
93 """Executed when the widget event kicks off."""
94 return self.emit("option_changed", self)
95
96
97def create_option_from_node (node, groupname):
98 """Create an Option from an XML-node with option-metadata."""
99 #print "TODO OPTION: " + str(cn)
100 otype = node.getAttribute("type")
101 oname = node.getAttribute("name")
102 ohidden = node.getAttribute("hidden")
103 options = {
104 'default' : None,
105 'info' : '',
106 'label' : '',
107 'min' : None,
108 'max' : None,
109 'increment' : 1,
110 'choices' : '',
111 'digits' : None,
112 }
113 if otype and oname:
114 # parse children of option-node and save all useful attributes
115 for attr in node.childNodes:
116 if attr.nodeType == Node.ELEMENT_NODE and attr.nodeName in options.keys():
117 options[attr.nodeName] = attr.firstChild.nodeValue
118 # if we have all needed values, create the Option
119 if options['default']:
120 # create correct classname here
121 cls = otype[0].upper() + otype.lower()[1:] + 'Option'
122 #print 'Create: ' +cls +' / ' + oname + ' ('+otype+')'
123 # and build new instance (we use on_import for setting default val)
124 clsobj = getattr(__import__(__name__), cls)
125 opt = clsobj(groupname, oname, default=None,
126 label=options['label'], desc=options['info'])
127 opt.default = opt.on_import(options['default'])
128 # set values to the correct types
129 if cls == 'IntOption':
130 if options['min']:
131 opt.min = int(options['min'])
132 if options['max']:
133 opt.max = int(options['max'])
134 if options['increment']:
135 opt.increment = int(options['increment'])
136 elif cls == 'FloatOption':
137 if options['digits']:
138 opt.digits = int(options['digits'])
139 if options['min']:
140 opt.min = float(options['min'])
141 if options['min']:
142 opt.max = float(options['max'])
143 if options['increment']:
144 opt.increment = float(options['increment'])
145 elif cls == 'StringOption':
146 if options['choices']:
147 opt.choices = options['choices']
148 return opt
149 return None
150
151
152class EditableOptions(object):
153 """The EditableOptions can be inherited from to allow objects to export
154 editable options for editing them with the OptionsEditor-class.
155 NOTE: This could use some improvement and is very poorly coded :) ..."""
156
157 def __init__ (self):
158 self.__options__ = []
159 self.__options_groups__ = {}
160 # This is a workaround to remember the order of groups
161 self.__options_groups_ordered__ = []
162
163 def add_option (self, option, callback=None, realtime=True):
164 """Add an editable option to this object. Editable Options can be edited
165 and configured using the OptionsDialog. The optional callback-arg can be
166 used to set a callback that gets notified when the option changes its
167 value."""
168 #print "Add option: "+option.name
169 # if option already editable (i.e. initialized), return
170 for o in self.__options__:
171 if o.name == option.name:
172 return False
173 self.__dict__[option.name] = option.default
174 # set auto-update (TEMPORARY?)
175 option.realtime = realtime
176 # add option to group (output error if group is undefined)
177 try:
178 self.__options_groups__[option.group]['options'].append(option)
179 except:
180 print "Options: Error - group %s not defined." % option.group
181 return False
182 # now add the option
183 self.__options__.append(option)
184 # if callback is set, add callback
185 if callback:
186 option.connect("option_changed", callback)
187 option.connect("option_changed", self.callback_value_changed)
188 return True
189
190
191 def add_options_group (self, name, group_info):
192 """Add a new options-group to this Options-object"""
193 self.__options_groups__[name] = {'label':name,
194 'info':group_info, 'options':[]}
195 self.__options_groups_ordered__.append(name)
196 #print self.options_groups
197
198 def disable_option(self, name):
199 """Disable the inputs for a certain Option."""
200 for o in self.__options__:
201 if o.name == name:
202 o.disabled = True
203 return True
204 return False
205
206 def enable_option(self, name):
207 """Enable the inputs for a certain Option."""
208 for o in self.__options__:
209 if o.name == name:
210 o.disabled = False
211 return True
212 return False
213
214 def export_options_as_list(self):
215 """Returns all editable options within a list (without groups)
216 as key/value tuples."""
217 lst = []
218 for o in self.__options__:
219 lst.append((o.name, o.on_export(getattr(self, o.name))))
220 return lst
221
222 def callback_value_changed(self, sender, option):
223 """Called when a widget has updated and this needs calling."""
224 if hasattr(self, option.name):
225 return setattr(self, option.name, option.value)
226 raise KeyError, "Callback tried to set an option that wasn't defined."
227
228 def get_option_by_name (self, name):
229 """Returns an option in this Options by it's name (or None).
230 TODO: this gives wrong results in childclasses ... maybe access
231 as class-attribute??"""
232 for o in self.__options__:
233 if o.name == name:
234 return o
235 return None
236
237 def remove_option (self, name):
238 """Remove an option from this Options."""
239 for o in self.__options__:
240 if o.name == name:
241 del o
242 return True
243 return True
244
245 def add_options_from_file (self, filename):
246 """This function creates options from an XML-file with option-metadata.
247 TODO: make this more reusable and place it into module (once the groups
248 are own objects)"""
249 # create xml document
250 try:
251 doc = xml.dom.minidom.parse(filename)
252 except:
253 raise Exception('Invalid XML in metadata-file (or file missing): "%s".' % filename)
254 # get rootnode
255 root = doc.firstChild
256 if not root or root.nodeName != 'screenlet':
257 raise Exception('Missing or invalid rootnode in metadata-file: "%s".' % filename)
258 # ok, let's check the nodes: this one should contain option-groups
259 groups = []
260 for node in root.childNodes:
261 # we only want element-nodes
262 if node.nodeType == Node.ELEMENT_NODE:
263 #print node
264 if node.nodeName != 'group' or not node.hasChildNodes():
265 # we only allow groups in the first level (groups need children)
266 raise Exception('Error in metadata-file "%s" - only <group>-tags allowed in first level. Groups must contain at least one <info>-element.' % filename)
267 else:
268 # ok, create a new group and parse its elements
269 group = {}
270 group['name'] = node.getAttribute("name")
271 if not group['name']:
272 raise Exception('No name for group defined in "%s".' % filename)
273 group['info'] = ''
274 group['options'] = []
275 # check all children in group
276 for on in node.childNodes:
277 if on.nodeType == Node.ELEMENT_NODE:
278 if on.nodeName == 'info':
279 # info-node? set group-info
280 group['info'] = on.firstChild.nodeValue
281 elif on.nodeName == 'option':
282 # option node? parse option node
283 opt = create_option_from_node (on, group['name'])
284 # ok? add it to list
285 if opt:
286 group['options'].append(opt)
287 else:
288 raise Exception('Invalid option-node found in "%s".' % filename)
289
290 # create new group
291 if len(group['options']):
292 self.add_options_group(group['name'], group['info'])
293 for o in group['options']:
294 self.add_option(o)
295 # add group to list
296 #groups.append(group)
297
298
299class OptionsDialog(gtk.Dialog):
300 """A dynamic options-editor for editing Screenlets which are implementing
301 the EditableOptions-class."""
302
303 __shown_object = None
304
305 def __init__ (self, width, height):
306 # call gtk.Dialog.__init__
307 super(OptionsDialog, self).__init__(
308 _("Edit Options"), flags=gtk.DIALOG_DESTROY_WITH_PARENT |
309 gtk.DIALOG_NO_SEPARATOR,
310 buttons = (#gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_APPLY,
311 gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
312 # set size
313 self.resize(width, height)
314 self.set_keep_above(True) # to avoid confusion
315 self.set_border_width(10)
316 # create attribs
317 self.page_about = None
318 self.page_options = None
319 self.page_themes = None
320 self.vbox_editor = None
321 self.hbox_about = None
322 self.infotext = None
323 self.infoicon = None
324 # create theme-list
325 self.liststore = gtk.ListStore(object)
326 self.tree = gtk.TreeView(model=self.liststore)
327 # create/add outer notebook
328 self.main_notebook = gtk.Notebook()
329 self.main_notebook.show()
330 self.vbox.add(self.main_notebook)
331 # create/init notebook pages
332 self.create_about_page()
333 self.create_themes_page()
334 self.create_options_page()
335
336 # "public" functions
337
338 def reset_to_defaults(self):
339 """Reset all entries for the currently shown object to their default
340 values (the values the object has when it is first created).
341 NOTE: This function resets ALL options, so BE CARFEUL!"""
342 if self.__shown_object:
343 for o in self.__shown_object.__options__:
344 # set default value
345 setattr(self.__shown_object, o.name, o.default)
346
347 def set_info(self, name, info, copyright='', version='', icon=None):
348 """Update the "About"-page with the given information."""
349 # convert infotext (remove EOLs and TABs)
350 info = info.replace("\n", "")
351 info = info.replace("\t", " ")
352 # create markup
353 markup = '\n<b><span size="xx-large">' + name + '</span></b>'
354 if version:
355 markup += ' <span size="large"><b>' + version + '</b></span>'
356 markup += '\n\n'+info+'\n<span size="small">\n'+copyright+'</span>'
357 self.infotext.set_markup(markup)
358 # icon?
359 if icon:
360 # remove old icon
361 if self.infoicon:
362 self.infoicon.destroy()
363 # set new icon
364 self.infoicon = icon
365 self.infoicon.set_alignment(0.0, 0.10)
366 self.infoicon.show()
367 self.hbox_about.pack_start(self.infoicon, 0, 1, 10)
368 else:
369 self.infoicon.hide()
370
371 def show_options_for_object (self, obj):
372 """Update the OptionsEditor to show the options for the given Object.
373 The Object needs to be an EditableOptions-subclass.
374 NOTE: This needs heavy improvement and should use OptionGroups once
375 they exist"""
376 self.__shown_object = obj
377 # create notebook for groups
378 notebook = gtk.Notebook()
379 self.vbox_editor.add(notebook)
380 for group in obj.__options_groups_ordered__:
381 group_data = obj.__options_groups__[group]
382 # create box for tab-page
383 page = gtk.VBox()
384 page.set_border_width(10)
385 if group_data['info'] != '':
386 info = gtk.Label(group_data['info'])
387 info.show()
388 info.set_alignment(0, 0)
389 page.pack_start(info, 0, 0, 7)
390 sep = gtk.HSeparator()
391 sep.show()
392 #page.pack_start(sep, 0, 0, 5)
393 # create VBox for inputs
394 box = gtk.VBox()
395 box.show()
396 box.set_border_width(5)
397 # add box to page
398 page.add(box)
399 page.show()
400 # add new notebook-page
401 label = gtk.Label(group_data['label'])
402 label.show()
403 notebook.append_page(page, label)
404 # and create inputs
405 for option in group_data['options']:
406 if option.hidden == False:
407 val = getattr(obj, option.name)
408 w = self.generate_widget( option, val )
409 if w:
410 box.pack_start(w, 0, 0)
411 w.show()
412 notebook.show()
413 # show/hide themes tab, depending on whether the screenlet uses themes
414 if obj.uses_theme and obj.theme_name != '':
415 self.show_themes_for_screenlet(obj)
416 else:
417 self.page_themes.hide()
418
419 def generate_widget(self, option, value):
420 """Generate the required widgets and add the label."""
421 widget = option.generate_widget(value)
422 hbox = gtk.HBox()
423 label = gtk.Label()
424 label.set_alignment(0.0, 0.0)
425 label.set_label(option.label)
426 label.set_size_request(180, 28)
427 label.show()
428 hbox.pack_start(label, 0, 1)
429 if widget:
430 if option.disabled:
431 widget.set_sensitive(False)
432 label.set_sensitive(False)
433 widget.set_tooltip_text(option.desc)
434 widget.show()
435 # check if needs Apply-button
436 if option.realtime == False:
437 but = gtk.Button(_('Apply'), gtk.STOCK_APPLY)
438 but.show()
439 but.connect("clicked", option.has_changed)
440 b = gtk.HBox()
441 b.show()
442 b.pack_start(widget, 0, 0)
443 b.pack_start(but, 0, 0)
444 hbox.pack_start(b, 0, 0)
445 else:
446 hbox.pack_start(widget, 0, 0)
447 return hbox
448
449
450 def show_themes_for_screenlet (self, obj):
451 """Update the Themes-page to display the available themes for the
452 given Screenlet-object."""
453 dircontent = []
454 screenlets.utils.refresh_available_screenlet_paths()
455 # now check all paths for themes
456 for path in screenlets.SCREENLETS_PATH:
457 p = path + '/' + obj.get_short_name() + '/themes'
458 print p
459 #p = '/usr/local/share/screenlets/Clock/themes' # TEMP!!!
460 try:
461 dc = os.listdir(p)
462 for d in dc:
463 dircontent.append({'name':d, 'path':p+'/'})
464 except:
465 print "Path %s not found." % p
466 # list with found themes
467 found_themes = []
468 # check all themes in path
469 for elem in dircontent:
470 # load themes with the same name only once
471 if found_themes.count(elem['name']):
472 continue
473 found_themes.append(elem['name'])
474 # build full path of theme.conf
475 theme_conf = elem['path'] + elem['name'] + '/theme.conf'
476 # if dir contains a theme.conf
477 if os.access(theme_conf, os.F_OK):
478 # load it and create new list entry
479 ini = screenlets.utils.IniReader()
480 if ini.load(theme_conf):
481 # check for section
482 if ini.has_section('Theme'):
483 # get metainfo from theme
484 th_fullname = ini.get_option('name',
485 section='Theme')
486 th_info = ini.get_option('info',
487 section='Theme')
488 th_version = ini.get_option('version',
489 section='Theme')
490 th_author = ini.get_option('author',
491 section='Theme')
492 # create array from metainfo and add it to liststore
493 info = [elem['name'], th_fullname, th_info, th_author,
494 th_version]
495 self.liststore.append([info])
496 else:
497 # no theme section in theme.conf just add theme-name
498 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
499 else:
500 # no theme.conf in dir? just add theme-name
501 self.liststore.append([[elem['name'], '-', '-', '-', '-']])
502 # is it the active theme?
503 if elem['name'] == obj.theme_name:
504 # select it in tree
505 print "active theme is: %s" % elem['name']
506 sel = self.tree.get_selection()
507 if sel:
508 it = self.liststore.get_iter_from_string(\
509 str(len(self.liststore)-1))
510 if it:
511 sel.select_iter(it)
512 # UI-creation
513
514 def create_about_page (self):
515 """Create the "About"-tab."""
516 self.page_about = gtk.HBox()
517 # create about box
518 self.hbox_about = gtk.HBox()
519 self.hbox_about.show()
520 self.page_about.add(self.hbox_about)
521 # create icon
522 self.infoicon = gtk.Image()
523 self.infoicon.show()
524 self.page_about.pack_start(self.infoicon, 0, 1, 10)
525 # create infotext
526 self.infotext = gtk.Label()
527 self.infotext.use_markup = True
528 self.infotext.set_line_wrap(True)
529 self.infotext.set_alignment(0.0, 0.0)
530 self.infotext.show()
531 self.page_about.pack_start(self.infotext, 1, 1, 5)
532 # add page
533 self.page_about.show()
534 self.main_notebook.append_page(self.page_about, gtk.Label(_('About ')))
535
536 def create_options_page (self):
537 """Create the "Options"-tab."""
538 self.page_options = gtk.HBox()
539 # create vbox for options-editor
540 self.vbox_editor = gtk.VBox(spacing=3)
541 self.vbox_editor.set_border_width(5)
542 self.vbox_editor.show()
543 self.page_options.add(self.vbox_editor)
544 # show/add page
545 self.page_options.show()
546 self.main_notebook.append_page(self.page_options, gtk.Label(_('Options ')))
547
548 def create_themes_page (self):
549 """Create the "Themes"-tab."""
550 self.page_themes = gtk.VBox(spacing=5)
551 self.page_themes.set_border_width(10)
552 # create info-text list
553 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.'))
554 txt.set_size_request(450, -1)
555 txt.set_line_wrap(True)
556 txt.set_alignment(0.0, 0.0)
557 txt.show()
558 self.page_themes.pack_start(txt, False, True)
559 # create theme-selector list
560 self.tree.set_headers_visible(False)
561 self.tree.connect('cursor-changed', self.__tree_cursor_changed)
562 self.tree.show()
563 col = gtk.TreeViewColumn('')
564 cell = gtk.CellRendererText()
565 col.pack_start(cell, True)
566 #cell.set_property('foreground', 'black')
567 col.set_cell_data_func(cell, self.__render_cell)
568 self.tree.append_column(col)
569 # wrap tree in scrollwin
570 sw = gtk.ScrolledWindow()
571 sw.set_shadow_type(gtk.SHADOW_IN)
572 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
573 sw.add(self.tree)
574 sw.show()
575 # add vbox and add tree/buttons
576 vbox = gtk.VBox()
577 vbox.pack_start(sw, True, True)
578 vbox.show()
579 # show/add page
580 self.page_themes.add(vbox)
581 self.page_themes.show()
582 self.main_notebook.append_page(self.page_themes, gtk.Label(_('Themes ')))
583
584 def __render_cell(self, tvcolumn, cell, model, iter):
585 """Callback for rendering the cells in the theme-treeview."""
586 # get attributes-list from Treemodel
587 attrib = model.get_value(iter, 0)
588
589 # set colors depending on state
590 col = '555555'
591 name_uc = attrib[0][0].upper() + attrib[0][1:]
592 # create markup depending on info
593 if attrib[1] == '-' and attrib[2] == '-':
594 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
595 '</span></b> (' + _('no info available') + ')'
596 else:
597 if attrib[1] == None : attrib[1] = '-'
598 if attrib[2] == None : attrib[2] = '-'
599 if attrib[3] == None : attrib[3] = '-'
600 if attrib[4] == None : attrib[4] = '-'
601 mu = '<b><span weight="ultrabold" size="large">' + name_uc + \
602 '</span></b> v' + attrib[4] + '\n<small><span color="#555555' +\
603 '">' + attrib[2].replace('\\n', '\n') + \
604 '</span></small>\n<i><small>by '+str(attrib[3])+'</small></i>'
605 # set markup
606 cell.set_property('markup', mu)
607
608 # UI-callbacks
609
610 def __tree_cursor_changed (self, treeview):
611 """Callback for handling selection changes in the Themes-treeview."""
612 sel = self.tree.get_selection()
613 if sel:
614 s = sel.get_selected()
615 if s:
616 it = s[1]
617 if it:
618 attribs = self.liststore.get_value(it, 0)
619 if attribs and self.__shown_object:
620 #print attribs
621 # set theme in Screenlet (if not already active)
622 if self.__shown_object.theme_name != attribs[0]:
623 self.__shown_object.theme_name = attribs[0]
624
0625
=== added file 'src/lib/options/boolean_option.py'
--- src/lib/options/boolean_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/boolean_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,54 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Boolean options, these classes will display a boolean
20Checkbox as required and control the formating of data.
21"""
22
23import gtk
24
25from screenlets.options import _
26from base import Option
27
28class BoolOption(Option):
29 """An Option for boolean values."""
30 def on_import(self, strvalue):
31 """When a boolean is imported from the config."""
32 return strvalue.lower() == "true"
33
34 def on_export(self, value):
35 """When a boolean is exported to the config."""
36 return str(value)
37
38 def generate_widget(self, value):
39 """Generate a checkbox for a boolean option."""
40 if not self.widget:
41 self.widget = gtk.CheckButton()
42 self.set_value(value)
43 self.widget.connect("toggled", self.has_changed)
44 return self.widget
45
46 def set_value(self, value):
47 """Set the true/false value to the checkbox widget"""
48 self.value = value
49 self.widget.set_active(self.value)
50
51 def has_changed(self, widget):
52 """Executed when the widget event kicks off."""
53 self.set_value( self.widget.get_active() )
54 super(BoolOption, self).has_changed()
055
=== added file 'src/lib/options/colour_option.py'
--- src/lib/options/colour_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/colour_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,149 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Color options, these classes will display a text box.
20"""
21
22import gtk
23
24from screenlets.options import _
25from base import Option
26
27class ColorOption(Option):
28 """An Option for color options."""
29 def on_import(self, strvalue):
30 """Import (r, g, b, a) from comma-separated string."""
31 # strip braces and spaces
32 strvalue = strvalue.lstrip('(')
33 strvalue = strvalue.rstrip(')')
34 strvalue = strvalue.strip()
35 # split value on commas
36 tmpval = strvalue.split(',')
37 outval = []
38 for f in tmpval:
39 # create list and again remove spaces
40 outval.append(float(f.strip()))
41 return outval
42
43 def on_export(self, value):
44 """Export r, g, b, a to comma-separated string."""
45 l = len(value)
46 outval = ''
47 for i in xrange(l):
48 if type(value[i]) == float:
49 outval += "%0.5f" % value[i]
50 else:
51 outval += str(value[i])
52 if i < l-1:
53 outval += ','
54 return outval
55
56 def generate_widget(self, value):
57 """Generate a textbox for a color options"""
58 self.widget = self.get_box_from_colour( value )
59 self.set_value(value)
60 return self.widget
61
62 def set_value(self, value):
63 """Set the color value as required."""
64 self.value = value
65
66 def has_changed(self, widget):
67 """Executed when the widget event kicks off."""
68 self.value = self.get_colour_from_box(self.widget)
69 super(ColorOption, self).has_changed()
70
71 def get_box_from_colour(self, colour):
72 """Turn a colour array into a colour widget"""
73 result = gtk.ColorButton(gtk.gdk.Color(
74 int(colour[0]*65535), int(colour[1]*65535), int(colour[2]*65535)))
75 result.set_use_alpha(True)
76 result.set_alpha(int(colour[3]*65535))
77 result.connect("color-set", self.has_changed)
78 return result
79
80 def get_colour_from_box(self, widget):
81 """Turn a colour widget into a colour array"""
82 colour = widget.get_color()
83 return (
84 colour.red/65535.0,
85 colour.green/65535.0,
86 colour.blue/65535.0,
87 widget.get_alpha()/65535.0
88 )
89
90
91class ColorsOption(ColorOption):
92 """Allow a list of colours to be created"""
93 def on_import(self, value):
94 """Importing multiple colours"""
95 result = []
96 for col in value.split(';'):
97 if col:
98 result.append(super(ColorsOption, self).on_import(col))
99 return result
100
101 def on_export(self, value):
102 """Exporting multiple colours"""
103 result = ""
104 for col in value:
105 result += super(ColorsOption, self).on_export(col)+';'
106 return result
107
108 def generate_widget(self, value):
109 """Generate a textbox for a color options"""
110 self.widget = gtk.HBox()
111 if type(value[0]) in [int, float]:
112 value = [value]
113 for col in value:
114 self.add_colour_box(self.widget, col, False)
115
116 but = gtk.Button('Add', gtk.STOCK_ADD)
117 but.show()
118 but.connect("clicked", self.add_colour_box)
119 self.widget.pack_end(but)
120
121 self.set_value(value)
122 return self.widget
123
124 def del_colour_box(self, widget, event):
125 """Remove a colour box from the array when right clicked"""
126 if event.button == 3:
127 if len(self.widget.get_children()) > 2:
128 self.widget.remove(widget)
129 self.has_changed(widget)
130
131 def add_colour_box(self, widget, col=None, update=True):
132 """Add a new box for colours"""
133 if not col:
134 col = self.value[-1]
135 new_box = self.get_box_from_colour( col )
136 new_box.connect("button_press_event", self.del_colour_box)
137 self.widget.pack_start(new_box, padding=1)
138 new_box.show()
139 if update:
140 self.has_changed(widget)
141
142 def has_changed(self, widget):
143 """The colour widgets have changed!"""
144 self.value = []
145 for c in self.widget.get_children():
146 if type(c) == gtk.ColorButton:
147 self.value.append(self.get_colour_from_box( c ))
148 super(ColorOption, self).has_changed()
149
0150
=== added file 'src/lib/options/file_option.py'
--- src/lib/options/file_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/file_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,179 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19File options, these classes will display a file select widget.
20"""
21
22import gtk
23import os
24
25from screenlets.options import _
26from base import Option
27
28class FileOption(Option):
29 """
30 An Option-subclass for string-values that contain filenames. Adds
31 a patterns-attribute that can contain a list of patterns to be shown
32 in the assigned file selection dialog. The show_pixmaps-attribute
33 can be set to True to make the filedialog show all image-types.
34 supported by gtk.Pixmap. If the directory-attributue is true, the
35 dialog will ony allow directories.
36
37 XXX - Some of this doen't yet work, unknown reason.
38 """
39 patterns = [ ( 'All Files', ['*'] ) ]
40 image = False
41 _gtk_file_mode = gtk.FILE_CHOOSER_ACTION_OPEN
42 _opener_title = _("Choose File")
43
44 def on_import(self, strvalue):
45 """When a file is imported from the config."""
46 return strvalue
47
48 def on_export(self, value):
49 """When a file is exported to the config."""
50 return str(value)
51
52 def generate_widget(self, value):
53 """Generate a special widget for file options"""
54 dlg = self.generate_file_opener()
55 self.widget = gtk.FileChooserButton(dlg)
56 self.widget.set_title(self._opener_title)
57 self.widget.set_size_request(180, 28)
58 self.set_value(value)
59 self.widget.connect("selection-changed", self.has_changed)
60 return self.widget
61
62 def generate_file_opener(self):
63 """Generate a file opener widget"""
64 dlg = gtk.FileChooserDialog(action=self._gtk_file_mode,
65 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
66 gtk.STOCK_OPEN, gtk.RESPONSE_OK),
67 )
68 dlg.set_keep_above(True)
69 self.set_filters(dlg)
70 return dlg
71
72 def set_filters(self, dlg):
73 """Add file filters to the dialog widget"""
74 if self.patterns:
75 for filter in self.patterns:
76 fil = gtk.FileFilter()
77 fil.set_name("%s (%s)" % (filter[0], ','.join(filter[1])))
78 for pattern in filter[1]:
79 fil.add_pattern(pattern)
80 dlg.add_filter(fil)
81
82 def set_value(self, value):
83 """Set the file value as required."""
84 self.widget.set_filename(value)
85 self.value = value
86
87 def has_changed(self, widget):
88 """Executed when the widget event kicks off."""
89 self.value = self.widget.get_filename()
90 super(FileOption, self).has_changed()
91
92
93class DirectoryOption(FileOption):
94 """Directory is based on file widgets"""
95 _gtk_file_mode = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
96 _opener_title = _("Choose Directory")
97
98
99class ImageOption(FileOption):
100 """Image is based on file widgets"""
101 _opener_title = _("Choose Image")
102
103 def set_filters(self, dlg):
104 """Add the standard pixbug formats"""
105 flt = gtk.FileFilter()
106 flt.add_pixbuf_formats()
107 dlg.set_filter(flt)
108
109 def generate_widget(self, value):
110 """Crazy image opener widget generation."""
111 # create entry and button (entry is hidden)
112 self._entry = gtk.Entry()
113 self._entry.set_text(value)
114 self._entry.set_editable(False)
115 but = gtk.Button()
116 # load preview image
117 but.set_image(self.create_preview(value))
118 but.connect('clicked', self.but_callback)
119 # create widget
120 self.widget = gtk.HBox()
121 self.widget.add(self._entry)
122 self.widget.add(but)
123 but.show()
124 self.widget.show()
125 # add tooltips
126 but.set_tooltip_text(_('Select Image ...'))
127 but.set_tooltip_text(self.desc)
128 return self.widget
129
130 def create_preview(self, filename):
131 """Utililty method to reload preview image"""
132 if filename and os.path.isfile(filename):
133 pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 64, -1)
134 if pb:
135 img = gtk.Image()
136 img.set_from_pixbuf(pb)
137 return img
138 img = gtk.image_new_from_stock(gtk.STOCK_MISSING_IMAGE,
139 gtk.ICON_SIZE_LARGE_TOOLBAR)
140 img.set_size_request(64, 64)
141 return img
142
143 def but_callback(self, widget):
144 """Create button"""
145 dlg = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,
146 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
147 dlg.set_keep_above(True)
148 dlg.set_filename(self._entry.get_text())
149 prev = gtk.Image()
150 box = gtk.VBox()
151 box.set_size_request(150, -1)
152 box.add(prev)
153 prev.show()
154 dlg.set_preview_widget_active(True)
155 dlg.connect('selection-changed', self.preview_callback, dlg, prev)
156 dlg.set_preview_widget(box)
157 response = dlg.run()
158 if response == gtk.RESPONSE_OK:
159 self._entry.set_text(dlg.get_filename())
160 widget.set_image(self.create_preview(dlg.get_filename()))
161 self.has_changed(self.widget)
162 dlg.destroy()
163
164 def preview_callback(self, widget, dlg, prev):
165 """add preview widget to filechooser"""
166 fname = dlg.get_preview_filename()
167 if fname and os.path.isfile(fname):
168 pb = gtk.gdk.pixbuf_new_from_file_at_size(fname, 150, -1)
169 if pb:
170 prev.set_from_pixbuf(pb)
171 dlg.set_preview_widget_active(True)
172 else:
173 dlg.set_preview_widget_active(False)
174
175 def has_changed(self, widget):
176 """Executed when the widget event kicks off."""
177 self.value = self._entry.get_text()
178 super(FileOption, self).has_changed()
179
0180
=== added file 'src/lib/options/font_option.py'
--- src/lib/options/font_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/font_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,52 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Font options, these classes will display a text box.
20"""
21
22import gtk
23
24from screenlets.options import _
25from base import Option
26
27class FontOption(Option):
28 """An class for font options."""
29 def on_import(self, strvalue):
30 """When a font is imported from the config."""
31 return strvalue
32
33 def on_export(self, value):
34 """When a font is exported to the config."""
35 return str(value)
36
37 def generate_widget(self, value):
38 """Generate a special widget for font options"""
39 self.widget = gtk.FontButton()
40 self.set_value(value)
41 self.widget.connect("font-set", self.has_changed)
42 return self.widget
43
44 def set_value(self, value):
45 """Set the font value as required."""
46 self.widget.set_font_name(value)
47 self.value = value
48
49 def has_changed(self, widget):
50 """Executed when the widget event kicks off."""
51 self.value = widget.get_font_name()
52 super(FontOption, self).has_changed()
053
=== added file 'src/lib/options/list_option.py'
--- src/lib/options/list_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/list_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,210 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19List options, these classes will display all sorts of crazy shit.
20"""
21
22import gtk
23
24from screenlets.options import _
25from base import Option
26
27class ListOption(Option):
28 """An Option for string options."""
29 def on_import(self, strvalue):
30 """When a list is imported from the config."""
31 return eval(strvalue)
32
33 def on_export(self, value):
34 """When a list is exported to the config."""
35 return str(value)
36
37 def generate_widget(self, value):
38 """Generate some widgets for a list."""
39 self._entry = gtk.Entry()
40 self._entry.set_editable(False)
41 self.set_value(value)
42 self._entry.show()
43 img = gtk.Image()
44 img.set_from_stock(gtk.STOCK_EDIT, 1)
45 but = gtk.Button()
46 but.set_image(img)
47 but.show()
48 but.connect("clicked", self.open_listeditor)
49 but.set_tooltip_text(_('Open List-Editor ...'))
50 self._entry.set_tooltip_text(self.desc)
51 self.widget = gtk.HBox()
52 self.widget.add(self._entry)
53 self.widget.add(but)
54 return self.widget
55
56 def open_listeditor(self, event):
57 # open dialog
58 dlg = ListOptionDialog()
59 # read string from entry and import it through option-class
60 # (this is needed to always have an up-to-date value)
61 dlg.set_list(self.on_import(self._entry.get_text()))
62 resp = dlg.run()
63 if resp == gtk.RESPONSE_OK:
64 # set text in entry
65 self._entry.set_text(str(dlg.get_list()))
66 # manually call the options-callback
67 self.has_changed(dlg)
68 dlg.destroy()
69
70 def set_value(self, value):
71 """Set the list string value as required."""
72 self._entry.set_text(str(value))
73 self.value = value
74
75 def has_changed(self, widget):
76 """Executed when the widget event kicks off."""
77 self.value = widget.get_list()
78 super(ListOption, self).has_changed()
79
80
81class ListOptionDialog(gtk.Dialog):
82 """An editing dialog used for editing options of the ListOption-type."""
83 model = None
84 tree = None
85 buttonbox = None
86
87 # call gtk.Dialog.__init__
88 def __init__ (self):
89 super(ListOptionDialog, self).__init__(
90 "Edit List",
91 flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
92 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
93 gtk.STOCK_OK, gtk.RESPONSE_OK)
94 )
95 # set size
96 self.resize(300, 370)
97 self.set_keep_above(True)
98 # init vars
99 self.model = gtk.ListStore(str)
100 # create UI
101 self.create_ui()
102
103 def create_ui (self):
104 """Create the user-interface for this dialog."""
105 # create outer hbox (tree|buttons)
106 hbox = gtk.HBox()
107 hbox.set_border_width(10)
108 hbox.set_spacing(10)
109 # create tree
110 self.tree = gtk.TreeView(model=self.model)
111 self.tree.set_headers_visible(False)
112 self.tree.set_reorderable(True)
113 #self.tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
114 col = gtk.TreeViewColumn('')
115 cell = gtk.CellRendererText()
116 #cell.set_property('cell-background', 'cyan')
117 cell.set_property('foreground', 'black')
118 col.pack_start(cell, False)
119 col.set_attributes(cell, text=0)
120 self.tree.append_column(col)
121 self.tree.show()
122 hbox.pack_start(self.tree, True, True)
123 #sep = gtk.VSeparator()
124 #sep.show()
125 #hbox.add(sep)
126 # create buttons
127 self.buttonbox = bb = gtk.VButtonBox()
128 self.buttonbox.set_layout(gtk.BUTTONBOX_START)
129 b1 = gtk.Button(stock=gtk.STOCK_ADD)
130 b2 = gtk.Button(stock=gtk.STOCK_EDIT)
131 b3 = gtk.Button(stock=gtk.STOCK_REMOVE)
132 b1.connect('clicked', self.button_callback, 'add')
133 b2.connect('clicked', self.button_callback, 'edit')
134 b3.connect('clicked', self.button_callback, 'remove')
135 bb.add(b1)
136 bb.add(b2)
137 bb.add(b3)
138 self.buttonbox.show_all()
139 #hbox.add(self.buttonbox)
140 hbox.pack_end(self.buttonbox, False)
141 # add everything to outer hbox and show it
142 hbox.show()
143 self.vbox.add(hbox)
144
145 def set_list (self, lst):
146 """Set the list to be edited in this editor."""
147 for el in lst:
148 self.model.append([el])
149
150 def get_list (self):
151 """Return the list that is currently being edited in this editor."""
152 lst = []
153 for i in self.model:
154 lst.append(i[0])
155 return lst
156
157 def remove_selected_item (self):
158 """Remove the currently selected item."""
159 sel = self.tree.get_selection()
160 if sel:
161 it = sel.get_selected()[1]
162 if it:
163 print self.model.get_value(it, 0)
164 self.model.remove(it)
165
166 def entry_dialog (self, default = ''):
167 """Show entry-dialog and return string."""
168 entry = gtk.Entry()
169 entry.set_text(default)
170 entry.show()
171 dlg = gtk.Dialog("Add/Edit Item", flags=gtk.DIALOG_DESTROY_WITH_PARENT,
172 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
173 gtk.RESPONSE_OK))
174 dlg.set_keep_above(True)
175 dlg.vbox.add(entry)
176 resp = dlg.run()
177 ret = None
178 if resp == gtk.RESPONSE_OK:
179 ret = entry.get_text()
180 dlg.destroy()
181 return ret
182
183 def button_callback (self, widget, id):
184 print "PRESS: %s" % id
185 if id == 'remove':
186 self.remove_selected_item()
187 if id == 'add':
188 new = self.entry_dialog()
189 if new != None:
190 self.model.append([new])
191 if id == 'edit':
192 sel = self.tree.get_selection()
193 if sel:
194 it = sel.get_selected()[1]
195 if it:
196 new = self.entry_dialog(self.model.get_value(it, 0))
197 if new != None:
198 #self.model.append([new])
199 self.model.set_value(it, 0, new)
200
201# TEST>-
202"""dlg = ListOptionDialog()
203dlg.set_list(['test1', 'afarew34s', 'fhjh23faj', 'yxcdfs58df', 'hsdf7jsdfh'])
204dlg.run()
205print "RESULT: " + str(dlg.get_list())
206dlg.destroy()
207import sys
208sys.exit(1)"""
209# /TEST
210
0211
=== added file 'src/lib/options/number_option.py'
--- src/lib/options/number_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/number_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,90 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19Integer and Float options, these classes will display a spin box.
20"""
21
22import gtk
23
24from screenlets.options import _
25from base import Option
26
27class IntOption(Option):
28 """An Option for integer options."""
29 min = -100000
30 max = 100000
31 increment = 1
32
33 def on_import(self, strvalue):
34 """When a integer is imported from the config."""
35 try:
36 if strvalue[0]=='-':
37 return int(strvalue[1:]) * -1
38 return int(strvalue)
39 except:
40 sys.stderr.write(_("Error during on_import - option: %s.\n") % self.name)
41 return 0
42
43 return int(strvalue)
44
45 def on_export(self, value):
46 """When a string is exported to the config."""
47 return str(value)
48
49 def generate_widget(self, value):
50 """Generate a spin button for integer options"""
51 self.widget = gtk.SpinButton()
52 self.widget.set_increments(self.increment, int(self.max / self.increment))
53 if self.min != None and self.max != None:
54 self.widget.set_range(self.min, self.max)
55 self.set_value(value)
56 self.widget.connect("value-changed", self.has_changed)
57 return self.widget
58
59 def set_value(self, value):
60 """Set the int value, including the value of the widget."""
61 self.value = value
62 self.widget.set_value(value)
63
64 def has_changed(self, widget):
65 """Executed when the widget event kicks off."""
66 self.value = int(widget.get_value())
67 super(IntOption, self).has_changed()
68
69
70class FloatOption(IntOption):
71 """An option for float numbers."""
72 digits = 0
73
74 def on_import (self, strvalue):
75 """Called when FloatOption gets imported. Converts str to float."""
76 if strvalue[0]=='-':
77 return float(strvalue[1:]) * -1.0
78 return float(strvalue)
79
80 def generate_widget(self, value):
81 """Do the same as int but add the number of ditgits"""
82 super(FloatOption, self).generate_widget(value)
83 self.widget.set_digits(self.digits)
84 return self.widget
85
86 def has_changed(self, widget):
87 """Executed when the widget event kicks off."""
88 self.value = float(widget.get_value())
89 super(IntOption, self).has_changed()
90
091
=== added file 'src/lib/options/string_option.py'
--- src/lib/options/string_option.py 1970-01-01 00:00:00 +0000
+++ src/lib/options/string_option.py 2011-03-28 16:19:24 +0000
@@ -0,0 +1,79 @@
1#
2# Copyright (C) 2009 Martin Owens (DoctorMO) <doctormo@gmail.com>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18"""
19String options, these classes will display a text box.
20"""
21
22import gtk
23
24from screenlets.options import _
25from base import Option
26
27class StringOption(Option):
28 """An Option for string options."""
29 choices = None
30 password = False
31
32 def on_import(self, strvalue):
33 """When a string is imported from the config."""
34 return strvalue
35
36 def on_export(self, value):
37 """When a string is exported to the config."""
38 return str(value)
39
40 def generate_widget(self, value):
41 """Generate a textbox for a string options"""
42 if self.choices:
43 # if a list of values is defined, show combobox
44 self.widget = gtk.combo_box_new_text()
45 p = -1
46 i = 0
47 for s in self.choices:
48 self.widget.append_text(s)
49 if s==value:
50 p = i
51 i+=1
52 self.widget.set_active(p)
53 else:
54 self.widget = gtk.Entry()
55 # if it is a password, set text to be invisible
56 if self.password:
57 self.widget.set_visibility(False)
58