source: branch/logging/log.py @ 1505

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

track with relative phase & histogram numbers

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