Newbie right-click issue when using OGL canvas

Hi,

I'm trying to modify the OGL example at http://wiki.wxpython.org/wxOGL
such that right clicking on the canvas presents a user with a drop-
down selection menu, whereas right-clicking on a shape has no such
effect (I intend using that later). I've tried to cater for these
requirements by adding the following: First I've added a OnRightClick
method to the MyEvtHandler class in that example, which works just
fine if it's the only event handler that deals with right-clicks (it
only picks right-clicks on shapes). However, when I add an event
handler to the canvas class via:

canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu)

where OnContextMenu is a callback which brings up the menu for right-
clicks on blank areas of the canvas (adapted from example 8, chapter 3
of Cody Precord's new book), then this handler takes over *all* right
click events, so right-clicking on a shape no longer fires the correct
OnRightClick method mentioned above, but this OnContextMenu too. I'm
guessing that the latter method has overridden the former method.

So guys, can anyone show me the error of my ways and suggest a
solution?

Bob

You might want to attach your code, should make it easier to spot what is happening.

Just a guess, are you doing event.Skip()

http://wiki.wxpython.org/EventPropagation

Werner

···

On 01/03/2011 13:02, Bob wrote:

Hi,

I'm trying to modify the OGL example at wxOGL - wxPyWiki
such that right clicking on the canvas presents a user with a drop-
down selection menu, whereas right-clicking on a shape has no such
effect (I intend using that later). I've tried to cater for these
requirements by adding the following: First I've added a OnRightClick
method to the MyEvtHandler class in that example, which works just
fine if it's the only event handler that deals with right-clicks (it
only picks right-clicks on shapes). However, when I add an event
handler to the canvas class via:

canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu)

where OnContextMenu is a callback which brings up the menu for right-
clicks on blank areas of the canvas (adapted from example 8, chapter 3
of Cody Precord's new book), then this handler takes over *all* right
click events, so right-clicking on a shape no longer fires the correct
OnRightClick method mentioned above, but this OnContextMenu too. I'm
guessing that the latter method has overridden the former method.

So guys, can anyone show me the error of my ways and suggest a
solution?

Hi Werner,

Thanks for the interest. I've included the code below. In reply to
your question, no, I'm not doing an event.Skip() on any right click
event. One thing I don't understand is that in Cody Precord's book he
can initiate a right-click event by binding the callback to a
EVT_CONTEXT_MENU event, whereas I have to use a EVT_RIGHT_DOWN since
the former seems to have has no effect in the context of the canvas.
Anyway, here's the code (please bear in mind that this is a first
attempt with wxPython, so be gentle...).

···

On Mar 1, 4:27 pm, werner <wbru...@free.fr> wrote:

On 01/03/2011 13:02, Bob wrote:

> Hi,

> I'm trying to modify the OGL example athttp://wiki.wxpython.org/wxOGL
> such that right clicking on the canvas presents a user with a drop-
> down selection menu, whereas right-clicking on a shape has no such
> effect (I intend using that later). I've tried to cater for these
> requirements by adding the following: First I've added a OnRightClick
> method to the MyEvtHandler class in that example, which works just
> fine if it's the only event handler that deals with right-clicks (it
> only picks right-clicks on shapes). However, when I add an event
> handler to the canvas class via:

> canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu)

> where OnContextMenu is a callback which brings up the menu for right-
> clicks on blank areas of the canvas (adapted from example 8, chapter 3
> of Cody Precord's new book), then this handler takes over *all* right
> click events, so right-clicking on a shape no longer fires the correct
> OnRightClick method mentioned above, but this OnContextMenu too. I'm
> guessing that the latter method has overridden the former method.

> So guys, can anyone show me the error of my ways and suggest a
> solution?

You might want to attach your code, should make it easier to spot what
is happening.

Just a guess, are you doing event.Skip()

EventPropagation - wxPyWiki

Werner

######################
#!/usr/bin/env python

import wx
import wx.lib.ogl as ogl

CLICK_TO_DRAG = True

# Menu ID's
ID_RED = wx.NewId()
ID_GREEN = wx.NewId()
ID_BLUE = wx.NewId()

class PopupMenuMixin(object):
    def __init__(self, canvas):
        super(PopupMenuMixin, self).__init__()

        # Attributes
        self._menu = None

        # Event Handlers
        canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu)
        # canvas.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)

    def OnContextMenu(self, event):
        """Creates and shows the Menu"""
        if self._menu is not None:
            self._menu.Destroy()

        self._menu = wx.Menu()
        self.CreateContextMenu(self._menu)
        self.PopupMenu(self._menu)

    def CreateContextMenu(self, menu):
        """Override in subclass to create the menu"""
        raise NotImplementedError

class MyApp(wx.App):
    def OnInit(self):
        ogl.OGLInitialize()
        self.frame = MyFrame(None, title="PyDAG")
        self.SetTopWindow(self.frame)

        self.frame.Show()

        return True

class MyEvtHandler(ogl.ShapeEvtHandler):
    """
    Overwrite the default event handler to implement some custom
features.
    """
    def __init__(self):
        ogl.ShapeEvtHandler.__init__(self)

    def OnLeftClick(self, x, y, keys = 0, attachment = 0):
        """
        The dragging is done here.
        """
        shape = self.GetShape()
        print shape.__class__, shape.GetClassName(), shape.a
        canvas = shape.GetCanvas()
        dc = wx.ClientDC(canvas)
        canvas.PrepareDC(dc)

        if shape.Selected():
            shape.Select(False, dc)
            canvas.Redraw(dc)
        else:
            redraw = False
            shapeList = canvas.GetDiagram().GetShapeList()
            toUnselect =
            for s in shapeList:
                if s.Selected():
                    toUnselect.append(s)

            shape.Select(True, dc)

            if toUnselect:
                for s in toUnselect:
                    s.Select(False, dc)
                canvas.Redraw(dc)

    def OnRightClick(self, x, y, keys = 0, attachment = 0):
       print "OnRightClick activated"

class OGLCanvas(ogl.ShapeCanvas, PopupMenuMixin):
    def __init__(self, parent, frame):
        ogl.ShapeCanvas.__init__(self, parent)

        # Pull in right-click mixin here
        PopupMenuMixin.__init__(self, self)
        self.Bind(wx.EVT_MENU, self.OnMenu)

        self.SetBackgroundColour("LIGHT BLUE")
        self.diagram = ogl.Diagram()
        self.SetDiagram(self.diagram)
        self.diagram.SetCanvas(self)

        self.circle = ogl.CircleShape(100)
        self.circle.SetCanvas(self)
        self.circle.a="Circle identified"
        self.diagram.AddShape(self.circle)
        self.circle.Show(True)

        if CLICK_TO_DRAG:
            self.evthandler = MyEvtHandler()
            self.evthandler.SetShape(self.circle)

self.evthandler.SetPreviousHandler(self.circle.GetEventHandler())
            self.circle.SetEventHandler(self.evthandler)
        else:
            self.Bind(wx.EVT_MOTION, self.OnMotion, self)

    def OnMotion(self, event):
        shape = self.circle

        bx = shape.GetX()
        by = shape.GetY()
        bw, bh = shape.GetBoundingBoxMax()
        oldrect = wx.Rect(int(bx-bw/2)-1, int(by-bh/2)-1, int(bw)+2,
int(bh)+2)

        canvas = shape.GetCanvas()
        dc = wx.ClientDC(canvas)
        canvas.PrepareDC(dc)

        shape.Move(dc, event.GetPosition()[0], event.GetPosition()[1])
        canvas.Refresh(False, oldrect)
        event.Skip()

    def OnMenu(self, event):
        """Handle menu events from the popup menu"""
        evt_id = event.GetId()
        colour_map = {ID_RED : wx.RED,
                      ID_GREEN : wx.GREEN,
                      ID_BLUE : wx.BLUE}
        if evt_id in colour_map:
            colour = colour_map.get(evt_id)
            self.SetBackgroundColour(colour)
            self.Refresh()
        else:
            event.Skip()

    def CreateContextMenu(self, menu):
        """Overrides PopupMenuMixin.CreateContextMenu"""
        menu.Append(ID_RED, "Red",
                    "Change background to Red")
        menu.Append(ID_GREEN, "Green",
                    "Change background to Green")
        menu.Append(ID_BLUE, "Blue",
                    "Change background to Blue")

class MyFrame(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title="",
                 pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=wx.DEFAULT_FRAME_STYLE,
                 name="MyFrame"):
        super(MyFrame, self).__init__(parent, id, title,
                                      pos, size, style, name)

        # Attributes
        self.SetBackgroundColour(wx.Colour(8, 197, 248))

        # The menu bar
        menu_bar = wx.MenuBar()
        edit_menu = wx.Menu()
        edit_menu.Append(wx.NewId(), "Jobs")
        edit_menu.Append(wx.ID_PREFERENCES)
        menu_bar.Append(edit_menu, "Edit")
        self.SetMenuBar(menu_bar)
        self.canvas = OGLCanvas(self, self)

        # Layout
        self.CreateStatusBar() # To show help text

if __name__ == "__main__":
    wx.InitAllImageHandlers()
    app = MyApp(False)
    app.MainLoop()

Thanks for the interest. I've included the code below. In reply to
your question, no, I'm not doing an event.Skip() on any right click
event.

That is the problem. By not calling Skip you are telling wx that you have fully processed the event and so it does not continue to look for matching event bindings. IOW, you are preventing the canvas class from seeing the event and in turn looking for Shape objects that should be notified of the event.

One thing I don't understand is that in Cody Precord's book he
can initiate a right-click event by binding the callback to a
EVT_CONTEXT_MENU event, whereas I have to use a EVT_RIGHT_DOWN since
the former seems to have has no effect in the context of the canvas.
Anyway, here's the code (please bear in mind that this is a first
attempt with wxPython, so be gentle...).

EVT_CONTEXT_MENU events are only sent if there was no handler for EVT_RIGHT_DOWN (or _UP depending on platform). Since the Canvas class (and now yours) is catching the right-down without calling Skip then the context menu event will never be sent.

···

On 3/1/11 9:52 AM, Bob wrote:

--
Robin Dunn
Software Craftsman

Hi Robin,

Thanks for the info. I've now got it working by keeping the
EVT_RIGHT_DOWN event and skipping it if a shape has been selected
(i.e. left clicked first). I can't pretend to fully understand what's
going on (I still can't get the EVT_CONTEXT_MENU event to work
properly), but I suppose that's all part of climbing the learning
curve.

Bob

And in this case you probably won't since the canvas class is not calling Skip in its event handler. Since Skip was not called then wx assumes that the event was fully handled and so it doesn't have a chance to be converted to a context menu event.

···

On 3/2/11 2:27 AM, Bob wrote:

(I still can't get the EVT_CONTEXT_MENU event to work
properly),

--
Robin Dunn
Software Craftsman