source: branch/logging/log.py @ 1497

Last change on this file since 1497 was 1497, checked in by toby, 7 years ago

logging more complete

  • Property svn:eol-style set to native
File size: 16.3 KB
Line 
1'Module to provide logging services'
2import wx
3import GSASIIgrid as G2gd
4MenuBindingLookup = {}
5G2logList = [None]
6'Contains a list of logged actions; first item is ignored'
7LogInfo = {'Logging':False, 'Tree':None}
8'Contains a dict with values that are needed in the module'
9
10debug = True
11
12# TODO:
13### Note: no provinance info for histogram instrument parameters: need to remove insVal etc.
14
15# track histograms and phases with relative indices
16
17#===========================================================================
18# objects for logging variables
19def _l2s(lst,separator='+'):
20    'Combine a list of objects into a string, with a separator'
21    s = ''
22    for i in lst: 
23        if s != '': s += separator
24        s += '"'+str(i)+'"'
25    return s
26
27class VarLogEntry(object):
28    'object that tracks changes to a variable'
29    def __init__(self,treeRefs,indexRefs,value):
30        self.treeRefs = treeRefs
31        self.indexRefs = indexRefs
32        self.value = value
33        if debug: print 'Logging var change: w/treeRefs',treeRefs,'indexRefs',indexRefs,'new value=',value
34    def __str__(self):
35        return 'Variable change: Key(s)= '+_l2s(self.indexRefs)+' to value='+str(self.value)
36
37class MenuLogEntry(object):
38    'object that tracks when a menu command is executed'
39    def __init__(self,menulabellist):
40        self.menulabellist = menulabellist
41        if debug:
42            t = menulabellist[:]
43            t.reverse()
44            l = ''
45            for item in t:
46                if l: l += ' -> '
47                l += item
48        if debug: print 'Logging menu command: '+l
49    def __str__(self):
50        return 'Menu press: From '+_l2s(self.menulabellist,'/')
51           
52class TabLogEntry(object):
53    'Object to track when tabs are pressed in the DataFrame window'
54    def __init__(self,title,tabname):
55        self.wintitle = title
56        self.tablabel = tabname
57        if debug: print 'Logging tab: "'+tabname+'" on window titled '+title
58    def __str__(self):
59        return 'Tab press: Tab='+_l2s([self.tablabel])+' on window labeled '+str(self.wintitle)
60
61class TreeLogEntry(object):
62    'Object to track when tree items are pressed in the main window'
63    def __init__(self,itemlist):
64        self.treeItemList = itemlist
65        if debug: print 'Logging press on tree: "',itemlist
66    def __str__(self):
67        return 'Tree item pressed: '+_l2s(self.treeItemList)
68           
69def _wrapper(func):
70    def _wrapped(self, *args, **kwargs):
71        return getattr(self.obj, func)(*args, **kwargs)
72    return _wrapped
73
74class DictMeta(type):
75    def __new__(cls, name, bases, dct):
76        default_attrs = dir(object) + ['__getitem__', '__str__']
77        for attr in dir(dict):
78            if attr not in default_attrs:
79                dct[attr] = _wrapper(attr)
80        return type.__new__(cls, name, bases, dct)
81
82class dictLogged(object):
83    '''A version of a dict object that tracks the source of the
84    object back to the location on the G2 tree.
85    If a list (tuple) or dict are pulled from inside this object
86    the source information is appended to the provinance tracking
87    lists.
88
89    tuples are converted to lists.
90    '''
91    __metaclass__ = DictMeta
92
93    def __init__(self, obj, treeRefs, indexRefs=[]):
94        self.treeRefs = treeRefs
95        self.indexRefs = indexRefs
96        self.obj = obj
97
98    def __getitem__(self,key):
99        val = self.obj.__getitem__(key)   
100        if type(val) is tuple:
101            #if debug: print 'Converting to list',key
102            val = list(val)
103            self.obj[key] = val
104        if type(val) is dict:
105            #print 'dict'
106            return dictLogged(val,self.treeRefs,self.indexRefs+[key])
107        elif type(val) is list:
108            #print 'list'
109            return listLogged(val,self.treeRefs,self.indexRefs+[key])
110        else:
111            #print type(val)
112            return val
113
114    def __str__(self):
115        return self.obj.__str__() + " : " + str(self.treeRefs) + ',' + str(self.indexRefs)
116
117class ListMeta(type):
118    def __new__(cls, name, bases, dct):
119        default_attrs = dir(object) + ['__getitem__', '__str__']
120        for attr in dir(list):
121            if attr not in default_attrs:
122                dct[attr] = _wrapper(attr)
123        return type.__new__(cls, name, bases, dct)
124
125class listLogged(object):
126    '''A version of a list object that tracks the source of the
127    object back to the location on the G2 tree.
128    If a list (tuple) or dict are pulled from inside this object
129    the source information is appended to the provinance tracking
130    lists.
131   
132    tuples are converted to lists.
133    '''
134    __metaclass__ = ListMeta
135
136    def __init__(self, obj, treeRefs, indexRefs=[]):
137        self.treeRefs = treeRefs
138        self.indexRefs = indexRefs
139        self.obj = obj
140
141    def __getitem__(self,key):
142        val = self.obj.__getitem__(key)   
143        if type(val) is tuple:
144            #if debug: print 'Converting to list',key
145            val = list(val)
146            self.obj[key] = val
147        if type(val) is dict:
148            #print 'dict'
149            return dictLogged(val,self.treeRefs,self.indexRefs+[key])
150        elif type(val) is list:
151            #print 'list'
152            return listLogged(val,self.treeRefs,self.indexRefs+[key])
153        else:
154            #print type(val)
155            return val
156
157    def __str__(self):
158        return self.obj.__str__() + " : " + str(self.treeRefs) + ',' + str(self.indexRefs)
159
160#===========================================================================
161class G2TreeCtrl(wx.TreeCtrl):
162    '''Create a wrapper around the standard TreeCtrl so we can "wrap"
163    various events.
164   
165    This logs when a tree item is selected (in :meth:`onSelectionChanged`)
166
167    This also wraps lists and dicts pulled out of the tree to track where
168    they were retrieved from.
169    '''
170    def __init__(self,parent=None,*args,**kwargs):
171        super(self.__class__,self).__init__(parent=parent,*args,**kwargs)
172        self.G2frame = parent.GetParent()
173        self.root = self.AddRoot('Loaded Data: ')
174        self.SelectionChanged = None
175        self.repaintAction = None
176        LogInfo['Tree'] = self
177
178    def _getTreeItemsList(self,item):
179        '''Get the full tree hierarchy from a reference to a tree item.
180        Note that this effectively hard-codes phase and histogram names in the
181        returned list. We may want to make these names relative in the future.
182        '''
183        textlist = [self.GetItemText(item)]
184        parent = self.GetItemParent(item)
185        while parent:
186            if parent == self.root: break
187            textlist.insert(0,self.GetItemText(parent))
188            parent = self.GetItemParent(parent)
189        return textlist
190
191    def onSelectionChanged(self,event):
192        '''Log each press on a tree item here.
193        '''
194        if self.SelectionChanged:
195            textlist = self._getTreeItemsList(event.GetItem())
196            if LogInfo['Logging']: 
197                G2logList.append(TreeLogEntry(textlist))
198            self.SelectionChanged(event)
199
200    def Bind(self,eventtype,handler,*args,**kwargs):
201        '''Override the Bind() function so that page change events can be trapped
202        '''
203        if eventtype == wx.EVT_TREE_SEL_CHANGED:
204            self.SelectionChanged = handler
205            wx.TreeCtrl.Bind(self,eventtype,self.onSelectionChanged)
206            return
207        wx.TreeCtrl.Bind(self,eventtype,handler,*args,**kwargs)
208
209    def GetItemPyData(self,*args,**kwargs):
210        '''Override the standard method to wrap the contents
211        so that the source can be tracked
212        '''
213        data = super(self.__class__,self).GetItemPyData(*args,**kwargs)
214        textlist = self._getTreeItemsList(args[0])
215        if type(data) is dict:
216            return dictLogged(data,textlist)
217        if type(data) is list:
218            return listLogged(data,textlist)
219        if type(data) is tuple: #N.B. tuples get converted to lists
220            return listLogged(list(data),textlist)
221        return data
222
223    def ClearDataRepaint(self):
224        self.repaintAction = None
225
226    def RepaintDataWindow(self):
227        item = self.repaintAction
228        if isinstance(item,TreeLogEntry):
229            self.SelectItem(self.root) # need to select something else
230            self.ReplayTreePress(item)
231        elif isinstance(item,TabLogEntry):
232            self.ReplayTabPress(item)
233           
234    def ReplayLogItem(self,item):
235        'Execute an action taken from a log file entry'
236        if isinstance(item,MenuLogEntry):
237            self.ReplayMenuCommand(item)
238        elif isinstance(item,TreeLogEntry):
239            self.ReplayTreePress(item)
240            self.repaintAction = item
241        elif isinstance(item,VarLogEntry):
242            self.ReplayVariableChange(item)
243        elif isinstance(item,TabLogEntry):
244            self.ReplayTabPress(item)
245            self.repaintAction = item
246        else:
247            raise Exception("Unknown object in log: "+str(type(item))+": "
248                            +str(item))
249       
250    def ReplayTreePress(self,logitem):
251        'Perform a Tree press action when read from the log'
252        parent = self.root
253        for txt in logitem.treeItemList:
254            item = G2gd.GetPatternTreeItemId(self.G2frame,parent,txt)
255            if not item:
256                print 'Not found',txt
257                return
258            else:
259                parent = item
260        else:
261            self.SelectItem(item)
262               
263    def ReplayTabPress(self,logitem):
264        'Perform a Tab press action when read from the log'
265        wintitle = logitem.wintitle
266        tabname = logitem.tablabel
267        if self.G2frame.dataFrame.GetTitle() != wintitle:
268            print self.G2frame.dataFrame.GetTitle(),' != ',wintitle
269            raise Exception('tab in wrong window')
270        for PageNum in range(self.G2frame.dataDisplay.GetPageCount()):
271            if tabname == self.G2frame.dataDisplay.GetPageText(PageNum):
272                self.G2frame.dataDisplay.SetSelection(PageNum)
273                return
274        else:
275            print tabname,'not in',[
276                self.G2frame.dataDisplay.GetPageText(PageNum) for
277                PageNum in range(self.G2frame.dataDisplay.GetPageCount())]
278            raise Exception('tab not found')
279    def ReplayVariableChange(self,logitem):
280        'Perform a Variable Change action, when read from the log'
281        parentId = self.root
282        for treeitem in logitem.treeRefs:
283            item, cookie = self.GetFirstChild(parentId)
284            while item:
285                if self.GetItemText(item) == treeitem:
286                    parentId = item
287                    break
288                else:
289                    item, cookie = self.GetNextChild(parentId, cookie)
290            else:
291                raise Exception("Tree item not found for "+str(logitem))
292        # get the inner most data array
293        data = super(self.__class__,self).GetItemPyData(item)
294        for item in logitem.indexRefs[:-1]:
295            data = data[item]
296        # set the value
297        data[logitem.indexRefs[-1]] = logitem.value
298    def ReplayMenuCommand(self,logitem):
299        'Perform a Menu item action when read from the log'
300        key = ''
301        for item in logitem.menulabellist:
302            if key: key += '+'
303            key += item
304        if MenuBindingLookup.get(key):
305            MenuBindingLookup[key](None)
306        else:
307            raise Exception('No binding for menu item '+key)       
308       
309#===========================================================================
310# variable tracking
311def LogVarChange(result,key):
312    'Called when a variable is changed to log that action'
313    if not LogInfo['Logging']: return
314    if hasattr(result,'treeRefs'):
315        lastLog = G2logList[-1]
316        fullrefs = result.indexRefs+[key]
317        if type(lastLog) is VarLogEntry:
318            if lastLog.treeRefs == result.treeRefs and lastLog.indexRefs == fullrefs:
319                lastLog.value = result[key]
320                if debug: print 'update last log to ',result[key]
321                return
322        G2logList.append(VarLogEntry(result.treeRefs,fullrefs,result[key]))
323    else:
324        print key,'Error: var change has no provenance info'
325
326#===========================================================================
327# menu command tracking
328def _getmenuinfo(id,G2frame,handler):
329    '''Look up the menu/menu-item label tree from a menuitem's Id
330   
331    Note that menubars contain multiple menus which contain multiple menuitems.
332    A menuitem can itself point to a menu and if so that menu can contain
333    multiple menuitems.
334
335    Here we start with the last menuitem and look up the label for that as well
336    as all parents, which will be found in parent menuitems (if any) and the menubar.
337   
338        menuitem    ->  menu ->  menubar
339           |                          |
340           |->itemlabel               |-> menulabel
341           
342    or
343
344        menuitem    ->  (submenu -> menuitem)*(n times) -> menu -> menubar
345           |                            |                             |
346           |->itemlabel                 |-> sublabel(s)               |-> menulabel
347           
348    :returns: a list containing all the labels (or None)           
349    '''
350    # don't worry about help menuitems
351    if id == wx.ID_ABOUT: return
352    # get the menu item object by searching through all menubars and then its label
353    for menubar in G2frame.dataMenuBars:
354        menuitem = menubar.FindItemById(id)
355        if menuitem:
356            #print 'getmenuinfo found',id,menuitem
357            break
358    else:
359        print '****** getmenuinfo failed for id=',id,'binding to=',handler
360        #raise Exception('debug: getmenuinfo failed')
361        return
362    menuLabelList = [menuitem.GetItemLabel()]
363   
364    # get the menu where the current item is located
365    menu = menuitem.GetMenu()
366    while menu.GetParent(): # is this menu a submenu of a previous menu?
367        parentmenu = menu.GetParent()
368        # cycle through the parentmenu until we find the menu
369        for i in range(parentmenu.GetMenuItemCount()):
370            if parentmenu.FindItemByPosition(i).GetSubMenu()==menu:
371                menuLabelList += [parentmenu.FindItemByPosition(i).GetItemLabel()]
372                break
373        else:
374            # menu not found in menu, something is wrong
375            print 'error tracing menuitem to parent menu',menuLabelList
376            #raise Exception('debug1: error tracing menuitem')
377            return
378        menu = parentmenu
379
380    menubar = menu.MenuBar
381    for i in range(menubar.GetMenuCount()):
382        if menubar.GetMenu(i) == menu:
383            menuLabelList += [menubar.GetMenuLabel(i)]
384            break
385    else:
386        # menu not found in menubar, something is wrong
387        print 'error tracing menuitem to menubar',menuLabelList
388        #raise Exception('debug2: error tracing menuitem')
389        return
390    return menuLabelList
391
392def SaveMenuCommand(id,G2frame,handler):
393    '''Creates a table of menu items and their pseudo-bindings
394    '''
395    menuLabelList = _getmenuinfo(id,G2frame,handler)
396    if not menuLabelList: return
397    key = ''
398    for item in menuLabelList:
399        if key: key += '+'
400        key += item
401    MenuBindingLookup[key] = handler
402    return menuLabelList
403
404def InvokeMenuCommand(id,G2frame,event):
405    '''Called when a menu item is used to log the action as well as call the
406    routine "bind"ed to that menu item
407    '''
408    menuLabelList = _getmenuinfo(id,G2frame,None)
409    key = ''
410    if menuLabelList: 
411        for item in menuLabelList:
412            if key: key += '+'
413            key += item
414    if key in MenuBindingLookup:
415        if LogInfo['Logging']: 
416            G2logList.append(MenuLogEntry(menuLabelList))
417        MenuBindingLookup[key](event)
418    else:
419        print 'Error no binding for menu command',menuLabelList,'id=',id
420        return
421
422#===========================================================================
423# Misc externally callable routines
424def LogOn():
425    'Turn On logging of actions'
426    LogInfo['Logging'] = True
427
428def LogOff():
429    'Turn Off logging of actions'
430    LogInfo['Logging'] = False
431
432def ShowLogStatus():
433    'Return the logging status'
434    return LogInfo['Logging']
435
436def ReplayLog(event):
437    'replay the logged actions (needs to be a wx.widget)'
438    LogOff() # TODO: need to update menu item as well
439    LogInfo['Tree'].ClearDataRepaint()
440    print 70*'='
441    print 'Performing logged actions:'
442    for item in G2logList:
443        if item:
444            print item
445            LogInfo['Tree'].ReplayLogItem(item)
446            wx.Yield()
447    # do repaint here
448    LogInfo['Tree'].RepaintDataWindow()
449    print 70*'='
450   
451   
452   
Note: See TracBrowser for help on using the repository browser.