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
11 """Frame to define and view the entity's available actions
12 """
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
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
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
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
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
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
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
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
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
122
128
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
143
147
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
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
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
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
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
211 pass
212 for name in widget.components():
213 if widget.componentgroup(name) == action:
214 widget.destroycomponent(name)
215
216
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
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
247 if len(selected) < 2:
248 tkMessageBox.showerror('Illegal Selection','You must select multiple actions to be performed concurrently.')
249 else:
250
251 option = []
252 for action in selected.keys():
253
254 option.append(Action(action))
255
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
263 tkMessageBox.showerror('Illegal Selection','That concurrent action already exists in this agent\'s decision space')
264 break
265 else:
266
267 self['entity'].actions.directAdd(option)
268 self.redrawDescendents()
269
276
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
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
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
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
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
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
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
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
418 for name in components:
419 self.deleteAction(name[7:])
420
426
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
448 key = str(option)
449 if value is None:
450
451 society = self['entity'].hierarchy
452 value = self.variables[str(option)].get()
453 else:
454
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
462 agents = self['entity'].hierarchy.descendents(self['entity'].name)
463 for name in agents:
464
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
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
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
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
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
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
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