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
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 """
20
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
204
205
206 widget.grid(row=index+2,column=1,sticky='ew')
207
208
209
210
211
212
213
214
215
216
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
224
225 index += 1
226
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
235 palette = Pmw.Color.getdefaultpalette(self.component('hull'))
236 if self.selection:
237
238 widget = self.component('%s-label' % (self.selection))
239 widget.configure(fg=palette['foreground'],
240 bg=palette['background'],bd=0)
241
242 self.selection = feature
243 scale = self.component(feature)
244 event.widget.configure(fg=palette['selectForeground'],
245 bg=palette['selectBackground'],bd=3)
246
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
259 if self['generic']:
260 if feature in self['entity'].getStateFeatures():
261 self.locked = False
262 if self['entity'].getInheritor('state',feature,False):
263
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
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
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
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
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
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
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
401 self.destroycomponent('%s-label' % (name))
402 self.destroycomponent(name)
403
404 if self.selection == name:
405
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
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
426 try:
427 del self['entity'].dynamics[feature]
428 except KeyError:
429 pass
430
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
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
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
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
473 """Prompts user for name of new state feature
474 """
475 self.component('newdialog').activate(geometry = 'centerscreenalways')
476
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
502
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
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
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
568 remaining = [dynamics.getTree()]
569 while len(remaining) > 0:
570 tree = remaining.pop()
571 if tree.isLeaf():
572
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
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
593 if entity.name in descendents and \
594 key['feature'] != feature:
595
596 refs.append(entry)
597 elif key['entity'] == 'actor':
598
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
608 continue
609 break
610 elif key['entity'] == 'object':
611
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
624 for other in entity.relationships[key['entity']]:
625 if other in descendents:
626 refs.append(entry)
627 break
628 return refs
629