Package screenlets :: Module options

[hide private]
[frames] | no frames]

# Source Code for Module screenlets.options

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