1 from teamwork.utils.FriendlyFloat import simpleFloat
2 from Tkinter import *
3 import Pmw
4
5 -def blend(color1,color2,percentage):
6 """
7 Generates a color a given point between two extremes. If the percentage is less than 0, then color1 is returned. If over 1, then color2 is returned.
8 @param color1,color2: the two colors representing the opposite ends of the spectrum
9 @type color1,color2: str
10 @param percentage: the percentage of the spectrum between the two where this point is (0. represents color1, 1. represents color2)
11 @type percentage: float
12 @return: string RGB blending two colors (string RGB) by float percent
13 @rtype: str
14 """
15 red1 = int(color1[1:3],16)
16 red2 = int(color2[1:3],16)
17 green1 = int(color1[3:5],16)
18 green2 = int(color2[3:5],16)
19 blue1 = int(color1[5:7],16)
20 blue2 = int(color2[5:7],16)
21 hi = {'r':max(red1,red2),'g':max(green1,green2),'b':max(blue1,blue2)}
22 lo = {'r':min(red1,red2),'g':min(green1,green2),'b':min(blue1,blue2)}
23 red = (1.-percentage)*float(red1) + percentage*float(red2)
24 red = min(max(red,lo['r']),hi['r'])
25 green = (1.-percentage)*float(green1) + percentage*float(green2)
26 green = min(max(green,lo['g']),hi['g'])
27 blue = (1.-percentage)*float(blue1) + percentage*float(blue2)
28 blue = min(max(blue,lo['b']),hi['b'])
29 return '#%02x%02x%02x' % (red,green,blue)
30
33 optiondefs = (
34 ('expert', 0, self.setExpert),
35 ('orient', 'horizontal', Pmw.INITOPT),
36 ('lo', 0., Pmw.INITOPT),
37 ('hi', 1., Pmw.INITOPT),
38 ('resolution', 0.01, Pmw.INITOPT),
39 ('locolor', '#ffffff', Pmw.INITOPT),
40 ('hicolor', '#000000', Pmw.INITOPT),
41 ('command', None, None),
42 ('disabledforeground',None,None),
43 ('foreground',None,self.setForeground),
44 ('background',None,self.setBackground),
45 ('sort', cmp, Pmw.INITOPT),
46 ('state', 'normal', self.setState),
47 ('toggle', None, Pmw.INITOPT),
48 )
49 self.defineoptions(kw,optiondefs)
50 self.selected = StringVar()
51 Pmw.MegaWidget.__init__(self,parent)
52 self.span = float(self['hi'])-float(self['lo'])
53
54 widget = self.createcomponent('box',(),None,Pmw.ButtonBox,
55 (self.interior(),),
56 )
57
58 widget.grid(columnspan=2,row=0,column=0)
59
60 if self['orient'] == 'horizontal':
61 self.interior().grid_columnconfigure(0,weight=0)
62 self.interior().grid_columnconfigure(1,weight=1)
63 self.initialiseoptions(MultiScale)
64 widget = Button(self.interior())
65 if self['disabledforeground'] is None:
66 self['disabledforeground'] = widget.cget('disabledforeground')
67 if self['foreground'] is None:
68 self['foreground'] = widget.cget('foreground')
69 if self['background'] is None:
70 self['background'] = widget.cget('background')
71
73 """Sets the state to the given value (NORMAL/DISABLED/?)"""
74 if not isinstance(self,DistributionScale) and self['state'] == 'disabled':
75 raise UserWarning
76 widget = self.component('box')
77 for button in widget.components():
78 if not button in ['frame','hull','label']:
79 button = widget.component(button)
80 button.configure(state=self['state'])
81 if self['state'] == 'disabled':
82 fg = self['disabledforeground']
83 else:
84 fg = self['foreground']
85 labels = self.labels()
86 if len(labels) > 1:
87 for label in labels:
88 self.component(label).configure(state=self['state'],
89 foreground=fg)
90
92 if self['state'] == 'disabled':
93 fg = self['disabledforeground']
94 else:
95 fg = self['foreground']
96 widget = self.component('box')
97 for label in widget.components():
98 if widget.componentgroup(label) == 'Button':
99 widget.component(label).configure(fg=fg)
100 labels = self.labels()
101 if len(labels) > 1:
102 for label in labels:
103 self.component(label).configure(foreground=fg)
104
106 if self['state'] == 'disabled':
107 bg = self['disabledbackground']
108 else:
109 bg = self['background']
110 self.component('hull').configure(bg=self['background'])
111 widget = self.component('box')
112 for label in widget.components():
113 if widget.componentgroup(label) == 'Button':
114 widget.component(label).configure(bg=bg)
115 labels = self.labels()
116 if len(labels) > 1:
117 for label in labels:
118 self.component(label).configure(bg=bg)
119
120 - def set(self,label,value):
121 """Sets the scale named 'label' to the given value"""
122 try:
123 widget = self.component(self.generateName(label))
124 except KeyError:
125 widget = self.addScale(label,value)
126 cmd = self['command']
127 self['command'] = None
128 state = widget.cget('state')
129 widget.configure(state='normal')
130 widget.set(value)
131
132 if not state:
133 state = 'disabled'
134 widget.configure(state=state)
135 self['command'] = cmd
136
138 """Returns all of the possible scale labels"""
139 labels = filter(lambda name:name[:5] == 'scale',self.components())
140 if self['sort']:
141 labels.sort(self['sort'])
142 return labels
143
144 - def get(self,label):
145 """Returns the value of the scale named 'label'"""
146 widget = self.component(self.generateName(label))
147 return widget.get()
148
149 - def addScale(self,label,value=None,**kw):
150 """Adds a new scale to this widget
151 @param label: The label to be attached to this scale
152 @param value: The initial value to set the scale to
153 @param kw: Additional keyword arguments to pass to the Scale widget
154 """
155 widgetName = self.generateName(label)
156 try:
157
158 return self.component(widgetName)
159 except KeyError:
160 pass
161 widget = self.createcomponent(widgetName,
162 (),'scales',Scale,
163 (self.interior(),),
164 orient=self['orient'],
165 label=label,
166 from_=self['lo'],to=self['hi'],
167 tickinterval=1.,
168 resolution=self['resolution'],
169 command=lambda value,s=self,l=label:\
170 s.updateValue(l,value),
171 **kw
172 )
173 selector = self.createcomponent('select%s' % (label),
174 (),'radios',Radiobutton,
175 (self.interior(),),
176 value=label,
177 variable=self.selected)
178 if value:
179 self.set(label,value)
180
181
182 labels = self.labels()
183 if len(labels) == 1:
184 widget.configure(state='disabled')
185 elif len(labels) == 2:
186 self.configure(state='normal')
187 self.reorder()
188 self.selected.set(labels[0][6:])
189 self.setExpert()
190 return widget
191
193 labels = map(lambda n:n[6:],self.labels())
194
195 for name in self.components():
196 if name[:5] == 'scale' or name[:6] == 'select' or name == 'box':
197 widget = self.component(name)
198 widget.grid_forget()
199
200 for index in range(len(labels)):
201 widget = self.component('select%s' % (labels[index]))
202 if self['orient'] == 'horizontal':
203 widget.grid(row=index,column=0,sticky='')
204 else:
205 widget.grid(column=index,row=0,sticky='')
206 widget = self.component(self.generateName(labels[index]))
207 if self['orient'] == 'horizontal':
208 widget.grid(row=index,column=1,sticky='EW')
209 else:
210 widget.grid(column=index,row=1,sticky='NS')
211 widget = self.component('box')
212 widget.grid(row=len(labels),columnspan=2,column=0,sticky='EW')
213
214
215
216
217
218
219
220
221
222
224 """Callback for removing the selected scale"""
225 if label is None:
226 label = self.selected.get()
227 self.destroycomponent('select%s' % (label))
228 self.destroycomponent(self.generateName(label))
229 self.reorder()
230 try:
231 widget = self.component('select%s' % (self.labels()[0][6:]))
232 widget.invoke()
233 except IndexError:
234 pass
235
236 labels = self.labels()
237 if len(labels) == 1:
238 widget = self.component(labels[0])
239 widget.configure(state='disabled')
240 return label
241
243 """Returns canonical name for this label's component scale widget"""
244
245
246
247 return 'scale %s' % (label)
248
250 """Callback invoked whenever a scale value changes"""
251 widget = self.component(self.generateName(label))
252 percent = (float(value)-float(self['lo']))/self.span
253 widget.configure(troughcolor=blend(self['locolor'],self['hicolor'],
254 percent))
255 if self['command']:
256 self['command'](label,value)
257
259 for label in self.labels():
260 widget = self.component(label)
261 widget.configure(showvalue=self['expert'])
262
265 optiondefs = (
266 ('distribution', None, self.update),
267 )
268 self.defineoptions(kw,optiondefs)
269 MultiScale.__init__(self,parent)
270 box = self.component('box')
271
272
273 widget = self.createcomponent('newValue',(),None,Scale,
274 (self.interior(),),
275 orient='horizontal',
276 tickinterval=1.,
277 resolution=self['resolution'],
278 from_=-1.,to=1.,
279 showvalue=1,
280 command=self.updateNew,
281 )
282 self.details = True
283 if self['distribution'] and len(self['distribution']) == 1:
284 self.toggleDetails()
285 else:
286 self.update()
287 self.initialiseoptions(DistributionScale)
288
290 """Sets the state to the given value (NORMAL/DISABLED/?)"""
291 MultiScale.setState(self)
292 widget = self.component('newValue')
293 widget.configure(state=self['state'])
294 if self['state'] == 'disabled':
295 widget.configure(foreground=self['disabledforeground'])
296 else:
297 widget.configure(foreground=self['foreground'])
298
300 MultiScale.setForeground(self)
301 if self['state'] == 'disabled':
302 fg = self['disabledforeground']
303 else:
304 fg = self['foreground']
305 try:
306 self.component('newValue').configure(foreground=fg)
307 except KeyError:
308
309 pass
310
312 MultiScale.setBackground(self)
313 if self['state'] == 'disabled':
314 bg = self['disabledbackground']
315 else:
316 bg = self['background']
317 try:
318 self.component('newValue').configure(bg=bg)
319 except KeyError:
320
321 pass
322
324 """Redraws the scales to correspond to the current distribution"""
325 if len(self['distribution']) > 1 or self.details:
326 widget = self.component('box')
327 try:
328 widget.component('Replace')
329 except KeyError:
330 widget.insert('Replace',0,command=self.replaceValue)
331 widget.insert('Delete',0,command=self.deleteScale)
332 widget.insert('Add',0,command=self.addScale)
333 valid = []
334 for value,prob in self['distribution'].items():
335 try:
336 widget = self.component(self.generateName(value))
337 except KeyError:
338 widget = self.addScale(value)
339 self.set(value,prob)
340 valid.append(widget)
341 for label in self.components():
342 if label[:5] == 'scale':
343 widget = self.component(label)
344 if not widget in valid:
345 self.destroycomponent(label)
346 self.reorder()
347 else:
348 widget = self.component('newValue')
349 state = widget.cget('state')
350 widget.configure(state='normal')
351 value = self['distribution'].keys()[0]
352 widget.set(value)
353 if state != 'normal':
354 widget.configure(state=state)
355 widget.grid(row=0,columnspan=2,column=0,sticky='EW')
356 for name in self.components():
357 if self.componentgroup(name) in ['scales','radios']:
358 self.component(name).grid_forget()
359 widget = self.component('box')
360 widget.grid(row=1,columnspan=2,column=0,sticky='EW')
361 try:
362 widget.component('Delete')
363 widget.delete('Delete')
364 widget.delete('Add')
365 widget.delete('Replace')
366 except KeyError:
367 pass
368
374
376 widget = self.component('newValue')
377 widget.configure(troughcolor=blend('#ff0000','#00ff00',
378 (float(value)+1.)/2.))
379 if not self.details:
380 self['distribution'].clear()
381 self['distribution'][float(value)] = 1.
382 if self['command']:
383 self['command'](None,value)
384
386 """Callback invoked whenever a scale value changes"""
387 MultiScale.updateValue(self,label,value)
388 epsilon = self['distribution'].epsilon
389 if abs(self['distribution'][label]-float(value)) > epsilon:
390
391 try:
392 factor = (1.-float(value))/(1.-self['distribution'][label])
393 except ZeroDivisionError:
394 try:
395 factor = (1.-float(value))/\
396 float(len(self['distribution'])-1)
397 except ZeroDivisionError:
398 factor = 1.
399 for row,prob in self['distribution'].items():
400 if row == label:
401 self['distribution'][row] = float(value)
402 else:
403 if self['distribution'][row]:
404 self['distribution'][row] *= factor
405 else:
406
407 self['distribution'][row] = factor
408 self.set(row,self['distribution'][row])
409 if self['command']:
410 self['command'](label,value)
411
413 if label is None:
414 label = self.component('newValue').get()
415 if not self['distribution'].has_key(label):
416 if len(self['distribution']) > 0:
417 self['distribution'][label] = 0.
418 else:
419 self['distribution'][label] = 1.
420 if len(self['distribution']) > 1:
421 if self['toggle']:
422 widget = self['toggle']
423 else:
424 try:
425 widget = self.component('box').button('Make Deterministic')
426 except ValueError:
427 box = self.component('box')
428 widget = box.add('Make Deterministic',
429 command=self.toggleDetails,width=15)
430 widget.configure(state='disabled')
431 self.details = True
432 widget = MultiScale.addScale(self,label,self['distribution'][label])
433 widget.configure(label=self.labelValue(label))
434 return widget
435
439
441 """Removes the selected value and re-normalizes"""
442 if label is None:
443 label = float(self.selected.get())
444 MultiScale.deleteScale(self,label)
445 mass = self['distribution'][label]
446 total = round(1.0 - mass,10)
447 del self['distribution'][label]
448 for row,prob in self['distribution'].items():
449 try:
450 ratio = prob/total
451 except ZeroDivisionError:
452 ratio = 1./float(len(self['distribution']))
453 self['distribution'][row] += mass*ratio
454 self.set(row,self['distribution'][row])
455 if len(self['distribution']) == 1:
456 if self['toggle']:
457 widget = self['toggle']
458 else:
459 widget = self.component('box').button('Make Deterministic')
460 widget.configure(state='normal')
461
463 """Returns the expert-sensitive label for the given value"""
464 try:
465 num = float(value)
466 except ValueError:
467 return value
468 if self['expert']:
469 return '%4.2f' % (num)
470 else:
471 return simpleFloat(num)
472
474 """Configure the scales according to the current expert mode"""
475 if self.details:
476 for value in self['distribution'].keys():
477 try:
478 widget = self.component(self.generateName(value))
479 widget.configure(showvalue=self['expert'])
480 widget.configure(label=self.labelValue(value))
481 except KeyError:
482
483 pass
484 widget = self.component('newValue')
485 widget.configure(showvalue=self['expert'])
486 if self['expert']:
487 widget.configure(resolution=self['resolution'])
488 if self['toggle'] is None:
489 box = self.component('box')
490 try:
491 widget = box.add('Make Deterministic',
492 command=self.toggleDetails,width=15)
493 except ValueError:
494 widget = None
495 if widget:
496 self.details = True
497 self.toggleDetails(False)
498 else:
499 widget.configure(resolution=0.1)
500 if self['toggle'] is None:
501 box = self.component('box')
502 try:
503 box.delete('Make Deterministic')
504 except ValueError:
505 pass
506 MultiScale.setExpert(self)
507
509 self.details = not self.details
510 if self['toggle']:
511 widget = self['toggle']
512 else:
513 try:
514 widget = self.component('box').button('Make Deterministic')
515 except ValueError:
516 widget = None
517 if widget:
518 if self.details:
519 widget.configure(text='Make Deterministic')
520 else:
521 widget.configure(text='Make Probabilistic')
522 if update:
523 self.update()
524
525 if __name__ == '__main__':
526 import unittest
527 from teamwork.test.widgets.testMultiscale import *
528
529 unittest.main()
530