Package teamwork :: Package widgets :: Module multiscale
[hide private]
[frames] | no frames]

Source Code for Module teamwork.widgets.multiscale

  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
31 -class MultiScale(Pmw.MegaWidget):
32 - def __init__(self,parent=None,**kw):
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 ## widget.add('Delete',command=self.deleteScale) 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
72 - def setState(self):
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
91 - def setForeground(self):
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
105 - def setBackground(self):
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 # For some reason, this state can be None some times... this is bad 132 if not state: 133 state = 'disabled' 134 widget.configure(state=state) 135 self['command'] = cmd
136
137 - def labels(self):
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 # Check whether this widget already exists (if so, nothing to do) 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 # Set the state of the widgets correctly, according to the number of 181 # sliders 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
192 - def reorder(self):
193 labels = map(lambda n:n[6:],self.labels()) 194 # Undraw everything 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 # (re)Position all of the sliders 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 ## try: 214 ## widget = widget.component('Delete') 215 ## except KeyError: 216 ## widget = None 217 ## if widget: 218 ## if len(labels) < 2: 219 ## widget.configure(state=DISABLED) 220 ## else: 221 ## widget.configure(state=NORMAL) 222
223 - def deleteScale(self,label=None):
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 # Check whether we're down to only one scale 236 labels = self.labels() 237 if len(labels) == 1: 238 widget = self.component(labels[0]) 239 widget.configure(state='disabled') 240 return label
241
242 - def generateName(self,label):
243 """Returns canonical name for this label's component scale widget""" 244 ## if isinstance(label,float): 245 ## return 'scale %5.3f' % (label) 246 ## else: 247 return 'scale %s' % (label)
248
249 - def updateValue(self,label,value):
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
258 - def setExpert(self):
259 for label in self.labels(): 260 widget = self.component(label) 261 widget.configure(showvalue=self['expert'])
262
263 -class DistributionScale(MultiScale):
264 - def __init__(self,parent=None,**kw):
265 optiondefs = ( 266 ('distribution', None, self.update), 267 ) 268 self.defineoptions(kw,optiondefs) 269 MultiScale.__init__(self,parent) 270 box = self.component('box') 271 ## box.add('Add',command=self.addScale) 272 ## box.add('Replace',command=self.replaceValue) 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
289 - def setState(self):
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
299 - def setForeground(self):
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 # Must be initialization; haven't created scale yet 309 pass
310
311 - def setBackground(self):
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 # Must be initialization; haven't created scale yet 321 pass
322
323 - def update(self):
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
369 - def reorder(self):
370 MultiScale.reorder(self) 371 index = len(self.labels())+1 372 self.component('newValue').grid(row=index, 373 columnspan=2,column=0,sticky='EW')
374
375 - def updateNew(self,value):
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
385 - def updateValue(self,label,value):
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 # Must re-normalize 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 # Previously 0 407 self['distribution'][row] = factor 408 self.set(row,self['distribution'][row]) 409 if self['command']: 410 self['command'](label,value)
411
412 - def addScale(self,label=None):
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
436 - def replaceValue(self):
437 self.deleteScale() 438 self.addScale()
439
440 - def deleteScale(self,label=None):
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
462 - def labelValue(self,value):
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
473 - def setExpert(self):
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 # Haven't drawn this scale yet 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
508 - def toggleDetails(self,update=True):
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