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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
57
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 """
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))
88 """Requests that .active include settings from updates, by calling .OnSet(updates)"""
89 self.OnSet(self, updates)
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)
99 - def save(self, name):
102 - def load(self, name):
108 - def read(self, name):
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
145
146
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 """
158 self.gdir = gdir
159 self.udir = udir
160 self.cats = {}
162 """Saves the active settings in all categories."""
163 for cat in self.cats.itervalues():
164 cat.saveActive()
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[:] = []
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[:] = []
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):
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)
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
222 if not self.cats.has_key(cat):
223 self.cats[cat] = Settings(self, cat)
224 return self.cats[cat]
225
226
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):
241
242
246
254
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
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)])
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)
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
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
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
391 return Cls
392 return Decorator
393
394
395
396 SettingsMgr = 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('~')
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