MailSneaker - Part 3 SystemTray/TaskBar-Icons with Python and wxPython

Welcome to part 3 of the mailsneaker series.
This entry is UNRELATED to the code of the former two parts !
You can use it seperately.
Today we are going to implement a SystemTrayIcon with a small and nice menu.
Ok, it’s actually NOT called SystemTray. But everybody calls it that way.
We can also say TaskBarIcon although in my view that naming is even more horrible than the other.

Sadly we can’t so easily try this out with IDLE, so i will start directly with the classes ok ?
For the GUI we will use wxPython this time. It’s easy to learn and easy to use.

Let’s start the classic way:

import sys, wx, webbrowser

is a good start. The first thing we will do is to add the startup routines at the END of the file:

#---------------- run the program -----------------------  
def main(argv=None):  
    app = wx.App(False)  
    frame = MailFrame(None, -1, ' ')  
    frame.Center(wx.BOTH)  
    frame.Show(False)  
    app.MainLoop()

if __name__ == '__main__':  
    main()

As you can guess we will use the standard Applicationframework wx provides. Our own program will be placed inside our MailFrame class.
The Frame wont ever be visible. We use the center only to make it easier for later dialogs to align correctly on the screen. It’s not needed however.

So what will the MailFrame class look like ? A good start would be this one (paste that above the startup routine):

class MailFrame(wx.Frame):

    def __init__(self, parent, id, title):  
        wx.Frame.__init__(self, parent, -1, title, size = (1, 1),  
            style=wx.FRAME_NO_TASKBAR|wx.NO_FULL_REPAINT_ON_RESIZE)

        self.tbicon = MailTaskBarIcon(self)  
        self.tbicon.Bind(wx.EVT_MENU, self.exitApp, id=wx.ID_EXIT)   
        self.Show(True)

    def exitApp(self,event):  
        self.tbicon.RemoveIcon()  
        self.tbicon.Destroy()  
        sys.exit()

Pretty simple, isn’t it ?
We define a frame, we include a TaskBarIcon. And we define an exit-method that will let us quit the application.

So let’s get to the second class today…

class MailTaskBarIcon(wx.TaskBarIcon):

    def __init__(self, parent):  
        wx.TaskBarIcon.__init__(self)  
        self.parentApp = parent  
        self.noMailIcon = wx.Icon("nomail.png",wx.BITMAP_TYPE_PNG)  
        self.youHaveMailIcon = wx.Icon("mail-message-new.png",wx.BITMAP_TYPE_PNG)  
        self.CreateMenu()  
        self.SetIconImage()

    def CreateMenu(self):  
        self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.ShowMenu)  
        #self.Bind(wx.EVT_MENU, self.parentApp.OpenBrowser, id=OPEN_BROWSER)  
        #self.Bind(wx.EVT_MENU, self.parentApp.OpenPrefs, id=OPEN_PREFS)  
        self.menu=wx.Menu()  
        #self.menu.Append(OPEN_BROWSER, "Open Browsers for mail","This will open a new Browser with tabs for each account")  
        #self.menu.Append(OPEN_PREFS, "Open Preferences")  
        self.menu.AppendSeparator()  
        self.menu.Append(wx.ID_EXIT, "Close App")

    def ShowMenu(self,event):  
        self.PopupMenu(self.menu)

    def SetIconImage(self, mail=False):  
        if mail:  
            self.SetIcon(self.youHaveMailIcon, "You have mail")  
        else:  
            self.SetIcon(self.noMailIcon, "No mail")

I commented some lines. We come to those later.
The MailTaskBarIcon-Class (did you also have to take a deep breath in order to say that loud ?) extends the normal wx.TaskBarIcon.
All we need to do is to define (in our case) 2 icons to display. One in case we have mail, one in case we have none.
Then we add a nice menu which should pop up when we right click on the icon.
The menu so far only allows us to quit the application.

If you run this it SHOULD already work (provided I didn’t do any copy&paste mistakes now).
Try it out !
Ups… you might need two icons ;-)


If everything went fine so far we can take the comments away in the class above. by that we will include two more menu entries. One for opening up a browser in order to read the mails, the second in order to edit preferences and add new mail accounts we would like to watch.
Starting the app again will result in mainly two errors. First of all the ids for the two new events are not defined, secondly the methods OpenBrowser and OpenPreds are not existent.

Below the import statements insert:

OPEN_BROWSER=wx.NewId()  
OPEN_PREFS=wx.NewId()

This will do for the IDs.

In the MailFrame-class add the two methods we need.

 def OpenBrowser(self,event):  
        webbrowser.open('http://codeboje.de/blog/pages/donate.html')

  def OpenPrefs(self,event):  
        pass

Forgive me for linking to the donations page, i used it for testing purposes ;-)
We will replace that in one of the next lessons by other code.
Since we imported “webbrowser” the program should work now already in so far as if you click on the “Open Browsers” menu item you should get a new Browsertab with the url specified here.

Again … lets simply post the whole code at once

import sys, wx, webbrowser

ID_ICON_TIMER = wx.NewId()  
OPEN_BROWSER=wx.NewId()  
OPEN_PREFS=wx.NewId()

class MailTaskBarIcon(wx.TaskBarIcon):

    def __init__(self, parent):  
        wx.TaskBarIcon.__init__(self)  
        self.parentApp = parent  
        self.noMailIcon = wx.Icon("nomail.png",wx.BITMAP_TYPE_PNG)  
        self.youHaveMailIcon = wx.Icon("mail-message-new.png",wx.BITMAP_TYPE_PNG)  
        self.CreateMenu()  
        self.SetIconImage()

    def CreateMenu(self):  
        self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.ShowMenu)  
        self.Bind(wx.EVT_MENU, self.parentApp.OpenBrowser, id=OPEN_BROWSER)  
        self.Bind(wx.EVT_MENU, self.parentApp.OpenPrefs, id=OPEN_PREFS)  
        self.menu=wx.Menu()  
        self.menu.Append(OPEN_BROWSER, "Open Browsers for mail","This will open a new Browser with tabs for each account")  
        self.menu.Append(OPEN_PREFS, "Open Preferences")  
        self.menu.AppendSeparator()  
        self.menu.Append(wx.ID_EXIT, "Close App")

    def ShowMenu(self,event):  
        self.PopupMenu(self.menu)

    def SetIconImage(self, mail=False):  
        if mail:  
            self.SetIcon(self.youHaveMailIcon, "You have mail")  
        else:  
            self.SetIcon(self.noMailIcon, "No mail")

class MailFrame(wx.Frame):

    def __init__(self, parent, id, title):  
        wx.Frame.__init__(self, parent, -1, title, size = (1, 1),  
            style=wx.FRAME_NO_TASKBAR|wx.NO_FULL_REPAINT_ON_RESIZE)

        self.tbicon = MailTaskBarIcon(self)  
        self.tbicon.Bind(wx.EVT_MENU, self.exitApp, id=wx.ID_EXIT)   
        self.Show(True)

    def exitApp(self,event):  
        self.tbicon.RemoveIcon()  
        self.tbicon.Destroy()  
        sys.exit()

    def OpenBrowser(self,event):  
        webbrowser.open('http://codeboje.de/blog/pages/donate.html')

    def OpenPrefs(self,event):  
        pass

#---------------- run the program -----------------------  
def main(argv=None):  
    app = wx.App(False)  
    frame = MailFrame(None, -1, ' ')  
    #frame.Center(wx.BOTH)  
    frame.Show(False)  
    app.MainLoop()

if __name__ == '__main__':  
    main()

Bingo …

Whats missing now ?
Obviously we should connect an account class with the fetching and the browser and the fetching with the iconimage.