source: branch/logging/log.py @ 1509

Last change on this file since 1509 was 1509, checked in by toby, 8 years ago

logging refactored, and much cleaner\!

  • Property svn:eol-style set to native
File size: 18.4 KB
Line 
1'Module to provide logging services'
2import wx
3import GSASIIgrid as G2gd
4MenuBindingLookup = {}
5'Lookup table for Menu buttons'
6ButtonBindingLookup = {}
7'Lookup table for button objects'
8G2logList = [None]
9'Contains a list of logged actions; first item is ignored'
10LogInfo = {'Logging':False, 'Tree':None, 'G2frame':None, 'LastPaintAction':None}
11'Contains a dict with values that are needed in the module'
12
13# TODO:
14# might want to save the last displayed screen with some objects to make sure
15# the commands are executed in a sensible order
16
17# Problem: checks on Logging not set properly (still!)
18
19debug = True
20#===========================================================================
21# objects for logging variables
22def _l2s(lst,separator='+'):
23    'Combine a list of objects into a string, with a separator'
24    s = ''
25    for i in lst: 
26        if s != '': s += separator
27        s += '"'+str(i)+'"'
28    return s
29
30# Define logging objects. These store information on events in a manner that can be
31# pickled and saved -- direct references to wx objects is not allowed.
32# each object should define:
33#     __init__: stores the information needed to log & later recreate the action
34#     __str__ : shows a nice ASCII string for each action
35#     Replay:   recreates the action when the log is played
36# optional
37#     Repaint:  redisplays the current window
38
39class VarLogEntry(object):
40    'object that tracks changes to a variable'
41    def __init__(self,treeRefs,indexRefs,value):
42        self.treeRefs = treeRefs
43        self.indexRefs = indexRefs
44        self.value = value
45        if debug: print 'Logging var change: w/treeRefs',treeRefs,'indexRefs',indexRefs,'new value=',value
46    def __str__(self):
47        treeList = self.treeRefs[:]
48        if type(treeList[0]) is tuple:
49            treeList[0] = 'Hist #'+str(treeList[0][1]+1)+' of type '+treeList[0][0]
50        elif len(treeList) > 1 and type(treeList[1]) is int:
51            treeList[1] = 'Phase #'+str(treeList[1]+1)
52        return 'Variable change: Key(s)= '+_l2s(self.indexRefs)+' to value='+str(self.value)
53    def Replay(self):
54        'Perform a Variable Change action, when read from the log'
55        parentId = LogInfo['Tree'].root
56        for i,treeitem in enumerate(self.treeRefs):
57            if i == 0 and type(treeitem) is tuple:
58                treeitem = LogInfo['Tree'].ConvertRelativeHistNum(*treeitem)
59            item, cookie = LogInfo['Tree'].GetFirstChild(parentId)
60            while item:
61                if LogInfo['Tree'].GetItemText(item) == treeitem:
62                    parentId = item
63                    break
64                else:
65                    item, cookie = LogInfo['Tree'].GetNextChild(parentId, cookie)
66            else:
67                raise Exception("Tree item not found for "+str(self))
68        # get the inner most data array
69        data = LogInfo['Tree'].GetItemPyData(item)
70        for item in self.indexRefs[:-1]:
71            data = data[item]
72        # set the value
73        data[self.indexRefs[-1]] = self.value
74
75class MenuLogEntry(object):
76    'object that tracks when a menu command is executed'
77    def __init__(self,menulabellist):
78        self.menulabellist = menulabellist
79        if debug:
80            t = menulabellist[:]
81            t.reverse()
82            l = ''
83            for item in t:
84                if l: l += ' -> '
85                l += item
86            print 'Logging menu command: '+l
87    def __str__(self):
88        return 'Menu press: From '+_l2s(self.menulabellist,'/')
89    def Replay(self):
90        'Perform a Menu item action when read from the log'
91        key = ''
92        for item in self.menulabellist:
93            if key: key += '+'
94            key += item
95        if MenuBindingLookup.get(key):
96            handler,id,menuobj = MenuBindingLookup[key]
97            MyEvent = wx.CommandEvent(wx.EVT_MENU.typeId, id)
98            MyEvent.SetEventObject(menuobj)
99            handler(MyEvent)
100        else:
101            raise Exception('No binding for menu item '+key)       
102           
103class TabLogEntry(object):
104    'Object to track when tabs are pressed in the DataFrame window'
105    def __init__(self,title,tabname):
106        self.wintitle = title
107        self.tablabel = tabname
108        if debug: print 'Logging tab: "'+tabname+'" on window titled '+title
109    def __str__(self):
110        return 'Tab press: Tab='+_l2s([self.tablabel])+' on window labeled '+str(self.wintitle)
111    def Repaint(self):
112        if debug: print 'Repaint'
113        saveval = LogInfo['LastPaintAction']
114        self.Replay()
115        LogInfo['LastPaintAction'] = saveval
116    def Replay(self):
117        'Perform a Tab press action when read from the log'
118        wintitle = self.wintitle
119        tabname = self.tablabel
120        LogInfo['LastPaintAction'] = self
121        if LogInfo['Tree'].G2frame.dataFrame.GetTitle() != wintitle:
122            print LogInfo['Tree'].G2frame.dataFrame.GetTitle(),' != ',wintitle
123            raise Exception('tab in wrong window')
124        for PageNum in range(LogInfo['Tree'].G2frame.dataDisplay.GetPageCount()):
125            if tabname == LogInfo['Tree'].G2frame.dataDisplay.GetPageText(PageNum):
126                LogInfo['Tree'].G2frame.dataDisplay.SetSelection(PageNum)
127                return
128        else:
129            print tabname,'not in',[
130                LogInfo['Tree'].G2frame.dataDisplay.GetPageText(PageNum) for
131                PageNum in range(LogInfo['Tree'].G2frame.dataDisplay.GetPageCount())]
132            raise Exception('tab not found')
133
134class TreeLogEntry(object):
135    'Object to track when tree items are pressed in the main window'
136    def __init__(self,itemlist):
137        self.treeItemList = itemlist
138        if debug: print 'Logging press on tree: ',itemlist
139    def __str__(self):
140        treeList = self.treeItemList[:]
141        if type(treeList[0]) is tuple:
142            treeList[0] = 'Hist #'+str(treeList[0][1]+1)+' of type '+treeList[0][0]
143        elif len(treeList) > 1 and type(treeList[1]) is int:
144            treeList[1] = 'Phase #'+str(treeList[1]+1)
145        return 'Tree item pressed: '+_l2s(treeList)
146    def Repaint(self):
147        if debug: print 'Repaint'
148        saveval = LogInfo['LastPaintAction']
149        LogInfo['Tree'].SelectItem(LogInfo['Tree'].root) # need to select something else
150        wx.Yield()
151        self.Replay()
152        LogInfo['LastPaintAction'] = saveval
153    def Replay(self):
154        'Perform a Tree press action when read from the log'
155        LogInfo['LastPaintAction'] = self
156        parent = LogInfo['Tree'].root
157        for i,txt in enumerate(self.treeItemList):
158            if i == 0 and type(txt) is tuple:
159                txt = LogInfo['Tree'].ConvertRelativeHistNum(*txt)
160            elif i == 1 and type(txt) is int and self.treeItemList[0] == "Phases":
161                txt = LogInfo['Tree'].ConvertRelativePhaseNum(txt)
162            item = G2gd.GetPatternTreeItemId(LogInfo['Tree'].G2frame,parent,txt)
163            if not item:
164                print 'Not found',txt
165                return
166            else:
167                parent = item
168        else:
169            LogInfo['Tree'].SelectItem(item)
170
171class ButtonLogEntry(object):
172    'Object to track button press'
173    def __init__(self,locationcode,label):
174        self.locationcode = locationcode
175        self.label = label
176        if debug: print 'Logging '+label+' button press in '+locationcode
177    def __str__(self):
178        return 'Press of '+self.label+' button in '+self.locationcode
179    def Replay(self):
180        key = self.locationcode + '+' + self.label
181        if ButtonBindingLookup.get(key):
182            btn = ButtonBindingLookup[key]
183            clickEvent = wx.CommandEvent(wx.EVT_BUTTON.typeId, btn.GetId())
184            clickEvent.SetEventObject(btn)
185            #btn.GetEventHandler().ProcessEvent(clickEvent)
186            btn.handler(clickEvent)
187           
188def _wrapper(func):
189    def _wrapped(self, *args, **kwargs):
190        return getattr(self.obj, func)(*args, **kwargs)
191    return _wrapped
192
193class DictMeta(type):
194    def __new__(cls, name, bases, dct):
195        default_attrs = dir(object) + ['__getitem__', '__str__']
196        for attr in dir(dict):
197            if attr not in default_attrs:
198                dct[attr] = _wrapper(attr)
199        return type.__new__(cls, name, bases, dct)
200
201class dictLogged(object):
202    '''A version of a dict object that tracks the source of the
203    object back to the location on the G2 tree.
204    If a list (tuple) or dict are pulled from inside this object
205    the source information is appended to the provinance tracking
206    lists.
207
208    tuples are converted to lists.
209    '''
210    __metaclass__ = DictMeta
211
212    def __init__(self, obj, treeRefs, indexRefs=[]):
213        self.treeRefs = treeRefs
214        self.indexRefs = indexRefs
215        self.obj = obj
216
217    def __getitem__(self,key):
218        val = self.obj.__getitem__(key)   
219        if type(val) is tuple:
220            #if debug: print 'Converting to list',key
221            val = list(val)
222            self.obj[key] = val
223        if type(val) is dict:
224            #print 'dict'
225            return dictLogged(val,self.treeRefs,self.indexRefs+[key])
226        elif type(val) is list:
227            #print 'list'
228            return listLogged(val,self.treeRefs,self.indexRefs+[key])
229        else:
230            #print type(val)
231            return val
232
233    def __str__(self):
234        return self.obj.__str__() + " : " + str(self.treeRefs) + ',' + str(self.indexRefs)
235
236class ListMeta(type):
237    def __new__(cls, name, bases, dct):
238        default_attrs = dir(object) + ['__getitem__', '__str__']
239        for attr in dir(list):
240            if attr not in default_attrs:
241                dct[attr] = _wrapper(attr)
242        return type.__new__(cls, name, bases, dct)
243
244class listLogged(object):
245    '''A version of a list object that tracks the source of the
246    object back to the location on the G2 tree.
247    If a list (tuple) or dict are pulled from inside this object
248    the source information is appended to the provinance tracking
249    lists.
250   
251    tuples are converted to lists.
252    '''
253    __metaclass__ = ListMeta
254
255    def __init__(self, obj, treeRefs, indexRefs=[]):
256        self.treeRefs = treeRefs
257        self.indexRefs = indexRefs
258        self.obj = obj
259
260    def __getitem__(self,key):
261        val = self.obj.__getitem__(key)   
262        if type(val) is tuple:
263            #if debug: print 'Converting to list',key
264            val = list(val)
265            self.obj[key] = val
266        if type(val) is dict:
267            #print 'dict'
268            return dictLogged(val,self.treeRefs,self.indexRefs+[key])
269        elif type(val) is list:
270            #print 'list'
271            return listLogged(val,self.treeRefs,self.indexRefs+[key])
272        else:
273            #print type(val)
274            return val
275
276    def __str__(self):
277        return self.obj.__str__() + " : " + str(self.treeRefs) + ',' + str(self.indexRefs)
278
279#===========================================================================
280# variable tracking
281def LogVarChange(result,key):
282    'Called when a variable is changed to log that action'
283    if not LogInfo['Logging']: return
284    if hasattr(result,'treeRefs'):
285        treevars = result.treeRefs[:]
286        treevars[0] = LogInfo['Tree'].GetRelativeHistNum(treevars[0])
287        if treevars[0] == "Phases" and len(treevars) > 1:
288            treevars[1] = LogInfo['Tree'].GetRelativePhaseNum(treevars[1])
289        lastLog = G2logList[-1]
290        fullrefs = result.indexRefs+[key]
291        if type(lastLog) is VarLogEntry:
292            if lastLog.treeRefs == treevars and lastLog.indexRefs == fullrefs:
293                lastLog.value = result[key]
294                if debug: print 'update last log to ',result[key]
295                return
296        G2logList.append(VarLogEntry(treevars,fullrefs,result[key]))
297    else:
298        print key,'Error: var change has no provenance info'
299
300#===========================================================================
301# menu command tracking
302def _getmenuinfo(id,G2frame,handler):
303    '''Look up the menu/menu-item label tree from a menuitem's Id
304   
305    Note that menubars contain multiple menus which contain multiple menuitems.
306    A menuitem can itself point to a menu and if so that menu can contain
307    multiple menuitems.
308
309    Here we start with the last menuitem and look up the label for that as well
310    as all parents, which will be found in parent menuitems (if any) and the menubar.
311   
312        menuitem    ->  menu ->  menubar
313           |                          |
314           |->itemlabel               |-> menulabel
315           
316    or
317
318        menuitem    ->  (submenu -> menuitem)*(n times) -> menu -> menubar
319           |                            |                             |
320           |->itemlabel                 |-> sublabel(s)               |-> menulabel
321           
322    :returns: a list containing all the labels and the menuitem object
323       or None if the menu object will not be cataloged.
324    '''
325    # don't worry about help menuitems
326    if id == wx.ID_ABOUT: return
327    # get the menu item object by searching through all menubars and then its label
328    for menubar in G2frame.dataMenuBars:
329        menuitem = menubar.FindItemById(id)
330        if menuitem:
331            #print 'getmenuinfo found',id,menuitem
332            break
333    else:
334        print '****** getmenuinfo failed for id=',id,'binding to=',handler
335        #raise Exception('debug: getmenuinfo failed')
336        return
337    menuLabelList = [menuitem.GetItemLabel()]
338   
339    # get the menu where the current item is located
340    menu = menuitem.GetMenu()
341    while menu.GetParent(): # is this menu a submenu of a previous menu?
342        parentmenu = menu.GetParent()
343        # cycle through the parentmenu until we find the menu
344        for i in range(parentmenu.GetMenuItemCount()):
345            if parentmenu.FindItemByPosition(i).GetSubMenu()==menu:
346                menuLabelList += [parentmenu.FindItemByPosition(i).GetItemLabel()]
347                break
348        else:
349            # menu not found in menu, something is wrong
350            print 'error tracing menuitem to parent menu',menuLabelList
351            #raise Exception('debug1: error tracing menuitem')
352            return
353        menu = parentmenu
354
355    menubar = menu.MenuBar
356    for i in range(menubar.GetMenuCount()):
357        if menubar.GetMenu(i) == menu:
358            menuLabelList += [menubar.GetMenuLabel(i)]
359            break
360    else:
361        # menu not found in menubar, something is wrong
362        print 'error tracing menuitem to menubar',menuLabelList
363        #raise Exception('debug2: error tracing menuitem')
364        return
365    return menuLabelList,menuitem
366
367def SaveMenuCommand(id,G2frame,handler):
368    '''Creates a table of menu items and their pseudo-bindings
369    '''
370    menuinfo = _getmenuinfo(id,G2frame,handler)
371    if not menuinfo: return
372    menuLabelList,menuobj = menuinfo
373    key = ''
374    for item in menuLabelList:
375        if key: key += '+'
376        key += item
377    MenuBindingLookup[key] = [handler,id,menuobj]
378    return menuLabelList
379
380def InvokeMenuCommand(id,G2frame,event):
381    '''Called when a menu item is used to log the action as well as call the
382    routine "bind"ed to that menu item
383    '''
384    menuLabelList = _getmenuinfo(id,G2frame,None)
385    key = ''
386    if menuLabelList: 
387        for item in menuLabelList:
388            if key: key += '+'
389            key += item
390    if key in MenuBindingLookup:
391        if LogInfo['Logging']: 
392            G2logList.append(MenuLogEntry(menuLabelList))
393        handler = MenuBindingLookup[key][0]
394        handler(event)
395    else:
396        print 'Error no binding for menu command',menuLabelList,'id=',id
397        return
398
399#===========================================================================
400# Misc externally callable routines
401def LogOn():
402    'Turn On logging of actions'
403    if debug: print 'LogOn'
404    LogInfo['Logging'] = True
405
406def LogOff():
407    'Turn Off logging of actions'
408    if debug: print 'LogOff'
409    LogInfo['Logging'] = False
410   
411def ShowLogStatus():
412    'Return the logging status'
413    return LogInfo['Logging']
414
415def OnReplayPress(event):
416    'execute one or more commands when the replay button is pressed'
417    clb = LogInfo['clb']
418    dlg = clb.GetTopLevelParent()
419    sels = sorted(clb.GetSelections())
420    if not sels:
421        dlg1 = wx.MessageDialog(dlg,
422            'Select one or more items in the list box to replay',
423            'No selection actions',
424            wx.OK)
425        dlg1.CenterOnParent()
426        dlg1.ShowModal()
427        dlg1.Destroy()
428        return
429    logstat = ShowLogStatus()
430    if logstat: LogOff()
431    if debug: print 70*'='
432    for i in sels:
433        i += 1
434        item = G2logList[i]
435        if debug: print 'replaying',item
436        item.Replay()
437        wx.Yield()
438    if i >= len(G2logList)-1:
439         dlg.EndModal(wx.ID_OK)
440    else:
441        clb.DeselectAll()
442        clb.SetSelection(i)
443        if logstat: LogOn()
444    if debug: print 70*'='
445    # if the last command did not display a window, repaint it in
446    # case something on that window changed.
447    if item != LogInfo['LastPaintAction'] and hasattr(LogInfo['LastPaintAction'],'Repaint'):
448        LogInfo['LastPaintAction'].Repaint()
449     
450def ReplayLog(event):
451    'replay the logged actions'
452    LogInfo['LastPaintAction'] = None # clear the pointed to the last data window
453    # is this really needed? -- probably not.
454    commandList = []
455    for item in G2logList:
456        if item: # skip over 1st item in list (None)
457            commandList.append(str(item))
458    if not commandList:
459        dlg = wx.MessageDialog(LogInfo['Tree'],
460            'No actions found in log to replay',
461            'Empty Log',
462            wx.OK)
463        dlg.CenterOnParent()
464        dlg.ShowModal()
465        dlg.Destroy()
466        return
467    dlg = wx.Dialog(LogInfo['Tree'],wx.ID_ANY,'Replay actions from log',
468        style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE)
469    mainSizer = wx.BoxSizer(wx.VERTICAL)
470    mainSizer.Add((5,5))
471    clb = wx.ListBox(dlg, wx.ID_ANY, (30,100), wx.DefaultSize, commandList,
472                     style=wx.LB_EXTENDED)
473    LogInfo['clb'] = clb
474    mainSizer.Add(clb,1,wx.EXPAND,1)
475    mainSizer.Add((5,5))
476    btn = wx.Button(dlg, wx.ID_ANY,'Replay selected')
477    btn.Bind(wx.EVT_BUTTON,OnReplayPress)
478    mainSizer.Add(btn,0,wx.ALIGN_CENTER,0)
479    btnsizer = wx.StdDialogButtonSizer()
480    OKbtn = wx.Button(dlg, wx.ID_OK,'Close')
481    #OKbtn = wx.Button(dlg, wx.ID_CLOSE)
482    OKbtn.SetDefault()
483    OKbtn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK))
484    btnsizer.AddButton(OKbtn)
485    btnsizer.Realize()
486    mainSizer.Add((-1,5),1,wx.EXPAND,1)
487    mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,0)
488    mainSizer.Add((-1,5))
489    dlg.SetSizer(mainSizer)
490    dlg.CenterOnParent()
491    clb.SetSelection(0)
492    dlg.ShowModal()
493    dlg.Destroy()
494    return
495
496LogOn() # for debug
Note: See TracBrowser for help on using the repository browser.