Creating an ImageWidget

Aaron Straup Cope

All Rights Reserved.

Abstract

This article describes the ImageWidget Python classes and how to build a simple ImageWidget-derived application.


Table of Contents

Introduction
Requirements
Creating a simple ImageWidget
MyWidget.py
The Setup() method
The KeyDown() method
The About() method
The GetImage() method
That's it
Plugins
DeliciousTool
Conclusion
A. ImageWidget Class Reference
B. Default key bindings for ImageWidget-derived classes
C. Default menus for ImageWidget-derived classes
D. Required key-value pairs to be returned by a GetImage() method

Introduction

An ImageWidget is a small GUI desktop application that periodically requests an image from a resource and displays it. That's all.

Applications may be extended to include additional functionality, such as posting URLs to the del.icio.us bookmarking website, but their primary design is to show images and hopefully liven up a user's desktop.

Requirements

ImageWidgets are written in Python, using the wxPython GUI toolkit.

  • PythonThe applications described here were developed with version 2.3 of the language.

  • wxPythonThe applications described here were developed with version 2.5.4.1 of the package, and assume support for Unicode.

  • ImageWidgetThis is the base class for all ImageWidget tools. The applications described here were developed with version 1.0 of the package.

Important

  • This document is still a draft and is incomplete. It may contain errors.

  • This is not a tutorial for either the Python programming language or the wxPython toolkit. A working knowledge of both is assumed.

  • The ImageWidget code has not been throroughly tested in a Windows environment. There may be special-cases that still need to addressed.

  • I don't pretend to be the world's most knowledgeable Python (or GUI) programmer. There are probably better ways of doing things — I'd love to hear about them.

Creating a simple ImageWidget

Creating an application can be as simple as inheriting from the ImageWidget base class and redeclaring four methods.

MyWidget.py

First, create file named MyWidget.py and add the necessary code to declare a new class and import other libraries.

Example 1. 

#!/usr/bin/python

# Import the graphics toolkit and libraries
# for handling images as IO streams

import wx
import cStringIO

# Import the main ImageWidget library as well
# as one for handling errors. Also import some
# constant variables needed for an application
# specific About menu and dialogue and error
# reporting.

from ImageWidget.Widget    import ImageWidget
from ImageWidget.Error     import ImageWidgetError
from ImageWidget.Constants import IMAGEWIDGET_ID_ABOUT, IMAGEWIDGET_ERR_POLLING,IMAGEWIDGET_ERR_STREAMIFY

# Define some variables for use in our program
# I like to put this sort of thing in a separate
# package but that's your business. The important
# thing to note is that we are using Unicode objects
# for all our text. 

# The namespace variable is used for storing your
# application's configuration details alongside any
# defaults or plug-ins.

MYWIDGET_NAMESPACE   = u"http://www.example.com/mywidget#"
MYWIDGET_TITLE       = u"My First Widget"
MYWIDGET_ABOUT       = u"My First Widget\n\N{COPYRIGHT SIGN} 2005 Aaron Straup Cope"

MYWIDGET_ERR_PARSE   = u"failed to parse XML response"
MYWIDGET_ERR_FETCH   = u"failed to retrieve image"

# Python's default encoding, unless a user changes it
# in a global site preferences file is 'ascii'. You don't
# necessarily need to declare an encoding variable for
# your ImageWidget program but if you don't and accidentally
# pass it outside the expected range of charcters Bad Things
# will happen.

MYWIDGET_ENDPOINT_ENCODING = u"utf8"
MYWIDGET_ENDPOINT_XMLRPC   = u"http://example.com/mywidget/xml-rpc"
MYWIDGET_ENDPOINT_REST     = u"http://example.com/mywidget/rest"

# Create a new class for your widget and inherit from
# ImageWidget. ImageWidget itself inherits from wx.App

class MyWidget (ImageWidget) :
   

The Setup() method

The Setup() is where you can access your application's main frame, or window, before it is displayed to the user. At this point you can change how events already defined by the parent class are handled or add new ones.

Example 2. 

    def Setup (self,name,uri) :

        # By default the Setup() method returns a wx.Frame
        # object which we grab right off the bat to use
        # for registering events specific to our application

	frame = ImageWidget.Setup(self,
				  MYWIDGET_TITLE,
				  MYWIDGET_NAMESPACE)

        # Tell the frame's main panel to use our local
        # KeyDown() method when it intercepts keyboard
        # events

	wx.EVT_KEY_DOWN(frame.Panel,
			self.KeyDown)

        # Likewise when a menu event with ID 'IMAGEWIDGET_ID_ABOUT'
        # is triggered, use our About() method rather than
        # the default

	wx.EVT_MENU(frame,
		    IMAGEWIDGET_ID_ABOUT,
		    self.About)

        # If we were going to register plugins or add
        # application-specific functionality we would
        # do it here. Since we're not in this example,
        # just return the original wx.Frame object.

	return frame;
   

The KeyDown() method

ImageWidget applications are built with a number of default key-bindings. Depending on the nature of your application you may want to include additional keyboard shortcuts. At a minimum, you will want to intercept the A key and re-route it to a localized About() method to display information about your application.

Example 3. 

    def KeyDown (self,event) :

        # In this example, the only key we are concerned
        # with is the 'A' key which we will dispatch to
        # our local About method. Anything else we will
        # pass up the line.

        # By default, the GetKeyDown() method will only
        # return a value if the control (or command in OS
        # X) key is pressed.

	keyname = self.GetKeyDown(event)
        
	if keyname == "A":
            self.About(event)	    
	else :
	    return self.GetTopWindow().KeyDown(event)
   

The About() method

Example 4. 

    def About (self,event) :

        # To be perfectly honest, I am still thinking
        # about abstracting this one out a bit more. I
        # haven't done that so we simply call the parent
        # object's About() method will localized values.

	ImageWidget.About(self,
			  event,
			  MYWIDGET_TITLE,
			  MYWIDGET_ABOUT)
   

The GetImage() method

It is left up to you how and from where images to display are retrieved. Images could come from your local filesystem, or a database, a centralized server or be generated on-the-fly. The important part is that your GetImage() method return a hash containing a minimum set of key-value pairs.

Example 5. Fetching an image from an XML-RPC server

    def GetImage (self) :

        # In a more complicated application, you
        # might want to create a single 'api' object
        # in your '__init__' method and store it as
        # an attribute of 'self' but this works too.

        import xmlrpclib
        api = xmlrpclib.Server(MYWIDGET_ENDPOINT_XMLRPC)

        # Connect to the server and see what it
        # returns 

        try:
            res = api.image.Random()
        except Exception, e:
            raise ImageWidgetError(IMAGEWIDGET_ERR_POLLING,e);

	wx.SafeYield(self.GetTopWindow(),True);

        # Let's assume that the server returns the image
        # as a Base64 encoded string which our XML-RPC
        # library will decode. Take the result and create
        # a StringIO object which the app will use to
        # display the image.

        try:
            stream = cStringIO.StringIO(res["image"].data)         
        except Exception, e:
	    raise ImageWidgetError(IMAGEWIDGET_ERR_STREAMIFY,e);

	wx.SafeYield(self.GetTopWindow(),True);

        # Return a hash containing the StringIO object
        # and the other meta-data. At this point the
        # base class will take of displaying both.

	return {'image':stream,
		'title':unicode(res["title"],MYWIDGET_ENDPOINT_ENCODING),
		'description':unicode(res["description"],MYWIDGET_ENDPOINT_ENCODING),
		'location':unicode(res["location"],MYWIDGET_ENDPOINT_ENCODING),
		'date':unicode(res["date"],MYWIDGET_ENDPOINT_ENCODING),
		'url':unicode(res["url"],MYWIDGET_ENDPOINT_ENCODING),
		'uid':unicode(res["uid"],MYWIDGET_ENDPOINT_ENCODING),
		'license':unicode(res["license"],MYWIDGET_ENDPOINT_ENCODING),
		'creator':unicode(res["creator"],MYWIDGET_ENDPOINT_ENCODING)}
   

Example 6. Fetching an image using a REST-based service

    def GetImage (self) :

        # You don't have to use xmltramp; there are lots of
        # other XML parsing tools for Python but this one is
        # simple and mostly just "Does What I Mean"

        import urllib
        import xmltramp

        # Let's assume that the server will return an XML
        # string containing metadata elements including one
        # ('url') which we contain a pointer to an actual
        # image to fetch.

	try :
	    xml = urllib.urlopen(MYWIDGET_ENDPOINT_REST).read()
	except Exception, e:
	    raise ImageWidgetError(IMAGEWIDGET_ERR_POLLING,e);

	wx.SafeYield(self.GetTopWindow(),True);

        # Parse the XML

        try :
            res = xmltramp.parse(xml)
        except Exception, e
            raise ImageWidgetError(MYWIDGET_ERR_PARSE,e)

	wx.SafeYield(self.GetTopWindow(),True);

        # Retrieve the actual image from the interweb
        # and create a StringIO object as discussed above.

	try :
	    data = urllib.urlopen(res['url']).read();
	except Exception, e :
	    raise ImageWidgetError(MYWIDGET_ERR_FETCH,e)

	wx.SafeYield(self.GetTopWindow(),True);

	try :
	    stream = cStringIO.StringIO(data);
	except Exception, e :
	    raise WidgetError(IMAGEWIDGET_ERR_STREAMIFY,e);

        # Return a hash containing the StringIO object
        # and the other meta-data. At this point the
        # base class will take of displaying both.

        # xmltramp's __str__ method returns UTF-8
        # encoded Unicode objects
 
	wx.SafeYield(self.GetTopWindow(),True);

        return {'image':stream,
		'title':res.title.__str__(),
		'description':res.description.__str__(),
		'location':res.location.__str__(),
		'date':res.date.__str__(),
		'url':res.url.__str__(),
		'uid':res.uid.__str__(),
		'license':res.license.__str__(),
		'creator':res.creator.__str__()}
   

That's it

Example 7. 

if __name__ == "__main__" :
   widget = MyWidget ()
   widget.MainLoop()
   

Plugins

DeliciousTool

The ImageWidgetDeliciousTool is a plug-in that lets users post the URL for an image to the del.icio.us bookmarking website. It uses the ImageWidget.Login class for prompting and storing login details for the service.

Example 8. The minimum you need to do to use DeliciousTool

# Make sure to load the DeliciousTool package

from ImageWidgetDeliciousTool.Tool import DeliciousTool

# Make sure to include it in your application's
# inheritance tree

class MyWidget (ImageWidget,DeliciousTool) :

    # The Setup() method is exactly the same as described
    # above except that we also call the DeliciousTool Setup()
    # method passing both the current object and it's main
    # frame

    def Setup (self,name,uri) :

	frame = ImageWidget.Setup(self,
				  MYWIDGET_TITLE,
				  MYWIDGET_NAMESPACE)
	
	wx.EVT_MENU(frame,
		    IMAGEWIDGET_ID_ABOUT,
		    self.About)

	wx.EVT_KEY_DOWN(frame.Panel,
			self.KeyDown)

	DeliciousTool.Setup(self,frame)

	return frame;

    # Again, like above we intercept the 'A' key but
    # instead of automatically handing anything else
    # of to the parent ImageWidget classes we check
    # to see if DeliciousTool wants to do anything with
    # it

    # DeliciousTool
    def KeyDown (self,event) :

	keyname = self.GetKeyDown(event)

	if keyname == "A":
            self.About(event)	    
	elif self.DeliciousKeyDown(keyname) :
	    return self.DeliciousKeyDown(keyname,event)
	else :
	    return self.GetTopWindow().KeyDown(event)
   

Example 9. DeliciousTool methods


    # By default returns an empty string

    def DeliciousTags (self) :
	return u"foo bar baz"

    # The first item in the format string is
    # the value assigned to the 'creator' key
    # in the hash returned by the GetImage()
    # method; the second is the value assigned
    # to the 'title' key.

    # The default formatting string is "[%s] %s"

    def PostToDelicious (self,event) :
	fmt = u"My First Widget - %s, %s"
	DeliciousTool.PostToDelicious(self,event,fmt)

   

Conclusion

TBW

A. ImageWidget Class Reference

TBW

B. Default key bindings for ImageWidget-derived classes

Substitute Control for Command (FIXME) if you are developing applications for Mac OS X

  • A. Calls self.About to display a dialogue with information about the application.

  • Control + SHIFT + H. Calls self.GetTopWindow().SetMaxHeight() to display an input dialogue to set the maximum allowable height that an image may be displayed with. This value is saved using ImageWidget.Config

  • I. Calls self.Info to display a dialogue with information about the application.

  • L. Calls self.Log() which toggles whether or not events are written to log file. Log files are written to a file named [FIXME] in the current user's home directory. This value is saved using ImageWidget.Config.

  • P. Calls self.Pause which will prevent the application from fetching another image until [FIXME]

  • Q. Calls self.Quit and exits the application.

  • R. Calls self.Resume which will allow the application to begin fetching images again.

  • Control + SHIFT + W. Calls self.GetTopWindow().SetMaxWidth() to display an input dialogue to set the maximum allowable width that an image may be displayed with. This value is saved using ImageWidget.Config

C. Default menus for ImageWidget-derived classes

TBW

D. Required key-value pairs to be returned by a GetImage() method

  • image. An IOString object

  • title. A Unicode object

  • description. A Unicode object

  • location. A Unicode object

  • date. A Unicode object

  • uid. A Unicode object

  • url. A Unicode object

  • license. A Unicode object

  • creator. A Unicode object