1 """General-purpose types and utilities.
2
3 Copyright 2007-2012 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 collections
23 import heapq
24 import pprint
25 import traceback
26 import weakref
27 from math import *
28 from string import join
29 import re
30 import os
31 import sys
32
33 from contextlib import contextmanager
34
35 VERSION = "1.3.9"
36
37 ShowDropped = False
38
39 UNSET_VALUE = 86738.82583
42 """
43 Usage:
44
45 >>> myEvent = Event() # a, b
46 >>> callback1 = lambda a, b: dostuff(a, b)
47 >>> callback2 = lambda a, b: otherstuff(a, b)
48 >>> myEvent += callback1
49 >>> myEvent += callback2
50 >>> myEvent(a, b) # calls both callbacks
51 >>> myEvent -= callback1
52 >>> myEvent -= callback2
53 >>> myEvent(a, b) # no callbacks; does nothing
54
55 Note: this is a great source of circular references; see gc.collect()
56 """
57
59 self._listeners = []
60 self.enabled = True
62 if self.enabled:
63 for l in self._listeners:
64 l(*args, **kw)
66 if not l in self._listeners:
67 self._listeners.append(l)
68 return self
70 if l in self._listeners:
71 self._listeners.remove(l)
72 return self
74 return len(self._listeners)
75
77 """Prints and returns its argument(s). Handy for testing callbacks."""
78 if len(xx) == 1:
79 pprint.pprint(xx[0])
80
81 return xx[0]
82 else:
83 for x in xx:
84 print x,
85 print
86 return xx
87
89 """
90 Usage:
91
92 >>> myEvent = Event() # a, b
93 >>> callback1 = lambda a, b: dostuff(a, b)
94 >>> callback2 = lambda a, b: otherstuff(a, b)
95 >>> callback3 = self.callback3
96 >>> myEvent += callback1
97 >>> myEvent += callback2
98 >>> myEvent(a, b) # calls both callbacks
99 >>> myEvent -= callback1
100 >>> myEvent -= callback2
101 >>> myEvent(a, b) # no callbacks; does nothing
102
103 note: keep a ref somewhere, even to self.whatever, or it will be unlinked; see L{Reffer}
104 """
105
107 self._listeners = []
108 self._names = []
109 self._stacks = []
110 self.name = name
111 self.enabled = True
113 dead = []
114 if self.enabled:
115 for l in self._listeners:
116 listener = l()
117 if not (listener is None):
118 try:
119 listener(*args, **kw)
120 except:
121 traceback.print_exc()
122 else:
123 dead.append(l)
124 for l in dead:
125 i = self._listeners.index(l)
126 n = self._names[i]
127 if ShowDropped:
128 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n)
129 for line in self._stacks[i]:
130 sys.stdout.write(" > %s"%line)
131 self._listeners.pop(i)
132 self._names.pop(i)
133 self._stacks.pop(i)
135 try:
136 l, n = ln
137 except:
138 l, n = ln, ""
139 if not l in self._listeners:
140 self._listeners.append(weakref.ref(l))
141 self._names.append(n)
142 if ShowDropped:
143 self._stacks.append(traceback.format_stack()[:-1])
144 else:
145 self._stacks.append("")
146 return self
148 dead = []
149 found = None
150 for l in self._listeners:
151 listener = l()
152 if listener == lsub:
153 found = l
154 dead.append(l)
155 elif not listener:
156 dead.append(l)
157 for l in dead:
158 i = self._listeners.index(l)
159 if ShowDropped and (l != found):
160 n = self._names[i]
161 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n)
162 for line in self._stacks[i]:
163 sys.stdout.write(" > %s"%line)
164 self._listeners.pop(i)
165 self._names.pop(i)
166 self._stacks.pop(i)
167 if not found:
168 raise Exception('-=!~')
169 return self
171 return len(self._listeners)
172
176 """Like WeakEvent, except there's at most one listener, and it can return a value."""
177
179 self.name = name
180 self._listener = None
181 self._listener_name = ""
182 self.enabled = True
184 if self.enabled and not (self._listener is None):
185 listener = self._listener()
186 if not (listener is None):
187 try:
188 return listener(*args, **kw)
189 except:
190 traceback.print_exc()
191 else:
192 if ShowDropped:
193 n = self._listener_name
194 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n)
195 self._listener = None
196 self._listener_name = ""
197 - def assign(self, listener, name=""):
198 self._listener = None if (listener is None) else weakref.ref(listener)
199 self._listener_name = name
201 return not (self._listener is None)
202
203
204
205
206
207
208 -class Reffer(object):
209 """
210 Holds references to keep objects alive.
211 Intended in combo with WeakEvent for bound method references e.g. self.onEvent,
212 since self.onEvent creates a temporary instance that would otherwise be destroyed.
213
214 Usage:
215
216 >>> self.ref = Reffer()
217 >>> self.evt = WeakEvent()
218 >>> self.evt += self.ref(self.onEvt)
219 >>> ...
220 >>> self.evt -= self.ref(self.onEvt) # hashes the same each time
221 """
225 if not self.r.has_key(ref):
226 self.r[ref] = ref
227 return self.r[ref]
230
232 """Prints to stdout when it is finally freed, presumably at the same time as its owner.
233
234 Usage:
235
236 >>> self.death_rattle = DeathRattle('Free at last')
237 """
242
244 """
245 An object whose fields are the constructor keyword arguments.
246 Requesting any other field returns None rather than raise NameError.
247
248 Example:
249
250 >>> x = Anon(stuff='x',n=5)
251 >>> print x.stuff, x.n, x.whatever
252 x, 5, None
253 """
259 dicts = []
260 for arg in args:
261 if isinstance(arg, str):
262 try:
263 dicts.append( dict(eval(arg)) )
264 except:
265 print '%s(): ignoring non-dict string argument: %s' % (self.__class__, arg)
266 elif isinstance(arg, dict):
267 dicts.append( arg )
268 else:
269 try:
270 dicts.append( arg.__dict__ )
271 except:
272 print '%s(): ignoring non-mapping argument: %s (%s)' % (self.__class__, arg, arg.__class__)
273 dicts.append(kw)
274 return dicts
275
277 try:
278 return self.__dict__[name]
279 except:
280 return None
282 return repr(self.__dict__)
286 def val2str(v):
287 if isinstance(v, Anon):
288 return v._str_indent(indent+" | ")
289 else:
290 return str(v)
291 return '\n'.join([str(self.__class__)] +
292 ['%s%16s: %s' % (indent,k,val2str(v)) for k,v in self.__dict__.iteritems()])
294 return self.__class__(self, **kw)
295
298 for d in self._find_arg_dicts(*args, **kw):
299 for k,v in d.iteritems():
300 if isinstance(v, dict):
301 self.__dict__[k] = self.__class__(v)
302 print v
303 print
304 print self.__dict__[k]
305 print
306 else:
307 self.__dict__[k] = v
308
311 """like builtin reduce(), but recursing by splitting in half. Doesn't seem to make a difference."""
312 if len(seq) < 1:
313 return None
314 elif len(seq) == 1:
315 return seq[0]
316 elif len(seq) == 2:
317 return func(seq[0], seq[1])
318 else:
319 half = len(seq)/2
320 return func(breduce(func, seq[:half]), breduce(func, seq[half:]))
321
323 """
324 Returns wrapped func that caches return values for specific args.
325 All args must be immutable (e.g. tuples not lists).
326
327 Example:
328
329 >>> def factorial(n):
330 >>> if n > 1:
331 >>> return n * fast_fac(n-1)
332 >>> else:
333 >>> return 1
334 >>> fast_fac = memoize(factorial)
335 """
336 results = {}
337 def wrapper(*args):
338 if args in results:
339 return results[args]
340 else:
341 result = func(*args)
342 results[args] = result
343
344 return result
345 wrapper.results = results
346 wrapper.reset = results.clear
347 return wrapper
348
350 """
351 Returns wrapped func that caches return values for specific args.
352 All args must be immutable (e.g. tuples not lists).
353
354 Example:
355
356 >>> def factorial(n):
357 >>> if n > 1:
358 >>> return n * fast_fac(n-1)
359 >>> else:
360 >>> return 1
361 >>> fast_fac = memoize(factorial)
362 """
363 results = {}
364 order = []
365 next = [-1000000000]
366 total_weight = [0]
367 def wrapper(*args):
368 if args in results:
369 index, result = results[args]
370 for i in reversed(xrange(len(order))):
371 if order[i][0] == index:
372 results[args] = (next[0], result)
373 order[i] = (next[0], args)
374 next[0] += 1
375 heapq_sink(order, i)
376 break
377 return result
378 else:
379 result = func(*args)
380 weight = total_weight[0] + weigh(result)
381 while weight > max_weight:
382 rem_args = heapq.heappop(order)[1]
383 weight -= weigh(results[rem_args])
384 del results[rem_args]
385 total_weight[0] = weight
386 results[args] = (next[0], result)
387 heapq.heappush(order, (next[0], args))
388 next[0] += 1
389
390 return result
391 return wrapper
392
394 while (k > 0) and (heap[(k+1)/2-1] > heap[k]):
395 heap[k], heap[(k+1)/2-1] = heap[(k+1)/2-1], heap[k]
396
398 k2, N = (k+1)*2 - 1, len(heap)
399 if k2 >= N: return
400 if ((k2+1) < N) and (heap[k2] > heap[k2+1]):
401 k2 += 1
402 if heap[k] > heap[k2]:
403 heap[k], heap[k2] = heap[k2], heap[k]
404 heapq_sink(heap, k2)
405
406
407 -def bind(callback, *args, **kw):
408 """
409 Returns a function f which, when called, discards its own args and returns callback(*args, **kw).
410
411 Lambda can be useful to share one message handler among many sources, but there's a limitation:
412 >>> for i in xrange(N):
413 chk[i].OnToggle += self__ref(lambda chk: self.__onToggle(i))
414
415 Here, the lambda binds not the value of i, but the stack location whose contents are repeatedly changed by the for loop.
416 So all the lambdas are effectively doing self.__onToggle(N-1).
417
418 Instead:
419 >>> for i in xrange(N):
420 chk[i].OnToggle += self.__ref(bind(self.__onToggle, i))
421
422 This creates a unique stack frame for your unique i, so it does what you expect.
423 """
424
425 def act(*act_args, **act_kw):
426 return callback(*args, **kw)
427 return act
428
430 """Like bind, but it appends the callback's positional args after the bound positional args; returns callback(*(args+cb_args), **kw)."""
431 def act(*act_args, **act_kw):
432 use_kw = kw.copy()
433 for k,v in act_kw.iteritems():
434 use_kw[k] = v
435 return callback(*(args+act_args), **use_kw)
436 return act
437
439 """Like bind, but it prepends the callback's positional args before the bound positional args; returns callback(*(args+cb_args), **kw)."""
440 def act(*act_args, **act_kw):
441 use_kw = kw.copy()
442 for k,v in act_kw.iteritems():
443 use_kw[k] = v
444 return callback(*(act_args+args), **use_kw)
445 return act
446
450 """Returns (r,g,b) color coordinates corresponding to the hue, saturation and value. All coordinates are between 0 and 1."""
451 h, s, v = map(float, (h, s, v))
452 if h == 1.0: h = 0.0
453 h *= 360
454
455 if s == 0:
456 return v,v,v
457
458 sectorPos = h/60.0
459 sectorNumber = int(floor(sectorPos))
460 fractionalSector = sectorPos - sectorNumber
461
462 p = v * (1 - s)
463 q = v * (1 - (s * fractionalSector))
464 t = v * (1 - (s * (1 - fractionalSector)))
465
466 return {0 : (v, t, p),
467 1 : (q, v, p),
468 2 : (p, v, t),
469 3 : (p, q, v),
470 4 : (t, p, v),
471 5 : (v, p, q)} [sectorNumber]
472
474 """Returns the (hue, saturation, value) color coordinates corresponding to the RGB color. All coordinates are between 0 and 1."""
475 r, g, b = map(float, (r, g, b))
476 mn = min(r, g, b)
477 mx = max(r, g, b)
478 v = mx
479 delta = mx - mn
480 if (mx == 0.0) or (delta == 0.0):
481 return 0.0, 0.0, v
482 s = delta / mx
483 if r == mx:
484 h = ((g - b) / delta) % 6.0
485 elif g == mx:
486 h = 2.0 + (b - r) / delta
487 else:
488 h = 4.0 + (r - g) / delta
489 h *= 60.0
490 if h < 0: h += 360
491 return h/360.0, s, v
492
494 """
495 Returns r,g,b corresponding to x in [-1..1].
496 The scale moves smoothly around the color wheel from pink (-) to red (+),
497 with brightness proportional to abs(x).
498 """
499 y = sin(x*pi/2)
500 h = 0.44 * (1.0 - y)
501 v = abs(x)
502 return HSVtoRGB(h, saturation, v*(2.0 - v))
503
504
505
506 WITHALPHA = lambda tup: (len(tup) == 3) and tuple(list(tup)+[1]) or tup
507 NOALPHA = lambda tup: (len(tup) == 4) and tup[:3] or tup
508 SETALPHA = lambda tup, a: tuple(list(NOALPHA(tup)) + [a])
509 ADDALPHA = lambda tup, a: tuple(list(tup) + [a])
512 out = list(tup[:])
513 r,g,b = tup[:3]
514 h,s,v = RGBtoHSV(r, g, b)
515 out[:3] = HSVtoRGB((h+0.5) % 1.0, s, v)
516 return tuple(out)
517
518
519 COLOR = collections.defaultdict(lambda:(.5, .5, .5))
520 for i,c in enumerate([(0,0,0), (1,0,0), (0,0,1), (0,1,0), (0,1,1), (1,1,0), (1,0,1)]):
521 COLOR[i] = c
522 offset = len(COLOR)
523 for i in xrange(20):
524 COLOR[offset+i] = HSVtoRGB((i*2.0/20)%1.0, (40.0-i)/40, (20.0+i)/40)
525 COLOR_CLASS = lambda c: ('modelGTK.class[%i]' % c, COLOR[c])
530 """Returns the list of all path components '/a/b/c' -> ['/', 'a', 'b', 'c']"""
531 l,r = os.path.split(s)
532 if (l == ''):
533 return [r]
534 elif (l == s):
535 return [s]
536 else:
537 lst = path_splits(l)
538 lst.append(r)
539 return lst
540
542 """Returns path unmodified if it already has an extension, otherwise appends ext."""
543 if os.path.splitext(path)[1]:
544 return path
545 else:
546 return path + ext
547
551 self.serial = 0
552 self.__get = get or self.get
553 self.__last_serial = -1
554 self.__last_args = None
555 self.__last_val = None
556 self.OnInvalidate = WeakEvent()
558 self.serial += 1
559 self.OnInvalidate(self)
561 if (self.serial != self.__last_serial) or (args != self.__last_args):
562 self.__last_val = self.__get(*args)
563 self.__last_serial = self.serial
564 self.__last_args = args
565 return self.__last_val
566 val = property(get_val)
568 raise Exception('abstract')
569
572 """"Overrides" the base dict so keys are looked up first here, then if not found, in base dict."""
573 - def __init__(self, base=None, *args, **kw):
574 dict.__init__(self, *args, **kw)
575 self.base = base or {}
587 - def get(self, key):
592
610
624
644
648
649 class NestedBreakException(Exception):
650 pass
651 try:
652 yield NestedBreakException
653 except NestedBreakException:
654 pass
655
656
657
658
659
660
661
662
663
664