how to implement copy/paste with accelerators?

I know this sounds like a remarkably fundamental question, but the
answer just isn't obvious in all the documentation and I can't seem to
find a good answer on the web.

I have an app that may have several windows that accept focus. I also
have a menu item with an accelerator for the copy operation
(control-c). How and where do I bind to wx.ID_COPY so that whatever
window has focus will do the copy?

One approach that seems like it should work -- but doesn't -- is to
put a binding in the toplevel window which figures out which window
has focus and forwards the event to it. This works great for the
textctrls, but if I click on a ListBox and then press control-c my
code goes into a nasty infinite loop on my linux box.

So, what's the secret sauce to making this work? I'm looking for the
canonical example if there is such a thing.

Here's a short example that illustrates the infinite loop. Select the
item in "l1" and press control-c to see the infinite loop. What I'd
like to see is a modification to the code that lets control-c work
correctly when focus is in either text widget. I also want control-c
to do something in the listbox but I left that code out to keep the
size down.

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        menubar = wx.MenuBar()
        editMenu = wx.Menu()
        editMenu.Append(wx.ID_COPY)
        editMenu.Append(wx.ID_PASTE)
        menubar.Append(editMenu, "&Edit")
        self.SetMenuBar(menubar)
        self.menubar = menubar

        l1 = wx.ListBox(self, wx.ID_ANY, size=(400,100))
        l1.Append("This is l1")
        t1 = MyText(self, wx.ID_ANY, size=(400,200),style=wx.TE_MULTILINE)
        t2 = MyText(self, wx.ID_ANY, size=(400,200),style=wx.TE_MULTILINE)
        t1.AppendText("This is t1")
        t2.AppendText("This is t2")
        t1.name = "t1"
        t2.name = "t2"
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(l1, 0, wx.EXPAND)
        sizer.Add(t1, 1, wx.EXPAND)
        sizer.Add(t2, 1, wx.EXPAND)
        self.SetSizerAndFit(sizer)
        t1.SetFocus()

        wx.EVT_MENU(self, wx.ID_COPY, self.OnCopy)
        wx.EVT_MENU(self, wx.ID_PASTE, self.OnPaste)

    def OnCopy(self, event):
        focus = self.FindFocus()
        print "MyFrame.OnCopy...focus=", focus
        wx.PostEvent(focus, event)

    def OnPaste(self, event):
        print "MyFrame.OnPaste...focus=", focus
        wx.PostEvent(focus, event)

class MyText(wx.TextCtrl):
    def __init__(self, *args, **kwargs):
        wx.TextCtrl.__init__(self, *args, **kwargs)
        wx.EVT_MENU(self, wx.ID_COPY, self.OnCopy)
        wx.EVT_MENU(self, wx.ID_PASTE, self.OnPaste)
    def OnCopy(self, event):
        print "%s.OnCopy..." % self.name
    def OnPaste(self, event):
        print "%s.OnPaste..." % self.name

app=wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()

1. Add a handler for the EVT_UPDATE_UI event for each of the menu IDs.

2. In the handler for those events the code should be something like this:

         focus = wx.Window.FindFocus()
         event.Enable(focus and
                      hasattr(focus, 'CanCopy') and
                      focus.CanCopy())

That will cause the menu item to be enabled only when the focused widget has a CanCopy method and that method returns True.

3. In the EVT_MENU handler just call the focused widget's Copy or Paste or whatever method instead of reposting the event to it. Since this handler wont be called unless the focused widget has a CanCopy method then you can assume that it has a Copy method as well:

         focus = wx.Window.FindFocus()
  focus.Copy()

···

On 1/11/11 2:46 PM, Bryan Oakley wrote:

I know this sounds like a remarkably fundamental question, but the
answer just isn't obvious in all the documentation and I can't seem to
find a good answer on the web.

I have an app that may have several windows that accept focus. I also
have a menu item with an accelerator for the copy operation
(control-c). How and where do I bind to wx.ID_COPY so that whatever
window has focus will do the copy?

One approach that seems like it should work -- but doesn't -- is to
put a binding in the toplevel window which figures out which window
has focus and forwards the event to it. This works great for the
textctrls, but if I click on a ListBox and then press control-c my
code goes into a nasty infinite loop on my linux box.

So, what's the secret sauce to making this work? I'm looking for the
canonical example if there is such a thing.

--
Robin Dunn
Software Craftsman
http://wxPython.org