Package teamwork :: Package widgets :: Package PsychGUI :: Package AgentWindow :: Module StatePane
[hide private]
[frames] | no frames]

Source Code for Module teamwork.widgets.PsychGUI.AgentWindow.StatePane

  1  import copy 
  2  from getpass import getuser 
  3  import time 
  4   
  5  from Tkinter import * 
  6  import Pmw 
  7  import tkMessageBox 
  8  from teamwork.widgets.multiscale import * 
  9  from teamwork.widgets.pmfScale import PMFScale 
 10  from teamwork.math.Keys import StateKey 
 11  from DynamicsDialog import DynamicsDialog 
 12  from teamwork.widgets.images import loadImages 
 13   
14 -class StateFrame(Pmw.ScrolledFrame):
15 """Frame to display the state of an entity 16 @ivar locked: C{True} iff the override widget displays a lock icon 17 @type locked: bool 18 """
19 - def __init__(self,parent,**kw):
20 # Set up images if available 21 self.images = loadImages({'lock': 'icons/lock.png', 22 'unlock': 'icons/lock-unlock.png', 23 'del': 'icons/globe--minus.png', 24 'add': 'icons/chart--plus.png', 25 'prob': 'icons/chart--arrow.png', 26 'elem': 'icons/globe--arrow.png', 27 'new': 'icons/globe--plus.png', 28 'tree': 'icons/application-tree.png'}) 29 optiondefs = ( 30 ('entity', None,Pmw.INITOPT), 31 ('generic',False,Pmw.INITOPT), 32 ('society',{},None), 33 ('expert',True,self.setExpert), 34 ('balloon', None, Pmw.INITOPT), 35 ) 36 self.locked = True 37 self.defineoptions(kw, optiondefs) 38 fr = Pmw.ScrolledFrame.__init__(self,parent) 39 self.parent = parent 40 self.interior().columnconfigure(1,weight=1) 41 if self['generic']: 42 # Dialog for getting new symbol name 43 self.createcomponent('newdialog',(),None,Pmw.PromptDialog,(parent,), 44 title='New feature',entryfield_labelpos='w', 45 label_text='Feature:', 46 defaultbutton = 0,buttons = ('OK', 'Cancel'), 47 command = self.newFeature).withdraw() 48 # Draw the feature deletion dialog box 49 self.createcomponent('confirmation',(),None, 50 Pmw.MessageDialog,(parent,), 51 title='Confirm Delete', 52 buttons=('Yes','No'), 53 defaultbutton='No', 54 message_justify='left').withdraw() 55 # Toolbar 56 self.createcomponent('toolbar',(),None,Frame,(self.interior(),), 57 bd=2,relief='raised') 58 self.component('toolbar').grid(row=1,column=0,columnspan=3,sticky='ew') 59 if self['generic']: 60 # Button for adding new state feature 61 button = Label(self.component('toolbar')) 62 if self.images.has_key('new'): 63 button.configure(bd=0,image=self.images['new']) 64 else: 65 button.configure(text='New') 66 button.bind('<ButtonRelease-1>',self.askNew) 67 button.pack(side='left',ipadx=5) 68 if self['balloon']: 69 self['balloon'].bind(button,'Create new state feature') 70 # Button for deleting state feature 71 button = Label(self.component('toolbar')) 72 if self.images.has_key('del'): 73 button.configure(bd=0,image=self.images['del']) 74 else: 75 button.configure(text='Delete') 76 button.bind('<ButtonRelease-1>',self.delete) 77 button.pack(side='left',ipadx=5) 78 if self['balloon']: 79 self['balloon'].bind(button,'Delete state feature') 80 # Button for overriding inherited state values 81 self.createcomponent('override',(),None,Label, 82 (self.component('toolbar'),)) 83 self.component('override').pack(side='right',ipadx=5) 84 self.component('override').bind('<Double-Button-1>',self.override) 85 # Button for adding element to distribution 86 self.createcomponent('element',(),None,Label,(self.component('toolbar'),)) 87 self.component('element').bind('<ButtonRelease-1>',self.addElement) 88 if self.images.has_key('add'): 89 self.component('element').configure(image=self.images['add'],bd=0) 90 else: 91 self.component('element').configure(text='Add element') 92 if self['balloon']: 93 self['balloon'].bind(self.component('element'), 94 'Add element to probability distribution') 95 # Button for toggling element/probability view of distribution 96 self.createcomponent('probabilistic',(),None,Label, 97 (self.component('toolbar'),)) 98 self.component('probabilistic').bind('<ButtonRelease-1>',self.toggle) 99 if self.images.has_key('prob'): 100 self.component('probabilistic').configure(image=self.images['prob']) 101 else: 102 self.component('probabilistic').configure(text='Show probabilities') 103 if self['balloon']: 104 self['balloon'].bind(self.component('probabilistic'), 105 'Show probability distribution') 106 # Button for viewing dynamics of a state feature 107 self.createcomponent('dynamics',(),None,Label, 108 (self.component('toolbar'),)) 109 self.component('dynamics').bind('<ButtonRelease-1>',self.viewDynamics) 110 try: 111 self.component('dynamics').configure(image=self.images['tree']) 112 except KeyError: 113 self.component('dynamics').configure(text='Dynamics') 114 if self['balloon']: 115 self['balloon'].bind(self.component('dynamics'), 116 'View dynamics of selected state feature') 117 # Add the scales already present 118 if self['generic']: 119 featureList = self['entity'].getAllFillers('state') 120 else: 121 featureList = self['entity'].getStateFeatures() 122 featureList.sort(lambda x,y:cmp(x.lower(),y.lower())) 123 for i in range(len(featureList)): 124 self.addFeature(featureList[i]) 125 self.selection = None 126 self.initialiseoptions()
127
128 - def toggle(self,event):
129 if self.selection is None: 130 tkMessageBox.showerror('No selection','Please select the state feature whose view you wish to change.') 131 return 132 widget = self.component(self.selection) 133 view = widget.cget('viewprobs') 134 if view: 135 if self.images.has_key('prob'): 136 self.component('probabilistic').configure(image=self.images['prob']) 137 else: 138 self.component('probabilistic').configure(text='Show probabilities') 139 if self['balloon']: 140 self['balloon'].bind(self.component('probabilistic'), 141 'Show probability distribution') 142 else: 143 if self.images.has_key('elem'): 144 self.component('probabilistic').configure(image=self.images['elem']) 145 else: 146 self.component('probabilistic').configure(text='Show elements') 147 if self['balloon']: 148 self['balloon'].bind(self.component('probabilistic'), 149 'Show elements of distribution') 150 widget.configure(viewprobs=not view)
151
152 - def setExpert(self):
153 if self['expert']: 154 self.component('element').pack(side='left',ipadx=5) 155 self.component('probabilistic').pack(side='left',ipadx=5) 156 self.component('dynamics').pack(side='left',ipadx=5) 157 else: 158 self.component('element').pack_forget() 159 self.component('probabilistic').pack_forget() 160 self.component('dynamics').pack_forget()
161
162 - def featureString(self,feature):
163 """Converts a feature name into a Tk-friendly label string 164 @type feature: C{str} 165 @rtype: C{str} 166 """ 167 return feature.replace('_',' ')
168
169 - def addFeature(self,feature,position=None):
170 """Draws the widget for a new state feature 171 """ 172 name = self.featureString(feature) 173 if feature in self['entity'].getStateFeatures(): 174 entity = self['entity'] 175 else: 176 other = self['entity'].getInheritor('state',feature) 177 entity = self['society'][other] 178 distribution = entity.getState(feature) 179 # Place this scale in the correct alphabetical position 180 if self['generic']: 181 featureList = self['entity'].getAllFillers('state') 182 else: 183 featureList = self['entity'].getStateFeatures() 184 featureList = filter(lambda f: f in self.components(),featureList) 185 featureList.append(feature) 186 featureList.sort(lambda x,y:cmp(x.lower(),y.lower())) 187 index = featureList.index(feature) 188 # Feature name widget 189 widget = self.createcomponent('%s-label' % (name),(),'label',Label, 190 (self.interior(),), 191 text=feature) 192 widget.bind('<ButtonRelease-1>',self.select) 193 widget.bind('<Double-Button-1>',self.viewDynamics) 194 widget.grid(row=index+2,column=0,sticky='we',padx=10) 195 # Sliders 196 widget = self.createcomponent(name,(),'scale',PMFScale, 197 (self.interior(),), 198 distribution=distribution, 199 hull_bd=2,hull_relief='groove', 200 hull_padx=5,hull_pady=5, 201 command = self.setValue, 202 ) 203 # if self['balloon']: 204 # self['balloon'].add(widget=widget,entity=self['entity'], 205 # key=feature) 206 widget.grid(row=index+2,column=1,sticky='ew') 207 # # Dynamics button 208 # widget = self.createcomponent('%s-dynamics' % (name),(),'dynamics', 209 # Button,(self.interior(),), 210 # command=lambda s=self,f=feature: 211 # s.viewDynamics(f)) 212 # try: 213 # widget.configure(image=self.images['tree']) 214 # except KeyError: 215 # widget.configure(text='Dynamics') 216 # widget.grid(row=index+2,column=2,sticky='ew') 217 index += 1 218 while index < len(featureList): 219 name = self.featureString(featureList[index]) 220 self.component('%s-label' % (name)).grid(row=index+2,column=0, 221 sticky='ew') 222 self.component(name).grid(row=index+2,column=1,sticky='ew') 223 # self.component('%s-dynamics' % (name)).grid(row=index+2,column=2, 224 # sticky='ew') 225 index += 1
226
227 - def select(self,event):
228 """Callback when clicking on a feature""" 229 for name in self.components(): 230 if event.widget is self.component(name): 231 feature = name[:-6] 232 break 233 if self.selection != feature: 234 # Only if selection is changing 235 palette = Pmw.Color.getdefaultpalette(self.component('hull')) 236 if self.selection: 237 # Deselect previous selection 238 widget = self.component('%s-label' % (self.selection)) 239 widget.configure(fg=palette['foreground'], 240 bg=palette['background'],bd=0) 241 # Selecting a new scale 242 self.selection = feature 243 scale = self.component(feature) 244 event.widget.configure(fg=palette['selectForeground'], 245 bg=palette['selectBackground'],bd=3) 246 # Update label and state of make probabilistic/deterministic 247 button = self.component('probabilistic') 248 if scale.cget('viewprobs'): 249 if self.images.has_key('elem'): 250 button.configure(image=self.images['elem']) 251 else: 252 button.configure(text='Show elements') 253 else: 254 if self.images.has_key('prob'): 255 button.configure(image=self.images['prob']) 256 else: 257 button.configure(text='Show probabilities') 258 # Adjust buttons whether this feature is inherited or not 259 if self['generic']: 260 if feature in self['entity'].getStateFeatures(): 261 self.locked = False 262 if self['entity'].getInheritor('state',feature,False): 263 # Inherited, but overridden 264 if self.images.has_key('unlock'): 265 self.component('override').configure(image=self.images['unlock']) 266 else: 267 self.component('override').configure(text='unlocked') 268 if self['balloon']: 269 self['balloon'].bind(self.component('override'), 270 'Double-click to restore inherited default value') 271 else: 272 # Inherited, but not overridden 273 self.locked = True 274 if self.images.has_key('lock'): 275 self.component('override').configure(image=self.images['lock']) 276 else: 277 self.component('override').configure(text='locked') 278 if self['balloon']: 279 self['balloon'].bind(self.component('override'),'Double-click to override inherited default value')
280
281 - def setValue(self,widget,value=None):
282 for feature in self.components(): 283 if widget is self.component(feature): 284 break 285 if value is None: 286 value = widget['distribution'] 287 if feature in self['entity'].getStateFeatures(): 288 self['entity'].setState(feature,value) 289 if self['generic']: 290 for other in self['society'].members(): 291 if other.name != self['entity'].name and \ 292 other.isSubclass(self['entity'].name) and \ 293 feature not in other.getStateFeatures() and \ 294 other.attributes.has_key('window'): 295 other.attributes['window'].component('State').set(feature,value)
296
297 - def update(self):
298 if self['generic']: 299 features = self['entity'].getAllFillers('state') 300 else: 301 features = self['entity'].getStateFeatures() 302 for name in self.components(): 303 # Find out which features we already have scales for 304 if self.componentgroup(name) == 'scale': 305 try: 306 features.remove(name) 307 self.set(name) 308 except ValueError: 309 # This feature no longer there, so we don't need scale 310 self.delete(feature=name,confirm=True) 311 # Add needed scales 312 for feature in features: 313 self.addFeature(feature) 314 self.set(feature)
315
316 - def set(self,feature,value=None):
317 name = self.featureString(feature) 318 widget = self.component(name) 319 if value is None: 320 if feature in self['entity'].getStateFeatures(): 321 entity = self['entity'] 322 else: 323 other = self['entity'].getInheritor('state',feature) 324 entity = self['society'][other] 325 value = entity.getState(feature) 326 widget['distribution'] = value 327 if self['generic']: 328 for name in self['society'].descendents(self['entity'].name): 329 other = self['society'][name] 330 if other.attributes.has_key('window') and \ 331 other.name != self['entity'].name and \ 332 feature not in other.getStateFeatures(): 333 other.attributes['window'].component('State').set(feature, 334 value)
335
336 - def delete(self,event=None,feature=None,confirm=None):
337 if feature is None: 338 feature = self.selection 339 if feature is None: 340 tkMessageBox.showerror('No selection','Please select the state feature to delete') 341 return 342 if confirm is None: 343 # Let's check whether there are other references to this feature 344 refs = self.checkDynamics(feature) 345 if len(refs) > 0: 346 msg = [] 347 for ref in refs: 348 msg.append('Effect of %s on %s\'s %s' % \ 349 (ref['action'],ref['entity'].name, 350 ref['feature'])) 351 msg.sort() 352 msg.insert(0,'') 353 msg.insert(0,'There are references to this state feature in '\ 354 'the internal dynamics that I do not know how to '\ 355 'remove:') 356 msg.append('') 357 msg.append('Please remove these references before deleting.') 358 tkMessageBox.showerror('Unable to delete state feature', 359 '\n'.join(msg)) 360 return 361 else: 362 refs = [] 363 # Check dynamics of this feature 364 if self['entity'].dynamics.has_key(feature) and \ 365 len(self['entity'].dynamics[feature]) > 0: 366 refs.append({'type':'dynamics','entity':self['entity']}) 367 # Check goals for references to this feature 368 for entity in self['society'].members(): 369 for goal in entity.getGoals(): 370 key = goal.toKey() 371 if isinstance(key,StateKey) and key['feature'] == feature: 372 if key['entity'] == self['entity'].name: 373 refs.append({'type':'goal','entity':entity, 374 'key':key,'goal':goal}) 375 elif entity.name == self['entity'].name and \ 376 key['entity'] == 'self': 377 refs.append({'type':'goal','entity':entity, 378 'key':key,'goal':goal}) 379 dialog = self.component('confirmation') 380 if confirm is None: 381 query = 'Are you sure you wish to delete this state feature?' 382 if len(refs) > 0: 383 query += '\nIf you do so, the following references will be removed:' 384 for ref in refs: 385 if ref['type'] == 'goal': 386 query += '\n\t%s\'s goal to %s' % \ 387 (ref['entity'].name,str(ref['goal'])) 388 elif ref['type'] == 'dynamics': 389 entity = ref['entity'] 390 query += '\n\tdynamics of %s' % \ 391 (', '.join(entity.dynamics[feature].keys())) 392 dialog.configure(message_text=query, 393 command=lambda c,s=self,f=feature:\ 394 s.delete(feature=f,confirm=c=='Yes')) 395 dialog.activate() 396 else: 397 dialog.deactivate() 398 if confirm: 399 name = self.featureString(feature) 400 # Update GUI 401 self.destroycomponent('%s-label' % (name)) 402 self.destroycomponent(name) 403 # self.destroycomponent('%s-dynamics' % (name)) 404 if self.selection == name: 405 # Deselect 406 self.selection = None 407 self.component('override').configure(image=None,text=None) 408 if self['balloon']: 409 self['balloon'].unbind(self.component('override')) 410 if feature in self['entity'].getStateFeatures(): 411 self['entity'].deleteState(feature) 412 # Delete referring goals 413 names = {} 414 for ref in refs: 415 if ref['type'] == 'goal': 416 names[ref['entity'].name] = True 417 del self['society'][ref['entity'].name].goals[ref['goal']] 418 for name in names.keys(): 419 self['society'][name].normalizeGoals() 420 try: 421 window = self['society'][name].attributes['window'] 422 window.component('Goals').recreate_goals() 423 except KeyError: 424 pass 425 # Delete any dynamics 426 try: 427 del self['entity'].dynamics[feature] 428 except KeyError: 429 pass 430 # Remove state feature from all inheriting subclasses 431 if self['generic']: 432 for name in self['society'].descendents(self['entity'].name): 433 other = self['society'][name] 434 if other.name != self['entity'].name and \ 435 feature not in other.getStateFeatures() and \ 436 other.attributes.has_key('window'): 437 other.attributes['window'].component('State').delete(feature=feature,confirm=True)
438
439 - def override(self,event):
440 feature = self.selection 441 if feature is None: 442 tkMessageBox.showerror('No selection','Please select the state feature whose default you wish to override') 443 return 444 widget = self.component(self.featureString(feature)) 445 button = self.component('override') 446 # Figure out whether we're overriding or underriding 447 if self.locked: 448 value = self['entity'].getCumulativeState(feature) 449 self['entity'].setState(feature,value) 450 widget.configure(state='normal') 451 try: 452 button.configure(image=self.images['lock']) 453 except: 454 button.configure(text='Restore') 455 else: 456 self['entity'].deleteState(feature) 457 widget['distribution'] = self['entity'].getCumulativeState(feature) 458 widget.configure(state='disabled') 459 try: 460 button.configure(image=self.images['unlock']) 461 except: 462 button.configure(text='Override') 463 self.locked = not self.locked
464
465 - def addElement(self,event):
466 if self.selection is None: 467 tkMessageBox.showerror('No selection','Please select the state feature whose view you wish to add the element to.') 468 else: 469 widget = self.component(self.featureString(self.selection)) 470 widget.addElement()
471
472 - def askNew(self,event):
473 """Prompts user for name of new state feature 474 """ 475 self.component('newdialog').activate(geometry = 'centerscreenalways')
476
477 - def newFeature(self,button):
478 self.component('newdialog').deactivate() 479 feature = self.component('newdialog').getvalue() 480 self.component('newdialog').clear() 481 self.component('newdialog_entry').focus_set() 482 if button == 'OK': 483 if len(feature) > 0: 484 self['entity'].setState(feature,0.) 485 self.addFeature(feature) 486 if self['generic']: 487 for name in self['society'].descendents(self['entity'].name): 488 other = self['society'][name] 489 if other.name != self['entity'].name and \ 490 feature not in other.getStateFeatures() and \ 491 other.attributes.has_key('window'): 492 widget = other.attributes['window'].component('State') 493 widget.addFeature(feature) 494 else: 495 tkMessageBox.showerror('Illegal Feature Name', 496 'Please enter the name of the new state feature before adding it.')
497
498 - def setState(self,state):
499 """Sets the state Tk configuration feature on this widget""" 500 for feature in self['entity'].getStateFeatures(): 501 self.component(self.featureString(feature)).configure(state=state)
502
503 - def viewDynamics(self,event,feature=None):
504 """Pop up a modal dialog to view/edit dynamics of the given state feature 505 @param feature: the state feature whose dynamics should be displayed 506 @type feature: str 507 """ 508 if feature is None: 509 feature = self.selection 510 if feature is None: 511 tkMessageBox.showerror('No selection','Please select the state feature whose dynamics you wish to view') 512 return 513 if not self['entity'].dynamics.has_key(feature): 514 self['entity'].dynamics[feature] = {} 515 dynamics = self['entity'].dynamics[feature] 516 for action,function in dynamics.items(): 517 if isinstance(function,str): 518 dynamics[action] = self['entity'].society[function].dynamics[feature][action] 519 roles = ['actor','object','self'] + self['entity'].relationships.keys() 520 roles.sort(lambda x,y:cmp(x.lower(),y.lower())) 521 for entity in self['entity'].getEntityBeliefs(): 522 for newRole in entity.relationships.keys(): 523 if not newRole in roles: 524 roles.append(newRole) 525 if self['generic']: 526 society = self['entity'].hierarchy 527 else: 528 society = None 529 self.dialog = DynamicsDialog(self.parent, 530 dynamics=copy.deepcopy(dynamics), 531 expert=self['expert'], 532 buttons=('OK','Cancel'), 533 defaultbutton='OK', 534 title='Dynamics of %s of %s' % \ 535 (feature,self['entity'].name), 536 feature=feature, 537 editor_roles=roles, 538 command=self.changeDynamics, 539 society=society, 540 ) 541 self.dialog.activate()
542
543 - def changeDynamics(self,button):
544 """Upon clicking OK in dynamics dialog, change entity's dynamics accordingly 545 @param button: the button pressed to close the dialog (generated by Tk callback) 546 @type button: str 547 """ 548 if button == 'OK': 549 try: 550 dynamics = self['entity'].dynamics[self.dialog['feature']] 551 except KeyError: 552 self['entity'].dynamics[self.dialog['feature']] = {} 553 dynamics = self['entity'].dynamics[self.dialog['feature']] 554 dynamics.clear() 555 dynamics.update(self.dialog['dynamics']) 556 self.dialog.deactivate()
557
558 - def checkDynamics(self,toDelete):
559 """Identifies any references to the given state feature in dynamics 560 """ 561 refs = [] 562 descendents = self['society'].descendents(self['entity'].name) 563 for entity in self['society'].members(): 564 for feature,table in entity.dynamics.items(): 565 for actType,dynamics in table.items(): 566 keys = {} 567 # Iterate through all nodes 568 remaining = [dynamics.getTree()] 569 while len(remaining) > 0: 570 tree = remaining.pop() 571 if tree.isLeaf(): 572 # Check effect row for refs 573 row = tree.getValue().values()[0] 574 if row.sourceKey['feature'] == toDelete: 575 keys[row.sourceKey] = True 576 if isinstance(row.deltaKey,StateKey) and \ 577 row.deltaKey['feature'] == toDelete: 578 keys[row.deltaKey] = True 579 else: 580 # Check branch for refs 581 remaining += tree.children() 582 if not tree.isProbabilistic(): 583 for plane in tree.split: 584 for key in plane.weights.specialKeys: 585 if isinstance(key,StateKey) and \ 586 key['feature'] == toDelete: 587 keys[key] = True 588 for key in keys.keys(): 589 entry = {'type':'dynamics','entity':entity, 590 'feature':feature,'key':key,'action':actType} 591 if key['entity'] == 'self': 592 # Check whether I'm someone who has the feature 593 if entity.name in descendents and \ 594 key['feature'] != feature: 595 # Unless it's the dynamics of that feature 596 refs.append(entry) 597 elif key['entity'] == 'actor': 598 # Check whether any possible actor has the feature 599 for other in descendents: 600 agent = self['society'][other] 601 for action in sum(agent.actions.getOptions(), 602 []): 603 if action['type'] == actType: 604 refs.append(entry) 605 break 606 else: 607 # Didn't find any, so go to next agent 608 continue 609 break 610 elif key['entity'] == 'object': 611 # Check whether any possible objects 612 for other in self['society'].members(): 613 for action in sum(other.actions.getOptions(), 614 []): 615 if action['type'] == actType and \ 616 action['object'] in descendents: 617 refs.append(entry) 618 break 619 else: 620 continue 621 break 622 else: 623 # Relationship 624 for other in entity.relationships[key['entity']]: 625 if other in descendents: 626 refs.append(entry) 627 break 628 return refs
629