Stackless wxPython mouse example (was: setting mouse position)

Jean-Michel Fauth wrote:

Hi Christian,

Can you give me a hint? Or are you trying to make
fun of me... The latter would not make you really
happy, better *don't* try that.

Not at all. It was the wrong fct name .-(, the correct fct is WarpPointer

Sorry about that :slight_smile:

class MyWin(wxWindow):
    def __init__(self, parent, id, pos, size, style):
        ...

        EVT_KEY_DOWN(self, self.OnKeyDown)

    def OnKeyDown(self, event):
        (mousex, mousey) = wxGetMousePosition()
        mousex, mousey = self.ScreenToClientXY(mousex, mousey)
        if event.ControlDown():
            kc = event.KeyCode()
            if kc == WXK_LEFT:
                self.WarpPointer(mousex - 1, mousey)
            elif kc == WXK_RIGHT:
                self.WarpPointer(mousex + 1, mousey)
            elif kc == WXK_UP:
                self.WarpPointer(mousex, mousey - 1)
            elif kc == WXK_DOWN:
                self.WarpPointer(mousex, mousey + 1)
            else:
                pass
        event.Skip()

The absolute addressing is not necessary, you get the relative
coordinates from the event. It is also possible to write
the event handler as a general function, since the window
is an object in the event. So here is mine:

I use an extra class for the event for Stackless, since the
original event doesn't survive my taskswitching.

class MouseKeeper:
     """ clumsy, we have to copy what we need """
     def __init__(self, event):
         self.pos = event.GetPosition()
         self.enter = event.Entering()
         self.leave = event.Leaving()
         self.drag = event.Dragging()
         self.move = event.Moving()
         self.down1 = event.ButtonDown(1)
         self.up1 = event.ButtonUp(1)
         self.down2 = event.ButtonDown(2)
         self.up2 = event.ButtonUp(2)
         self.ctrl = event.ControlDown()
         self.shift = event.ShiftDown()
         self.alt = event.AltDown()

In my handler, I send all mouse events to a stackless.channel,
which is named "mouse". The real handler is then an endless
loop, which reads on the channel.
Here the "do-it-all" for the mouse event:

     def OnMouseEvents(self, event):
         self.SetFocus()
         if not self.graph:
             event.Skip()
             return
         # we must clone, since the C object dies
         # when we leave this stack.
         #dup = event.Clone()
         # oops, doesn't work either.
         # we need to unpack it all here :frowning:
         if not self.mouse or self._pending_refresh:
             return
         x = MouseKeeper(event)
         self.last_mouse_event = x
         self.mouse.send(x)

The reason why I am keeping the last mouse event is to
know the old position and to adjust it. I modify my
event instance, move the mouse, and send the event to
the channel:

     def OnKeyDown(self, event):
         # NOTE:
         # This works only, if we disable
         # wxTAB_TRAVERSAL style for all parent panels!!!
         m = self.last_mouse_event
         if not m:
             event.Skip()
             return
         key = event.GetKeyCode()
         win = event.GetEventObject()
         dx = key == WXK_RIGHT and 1 or key == WXK_LEFT and -1 or 0
         dy = key == WXK_UP and -1 or key == WXK_DOWN and 1 or 0
         if dx or dy:
             x, y = m.pos.x+dx, m.pos.y+dy
             m.pos = wxPoint(x, y)
             win.WarpPointer(x, y)
             self.mouse.send(m)
         else:
             event.Skip()

It might be more elegant to use a second channel for the
key merging stuff, instead of saving the last event in
a "global" self variable. But I have to get more used
to this programming style, at the moment I'm mixing
the old reactive pattern concept with the new "write small
loops for your handlers" style.

Now, to end this lengthy example, here comes my
"new style mouse handler", written as an endless loop.

     def mouseHandler(self):

         mat = [[0,0,0], [0,0,0], [0,0,0]]
         mat[-1][-1] = mat[ 1][ 1] = self.CurNESW
         mat[-1][ 1] = mat[ 1][-1] = self.CurNWSE
         mat[-1][ 0] = mat[ 1][ 0] = self.CurNS
         mat[ 0][-1] = mat[ 0][ 1] = self.CurWE

         mouse = self.mouse
         while 1:
             g = self.graph # which might change
             event = mouse.receive()
             if not g: continue

             x, y = event.pos.x, event.pos.y
             north = east = 0
             what = g.test(x, y)
             if what < 0 and g.canmove:
                 self.SetCursor(self.CurSize)
             elif what == 0 and g.cansize:
                 north, east = g.testNE(x, y)
                 self.SetCursor(mat[north][east])
             else:
                 self.SetCursor(wxSTANDARD_CURSOR)
             if event.down1:
                 if g.mouseDown(x, y):
                     self.update()
             elif event.up1:
                 if g.mouseUp(x, y):
                     self.update()
             if event.enter:
                 if g.mouseEnter(x, y):
                     self.update()
             elif event.leave:
                 if g.mouseLeave(x, y):
                     self.update()
             if event.drag:
                 if what < 0:
                     # shift
                     # stay in a loop or we might miss
                     while event.drag:
                         if g.allowedShift(x, y):
                             g.shiftTo(x, y)
                             if self.notify: self.notify(g)
                             self.update()
                         event = mouse.receive()
                         x, y = event.pos.x, event.pos.y
                         if event.leave and g.mouseLeave(x, y) \
                         or event.up1 and g.mouseUp(x, y):
                             if self.notify: self.notify(g)
                             self.update()
                 elif what == 0:
                     # grow
                     while event.drag:
                         if g.allowedGrowth(x, y, north, east):
                             g.growTo(x, y, north, east)
                             if self.notify: self.notify(g)
                             self.update()
                         event = mouse.receive()
                         x, y = event.pos.x, event.pos.y
                         if event.leave and g.mouseLeave(x, y) \
                         or event.up1 and g.mouseUp(x, y):
                             if self.notify: self.notify(g)
                             self.update()

There is a graph shown, and it is interrogated whether the mouse
is on one of the edges or inside. The mouse cursor is adjusted
accordingly.
Especially not the inner loops.
The one starting with
      if what < 0:
handles the case that we are inside the graph, and we want to
move it. The one starting with
      if what == 0:
handles the case that we are on an edge and we want to change size.

The graph is interrogated if it accepts the move or resize,
and then this is done. The loops are running just *while we move"
or "while we drag".
Not that this example is too simple, but compare this to the
standard approach of many callbacks and state variables:
Here we have one "main" program that does it all. It thinks to
be like a main, and it goes asleep when reading from the mosue
channel, and wakes up when an event occours.

Oh well, the mouse handler is spawned once, in the __init__ of
the class:

         self.mouse = channel()
         tasklet(self.mouseHandler)()

ciao - chris

···

--
Christian Tismer :^) <mailto:tismer@stackless.com>
Mission Impossible 5oftware : Have a break! Take a ride on Python's
Johannes-Niemeyer-Weg 9a : *Starship* http://starship.python.net/
14109 Berlin : PGP key -> http://wwwkeys.pgp.net/
work +49 30 89 09 53 34 home +49 30 802 86 56 mobile +49 173 24 18 776
PGP 0x57F3BF04 9064 F4E1 D754 C2FF 1619 305B C09C 5A3B 57F3 BF04
      whom do you want to sponsor today? http://www.stackless.com/