Package qubx :: Module settings
[hide private]
[frames] | no frames]

Source Code for Module qubx.settings

  1  """Preferences and presets management. 
  2   
  3  Copyright 2007-2014 Research Foundation State University of New York  
  4  This file is part of QUB Express.                                           
  5   
  6  QUB Express is free software; you can redistribute it and/or modify           
  7  it under the terms of the GNU General Public License as published by  
  8  the Free Software Foundation, either version 3 of the License, or     
  9  (at your option) any later version.                                   
 10   
 11  QUB Express is distributed in the hope that it will be useful,                
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 14  GNU General Public License for more details.                          
 15   
 16  You should have received a copy of the GNU General Public License,    
 17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 18  <http://www.gnu.org/licenses/>.                                       
 19   
 20  """ 
 21   
 22  import os 
 23  import os.path 
 24  import re 
 25  import weakref 
 26  import traceback 
 27  import qubx.tree 
 28  import numpy 
 29  import qubx.global_namespace 
 30  import qubx.pyenv 
 31  from qubx.util_types import Anon, bind_with_args_before, valid_identifier 
 32   
 33  # Sep 2005 Chris 
 34  # 
 35  # Preferences management 
 36  # Centralizes the disk access. 
 37  # To play along: 
 38  #     maintain your current prefs in a QUB_NODE 
 39  #     come up with a category Name 
 40  #     init: update my prefs from SettingsMgr[Name].Active 
 41  #           SettingsMgr[Name].active := my prefs tree 
 42  #           SettingsMgr[Name].OnSet := f(settings-cat, updates-tree) that updates my prefs tree 
 43  #     don't forget to unset OnSet before you are destroyed 
 44  #     qub will take care of saving and restoring Active across sessions 
 45  #     for multiple documents sharing a category: 
 46  #           only the front one should be active 
 47  #           (owning active and onSet) 
 48  #           the init procedure above will give new docs the same settings as the last viewed doc 
 49  #     when showing doc properties, switch active and onset over to the dialog 
 50  # app should start by calling InitSettings(central-settings-dir, user-settings-dir) 
 51   
 52  # todo:    the gui components provided handle loading,saving,switching 
 53   
54 -def printme(s):
55 print s 56 return s
57
58 -class Settings(object):
59 """The settings for one category. 60 61 @ivar active: qubx.tree.Node(), root of settings tree; 62 you must keep it up to date. 63 it is restored across sessions. 64 @ivar OnSet: function(Settings, updates_node); 65 this is where you are given requested changes to .active 66 """
67 - def __init__(self, mgr, name):
68 self.name = name 69 self.active = mgr.load(name, '__active') 70 if self.active.isNull: 71 self.active = qubx.tree.Node('Properties') 72 self.hDefOnSet = self._defOnSet 73 self._onSet = weakref.ref(self.hDefOnSet)
74 - def _defOnSet(self, settings, updates):
75 pass # ?
76 - def _setOnSet(self, onSet):
77 if onSet: 78 self._onSet = weakref.ref(onSet) # , lambda l:printme("released onSet")) 79 else: 80 self._onSet = weakref.ref(self.hDefOnSet) # , lambda l:printme("released def onSet"))
81 - def _getOnSet(self):
82 if not self._onSet(): 83 self.OnSet = None 84 print 'Settings category "%s" dropped handler.' % self.name 85 return self._onSet()
86 OnSet = property(lambda s: s._getOnSet(), lambda s,x: s._setOnSet(x))
87 - def setProperties(self, updates):
88 """Requests that .active include settings from updates, by calling .OnSet(updates)""" 89 self.OnSet(self, updates)
90 - def setPropertiesDict(self, **kw):
91 """Requests that .active use settings by keyword. Converted to a tree and passed to OnSet.""" 92 updates = qubx.tree.Node() 93 for k,v in kw.iteritems(): 94 updates[k].data = v 95 self.OnSet(self, updates)
96 - def listNames(self):
97 """Returns a list of available preset names""" 98 return SettingsMgr.listNames(self.name)
99 - def save(self, name):
100 """Saves the active settings as a named preset""" 101 return SettingsMgr.save(self.active, self.name, name)
102 - def load(self, name):
103 """calls setProperties(named preset)""" 104 props = SettingsMgr.load(self.name, name) 105 if not props.isNull: 106 self.setProperties(props) 107 return not props.isNull
108 - def read(self, name):
109 """Returns the qubx.tree.Node() for a named preset""" 110 return SettingsMgr.load(self.name, name)
111 - def exportFile(self, path):
112 """Saves the active settings at path""" 113 return self.active.clone().saveAs(path, as_copy=True)
114 - def importFile(self, path):
115 """Calls setProperties(Open(path))""" 116 props = qubx.tree.Open(path, True) 117 props.close() 118 if not props.isNull: 119 self.setProperties(props) 120 return not props.isNull
121 - def saveActive(self):
122 """Writes the active settings to disk; e.g. before quitting or risking a crash.""" 123 return SettingsMgr.save(self.active, self.name, '__active')
124 - def as_pairs(self, keys=None, enums=[]):
125 return settings_as_pairs(self.active, keys, enums)
126
127 -def settings_as_pairs(parent, keys=None, enums=[]):
128 result = [] 129 for node in qubx.tree.children(parent): 130 if (keys is None) or (node.name in keys): 131 if not node.child.isNull: 132 result.append((node.name, settings_as_pairs(node, enums=enums))) 133 elif node.data.type == qubx.tree.QTR_TYPE_STRING: 134 result.append((node.name, repr(str(node.data)))) 135 elif len(node.data) == 1: 136 if node.name in enums: 137 result.append((node.name, enums[node.name][node.data[0]])) 138 else: 139 result.append((node.name, node.data[0])) 140 elif node.data.cols == 1: 141 result.append((node.name, node.data[:])) 142 elif node.data: 143 result.append((node.name, numpy.array(node.storage.data, copy=True))) 144 return result
145 146
147 -class SettingsManager(object):
148 """ 149 Manager for all categories of settings. 150 151 Do not construct directly; call InitSettings() and use global SettingsMgr. 152 153 For individual category access (class Settings): 154 155 >>> category = SettingsMgr["name_of_category"] 156 """
157 - def __init__(self, gdir, udir):
158 self.gdir = gdir # global search-path 159 self.udir = udir # user search-path (first) 160 self.cats = {}
161 - def saveAll(self):
162 """Saves the active settings in all categories.""" 163 for cat in self.cats.itervalues(): 164 cat.saveActive()
165 - def resetAll(self):
166 for base, dirs, files in os.walk(self.udir): 167 for dir in dirs: 168 for cat_base, cat_dirs, cat_files in os.walk(os.path.join(base, dir)): 169 cat_dirs[:] = [] 170 n = len(cat_files) 171 if '__active.qpr' in cat_files: 172 os.remove(os.path.join(base, cat_base, '__active.qpr')) 173 n -= 1 174 if n == 0: 175 os.rmdir(os.path.join(base, cat_base)) 176 dirs[:] = []
177 - def listNames(self, cat):
178 """Returns a list of available preset names for a named category.""" 179 names = {} 180 for root in (self.gdir, self.udir): 181 for base, dirs, files in os.walk(os.path.join(root,cat)): 182 dirs[:] = [] # don't recurse 183 for f in files: 184 match = re.match(r"(.+)\.qpr", f) 185 if match and (f[:2] != "__"): 186 names[match.group(1)] = 1 187 return sorted(names.keys())
188 - def save(self, props, cat, name):
189 """Saves qubx.tree.Node props as a named preset in a category.""" 190 path = os.path.join(self.udir, cat) 191 try: 192 if not os.path.exists(path): 193 os.makedirs(path) 194 return props.clone().saveAs(os.path.join(path, name+'.qpr'), as_copy=True) 195 except: 196 traceback.print_exc() 197 return False
198 - def load(self, cat, name):
199 """Returns a named preset in a category as a qubx.tree.Node.""" 200 props = qubx.tree.Open(os.path.join(self.udir, cat, name+'.qpr'), True) 201 if props.isNull: 202 props = qubx.tree.Open(os.path.join(self.gdir, cat, name+'.qpr'), True) 203 props.close() 204 return props
205 - def rename(self, cat, old, new):
206 """Changes the name of a preset in a category.""" 207 if new in self.listNames(cat): 208 raise Exception('There is already a preset named '+new) 209 oldP, newP = [os.path.join(self.udir, cat, x+'.qpr') for x in (old, new)] 210 if os.path.exists(oldP): 211 os.rename(oldP, newP) 212 else: 213 os.copy(os.path.join(self.gdir, cat, x+'.qpr'), newP)
214 - def delete(self, cat, name):
215 """Removes a named preset from a category.""" 216 path = os.path.join(self.udir, cat, name+'.qpr') 217 if os.path.exists(path): 218 os.remove(path) 219 return True 220 return False
221 - def __getitem__(self, cat):
222 if not self.cats.has_key(cat): 223 self.cats[cat] = Settings(self, cat) 224 return self.cats[cat]
225 226
227 -class Property(object):
228 __slots__ = ['name', 'default', 'doc', 'in_tree', 'tree_alias', 'value_names', 'accept', 'format']
229 - def __init__(self, name, default=0.0, doc="", in_tree=True, tree_alias=None, value_names=None, accept=None, format=None):
230 self.name = name 231 self.default = default 232 self.doc = doc 233 self.in_tree = in_tree 234 self.tree_alias = name if (tree_alias is None) else tree_alias 235 self.value_names = {} if (value_names is None) else value_names 236 self.accept = str if (accept is None) else accept 237 self.format = str if (format is None) else format 238 main_globals = qubx.global_namespace.__dict__ 239 for val, nm in self.value_names.iteritems(): 240 main_globals[nm] = val
241 242
243 -class PropertyInstance(Anon):
244 - def __init__(self, spec):
245 Anon.__init__(self, spec=spec, value=spec.default)
246
247 -class PropertyStorage(dict):
248 - def __init__(self, spec_dict):
249 super(PropertyStorage, self).__init__() 250 self.spec_dict = spec_dict
251 - def __missing__(self, key):
252 item = self[key] = PropertyInstance(self.spec_dict[key]) 253 return item
254
255 -class PropertiedBase(object):
256 - def propertied_init(self, spec_dict=None):
257 if not (spec_dict is None): 258 self.propertied_spec_dict = spec_dict 259 if not 'propertied_items' in self.__dict__: 260 self.propertied_items = PropertyStorage(self.propertied_spec_dict)
261 - def propertied_get(self, name):
262 self.propertied_init() 263 return self.propertied_items[name].value
264 - def propertied_set(self, value, name):
265 self.propertied_init() 266 item = self.propertied_items[name] 267 spec = self.propertied_spec_dict[name] 268 item.value = value 269 if spec.in_tree and ('propertied_tree' in self.__dict__): 270 self.propertied_tree[spec.tree_alias].data = value 271 if item.entry: 272 item.entry.value = value 273 if item.check: 274 item.check.set_active(bool(value)) 275 if item.radios: 276 for rvalue, radio in item.radios: 277 if rvalue == value: 278 radio.set_active(True) 279 break
280 - def propertied_connect_NumEntry(self, name, entry):
281 self.propertied_init() 282 item = self.propertied_items[name] 283 item.entry = entry 284 entry.value = item.value 285 item.on_change = lambda txt, val: self.propertied_on_user_set(name, val) 286 entry.OnChange += item.on_change
287 - def propertied_connect_check(self, name, check):
288 self.propertied_init() 289 item = self.propertied_items[name] 290 item.check = check 291 check.set_active(bool(item.value)) 292 check.connect('toggled', lambda chk: self.propertied_on_user_set(name, chk.get_active()))
293 - def propertied_connect_radios(self, name, radios):
294 """radios: list of (value, radio)""" 295 self.propertied_init() 296 item = self.propertied_items[name] 297 item.radios = radios 298 for value, radio in radios: 299 if value == item.value: 300 radio.set_active(True) 301 for value, radio in radios: 302 radio.connect('toggled', self.propertied_on_radio, name, value)
303 - def propertied_on_radio(self, radio, name, value):
304 if radio.get_active(): 305 self.propertied_on_user_set(name, value)
306 - def propertied_on_user_set(self, name, value):
309 - def propertied_scriptable(self, name, value):
310 global_name = self.global_name if ('global_name' in self.__dict__) else '' 311 if global_name: 312 value_names = self.propertied_spec_dict[name].value_names 313 val_str = value_names[value] if (value_names and (value in value_names)) else repr(value) 314 if valid_identifier.match(name): 315 qubx.pyenv.env.scriptable_if_matching('%s.%s = %s' % (global_name, name, val_str), 316 [(global_name, self)]) 317 else: 318 qubx.pyenv.env.scriptable_if_matching('setattr(%s, %s, %s)' % (global_name, repr(name), val_str), 319 [(global_name, self)])
320 - def propertied_connect_settings(self, category_name):
321 self.propertied_init() 322 self.propertied_category = SettingsMgr[category_name] 323 self.__ref_on_preset = self.propertied_on_preset 324 self.propertied_category.OnSet = self.__ref_on_preset 325 self.propertied_tree = self.propertied_category.active 326 for spec in self.propertied_specs: 327 self.propertied_connect_settings1(spec)
328 - def propertied_connect_settings1(self, spec):
329 item = self.propertied_items[spec.name] 330 node = self.propertied_tree[spec.tree_alias] 331 if not spec.in_tree: 332 node.data = spec.default 333 elif isinstance(spec.default, str): 334 if spec.default and not node.data: 335 node.data = spec.default 336 item.value = str(node.data) or spec.default 337 elif len(node.data) > 1: 338 item.value = node.data[:] 339 elif node.data: 340 try: 341 spec.default.__iter__ 342 item.value = node.data[:] 343 except AttributeError: 344 item.value = node.data[0] 345 else: 346 node.data = spec.default
347 - def propertied_on_preset(self, category, updates): # virtual
348 for spec in self.propertied_specs: 349 if not spec.in_tree: 350 continue 351 upd = updates.find(spec.tree_alias) 352 if (isinstance(spec.default, str) and not upd.isNull): 353 self.propertied_set(str(upd.data), spec.name) 354 elif len(upd.data) > 1: 355 self.propertied_set(upd.data[:], spec.name) 356 elif upd.data: 357 self.propertied_set(upd.data[0], spec.name)
358 - def propertied_add_property(self, spec):
359 Cls = self.__class__ 360 Cls.propertied_specs.append(spec) 361 Cls.propertied_spec_dict[spec.name] = spec 362 name = spec.name 363 getter = bind_with_args_before(lambda self, nm: self.propertied_get(nm), name) 364 setter = bind_with_args_before(lambda self, val, nm: self.propertied_set(val, nm), name) 365 setattr(Cls, name, property(getter, setter, doc=spec.doc)) 366 self.propertied_connect_settings1(spec)
367
368 -def Propertied(*specs):
369 """Returns a class decorator which adds the desired propertied and settings/gui integration.""" 370 spec_dict = dict((spec.name, spec) for spec in specs) 371 def Decorator(Cls): 372 """Adds the desired properties and settings/gui integration to Cls and returns it.""" 373 Cls.__bases__ += (PropertiedBase,) 374 Cls.propertied_specs = list(specs) 375 Cls.propertied_spec_dict = spec_dict 376 for spec in specs: 377 name = spec.name 378 getter = bind_with_args_before(lambda self, nm: self.propertied_get(nm), name) 379 setter = bind_with_args_before(lambda self, val, nm: self.propertied_set(val, nm), name) 380 setattr(Cls, name, property(getter, setter, doc=spec.doc)) 381 ff = qubx.pyenv.generate_featured(Cls) 382 try: 383 ff.next() 384 # no StopIteration yet: Cls or a base have .__explore_featured, so Cls needs one 385 pname = qubx.pyenv.featured_property_name(Cls) 386 if not hasattr(Cls, pname): 387 setattr(Cls, pname, []) 388 Cls.__dict__[pname].extend([spec.name for spec in specs]) 389 except StopIteration: 390 pass # nothing already featured, so no need to add features 391 return Cls
392 return Decorator 393 394 395 396 SettingsMgr = None
397 -def InitSettings(gdir=None, udir=None):
398 """Creates the global var SettingsMgr, with specified global and user paths, or reasonable defaults.""" 399 global SettingsMgr 400 if not SettingsMgr: 401 global_dir = gdir or './qpr' 402 user_dir = udir 403 if not user_dir: 404 home = os.path.expanduser('~') # environ.get("HOME") 405 if home: 406 user_dir = os.path.join(home, ".qub", "qpr") 407 else: 408 user_dir = './qprl' 409 SettingsMgr = SettingsManager(global_dir, user_dir)
410