1 import copy
2 import os.path
3 import Pmw
4 from Queue import Queue,Empty
5 import string
6 from threading import *
7 from time import strftime,localtime,sleep
8 from Tkinter import *
9 from tkFileDialog import askopenfilename
10
11 from teamwork.agent.lightweight import PWLAgent
12 from teamwork.agent.Entities import PsychEntity
13 from teamwork.agent.AgentClasses import getEntityClassList
14 from teamwork.multiagent.pwlSimulation import PWLSimulation
15 from teamwork.policy.policyTable import PolicyTable
16 from teamwork.widgets.WizardShell import WizardShell
17 from teamwork.widgets.ProgressBar import ProgressBar
18 from teamwork.widgets.images import getImageDirectory,getImage
19 from objectives import ObjectivesDialog
20
22 """Setup wizard dialog widget"""
23 frameWidth = 700
24 frameHeight = 600
25 defaultImage = getImage('nobody.gif')
26
27 - def __init__(self, shell, parent=None, **kw):
28 optiondefs = (
29 ('doActions',False,Pmw.INITOPT),
30 ('doObjectives',False,Pmw.INITOPT),
31 ('society',{},Pmw.INITOPT),
32 ('leavesOnly',False,Pmw.INITOPT),
33 ('balloon',None,Pmw.INITOPT),
34 ('expert',False,Pmw.INITOPT),
35 ('command',None,Pmw.INITOPT),
36 ('agentClass',PsychEntity,None),
37 ('scenario',None,Pmw.INITOPT),
38 ('beta',False,Pmw.INITOPT),
39 )
40 self.defineoptions(kw, optiondefs)
41 self.scenario = None
42 if len(self['society']) == 1:
43
44 self.leaves = self['society'].keys()
45 elif self['leavesOnly']:
46
47 self.leaves = self['society'].leaves()
48 else:
49
50 leaves = filter(lambda e:len(e.getParents())>0,
51 self['society'].members())
52 self.leaves = map(lambda e:e.name,leaves)
53 self.leaves.sort(lambda x,y:cmp(x.lower(),y.lower()))
54 self.entities = {}
55 self.selected = {}
56 self.panes = len(self.leaves) + 2
57 if self['doObjectives']:
58 self.panes += 1
59 try:
60 self.nobody = PhotoImage(file=self.defaultImage)
61 except:
62 self.nobody = None
63 self.toImport = False
64 self.thread = None
65 WizardShell.__init__(self,parent)
66
72
77
78 - def createMain(self):
79 self.createPopulation(0)
80 for index in range(len(self.leaves)):
81 self.createClassPane(1+index,self.leaves[index])
82 self.createFinish(len(self.leaves)+1)
83
85 frame = Pmw.ScrolledFrame(self.pInterior(pane),)
86
87 self.counters = {}
88 for leaf in self.leaves:
89 self.counters[leaf] = self.__createCounter(frame.interior(),
90 leaf,0,leaf)
91 Pmw.alignlabels(self.counters.values())
92 frame.grid(row=0,column=0,sticky='ewns')
93 self.pInterior(pane).rowconfigure(0,weight=1)
94 if self['scenario']:
95 widget = Button(self.pInterior(pane),command=self.editScenario,
96 text='Import Current Scenario and Edit')
97 if self['balloon']:
98 self['balloon'].bind(widget,'Use the current scenario\'s settings for the initial settings in this wizard.')
99 widget.grid(row=1,column=0,sticky='ew')
100 widget = Button(self.pInterior(pane),command=self.useScenario,
101 text='Import Current Scenario and Finish')
102 if self['balloon']:
103 self['balloon'].bind(widget,'Use the current scenario\'s settings without any changes.')
104 widget.grid(row=2,column=0,sticky='ew')
105
107
108 group = Pmw.Group(self.pInterior(pane),tag_text='Progress')
109 group.grid(row=0,column=1,sticky='ewns')
110 self.pInterior(pane).rowconfigure(0,weight=1)
111 msg = 'All done! Click "Finish" to instantiate scenario.'
112 self.createcomponent('FinishLabel',(),None,
113 Label,(group.interior(),),
114 text=msg).pack(side=TOP,fill=BOTH,expand=YES)
115 self.progressBar = ProgressBar(master=group.interior(),
116 labelText='Progress:',
117 value=0,width=300,
118 background=None,
119 labelColor="black",
120 fillColor="darkgray",
121 )
122 self.progressBar.frame.pack_forget()
123
124 self.compileFlag = IntVar()
125 self.compileFlag.set(1)
126 self.distillFlag = IntVar()
127 self.distillFlag.set(0)
128 self.level = StringVar()
129 group = Pmw.Group(self.pInterior(pane),tag_text='Options')
130 self.createcomponent('Distill',(),None,
131 Checkbutton,(group.interior(),),
132 text='Create lightweight agents',
133 variable=self.distillFlag,
134 )
135 self.createcomponent('Compile Dynamics',(),None,
136 Checkbutton,(group.interior(),),
137 text='Compile dynamics',
138 variable=self.compileFlag,
139 )
140 self.createcomponent('Compile Policies',(),None,
141 Pmw.OptionMenu,(group.interior(),),
142 labelpos='w',
143 label_text='Compile policies for:',
144 menubutton_textvariable=self.level,
145 )
146 if self['beta']:
147 self.component('Distill').pack(side=TOP,fill=BOTH,expand=YES)
148 if self['expert']:
149 self.component('Compile Dynamics').pack(side=TOP,fill=BOTH,
150 expand=YES)
151 if self['beta']:
152 self.component('Compile Policies').pack(side=TOP,fill=BOTH,
153 expand=YES)
154 if self['expert']:
155 group.grid(row=1,column=1,sticky='ewns')
156 self.pInterior(pane).rowconfigure(1,weight=1)
157 self.pInterior(pane).columnconfigure(1,weight=1)
158
160 """Shorthand for L{importScenario} with C{finish=False}"""
161 self.importScenario(finish=False)
162
166
168 """Fills out everything using what's present in the current scenario
169 @param finish: if C{True}, then jump directly to end without editing any of the current scenario's settings
170 @type finish: bool
171 """
172 self.toImport = True
173 for leaf in self.leaves:
174
175 while int(self.counters[leaf].get()) > 0:
176 self.counters[leaf].decrement()
177
178 try:
179 self.entities[leaf].clear()
180 except KeyError:
181 pass
182 for entity in self['scenario'].members():
183
184 leaves = []
185 for leaf in self.leaves:
186 if entity.instanceof(leaf):
187 leaves.append(leaf)
188
189 for leaf in leaves[:]:
190 for className in self['society'][leaf].getParents():
191 try:
192 leaves.remove(className)
193 except ValueError:
194 pass
195
196 assert len(leaves) == 1
197 leaf = leaves[0]
198
199 self.counters[leaf].increment()
200
201 new = self['society'].instantiate(leaf,entity.name,
202 self['agentClass'])
203
204 for relation,others in entity.relationships.items():
205 new.relationships[relation] = others[:]
206 try:
207 self.entities[leaf][entity.name] = new
208 except KeyError:
209 self.entities[leaf] = {entity.name:new}
210 if finish:
211 self.next(self.panes - 1)
212 self.finish()
213
215 """Extract the number of each entity type selected from pane 0 of the wizard"""
216 self.population = {}
217 for leaf in self.leaves:
218
219 counter = self.counters[leaf]
220 count = int(counter.get())
221 self.population[leaf] = count
222 try:
223 while len(self.entities[leaf]) > count:
224
225 key = self.entities[leaf].keys()[-1]
226 del self.entities[leaf][key]
227 except KeyError:
228
229
230 self.entities[leaf] = {}
231 while len(self.entities[leaf]) < count:
232
233 if count > 1:
234 name = '%s %d' % (leaf,len(self.entities[leaf])+1)
235 else:
236 name = '%s' % (leaf)
237 newEntity = self['society'].instantiate(leaf,name,
238 self['agentClass'])
239 self.entities[leaf][name] = newEntity
240 for leaf in self.leaves:
241
242 box = self.component('%sEntitySelect' % (leaf))
243 nameList = self.entities[leaf].keys()
244 nameList.sort()
245 box.setitems(nameList)
246
247
248 book = self.component('%sNotebook' % (leaf))
249 if not self.selected.has_key(leaf):
250 try:
251 self.selectEntity(nameList[0],leaf,None)
252 except IndexError:
253
254 pass
255 self.createProps(book,leaf)
256
257 if self['doObjectives']:
258
259 objPaneName = 'ObjectivesPane'
260 try:
261 obj = self.component(objPaneName)
262 except KeyError:
263 eList = map(lambda e:e.values(),self.entities.values())
264 pane = len(self.leaves)+2
265 obj = self.createcomponent(objPaneName, (),None,
266 ObjectivesDialog,
267 (self.pInterior(pane),),
268 psyopFlag = 0,
269 entities=reduce(lambda x,y:x+y,
270 eList,[]))
271 obj.pack(side=TOP,fill=X,expand=YES)
272
273 count = 0
274 for leaf in self.leaves:
275 if self.population[leaf] > 0:
276 depth = self['society'][leaf].depth
277 if depth > count:
278 count = depth
279 self.levels = ['All agents']
280 self.levels += map(lambda n:'Agents at belief depth %d and below'\
281 % (n+1),range(count))
282 self.levels.append('No agents')
283 self.component('Compile Policies').setitems(self.levels)
284 self.component('Compile Policies').invoke(Pmw.END)
285
287 """Create the pane for the specified index and class"""
288
289 self.createcomponent('%sLabel' % (className), (), None,
290 Label,
291 (self.pInterior(pane),),
292 borderwidth=3,relief=RIDGE,
293 font=('helvetica',18,'bold'),
294 text=className).grid(row=0,column=0,
295 sticky='ew')
296 cmd = lambda label,s=self,c=className:s.selectEntity(label,c)
297
298 msg = 'Select which instance of %s you wish to customize:' \
299 % (className)
300 Label(self.pInterior(pane),text=msg).grid(row=1,column=0,sticky='ew')
301 self.createcomponent('%sEntitySelect' % (className), (), None,
302 Pmw.OptionMenu,
303 (self.pInterior(pane),),
304 labelpos=W,label_text='Entity:',
305 command=cmd).grid(row=2,column=0,sticky='ew')
306
307 book = self.createcomponent('%sNotebook' % (className), (), None,
308 Pmw.NoteBook,(self.pInterior(pane),))
309
310 book.add('Name')
311 widget = self.createcomponent('%sName' % (className), (), None,
312 Pmw.EntryField,(book.page(0),),
313 validate={'validator':
314 self.validateName},
315 modifiedcommand=self.updateName,
316 labelpos='nw',
317 label_text='Entity name:',
318 )
319 widget.grid(row=0,column=0,sticky='EW',ipadx=5)
320
321 widget = self.createcomponent('%sImage' % (className), (),None,
322 Button,(book.page(0),),
323 command=lambda s=self,c=className:\
324 s.selectImage(c),
325 width=100,height=100)
326 widget.grid(row=0,column=1)
327
328 widget = self.createcomponent('%sDescription' % (className),
329 (), None, Pmw.ScrolledText,
330 (book.page(0),),
331 text_height=5,text_width=40,
332 text_wrap='word',
333 labelpos='nw',
334 label_text='Entity description:',
335 )
336 widget.grid(row=1,column=0,columnspan=2,sticky='NESW',padx=5)
337 book.page(0).rowconfigure(1,weight=1)
338 book.page(0).columnconfigure(0,weight=1)
339 book.grid(row=3,column=0,sticky='ewns')
340 self.pInterior(pane).rowconfigure(3,weight=1)
341 self.pInterior(pane).columnconfigure(0,weight=1)
342
344 """Pops up a dialog to allow user to select image for an entity"""
345 filename = askopenfilename(parent=self.root,
346 initialdir = getImageDirectory())
347 if filename:
348 try:
349 image = PhotoImage(file=filename)
350 except:
351 image = None
352 if image:
353 widget = self.component('%sImage' % (className))
354 widget.configure(image=image)
355 entity = self.selected[className]
356 entity.attributes['image'] = image
357 entity.attributes['imageName'] = filename
358
360 """Creates all the background tabs for specializing an entity"""
361 try:
362 entity = self.entities[className].values()[0]
363 except IndexError:
364
365 return
366 generic = self['society'][className]
367 page = 1
368 if self['doActions']:
369
370 if len(generic.actions.getOptions()) > 0:
371 try:
372 parent.add('Actions')
373 menuLabel = '%sActions' % (className)
374 except ValueError:
375
376 menuLabel = None
377 if menuLabel:
378 menu = self.createcomponent(menuLabel,(),None,
379 Pmw.RadioSelect,
380 (parent.page(page),),
381 buttontype='checkbutton',
382 orient='vertical',
383 )
384 options = generic.actions.getOptions()
385 options.sort(lambda x,y:cmp(str(x),str(y)))
386 map(lambda n:menu.add(str(n)),options)
387 menu.pack(side='top',fill='both',expand='yes')
388 page += 1
389
390 relationships = {}
391 for cls in entity.classes:
392 relationships.update(self['society'][cls].relationships)
393 for relation,objClasses in relationships.items():
394 try:
395 newPage = parent.add(relation)
396 except ValueError:
397
398 continue
399 menuLabel = '%s%sRelation' % (className,relation)
400 try:
401 menu = self.component(menuLabel)
402 except KeyError:
403
404 cmd = lambda selected,value,s=self,c=className,r=relation:\
405 s.updateFiller(c,r,selected,value)
406 frame = Pmw.ScrolledFrame(parent.page(page))
407 menu = self.createcomponent(menuLabel, (),
408 None, Pmw.RadioSelect,
409 (frame.interior(),),
410 buttontype='checkbutton',
411 command=cmd,
412 orient='vertical')
413 frame.pack(side='top',fill='both',expand='yes')
414 menu.pack(side='top',fill='both',expand='yes')
415 page += 1
416 self.updateRelationships(entity,className)
417
419
420 relationships = {}
421 for cls in entity.classes:
422 relationships.update(self['society'][cls].relationships)
423 for relation,objClasses in relationships.items():
424
425 objList = []
426 for classSet in self.entities.values():
427 for other in classSet.values():
428 for objClass in objClasses:
429 if other.instanceof(objClass):
430 objList.append(other)
431 menuLabel = '%s%sRelation' % (className,relation)
432 menu = self.component(menuLabel)
433
434 nameList = map(lambda e:e.name,objList)
435 nameList.sort()
436
437 toDelete = filter(lambda n:menu.componentgroup(n) == 'Button' \
438 and not n in nameList,menu.components())
439 for name in toDelete:
440
441
442 menu.destroycomponent(name)
443 menu._buttonList.remove(name)
444 try:
445 menu.selection.remove(name)
446 except ValueError:
447
448 pass
449 for index in range(len(nameList)):
450 name = nameList[index]
451 try:
452 widget = menu.component(name)
453 except KeyError:
454 widget = menu.add(name)
455 widget.grid(row=index)
456 if not entity.relationships.has_key(relation):
457 entity.relationships[relation] = []
458 others = entity.relationships[relation][:]
459 menu.setvalue(others)
460
462 """Validator for name entry field.
463 Ensures that names are not zero length and that all names are
464 unique."""
465 if self.pCurrent == 0:
466 return Pmw.OK
467 elif len(name) > 0:
468 className = self.leaves[self.pCurrent-1]
469 entity = self.selected[className].name
470 for className in self.leaves:
471 for other in self.entities[className].keys():
472 if other != entity and name == other:
473 return Pmw.PARTIAL
474 else:
475 return Pmw.OK
476 else:
477 return Pmw.PARTIAL
478
480 if self.pCurrent > 0:
481 className = self.leaves[self.pCurrent-1]
482 entity = self.selected[className]
483 self.rename(className,entity.name,
484 self.component('%sName' % (className)).getvalue())
485
486 - def rename(self,className,old,new):
487 entity = self.entities[className][old]
488 entity.setName(new)
489
490 del self.entities[className][old]
491 self.entities[className][entity.name] = entity
492
493 for otherClass in self.leaves:
494 for other in self.entities[otherClass].values():
495 for relation,objList in other.relationships.items():
496 try:
497 index = objList.index(old)
498 objList[index] = entity.name
499 except ValueError:
500 pass
501
502 menu = self.component('%sEntitySelect' % (className))
503 menu.setitems(self.entities[className].keys(),new)
504
505 for otherClass in self.leaves:
506 try:
507 other = self.selected[otherClass]
508 except KeyError:
509 continue
510 self.updateRelationships(other,otherClass)
511
513 """Updates the relationships of the given entity in response to the click of a single button"""
514 entity = self.selected[className]
515 if not entity.relationships.has_key(relation):
516 entity.relationships[relation] = []
517 if value:
518 assert other not in entity.relationships[relation]
519 entity.relationships[relation].append(other)
520 else:
521 entity.relationships[relation].remove(other)
522
524 """Change the view when a new entity is selected for viewing/editing"""
525 oldName = ''
526 if saveOld:
527
528 entity = self.selected[className]
529 oldName = entity.name
530 widget = self.component('%sDescription' % (className))
531 entity.description = widget.get()
532 if label != oldName:
533
534 self.selected[className] = self.entities[className][label]
535 entity = self.selected[className]
536 widget = self.component('%sName' % (className))
537 widget.setvalue(entity.name)
538 widget = self.component('%sDescription' % (className))
539 widget.settext(entity.description)
540
541 widget = self.component('%sImage' % (className))
542 try:
543 widget.configure(image=entity.attributes['image'])
544 except KeyError:
545 widget.configure(image=self.nobody)
546
547 if self.pCurrent > 0:
548 self.updateRelationships(entity,className)
549
551 """Returns a dictionary of the available relationship menus"""
552 result = {}
553 try:
554 entity = self.selected[className]
555 except KeyError:
556 return result
557 relationships = {}
558 for cls in entity.classes:
559 relationships.update(self['society'][cls].relationships)
560 for relation in relationships.keys():
561 if relation[0] == '_':
562 label = relation[1:]
563 else:
564 label = relation
565 try:
566 menu = self.component('%s%sRelation' % (className,label))
567 except KeyError:
568
569 continue
570 result[relation] = menu
571 return result
572
574 """Applies any changes made to the currently selected entity"""
575 className = self.leaves[self.pCurrent-1]
576 entity = self.selected[className]
577 self.selectEntity(entity.name,className)
578
579 - def next(self,new=None):
580 """Moves the wizard forward to the next applicable pane"""
581 if self.pCurrent == 0:
582 self.setPopulation()
583 elif self.isClassPane():
584
585 self.saveProps()
586 done = None
587 cpane = self.pCurrent
588 while not done:
589 if new is None:
590 self.pCurrent = self.pCurrent + 1
591 else:
592 self.pCurrent = new
593 self.prevB['state'] = NORMAL
594 if self.pCurrent == self.panes - 1:
595 self.nextB['text'] = 'Finish'
596 self.nextB['command'] = self.finish
597 if self.isClassPane():
598 className = self.leaves[self.pCurrent-1]
599 self.component('%sName_entry' % (className)).focus_set()
600 done = self.legalPane()
601 self.pFrame[cpane].forget()
602 self.pFrame[self.pCurrent].pack(fill=BOTH, expand=YES)
603
605 """Moves the wizard backward to the next applicable pane"""
606 if self.isClassPane():
607
608 self.saveProps()
609 done = None
610 cpane = self.pCurrent
611 while not done:
612 self.pCurrent = self.pCurrent - 1
613 if self.pCurrent <= 0:
614 self.pCurrent = 0
615 self.prevB['state'] = DISABLED
616 if cpane == self.panes - 1:
617 self.nextB['text'] = 'Next'
618 self.nextB['command'] = self.next
619 done = self.legalPane()
620 self.pFrame[cpane].forget()
621 self.pFrame[self.pCurrent].pack(fill=BOTH, expand=YES)
622
624 """
625 @return: True iff the pane (defaults to current) is an entity pane"""
626 if pane < 0:
627 pane = self.pCurrent
628 if pane == 0:
629 return False
630 elif pane > len(self.leaves):
631 return False
632 else:
633 return True
634
636 """Returns true iff the pane (defaults to current) is legal,
637 given the current population breakdown"""
638 if pane < 0:
639 pane = self.pCurrent
640 if self.isClassPane(pane):
641 return len(self.entities[self.leaves[pane-1]]) > 0
642 else:
643 return 1
644
646 result = Pmw.integervalidator(count)
647 if result == Pmw.OK:
648 count = int(count)
649 if count < 0:
650 return Pmw.ERROR
651 total = 0
652 for other in self.leaves:
653 if other == className:
654 total += count
655 else:
656 try:
657 counter = self.component('Counter%s' % (other))
658 total += int(counter.getvalue())
659 except KeyError:
660 pass
661 if total < 1:
662 self.nextB['state'] = DISABLED
663 return Pmw.OK
664
665 else:
666 self.nextB['state'] = NORMAL
667 return Pmw.OK
668 else:
669 return result
670
671 - def __createCounter( self, master, labelText, initialValue,
672 className ):
673 """Creates a counter widget for changing the population"""
674 validator = lambda value,s=self,n=className:s.validateCount(value,n)
675
676
677 counter = self.createcomponent('Counter%s' % labelText, (), None,
678 Pmw.Counter,(master,),
679 padx=8, pady=0,
680 labelpos = W,
681 label_text = labelText,
682 entry_width = 3,
683 entryfield_validate = validator,
684 entryfield_value = initialValue )
685
686 if className and self['balloon']:
687 str = ''
688 try:
689 source = self['society'][className]['source']
690 except KeyError:
691 source = None
692 if source:
693 str += 'Created by ' + source['who'] + '\n'
694 str += strftime('%x %X',localtime(source['when']))
695 try:
696 sourceList = self['society'][className]['validated']
697 except KeyError:
698 sourceList = []
699 for source in sourceList:
700 str += '\nValidated by ' + source['who'] + '\n'
701 str += strftime('%x %X',localtime(source['when']))
702 if len(str) > 0:
703 self['balloon'].bind(counter,str)
704
705 counter.pack( side=TOP, padx=8)
706 return counter
707
709 self.component('FinishLabel').configure(text='Initializing...')
710 self.prevB['state'] = DISABLED
711 self.nextB['state'] = DISABLED
712 self.cancel['state'] = DISABLED
713 self.component('Compile Dynamics').configure(state='disabled')
714 self.component('Compile Policies').configure(menubutton_state='disabled')
715 self.progressBar.frame.pack(side=TOP)
716 self.progress = Queue()
717 if self['command']:
718 args = {'level': self.levels.index(self.level.get())}
719 eList = map(lambda e:e.values(),self.entities.values())
720 args['entities'] = reduce(lambda x,y:x+y,eList,[])
721 if self.distillFlag.get():
722 args['distill'] = True
723 else:
724 args['distill'] = False
725 if self.compileFlag.get():
726 args['compile'] = True
727 else:
728 args['compile'] = False
729 self.thread = Thread(target=self.__finish,kwargs=args)
730 self.thread.start()
731 result = True
732 while result is not None:
733 try:
734 result = self.progress.get_nowait()
735 except Empty:
736 result = True
737 if isinstance(result,tuple):
738 msg,inc = result
739 widget = self.component('FinishLabel')
740 widget.configure(text=msg)
741 self.updateProgress(inc)
742 self.mainloop(10)
743 self.update()
744 self.done()
745
746 - def __finish(self,entities,compile,distill,level):
751
753 if len(msg) == 0:
754 msg = 'Done.'
755 inc = self.progressBar.max
756 widget = self.component('FinishLabel')
757 widget.configure(text=msg)
758 self.updateProgress(inc)
759
763
765 self.quit()
766 self.root.destroy()
767