mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-27 05:32:41 -06:00
461 lines
16 KiB
Python
461 lines
16 KiB
Python
#----------------------------------------------------------------------------
|
|
# Name: wx.lib.eventwatcher
|
|
# Purpose: A widget that allows some or all events for a particular widget
|
|
# to be captured and displayed.
|
|
#
|
|
# Author: Robin Dunn
|
|
#
|
|
# Created: 21-Jan-2009
|
|
# Copyright: (c) 2009 by Total Control Software
|
|
# Licence: wxWindows license
|
|
#
|
|
# Tags: phoenix-port
|
|
#----------------------------------------------------------------------------
|
|
|
|
"""
|
|
A widget and supporting classes for watching the events sent to some other widget.
|
|
"""
|
|
|
|
import wx
|
|
from wx.lib.mixins.listctrl import CheckListCtrlMixin
|
|
|
|
#----------------------------------------------------------------------------
|
|
# Helpers for building the data structures used for tracking the
|
|
# various event binders that are available
|
|
|
|
_eventBinders = None
|
|
_eventIdMap = None
|
|
|
|
def _buildModuleEventMap(module):
|
|
count = 0
|
|
for name in dir(module):
|
|
if name.startswith('EVT_'):
|
|
item = getattr(module, name)
|
|
if isinstance(item, wx.PyEventBinder) and \
|
|
len(item.evtType) == 1 and \
|
|
item not in _eventBinders:
|
|
_eventBinders.append(item)
|
|
_eventIdMap[item.typeId] = name
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def buildWxEventMap():
|
|
"""
|
|
Add the event binders from the main wx namespace. This is called
|
|
automatically from the EventWatcher.
|
|
"""
|
|
global _eventBinders
|
|
global _eventIdMap
|
|
if _eventBinders is None:
|
|
_eventBinders = list()
|
|
_eventIdMap = dict()
|
|
_buildModuleEventMap(wx)
|
|
|
|
|
|
def addModuleEvents(module):
|
|
"""
|
|
Adds all the items in module that start with ``EVT_`` to the event
|
|
data structures used by the EventWatcher.
|
|
"""
|
|
if _eventBinders is None:
|
|
buildWxEventMap()
|
|
return _buildModuleEventMap(module)
|
|
|
|
|
|
# Events that should not be watched by default
|
|
_noWatchList = [
|
|
wx.EVT_PAINT,
|
|
wx.EVT_NC_PAINT,
|
|
wx.EVT_ERASE_BACKGROUND,
|
|
wx.EVT_IDLE,
|
|
wx.EVT_UPDATE_UI,
|
|
wx.EVT_UPDATE_UI_RANGE,
|
|
]
|
|
OTHER_WIDTH = 250
|
|
|
|
|
|
def _makeSourceString(wdgt):
|
|
if wdgt is None:
|
|
return "None"
|
|
else:
|
|
name = ''
|
|
id = 0
|
|
if hasattr(wdgt, 'GetName'):
|
|
name = wdgt.GetName()
|
|
if hasattr(wdgt, 'GetId'):
|
|
id = wdgt.GetId()
|
|
return '%s "%s" (%d)' % (wdgt.__class__.__name__, name, id)
|
|
|
|
def _makeAttribString(evt):
|
|
"Find all the getters"
|
|
attribs = ""
|
|
for name in dir(evt):
|
|
if (name.startswith('Get') or name.startswith('Is')) and \
|
|
name not in [ 'GetEventObject',
|
|
'GetEventType',
|
|
'GetId',
|
|
'GetSkipped',
|
|
'GetTimestamp',
|
|
'GetClientData',
|
|
'GetClientObject',
|
|
]:
|
|
try:
|
|
value = getattr(evt, name)()
|
|
attribs += "%s : %s\n" % (name, value)
|
|
except Exception:
|
|
pass
|
|
|
|
return attribs.rstrip()
|
|
|
|
def cmp(a, b):
|
|
return (a > b) - (a < b)
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
class EventLog(wx.ListCtrl):
|
|
"""
|
|
A virtual listctrl that displays information about the watched events.
|
|
"""
|
|
def __init__(self, *args, **kw):
|
|
kw['style'] = wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES
|
|
wx.ListCtrl.__init__(self, *args, **kw)
|
|
self.clear()
|
|
|
|
if 'wxMac' in wx.PlatformInfo:
|
|
self.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
|
|
|
|
self.InsertColumn(0, "#", format=wx.LIST_FORMAT_RIGHT, width=50)
|
|
self.InsertColumn(1, "Event", width=200)
|
|
self.InsertColumn(2, "Source", width=200)
|
|
|
|
self.SetMinSize((450+wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X), 450))
|
|
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
|
|
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onItemActivated)
|
|
self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.onItemActivated)
|
|
|
|
def append(self, evt):
|
|
evtName = _eventIdMap.get(evt.GetEventType(), None)
|
|
if evtName is None:
|
|
evtName = 'Unknown: %d' % evt.GetEventType()
|
|
source = _makeSourceString(evt.GetEventObject())
|
|
attribs = _makeAttribString(evt)
|
|
|
|
lastIsSelected = self.currItem == len(self.data)-1
|
|
self.data.append( (evtName, source, attribs) )
|
|
|
|
count = len(self.data)
|
|
self.SetItemCount(count)
|
|
self.RefreshItem(count-1)
|
|
if lastIsSelected:
|
|
self.Select(count-1)
|
|
self.EnsureVisible(count-1)
|
|
|
|
def clear(self):
|
|
self.data = []
|
|
self.SetItemCount(0)
|
|
self.currItem = -1
|
|
self.Refresh()
|
|
|
|
def OnGetItemText(self, item, col):
|
|
if col == 0:
|
|
val = str(item+1)
|
|
else:
|
|
val = self.data[item][col-1]
|
|
return val
|
|
|
|
def OnGetItemAttr(self, item): return None
|
|
def OnGetItemImage(self, item): return -1
|
|
|
|
def onItemSelected(self, evt):
|
|
self.currItem = evt.GetIndex()
|
|
|
|
def onItemActivated(self, evt):
|
|
idx = evt.GetIndex()
|
|
text = self.data[idx][2]
|
|
wx.CallAfter(wx.TipWindow, self, text, OTHER_WIDTH)
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
class EventChooser(wx.Panel):
|
|
"""
|
|
Panel with CheckListCtrl for selecting which events will be watched.
|
|
"""
|
|
|
|
class EventChooserLC(wx.ListCtrl, CheckListCtrlMixin):
|
|
def __init__(self, parent):
|
|
wx.ListCtrl.__init__(self, parent,
|
|
style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES)
|
|
CheckListCtrlMixin.__init__(self)
|
|
if 'wxMac' in wx.PlatformInfo:
|
|
self.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
|
|
|
|
# this is called by the base class when an item is checked/unchecked
|
|
def OnCheckItem(self, index, flag):
|
|
self.Parent.OnCheckItem(index, flag)
|
|
|
|
|
|
def __init__(self, *args, **kw):
|
|
wx.Panel.__init__(self, *args, **kw)
|
|
self.updateCallback = lambda: None
|
|
self.doUpdate = True
|
|
self._event_name_filter = wx.SearchCtrl(self)
|
|
self._event_name_filter.ShowCancelButton(True)
|
|
self._event_name_filter.Bind(wx.EVT_TEXT, lambda evt: self.setWatchList(self.watchList))
|
|
self._event_name_filter.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self._ClearEventFilter)
|
|
self.lc = EventChooser.EventChooserLC(self)
|
|
btn1 = wx.Button(self, -1, "All")
|
|
btn2 = wx.Button(self, -1, "None")
|
|
btn1.SetToolTip("Check all events")
|
|
btn2.SetToolTip("Uncheck all events")
|
|
|
|
self.Bind(wx.EVT_BUTTON, self.onCheckAll, btn1)
|
|
self.Bind(wx.EVT_BUTTON, self.onUncheckAll, btn2)
|
|
|
|
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onItemActivated, self.lc)
|
|
self.lc.InsertColumn(0, "Binder", width=OTHER_WIDTH)
|
|
|
|
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
btnSizer.Add(btn1, 0, wx.ALL, 5)
|
|
btnSizer.Add(btn2, 0, wx.ALL, 5)
|
|
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
sizer.Add(self._event_name_filter, 0, wx.EXPAND|wx.ALL, 5)
|
|
sizer.Add(self.lc, 1, wx.EXPAND)
|
|
sizer.Add(btnSizer)
|
|
self.SetSizer(sizer)
|
|
|
|
|
|
def setUpdateCallback(self, func):
|
|
self.updateCallback = func
|
|
|
|
def setWatchList(self, watchList):
|
|
self.doUpdate = False
|
|
searched = self._event_name_filter.GetValue().lower()
|
|
self.watchList = watchList
|
|
self.lc.DeleteAllItems()
|
|
count = 0
|
|
for index, (item, flag) in enumerate(watchList):
|
|
typeId = item.typeId
|
|
text = _eventIdMap.get(typeId, "[Unknown]")
|
|
if text.lower().find(searched) == -1:
|
|
continue
|
|
self.lc.InsertStringItem(count, text)
|
|
self.lc.SetItemData(count, index)
|
|
if flag:
|
|
self.lc.CheckItem(count)
|
|
count += 1
|
|
self.lc.SortItems(self.sortCompare)
|
|
self.doUpdate = True
|
|
self.updateCallback()
|
|
|
|
|
|
def OnCheckItem(self, index, flag):
|
|
index = self.lc.GetItemData(index)
|
|
item, f = self.watchList[index]
|
|
self.watchList[index] = (item, flag)
|
|
if self.doUpdate:
|
|
self.updateCallback()
|
|
|
|
|
|
def onItemActivated(self, evt):
|
|
self.lc.ToggleItem(evt.m_itemIndex)
|
|
|
|
def onCheckAll(self, evt):
|
|
self.doUpdate = False
|
|
for idx in range(self.lc.GetItemCount()):
|
|
self.lc.CheckItem(idx, True)
|
|
self.doUpdate = True
|
|
self.updateCallback()
|
|
|
|
def onUncheckAll(self, evt):
|
|
self.doUpdate = False
|
|
for idx in range(self.lc.GetItemCount()):
|
|
self.lc.CheckItem(idx, False)
|
|
self.doUpdate = True
|
|
self.updateCallback()
|
|
|
|
def sortCompare(self, data1, data2):
|
|
item1 = self.watchList[data1][0]
|
|
item2 = self.watchList[data2][0]
|
|
text1 = _eventIdMap.get(item1.typeId)
|
|
text2 = _eventIdMap.get(item2.typeId)
|
|
return cmp(text1, text2)
|
|
|
|
def _ClearEventFilter(self, evt):
|
|
self._event_name_filter.SetValue("")
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
class EventWatcher(wx.Frame):
|
|
"""
|
|
A frame that will catch and display all events sent to some widget.
|
|
"""
|
|
def __init__(self, *args, **kw):
|
|
wx.Frame.__init__(self, *args, **kw)
|
|
self.SetTitle("EventWatcher")
|
|
self.SetExtraStyle(wx.WS_EX_BLOCK_EVENTS)
|
|
self._watchedWidget = None
|
|
|
|
buildWxEventMap()
|
|
self.buildWatchList(_noWatchList)
|
|
|
|
# Make the widgets
|
|
self.splitter = wx.SplitterWindow(self)
|
|
panel = wx.Panel(self.splitter)
|
|
self.splitter.Initialize(panel)
|
|
self.log = EventLog(panel)
|
|
clearBtn = wx.Button(panel, -1, "Clear")
|
|
addBtn = wx.Button(panel, -1, "Add Module")
|
|
watchBtn = wx.ToggleButton(panel, -1, "Watch")
|
|
watchBtn.SetValue(True)
|
|
selectBtn = wx.ToggleButton(panel, -1, ">>>")
|
|
self.selectBtn = selectBtn
|
|
|
|
clearBtn.SetToolTip("Clear the event log")
|
|
addBtn.SetToolTip("Add the event binders in an additional package or module to the watcher")
|
|
watchBtn.SetToolTip("Toggle the watching of events")
|
|
selectBtn.SetToolTip("Show/hide the list of events to be logged")
|
|
|
|
# Do the layout
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
btnSizer.Add(clearBtn, 0, wx.RIGHT, 5)
|
|
btnSizer.Add(addBtn, 0, wx.RIGHT, 5)
|
|
btnSizer.Add((1,1), 1)
|
|
btnSizer.Add(watchBtn, 0, wx.RIGHT, 5)
|
|
btnSizer.Add((1,1), 1)
|
|
btnSizer.Add(selectBtn, 0, wx.RIGHT, 5)
|
|
sizer.Add(self.log, 1, wx.EXPAND)
|
|
sizer.Add(btnSizer, 0, wx.EXPAND|wx.ALL, 5)
|
|
panel.SetSizer(sizer)
|
|
self.Sizer = wx.BoxSizer()
|
|
self.Sizer.Add(self.splitter, 1, wx.EXPAND)
|
|
self.Fit()
|
|
|
|
# Bind events
|
|
self.Bind(wx.EVT_CLOSE, self.onCloseWindow)
|
|
self.Bind(wx.EVT_BUTTON, self.onClear, clearBtn)
|
|
self.Bind(wx.EVT_BUTTON, self.onAddModule, addBtn)
|
|
self.Bind(wx.EVT_TOGGLEBUTTON, self.onToggleWatch, watchBtn)
|
|
self.Bind(wx.EVT_TOGGLEBUTTON, self.onToggleSelectEvents, selectBtn)
|
|
|
|
|
|
|
|
def watch(self, widget):
|
|
assert self._watchedWidget is None, "Can only watch one widget at a time"
|
|
self.SetTitle("EventWatcher for " + _makeSourceString(widget))
|
|
for evtBinder, flag in self._watchedEvents:
|
|
if flag:
|
|
widget.Bind(evtBinder, self.onWatchedEvent)
|
|
self._watchedWidget = widget
|
|
|
|
|
|
def unwatch(self):
|
|
self.SetTitle("EventWatcher")
|
|
if self._watchedWidget:
|
|
for evtBinder, flag in self._watchedEvents:
|
|
self._watchedWidget.Unbind(evtBinder, handler=self.onWatchedEvent)
|
|
self._watchedWidget = None
|
|
|
|
|
|
def updateBindings(self):
|
|
widget = self._watchedWidget
|
|
self.unwatch()
|
|
self.watch(widget)
|
|
|
|
|
|
def onWatchedEvent(self, evt):
|
|
if self:
|
|
self.log.append(evt)
|
|
evt.Skip()
|
|
|
|
def buildWatchList(self, exclusions):
|
|
# This is a list of (PyEventBinder, flag) tuples where the flag indicates
|
|
# whether to bind that event or not. By default all execpt those in
|
|
# the _noWatchList wil be set to be watched.
|
|
self._watchedEvents = list()
|
|
for item in _eventBinders:
|
|
self._watchedEvents.append( (item, item not in exclusions) )
|
|
|
|
def onCloseWindow(self, evt):
|
|
self.unwatch()
|
|
evt.Skip()
|
|
|
|
def onClear(self, evt):
|
|
self.log.clear()
|
|
|
|
def onAddModule(self, evt):
|
|
try:
|
|
dlg = wx.TextEntryDialog(
|
|
self,
|
|
"Enter the package or module name to be scanned for \"EVT_\" event binders.",
|
|
"Add Module")
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
modname = dlg.GetValue()
|
|
try:
|
|
# Passing a non-empty fromlist will cause __import__ to
|
|
# return the imported submodule if a dotted name is passed.
|
|
module = __import__(modname, fromlist=[0])
|
|
except ImportError:
|
|
wx.MessageBox("Unable to import \"%s\"" % modname,
|
|
"Error")
|
|
return
|
|
count = addModuleEvents(module)
|
|
wx.MessageBox("%d new event binders found" % count,
|
|
"Success")
|
|
|
|
# Now unwatch and re-watch so we can get the new events bound
|
|
self.updateBindings()
|
|
finally:
|
|
dlg.Destroy()
|
|
|
|
|
|
def onToggleWatch(self, evt):
|
|
if evt.IsChecked():
|
|
self.watch(self._unwatchedWidget)
|
|
self._unwatchedWidget = None
|
|
else:
|
|
self._unwatchedWidget = self._watchedWidget
|
|
self.unwatch()
|
|
|
|
|
|
def onToggleSelectEvents(self, evt):
|
|
if evt.IsChecked():
|
|
self.selectBtn.SetLabel("<<<")
|
|
self._selectList = EventChooser(self.splitter)
|
|
self._selectList.setUpdateCallback(self.updateBindings)
|
|
self._selectList.setWatchList(self._watchedEvents)
|
|
|
|
self.SetSize(self.GetSize() + (OTHER_WIDTH,0))
|
|
self.splitter.SplitVertically(self.splitter.GetWindow1(),
|
|
self._selectList,
|
|
-OTHER_WIDTH)
|
|
else:
|
|
self.selectBtn.SetLabel(">>>")
|
|
sashPos = self.splitter.GetSashPosition()
|
|
self.splitter.Unsplit()
|
|
self._selectList.Destroy()
|
|
cs = self.GetClientSize()
|
|
self.SetClientSize((sashPos, cs.height))
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
if __name__ == '__main__':
|
|
app = wx.App(redirect=False)
|
|
frm = wx.Frame(None, title="Test Frame")
|
|
pnl = wx.Panel(frm)
|
|
txt = wx.TextCtrl(pnl, -1, "text", pos=(20,20))
|
|
btn = wx.Button(pnl, -1, "button", pos=(20,50))
|
|
frm.Show()
|
|
|
|
ewf=EventWatcher(frm)
|
|
ewf.watch(frm)
|
|
ewf.Show()
|
|
|
|
#import wx.lib.inspection
|
|
#wx.lib.inspection.InspectionTool().Show()
|
|
|
|
app.MainLoop()
|
|
|