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

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

  1  import copy 
  2  from Tkinter import * 
  3  import Pmw 
  4  import tkMessageBox 
  5  from DynamicsDialog import DynamicsDialog 
  6  from teamwork.action.PsychActions import Action 
  7  from teamwork.agent.Generic import GenericModel 
  8  from teamwork.widgets.images import loadImages 
  9   
10 -class ActionFrame(Pmw.ScrolledFrame):
11 """Frame to define and view the entity's available actions 12 """
13 - def __init__(self,parent,**kw):
14 self.images = loadImages({'add': 'icons/hammer--plus.png', 15 'del': 'icons/hammer--minus.png', 16 'group': 'icons/hammer-screwdriver.png', 17 'forbid': 'icons/slash.png', 18 'allow': 'icons/tick.png', 19 'tree': 'icons/application-tree.png'}) 20 palette = Pmw.Color.getdefaultpalette(parent) 21 optiondefs = ( 22 ('entity', None, Pmw.INITOPT), 23 ('expert',True,None), 24 ('balloon',None, None), 25 ('generic',False,Pmw.INITOPT), 26 ('command',None, None), 27 ('society',{},None), 28 ('entities',{},None), 29 ) 30 self.selection = {} 31 self.defineoptions(kw, optiondefs) 32 Pmw.ScrolledFrame.__init__(self,parent) 33 toolbar = Frame(self.interior(),bd=2,relief='raised') 34 self.interior().grid_columnconfigure(0,weight=1) 35 toolbar.grid(row=0,column=0,sticky='ew') 36 if self['generic']: 37 # Dialog for creating new action 38 self.createcomponent('dialog',(),None,Pmw.Dialog,(self.interior(),), 39 buttons=('OK','Cancel'),defaultbutton=0, 40 command=self.add) 41 self.component('dialog').withdraw() 42 # Type selector 43 b = self.createcomponent('type',(),None,Pmw.ComboBox, 44 (self.component('dialog').interior(),), 45 labelpos='n',label_text='Type', 46 autoclear=True,history=True,unique=True, 47 ) 48 b.pack(side='left',fill='both') 49 b = self.createcomponent('object',(),None,Pmw.ComboBox, 50 (self.component('dialog').interior(),), 51 labelpos='n',label_text='Object', 52 entry_state='disabled', 53 entry_disabledforeground=palette['foreground'], 54 entry_disabledbackground=palette['background'], 55 ) 56 b.pack(side='left',fill='both') 57 # Button for adding new actions 58 button = Label(toolbar) 59 button.bind('<ButtonRelease-1>',self.promptNew) 60 try: 61 button.configure(image=self.images['add']) 62 except KeyError: 63 button.configure(text='Add') 64 button.pack(side='left',ipadx=5,ipady=3) 65 if self['balloon']: 66 self['balloon'].bind(button,'Add new action') 67 # Button for deleting actions 68 button = Label(toolbar) 69 button.bind('<ButtonRelease-1>',self.delete) 70 try: 71 button.configure(image=self.images['del']) 72 except KeyError: 73 button.configure(text='Delete') 74 button.pack(side='left',ipadx=5,ipady=3) 75 if self['balloon']: 76 self['balloon'].bind(button,'Delete selected actions') 77 # Button for creating concurrent actions 78 button = Label(toolbar) 79 button.bind('<ButtonRelease-1>',self.group) 80 try: 81 button.configure(image=self.images['group']) 82 except KeyError: 83 button.configure(text='Group') 84 button.pack(side='left',ipadx=5,ipady=3) 85 if self['balloon']: 86 msg = 'Groups the selected actions into a single, concurrent '\ 87 'decision that takes place in a single time step.' 88 self['balloon'].bind(button,msg) 89 # Button for allowing actions 90 button = Label(toolbar) 91 button.bind('<ButtonRelease-1>',self.selectAll) 92 try: 93 button.configure(image=self.images['allow']) 94 except KeyError: 95 button.configure(text='Allow') 96 button.pack(side='left',ipadx=5,ipady=3) 97 if self['balloon']: 98 self['balloon'].bind(button,'Activate all actions') 99 # Button for allowing actions 100 button = Label(toolbar) 101 button.bind('<ButtonRelease-1>',self.deselectAll) 102 try: 103 button.configure(image=self.images['forbid']) 104 except KeyError: 105 button.configure(text='Forbid') 106 button.pack(side='left',ipadx=5,ipady=3) 107 if self['balloon']: 108 self['balloon'].bind(button,'Deactivate all actions') 109 # Frame for containing action buttons 110 self.createcomponent('options',(),None,Frame,(self.interior(),)) 111 self.component('options').grid(row=1,column=0,sticky='wnes', 112 padx=10,pady=10) 113 self.variables = {} 114 self.drawActions() 115 self.initialiseoptions()
116
117 - def getOptions(self):
118 if isinstance(self['entity'],GenericModel): 119 return self['entity'].getAllFillers('action') 120 else: 121 return self['entity'].actions.getOptions()
122
123 - def setState(self,state):
124 for action in self.getOptions(): 125 name = '%s %s' % (self['entity'].ancestry(),str(action)) 126 widget = self.component(name) 127 widget.configure(state=state)
128
129 - def getActions(self):
130 """ 131 @return: all of the actions selected 132 @rtype: L{Action<teamwork.action.PsychActions.Action>}[][] 133 """ 134 options = [] 135 for action in self.getOptions(): 136 if self.variables[str(action)].get(): 137 options.append(action) 138 return options
139
140 - def selectAll(self,event=None):
141 for action in self.getOptions(): 142 self.selectAction(action,True)
143
144 - def deselectAll(self,event=None):
145 for action in self.getOptions(): 146 self.selectAction(action,False)
147
148 - def delete(self,event=None):
149 undeleted = [] 150 if tkMessageBox.askyesno('Confirm Delete','Are you sure you want to permanently delete the selected action(s) from this generic society? If you simply want to omit the actions from the instantiated scenario, you need only de-select them before invoking the wizard.'): 151 deleted = [] 152 society = self['entity'].hierarchy 153 descendents = society.descendents(self['entity'].name) 154 for option in self.getOptions(): 155 if self.selection.has_key(str(option)): 156 try: 157 self['entity'].actions.extras.remove(option) 158 del self.selection[str(option)] 159 del self.variables[str(option)] 160 deleted.append(option) 161 if len(option) == 1: 162 # Remove goals that refer to this action 163 for name,agent in society.items(): 164 goals = agent.getGoals() 165 change = False 166 for goal in goals[:]: 167 if goal.type == 'actActor': 168 if goal.entity[-1] in descendents and \ 169 goal.key == option[0]['type']: 170 goals.remove(goal) 171 change = True 172 if change: 173 agent.setGoals(goals) 174 if agent.attributes.has_key('window'): 175 widget = agent.attributes['window'].component('Goals') 176 widget.recreate_goals() 177 # Remove action from choices 178 widget.selectEntity() 179 except ValueError: 180 undeleted.append(str(option)) 181 for option in deleted: 182 self.deleteAction(str(option)) 183 if len(undeleted) > 0: 184 tkMessageBox.showwarning(title='Unable to Delete',message='The following actions cannot be deleted, as they are inherited from ancestor classes: %s' % ', '.join(undeleted))
185
186 - def deleteAction(self,action):
187 """Deletes all widgets (in this window and in descendent windows) associated with the given action 188 @type action: str 189 """ 190 for prefix in ['Dynamics','Select']: 191 self.destroycomponent('%s %s' % (prefix,action)) 192 for name in self.components(): 193 if self.componentgroup(name) == action: 194 self.destroycomponent(name) 195 society = self['entity'].hierarchy 196 descendents = society.descendents(self['entity'].name) 197 for name in descendents: 198 # Remove from action pane of descendents 199 if name != self['entity'].name: 200 agent = society[name] 201 if agent.attributes.has_key('window'): 202 window = agent.attributes['window'] 203 widget = window.component('Actions') 204 if widget.selection.has_key(action): 205 del widget.selection[action] 206 for prefix in ['Dynamics','Select']: 207 try: 208 widget.destroycomponent('%s %s' % (prefix,action)) 209 except KeyError: 210 # this happens because it's already gone 211 pass 212 for name in widget.components(): 213 if widget.componentgroup(name) == action: 214 widget.destroycomponent(name)
215 216
217 - def promptNew(self,event):
218 self.updateTypes() 219 self.component('object').insert(0,'None') 220 self.component('object').selectitem(0,setentry=1) 221 self.updateObjects() 222 self.component('dialog').activate(geometry='centerscreenalways')
223
224 - def add(self,button):
225 self.component('dialog').deactivate() 226 if button == 'OK': 227 verb = self.component('type').get() 228 if len(verb) > 0: 229 action = Action({'actor':self['entity'].name,'type':verb}) 230 obj = self.component('object').get() 231 if self.objects[obj]: 232 action.update(self.objects[obj]) 233 self['entity'].actions.directAdd([action]) 234 self.redrawDescendents() 235 self.component('type_entryfield').clear() 236 else: 237 tkMessageBox.showerror('Illegal Action','Action type is empty.')
238
239 - def group(self,event=None):
240 # Accumulate individual actions selected (avoiding duplicates) 241 selected = {} 242 for original in self['entity'].actions.getOptions(): 243 if self.selection.has_key(str(original)): 244 for action in original: 245 selected[action] = True 246 # Generate concurrent actions 247 if len(selected) < 2: 248 tkMessageBox.showerror('Illegal Selection','You must select multiple actions to be performed concurrently.') 249 else: 250 # Generate the new combination 251 option = [] 252 for action in selected.keys(): 253 # Copy the original action 254 option.append(Action(action)) 255 # Make sure that this is a new combination 256 for original in self.getOptions(): 257 if len(original) == len(option): 258 for action in original: 259 if not str(action) in map(str,option): 260 break 261 else: 262 # Match 263 tkMessageBox.showerror('Illegal Selection','That concurrent action already exists in this agent\'s decision space') 264 break 265 else: 266 # New option 267 self['entity'].actions.directAdd(option) 268 self.redrawDescendents()
269
270 - def redrawDescendents(self):
271 agents = self['entity'].hierarchy.descendents(self['entity'].name) 272 for name in agents: 273 agent = self['entity'].hierarchy[name] 274 if agent.attributes.has_key('window'): 275 agent.attributes['window'].component('Actions').drawActions()
276
277 - def updateTypes(self,event=None):
278 """Refreshes the list of possible types to choose from when creating a new action""" 279 items = {} 280 for name in self['entity'].ancestors(): 281 entity = self['entity'].hierarchy[name] 282 for option in entity.actions.getOptions(): 283 for action in option: 284 items[action['type']] = True 285 items = items.keys() 286 items.sort(lambda x,y:cmp(x.lower(),y.lower())) 287 self.component('type_scrolledlist').setlist(items)
288
289 - def updateObjects(self,event=None):
290 """Refreshes the list of possible objects to choose from when creating a new action""" 291 noneLabel = 'None' 292 self.objects = {noneLabel:None} 293 for cls,agent in self['entity'].hierarchy.items(): 294 if self['entity'].isSubclass(cls): 295 self.objects['%s (including self)' % (cls)] = {'object':cls, 296 'self':True} 297 self.objects['%s (but not self)' % (cls)] = {'object':cls, 298 'self':False} 299 for relationship in agent.relationships.keys(): 300 if not self.objects.has_key(relationship): 301 self.objects[relationship] = {'object':relationship} 302 else: 303 self.objects[cls] = {'object':cls} 304 items = self.objects.keys() 305 items.remove(noneLabel) 306 items.sort(lambda x,y:cmp(x.lower(),y.lower())) 307 items.insert(0,noneLabel) 308 self.component('object_listbox').delete(0,END) 309 for option in items: 310 self.component('object_listbox').insert(END,option)
311
312 - def drawActions(self):
313 """Draws the buttons or labels for the current decision space 314 """ 315 components = filter(lambda n:self.componentgroup(n) == 'Select', 316 self.components()) 317 options = self.getOptions() 318 options.sort(lambda x,y:cmp(sum(map(lambda a:[a['type'].lower(), 319 a['actor'].lower(), 320 a['object']],x),[]), 321 sum(map(lambda a:[a['type'].lower(), 322 a['actor'].lower(), 323 a['object']],y),[]))) 324 row = 0 325 for index in range(len(options)): 326 action = options[index] 327 row += 1 328 name = 'Select %s' % (str(action)) 329 try: 330 b = self.component(name) 331 components.remove(name) 332 except KeyError: 333 # Create new button/label 334 self.variables[str(action)] = IntVar() 335 cmd = lambda s=self,a=action: s.selectAction(a) 336 b = self.createcomponent(name,(),'Select', 337 Checkbutton,(self.component('options'),), 338 variable=self.variables[str(action)], 339 command=cmd, 340 ) 341 # Update selector status 342 if self['entity'].actions.illegal.has_key(str(action)): 343 self.variables[str(action)].set(0) 344 else: 345 self.variables[str(action)].set(1) 346 b.grid(row=row,column=0) 347 # Draw dynamics button 348 name = 'Dynamics %s' % (str(action)) 349 try: 350 b = self.component(name) 351 except KeyError: 352 b = self.createcomponent(name,(),'Dynamics', 353 Button,(self.component('options'),), 354 command=lambda s=self,o=options[index]:\ 355 s.viewDynamics(o)) 356 try: 357 b.configure(image=self.images['tree']) 358 except KeyError: 359 b.configure(text='Dynamics...') 360 if self['balloon']: 361 self['balloon'].bind(b,'View dynamics for %s' % \ 362 (','.join(map(str,options[index])))) 363 if len(options[index]) > 1: 364 # For now, can't pop up an editor for parallel actions 365 b.configure(state='disabled') 366 if not self['generic'] and self['entity'].parent is None: 367 b.grid(row=row,column=2,sticky='we') 368 else: 369 b.grid(row=row,column=4,sticky='we') 370 # Action label/button 371 name = str(action) 372 exists = False 373 for widget in self.components(): 374 if self.componentgroup(widget) == name: 375 if widget[:6] == 'actor ': 376 self.component(widget).grid(row=row,column=1,sticky='ew') 377 elif widget[:5] == 'type ': 378 self.component(widget).grid(row=row,column=2,sticky='ew') 379 elif widget[:4] == 'obj ': 380 self.component(widget).grid(row=row,column=3,sticky='ew') 381 exists = True 382 if not exists: 383 if not self['generic'] and self['entity'].parent is None: 384 label = ', '.join(map(str,action)) 385 b = self.createcomponent(name,(),name,Button, 386 (self.component('options'),), 387 text=label) 388 cmd = lambda s=self,a=action:s.performAction(a) 389 b.configure(command=cmd) 390 b.grid(row=row,column=1,sticky='we') 391 else: 392 for i in range(len(action)): 393 b = self.createcomponent('actor %s %d' % (name,i),(),name,Label, 394 (self.component('options'),), 395 text=action[i]['actor'], 396 justify='left',anchor='w') 397 b.grid(row=row,column=1,sticky='ew') 398 b.bind('<Button-1>',self.select) 399 b = self.createcomponent('type %s %d' % (name,i),(),name,Label, 400 (self.component('options'),), 401 text=action[i]['type'], 402 justify='left',anchor='w') 403 b.grid(row=row,column=2,sticky='ew') 404 b.bind('<Button-1>',self.select) 405 if action[i]['object']: 406 obj = action[i]['object'] 407 else: 408 obj = '' 409 b = self.createcomponent('obj %s %d' % (name,i),(),name, 410 Label,(self.component('options'),), 411 text=obj,justify='left',anchor='w') 412 b.grid(row=row,column=3,sticky='ew') 413 b.bind('<Button-1>',self.select) 414 row += 1 415 if action: 416 row -= 1 417 # Delete leftover (i.e., out of date) action widgets 418 for name in components: 419 self.deleteAction(name[7:])
420
421 - def performAction(self,action):
422 """Callback wrapper for performing an action directly 423 """ 424 if self['command']: 425 self['command'](action)
426
427 - def select(self,event):
428 for name in self.components(): 429 if self.component(name) is event.widget: 430 break 431 else: 432 raise UserWarning,'Selection in unidentifiable widget' 433 action = ' '.join(name.split()[1:-1]) 434 palette = Pmw.Color.getdefaultpalette(self.component('hull')) 435 if self.selection.has_key(action): 436 fg = palette['foreground'] 437 bg = palette['background'] 438 del self.selection[action] 439 else: 440 fg = palette['selectForeground'] 441 bg = palette['selectBackground'] 442 self.selection[action] = True 443 for name in self.components(): 444 if self.componentgroup(name) == action: 445 self.component(name).configure(fg=fg,bg=bg)
446
447 - def selectAction(self,option,value=None,root=True):
448 key = str(option) 449 if value is None: 450 # Figure selection state 451 society = self['entity'].hierarchy 452 value = self.variables[str(option)].get() 453 else: 454 # Set to chosen state 455 self.variables[str(option)].set(value) 456 if not value: 457 self['entity'].actions.illegal[key] = option 458 elif self['entity'].actions.illegal.has_key(key): 459 del self['entity'].actions.illegal[key] 460 if root and self['generic']: 461 # Inheritance of selection 462 agents = self['entity'].hierarchy.descendents(self['entity'].name) 463 for name in agents: 464 # Update descendents 465 agent = self['entity'].hierarchy[name] 466 if agent.attributes.has_key('window'): 467 widget = agent.attributes['window'].component('Actions') 468 widget.selectAction(option,value,False) 469 else: 470 if not value: 471 agent.actions.illegal[key] = option 472 elif value and agent.actions.illegal.has_key(key): 473 del agent.actions.illegal[key]
474
475 - def viewDynamics(self,action):
476 """Pop up a modal dialog to view/edit dynamics of the given action 477 @param action: the action whose dynamics should be displayed 478 @type action: L{Action}[] 479 """ 480 assert len(action) == 1 481 if isinstance(self['entity'],GenericModel): 482 label = action[0]['type'] 483 society = self['entity'].hierarchy 484 else: 485 label = action[0] 486 society = None 487 if self['generic']: 488 entities = self['society'].members() 489 else: 490 entities = self['entities'].members() 491 dynamics = {} 492 roles = ['actor','object','self'] 493 roles.sort() 494 for entity in entities: 495 # Extract dynamics 496 for feature,subDynamics in entity.dynamics.items(): 497 try: 498 subDynamics = copy.deepcopy(subDynamics[label]) 499 except KeyError: 500 continue 501 try: 502 dynamics[feature][entity.name] = subDynamics 503 except KeyError: 504 dynamics[feature] = {entity.name: subDynamics} 505 # Extract relationships 506 for newRole in entity.relationships.keys(): 507 if not newRole in roles: 508 roles.append(newRole) 509 self.dialog = DynamicsDialog(self.interior(), 510 dynamics=dynamics, 511 expert=self['expert'], 512 buttons=('OK',), 513 defaultbutton='OK', 514 title='Dynamics of %s' % (label), 515 action=label, 516 editor_roles=roles, 517 society=society, 518 ) 519 if society: 520 self.dialog.configure(command=self.changeDynamics, 521 buttons=('OK','Cancel')) 522 self.dialog.activate()
523
524 - def changeDynamics(self,button):
525 """Upon clicking OK in dynamics dialog, change entity's dynamics accordingly 526 @param button: the button pressed to close the dialog (generated by Tk callback) 527 @type button: str 528 """ 529 if self['generic']: 530 entities = self['society'].members() 531 else: 532 entities = self['entities'].members() 533 if button == 'OK': 534 # Determine which effects have been deleted 535 for entity in entities: 536 for feature,subDynamics in entity.dynamics.items(): 537 if subDynamics.has_key(self.dialog['action']) and not self.dialog['dynamics'][feature].has_key(self.dialog['action']): 538 del subDynamics[self.dialog['action']] 539 # Update all others 540 for feature,subDynamics in self.dialog['dynamics'].items(): 541 for agent,dynamics in subDynamics.items(): 542 entity = self['entity'].hierarchy[agent] 543 try: 544 entity.dynamics[feature][self.dialog['action']] = dynamics 545 except KeyError: 546 entity.dynamics[feature] = {self.dialog['action']: dynamics} 547 self.dialog.deactivate()
548