## page was renamed from Controlling GUI with Publisher
== Introduction ==
You are looking for a smoother solution to the problem described in [[Pairing Notebook Panel
to Frame Menus]]? Or perhaps an example of how to {{{wx.lib.pubsub}}}? This page is a good
place to start.

See also the WxLibPubSub page for general info on pubsub and how it fits in wxPython, and 
the [[http://pubsub.sf.net|pubsub home page]] for additional documentation and examples. 

Note: Pubsub has two APIs for messaging: they are referred to as 'kwargs' and 'arg1'. The code on the
this page uses the 'kwargs' API, which became the default in wx.lib.pubsub *after* wxPython
2.8.11.0. Prior to this, the default wx.lib.pubsub API was the 'arg1' API. If you 
you want to use the now deprecated 'arg1' API, the code below
won't work. You will have to insert a {{{from wx.lib.pubsub import setuparg1}}} before the first
pubsub import statement seen by your application (typically, in your startup script), and the
{{{sendMessage()}}} calls will have to be changed (see the
[[http://pubsub.sourceforge.net/usage/howtos/index.html|migration docs]] for more info).
Pubsub is now a standalone library hosted on !SourceForge (wx.lib.pubsub is now a verbatim copy
of the pubsub from that site) so if you are having trouble using the below code, post to the
[[http://googlegroups.com/group/pypubsub|pubsub project users group]] (or the
[[https://groups.google.com/forum/#!forum/wxpython-users|wxPython users group]] of course).


== What Objects are Involved ==

 1. Two wx.Panels
 1. Two wx.Menus to match those panels
 1. One wx.Notebook
 1. One wx.Frame with a wx.Menubar and wx.Statusbar

The menubar will have a tab menu, and the matching menus

== Process Overview ==
Build and connect the various parts with the following methods for switching tabs in the application:

CHANGING A NOTEBOOK PAGE should...

 * enable the menu for the selected page
 * disable the menu for the other pages
 * disable the tabmenu item for the selected page
 * enable the tabmenu item for the other  pages
 * change the status bar to note the change

SELECTING AN OPTION IN THE TABMENU should...

 * change the notebook page
 * disable the tabmenu item for the selected page
 * enable the tabmenu item for the other pages
 * enable the menu for the current page
 * disable the menus for the other pages
 * change the status bar to note the change

== Special Concerns ==

Finding the right usage of pubsub messages is key. It is tempting to have one pubsub message
'notebook.change' generated with either method, and having the frame and notebook respond.

This can lead to confusion, so think of pubsub messages as specific messages to specific items:

 * The Notebook listens for messages of the 'notebook.select' topic
 * The Frame listens for messages of the 'statusbar.update', 'statusbar.clear', 'tabmenu.change' and 'menubar.change'

It is a good idea to keep the Frame subscribed to general topics, and use the handler to
decide what to do specifically based on the sub topics (if any) of the message.

Sidenote: If you have problems getting the panels to respond to menu events, there is a
simple solution in the panel code for this:

{{{
	top=self.GetTopLevelParent()
	top.Bind(wx.EVT_MENU,self.OnMeanie,id=meanieId)
}}}

== Code Sample ==
{{{
#!python
"""GUIPubSub.py
Use pubsub to control the GUI.
"""

import wx
from wx.lib.pubsub import pub

class BasePanel(wx.Panel):
    ### This panel assures the panel has a menu
    def __init__(self, parent, *args, **kwds):
        assert 'name' in kwds, "Panel must have a name"
        self.Menu = kwds.pop('menu', None)
        if not self.Menu: self.Menu = wx.Menu()

        wx.Panel.__init__(self, parent, *args, **kwds)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.label = wx.StaticText(self, label="This is a panel")
        sizer.Add(self.label)
        self.SetSizer(sizer)

    def GetMenu(self):
        """Returns the menu associated with this panel"""
        return self.Menu

class BluePanel(BasePanel):
    ### Subclass BasePanel with the particulars
    def __init__(self, parent):
        blueMenu = wx.Menu()
        meanieId = wx.NewId()
        blueMenu.Append(meanieId, "Blue meanie", "It's a blue world, Max.")
        BasePanel.__init__(self, parent, menu = blueMenu, name="Blue")
        self.SetBackgroundColour("Light Blue")

        ###Menu events are processed at the frame level,
        ### but by the time the panel is created, we have
        ### a frame and a notebook, so we can get to it
        ### with GetTopLevelParent()
        ### It's better than trying to bind everything at the
        ### Frame level. do it here instead.
        top=self.GetTopLevelParent()
        top.Bind(wx.EVT_MENU, self.OnMeanie, id=meanieId)

    def OnMeanie(self, evt):
        pub.sendMessage('statusbar.update', status='Send in the Apple Bonkers!')

class RedPanel(BasePanel):
    def __init__(self, parent):
        redMenu = wx.Menu()
        redMenu.Append(wx.ID_ANY, "Red Five", "I'm going in.")
        BasePanel.__init__(self, parent, menu=redMenu, name="Red")
        self.SetBackgroundColour("Pink")
        sizer = self.GetSizer()
        aButton = wx.Button(self, label="Luke, are you okay?")
        sizer.Add(aButton)
        self.Layout()

        self.Bind(wx.EVT_BUTTON, self.OnButton, aButton)

    def OnButton(self, evt):
        pub.sendMessage('statusbar.update', status="I'm okay but R2 is in trouble")

class MyNotebook(wx.Notebook):
    ### The notebook keeps track of pages
    ### We are subclassing to keep track of events and accept messages
    def __init__(self, parent, *args, **kwds):
        wx.Notebook.__init__(self, parent, *args, **kwds)

        bluePanel = BluePanel(self)
        redPanel = RedPanel(self)

        self.AddPage(bluePanel, bluePanel.GetName())
        self.AddPage(redPanel, redPanel.GetName())

        ### Bind events
        ### We use the page changed event to catch when the user clicks
        ### on a tab.
        ### To prevent this from happening, catch the
        ###  wx.EVT_NOTEBOOK_PAGE_CHANGING event and Veto the event if needed.
        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged)

        ### Subscribe to messages
        pub.subscribe(self.OnChange, 'notebook.select')

    def OnPageChanged(self, evt):
        name = self.GetPageText(evt.GetSelection())
        pub.sendMessage('tabmenu.change', itemLabel=name)
        pub.sendMessage('menubar.change', itemLabel=name)
        evt.Skip()

    def OnChange(self, index):
        name = self.ChangeSelection(index-1)


class MyFrame(wx.Frame):
    def __init__(self, parent, *args, **kwds):
        wx.Frame.__init__(self, parent, *args, **kwds)
        mbar = wx.MenuBar()
        sbar = wx.StatusBar(self)

        self.Book = MyNotebook(self)

        self.SetMenuBar(mbar)
        self.SetStatusBar(sbar)
        ### Create the tab menu
        self.tabMenu = tb = wx.Menu()
        mbar.Append(tb, "&Tabs")
        ### Populate the tab menu and the menubar
        for idx in range(self.Book.GetPageCount()):
            page = self.Book.GetPage(idx)
            tb.Append(idx+1,
                      "%s\tCtrl-%d" % (page.GetName(), idx+1),
                      "Go to %s page" % (page.GetName()))
            ### Additions to the menu bar
            menu = page.GetMenu()
            mbar.Append(menu, page.GetName())

        tb.AppendSeparator()
        tb.Append(wx.ID_CLOSE, "Close\tAlt-X", "Run away ... terribly fast.")
        # Add a help menu
        hMenu = wx.Menu()
        hMenu.Append(wx.ID_ABOUT)
        mbar.Append(hMenu, "&Help")
        self.SetStatusText("Welcome",0)

        ### EVENT BINDINGS
        self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_CLOSE)
        self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
        self.Bind(wx.EVT_MENU, self.OnTabMenu, id=0, id2=self.Book.GetPageCount())

        ### MESSAGE SUBSCRIPTIONS
        pub.subscribe(self.OnStatusUpdate,   'statusbar.update')
        pub.subscribe(self.OnStatusClear,    'statusbar.clear')
        pub.subscribe(self.OnTabMenuMessage, 'tabmenu.change')
        pub.subscribe(self.OnMenuBarMessage, 'menubar.change')

        ### SEND INITIAL MESSAGES
        idx = self.Book.GetSelection()
        name = self.Book.GetPageText(idx)
        pub.sendMessage('tabmenu.change', itemLabel=name)
        pub.sendMessage('menubar.change', itemLabel=name)

    def OnAbout(self, evt):
        pub.sendMessage('statusbar.update', status='Bragging about this app')
        wx.MessageBox("This is my program. Muy Neato, huh?",
                      "About this App",
                      wx.OK)

    def OnClose(self, evt):
        self.Close(True)

    def OnStatusUpdate(self, status):
        self.SetStatusText(status,0)

    def OnStatusClear(self):
        self.SetStatusText('',0)

    def OnTabMenuMessage(self, itemLabel):
        ### Respond to a message to change the tabmenu
        ### Disable all tab menu items (except the close item)
        for idx in range(self.Book.GetPageCount()):
            mi = self.tabMenu.FindItemById(idx+1)
            if mi.GetLabel() == itemLabel:
                mi.Enable(False) # Don't want to be able to switch to the current page
            else:
                mi.Enable(True)
            ### Another way to do this:
            #mi.Enable(mi.GetLabel() != itemLabel)
            ### The problem with this is there are several logical steps taken in one line

    def OnMenuBarMessage(self, itemLabel):
        ### Disable all page menus but the current one
        mbar = self.GetMenuBar()
        for idx in range(self.Book.GetPageCount()):
            page = self.Book.GetPage(idx) ## returns the page
            menu = mbar.FindMenu(page.GetName())  ## returns an integer

            if page.GetName() == itemLabel:
                mbar.EnableTop(menu, True)
            else:
                mbar.EnableTop(menu, False)

    def OnTabMenu(self, evt):
        ### Get the label of the menu item
        mbar=self.GetMenuBar()
        mi = mbar.FindItemById(evt.GetId())
        ### Send the messages
        pub.sendMessage('notebook.select', index = evt.GetId())
        pub.sendMessage('menubar.change', itemLabel = mi.GetLabel())
        pub.sendMessage('tabmenu.change', itemLabel = mi.GetLabel())

class App(wx.App):
    def OnInit(self):
        frame = MyFrame(None, title="Notebook Application")
        frame.CenterOnScreen()
        self.SetTopWindow(frame)
        frame.Show()
        return True

a = App(False)
a.MainLoop()
}}}

== Discussion ==

Part of this program simply reports changes in the notebook in the frame's statusbar. It is just as easy
to use:

{{{ self.GetTopLevelParent().SetStatusText('page changed') }}}

but why go through all that? pubsub gets the message across and makes for more readable code.

One thing to note about pubsub-subscribe architectures is that in large applications, it can
become difficult to keep track of message topics, sequences of listeners called and secondary
messages created (a message created as a result of a first message). The !PyPubSub version 3
API attempts to support pubsub-subscribe architectures within larger applications by
facilitating documented topic trees, requiring named message data arguments, providing a more
generic notification system to track what pubsub is doing, and giving more descriptive error
messages about pubsub errors.

== Example: A Self-Hiding pubsub-aware statusbar gauge ==

Here is one way to place a gauge in a status bar that can accept updates from any long process.

{{{
#!python
import time
import wx

try:
    from pubsub import pub
except ImportError:
    from wx.lib.pubsub import pub

class ListeningGauge(wx.Gauge):
    def __init__(self, *args, **kwargs):
        wx.Gauge.__init__(self, *args, **kwargs)
        pub.subscribe(self.start_listening, "progress_awake")
        pub.subscribe(self.stop_listening, "progress_sleep")

    def _update(self, this, total):
        try:
            self.SetRange(total)
            self.SetValue(this)
        except Exception as e:
            print e

    def start_listening(self, listen_to):
        rect = self.Parent.GetFieldRect(1)
        self.SetPosition((rect.x+2, rect.y+2))
        self.SetSize((rect.width-4, rect.height-4))
        self.Show()
        pub.subscribe(self._update, listen_to)

    def stop_listening(self, listen_to):
        pub.unsubscribe(self._update, listen_to)
        self.Hide()


class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title)

        status = self.statusbar = self.CreateStatusBar() # A StatusBar in the bottom of the window
        status.SetFieldsCount(3)
        status.SetStatusWidths([-2,200,-1])

        self.progress_bar = ListeningGauge(self.statusbar, style=wx.GA_HORIZONTAL|wx.GA_SMOOTH)
        rect = self.statusbar.GetFieldRect(1)
        self.progress_bar.SetPosition((rect.x+2, rect.y+2))
        self.progress_bar.SetSize((rect.width-4, rect.height-4))
        self.progress_bar.Hide()

        panel = wx.Panel(self)
        shortButton = wx.Button(panel, label="Run for 3 seconds")
        longButton = wx.Button(panel, label="Run for 6 seconds")

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(shortButton, 0, wx.ALL, 10)
        sizer.Add(longButton, 0, wx.ALL, 10)

        panel.SetSizerAndFit(sizer)

        shortButton.Bind(wx.EVT_BUTTON, self.Run3)
        longButton.Bind(wx.EVT_BUTTON, self.Run6)

    def Run3(self, event):
        pub.sendMessage('progress_awake', listen_to = 'short_update')
        wx.BeginBusyCursor()
        for x in range(6):
            pub.sendMessage('short_update', this = x+1, total = 6)
            time.sleep(0.5)
        wx.EndBusyCursor()
        pub.sendMessage('progress_sleep', listen_to = 'short_update')

    def Run6(self, event):
        pub.sendMessage('progress_awake', listen_to = 'long_update')
        wx.BeginBusyCursor()
        for x in range(12):
            pub.sendMessage('long_update', this = x+1, total = 12)
            time.sleep(0.5)
        wx.EndBusyCursor()
        pub.sendMessage('progress_sleep', listen_to = 'long_update')


app = wx.App(False)
frame = MainWindow(None, "Listening Gauge Demo")
frame.Show()
app.MainLoop()
}}}

=== Comments ===

Post questions here, on the wxPython users forum, or the pubsub help forum. Don't forget to 
look at WxLibPubSub and the [[http://pubsub.sf.net|pubsub home page]].