Tuesday, July 28, 2009

Highlevel Mouse Event Converter (wxPython)
















I really needed to make a good one (not a school assignment :) that convers lowlevel mouse events to highlevel ones. When the user left double clicks on a window, the window gets leftdown, leftup, leftdoubleclick, leftup events in this order. It is too low level. In this case all we want is a single double click event.

- A double click event doesn't come with friends.
- Whenever a mouse down event comes, the application can except a following mouse up event (it may be after dragging event stuff or enter/leave window event).
- A series of dragging events are always wrapped by a startdragging and an enddragging event.
- Capture mouse automatically. (See the comment in onButtonDown() method)
- Can set threshold in pixels so that anybody can double click with his trembling hand (e.g. my dad's)

BUG
While dragging, EnterWindow/LeaveWindow occurs when the mouse enter/leaves the parent window on GTK+.

UPDATES
Jul 29: Passing proper event object to mouse down/up handlers.
Jul 31: Get the right mouse position while mouse captured.
Aug 5 : Fixed missing double click detection failure.
Aug 8 : Modified to Register popup menu functions.


"""Low level -> high level mouse event converter."""
import wx

NORMAL = 0
FIRSTWAIT = 1
SECONDWAIT = 2
AFTERDCLICK = 3
MOUSEDOWN = 4
DRAGGING = 5

ID_FIRSTTIMER = wx.NewId()
ID_SECONDTIMER = wx.NewId()

class HighLevMouseEventConv(object):
def __init__(self, eventWindow, t1 = 200, t2 = 100, dragThreshPixel = 3):
"""HighLevelEvent(eventWindow, t1 = 200, t2 = 100, dragThreshPixel = 3)

eventWindow: The window that receives mouse events.
t1, t2: How long it waits to detect is a mouse down is a double click in mili seconds.
dragThreshPixel: Drag moves within this thresh do not produce drag related events.

When you want a popup menu associated with the window create a func to show the menu, register
it using set(Left, Middle, Right)PopupMenuFunc(). These functions should never return value.
"""
self.t1 = t1
self.t2 = t2
self.dragThreshPixel = dragThreshPixel
self.state = NORMAL
self.evwin = eventWindow

self.firstTimer = wx.Timer(eventWindow, ID_FIRSTTIMER)
self.secondTimer = wx.Timer(eventWindow, ID_SECONDTIMER)

dh = self.defaultHandler
l, r, m = wx.MOUSE_BTN_LEFT, wx.MOUSE_BTN_RIGHT, wx.MOUSE_BTN_MIDDLE

self.onHLVMouseDown = {l:dh, r:dh, m:dh}
self.onHLVMouseUp = {l:dh, r:dh, m:dh}
self.onHLVMouseDClick = {l:dh, r:dh, m:dh}
self.onHLVMouseStartDragging = {l:dh, r:dh, m:dh}
self.onHLVMouseDragging = {l:dh, r:dh, m:dh}
self.onHLVMouseEndDragging = {l:dh, r:dh, m:dh}
self.popupMenuFunc = {l:dh, r:dh, m:dh}

self.onHLVMotion = dh
self.onHLVEnterWindow = dh
self.onHLVLeaveWindow = dh

eventWindow.Bind(wx.EVT_MOUSE_EVENTS, self.onMouseEvents)
eventWindow.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.onMouseCaptureLost)
eventWindow.Bind(wx.EVT_TIMER, self.onFirstTimer, id = ID_FIRSTTIMER)
eventWindow.Bind(wx.EVT_TIMER, self.onSecondTimer, id = ID_SECONDTIMER)


def setOnHLVLeftDown(self, handler):
self.onHLVMouseDown[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVLeftUp(self, handler):
self.onHLVMouseUp[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVLeftDClick(self, handler):
self.onHLVMouseDClick[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVLeftStartDragging(self, handler):
self.onHLVMouseStartDragging[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVLeftDragging(self, handler):
self.onHLVMouseDragging[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVLeftEndDragging(self, handler):
self.onHLVMouseEndDragging[wx.MOUSE_BTN_LEFT] = handler

def setOnHLVMiddleDown(self, handler):
self.onHLVMouseDown[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVMiddleUp(self, handler):
self.onHLVMouseUp[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVMiddleDClick(self, handler):
self.onHLVMouseDClick[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVMiddleStartDragging(self, handler):
self.onHLVMouseStartDragging[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVMiddleDragging(self, handler):
self.onHLVMouseDragging[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVMiddleEndDragging(self, handler):
self.onHLVMouseEndDragging[wx.MOUSE_BTN_MIDDLE] = handler

def setOnHLVRightDown(self, handler):
self.onHLVMouseDown[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVRightUp(self, handler):
self.onHLVMouseUp[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVRightDClick(self, handler):
self.onHLVMouseDClick[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVRightStartDragging(self, handler):
self.onHLVMouseStartDragging[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVRightDragging(self, handler):
self.onHLVMouseDragging[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVRightEndDragging(self, handler):
self.onHLVMouseEndDragging[wx.MOUSE_BTN_RIGHT] = handler

def setOnHLVMotion(self, handler):
self.onHLVMotion = handler

def setOnHLVEnterWindow(self, handler):
"""It can detect the mouse enter/leaves from, to the parent window when the mouse is dragging.
There's no clear way but detecting mouse motion event to enter/leave the window itself
while the mouse is captured to the parent"""
self.onHLVEnterWindow = handler

def setOnHLVLeaveWindow(self, handler):
"""See setOnHLVLeaveWindow doc."""
self.onHLVLeaveWindow = handler

#popupMenu
def setLeftPopupMenuFunc(self, func):
self.popupMenuFunc[wx.MOUSE_BTN_LEFT] = func

def setMiddlePopupMenuFunc(self, func):
self.popupMenuFunc[wx.MOUSE_BTN_MIDDLE] = func

def setRightPopupMenuFunc(self, func):
self.popupMenuFunc[wx.MOUSE_BTN_RIGHT] = func


def defaultHandler(self, ev):
return True

def onButtonDown(self, ev):
if not self.popupMenuFunc[ev.GetButton()](ev):
return

if self.state == AFTERDCLICK:
self.state = NORMAL
self.evwin.CaptureMouse()

#On GTK, I knoticed if the window is a control, its parent gets mouse events when the window
#captures mouse events with CaptureMouse(). To call ReleaseMouse() properly this object receives
#the mouse leftup events sent to the parent window temporarily.
parent = self.evwin.GetParent()
if parent:
parent.Bind(wx.EVT_LEFT_UP, self.onParentButtonUp)
parent.Bind(wx.EVT_MIDDLE_UP, self.onParentButtonUp)
parent.Bind(wx.EVT_RIGHT_UP, self.onParentButtonUp)
parent.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.onMouseCaptureLost)
parent.Bind(wx.EVT_MOTION, self.onParentMouseEvents)
parent.Bind(wx.EVT_ENTER_WINDOW, self.onParentEntering)
parent.Bind(wx.EVT_LEAVE_WINDOW, self.onParentLeaving)

self.secondTimer.Stop()
state = self.state
if state == NORMAL:
self.button = ev.GetButton()
self.state = FIRSTWAIT
self.posx, self.posy = ev.GetPosition()
self.downevattrs = self._getEventAttrs(ev)
self.firstTimer.Start(self.t1, True)
elif state == SECONDWAIT:
self.state = AFTERDCLICK
self.onHLVMouseDClick[self.button](ev)

def onButtonUp(self, ev):
evwin = self.evwin
if evwin.HasCapture():
evwin.ReleaseMouse()
parent = evwin.GetParent()
if parent:
parent.Unbind(wx.EVT_LEFT_UP)
parent.Unbind(wx.EVT_MIDDLE_UP)
parent.Unbind(wx.EVT_RIGHT_UP)
parent.Unbind(wx.EVT_MOUSE_CAPTURE_LOST)
parent.Unbind(wx.EVT_MOTION)
parent.Unbind(wx.EVT_ENTER_WINDOW)
parent.Unbind(wx.EVT_LEAVE_WINDOW)

self.firstTimer.Stop()
state = self.state
if state == FIRSTWAIT:
self.state = SECONDWAIT
self.upevattrs = self._getEventAttrs(ev)
self.secondTimer.Start(self.t2, True)
elif state == AFTERDCLICK:
self.state = NORMAL
elif state == MOUSEDOWN:
self.state = NORMAL
self.onHLVMouseUp[self.button](ev)
elif state == DRAGGING:
self.state = NORMAL
self.onHLVMouseEndDragging[self.button](ev)
self.onHLVMouseUp[self.button](ev)

def onButtonDClick(self, ev):
self.secondTimer.Stop()
state = self.state
if state == NORMAL:
self.onButtonDown(ev)
elif state == SECONDWAIT:
self.state = AFTERDCLICK
self.onHLVMouseDClick[self.button](ev)

def onMoving(self, ev):
if self.state == NORMAL:
self.onHLVMotion(ev)

def onDragging(self, ev):
x, y = ev.GetPosition()
t = self.dragThreshPixel
state = self.state
if state == FIRSTWAIT:
if -t < x - self.posx < t and -t < y - self.posy < t:
return
else:
self.state = DRAGGING
self.firstTimer.Stop()
self.onHLVMouseDown[self.button](ev)
self.onHLVMouseStartDragging[self.button](ev)
elif state == MOUSEDOWN:
if -t < x - self.posx < t and -t < y - self.posy < t:
return
else:
self.state = DRAGGING
self.onHLVMouseStartDragging[self.button](ev)
elif state == DRAGGING:
self.onHLVMouseDragging[self.button](ev)

def onEntering(self, ev):
self.onHLVEnterWindow(ev)

def onLeaving(self, ev):
self.onHLVLeaveWindow(ev)


def onMouseEvents(self, ev):
ev.Skip()
if ev.Moving():
return self.onMoving(ev)
elif ev.Dragging():
return self.onDragging(ev)
elif ev.ButtonDown():
return self.onButtonDown(ev)
elif ev.ButtonUp():
return self.onButtonUp(ev)
elif ev.ButtonDClick():
return self.onButtonDClick(ev)
elif ev.Entering():
return self.onEntering(ev)
elif ev.Leaving():
return self.onLeaving(ev)

def onMouseCaptureLost(self, ev):
self.evwin.ReleaseMouse()
ev.Skip()

def onFirstTimer(self, ev):
self.state = MOUSEDOWN
downev = self._createMouseEvent(self.downevattrs)
self.onHLVMouseDown[self.button](downev)

def onSecondTimer(self, ev):
self.state = NORMAL
downev = self._createMouseEvent(self.downevattrs)
upev = self._createMouseEvent(self.upevattrs)
self.onHLVMouseDown[self.button](downev)
self.onHLVMouseUp[self.button](upev)

def onParentButtonUp(self, ev):
self._convetMouseEvent(ev)
self.onButtonUp(ev)
def onParentMouseEvents(self, ev):
self._convetMouseEvent(ev)
self.onMouseEvents(ev)
def onParentEntering(self, ev):
self._convetMouseEvent(ev)
self.onEntering(ev)
def onParentLeaving(self, ev):
self._convetMouseEvent(ev)
self.onLeaving(ev)

def _convetMouseEvent(self, ev):
evwin = self.evwin
parent = evwin.GetParent()
x, y = ev.GetPosition()
offsetx, offsety = evwin.GetPosition()
ev.m_x = x - offsetx
ev.m_y = y - offsety
ev.SetEventObject(evwin)

def _getEventAttrs(self, ev):
return [
ev.GetEventObject(),
ev.GetEventType(),
ev.GetId(),
ev.GetTimestamp(),
ev.m_altDown,
ev.m_controlDown,
ev.m_leftDown,
ev.m_middleDown,
ev.m_rightDown,
ev.m_metaDown,
ev.m_shiftDown,
ev.m_x,
ev.m_y,
ev.m_wheelRotation,
ev.m_wheelDelta,
ev.m_linesPerAction]

def _createMouseEvent(self, eventAttrs):
obj, typ, id, time, alt, ctrl, l, m, r, meta, shift, x, y, wr, wd, lpa = eventAttrs
ev = wx.MouseEvent(typ)
ev.SetEventObject(obj)
ev.GetEventObject()
ev.SetEventType(typ)
ev.SetId(id)
ev.SetTimestamp(time)
ev.m_altDown = alt
ev.m_controlDown = ctrl
ev.m_leftDown = l
ev.m_middleDown = m
ev.m_rightDown = r
ev.m_metaDown = meta
ev.m_shiftDown = shift
ev.m_x = x
ev.m_y = y
ev.m_wheelRotation = wr
ev.m_wheelDelta = wd
ev.m_linesPerAction = lpa
return ev


if __name__ == '__main__':
class TestWindow(wx.Button):
def __init__(self, parent):
super(TestWindow, self).__init__(parent, -1, pos = (50, 50), size = (150, 150))

hlev = HighLevMouseEventConv(self, dragThreshPixel = 15)

hlev.setOnHLVLeftDown(self.onHLVLeftDown)
hlev.setOnHLVLeftUp(self.onHLVLeftUp)
hlev.setOnHLVLeftDClick(self.onHLVLeftDClick)
hlev.setOnHLVLeftStartDragging(self.onHLVLeftStartDragging)
hlev.setOnHLVLeftDragging(self.onHLVLeftDragging)
hlev.setOnHLVLeftEndDragging(self.onHLVLeftEndDragging)
hlev.setOnHLVMiddleDown(self.onHLVMiddleDown)
hlev.setOnHLVMiddleUp(self.onHLVMiddleUp)
hlev.setOnHLVMiddleDClick(self.onHLVMiddleDClick)
hlev.setOnHLVMiddleStartDragging(self.onHLVMiddleStartDragging)
hlev.setOnHLVMiddleDragging(self.onHLVMiddleDragging)
hlev.setOnHLVMiddleEndDragging(self.onHLVMiddleEndDragging)
hlev.setOnHLVRightDown(self.onHLVRightDown)
hlev.setOnHLVRightUp(self.onHLVRightUp)
hlev.setOnHLVRightDClick(self.onHLVRightDClick)
hlev.setOnHLVRightStartDragging(self.onHLVRightStartDragging)
hlev.setOnHLVRightDragging(self.onHLVRightDragging)
hlev.setOnHLVRightEndDragging(self.onHLVRightEndDragging)
hlev.setOnHLVMotion(self.onHLVMotion)
hlev.setOnHLVEnterWindow(self.onHLVEnterWindow)
hlev.setOnHLVLeaveWindow(self.onHLVLeaveWindow)

self.lup = True
self.dragstate = 'normal'

def onHLVLeftDown(self, ev):
print 'onHLVLeftDown', ev.GetPosition(), type(ev.GetEventObject()).__name__
if not self.lup: print 'ERROR ' * 5
self.lup = False
def onHLVLeftUp(self, ev):
print 'onHLVLeftUp', ev.GetPosition(), type(ev.GetEventObject()).__name__
self.lup = True
def onHLVLeftDClick(self, ev):
print 'onHLVLeftDClick', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVLeftStartDragging(self, ev):
print 'onHLVLeftStartDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
if self.dragstate != 'normal': print 'ERROR ' * 5
self.dragstate = 'started'
def onHLVLeftDragging(self, ev):
print 'onHLVLeftDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
if not self.dragstate in ('started', 'dragging'): print 'ERROR ' * 5
self.dragstate = 'dragging'
def onHLVLeftEndDragging(self, ev):
print 'onHLVLeftEndDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
if not self.dragstate in ('started', 'dragging'): print 'ERROR ' * 5
self.dragstate = 'normal'
def onHLVMiddleDown(self, ev):
print 'onHLVMiddleDown', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMiddleUp(self, ev):
print 'onHLVMiddleUp', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMiddleDClick(self, ev):
print 'onHLVMiddleDClick', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMiddleStartDragging(self, ev):
print 'onHLVMiddleStartDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMiddleDragging(self, ev):
print 'onHLVMiddleDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMiddleEndDragging(self, ev):
print 'onHLVMiddleEndDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightDown(self, ev):
print 'onHLVRightDown', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightUp(self, ev):
print 'onHLVRightUp', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightDClick(self, ev):
print 'onHLVRightDClick', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightStartDragging(self, ev):
print 'onHLVRightStartDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightDragging(self, ev):
print 'onHLVRightDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVRightEndDragging(self, ev):
print 'onHLVRightEndDragging', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVMotion(self, ev):
print 'onHLVMotion', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVEnterWindow(self, ev):
print 'onHLVEnterWindow', ev.GetPosition(), type(ev.GetEventObject()).__name__
def onHLVLeaveWindow(self, ev):
print 'onHLVLeaveWindow', ev.GetPosition(), type(ev.GetEventObject()).__name__


app = wx.App()
frame = wx.Frame(None, -1, "Title", size = (300, 400))
panel = wx.Panel(frame, -1)
testwin = TestWindow(panel)
frame.Show()
app.MainLoop()

No comments: