OcempGUI Manual

Marcus von Appen

Redistribution and use in source (XML DocBook) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code (XML DocBook) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified.

  2. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Important: THIS DOCUMENTATION IS PROVIDED BY THE OCEMPGUI PROJECT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OCEMPGUI PROJECT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Table of Contents

Preface
Introduction
Installing OcempGUI
Dependencies
Binary packages
Building from source
Using CVS
Integration in projects
Modules
Making python objects accessible
2D drawing
Geometric objects
Image manipulation
Text rendering
Building event driven systems
How the event management works
Enabling objects to receive events
Setting up the event management
Sending events
Complete event management example
Making objects event capable - the better way
How the BaseObject works
Creating event capable objects
Events on demand: the ActionListener
Creating GUI applications
Getting started
Hello World with OcempGUI
Events and Signals
Widget overview
General settings
Common widget operations - the BaseWidget
Positioning and sizing
Keyboard support
Other features
Labels
Buttons
Button
ImageButton
ToggleButton
CheckButton
RadioButton
Entry boxes
Editable
Entry
Range widgets
Range
Scale
ScrollBar
Container widgets
Bin
Container
Frames
Table
ScrolledWindow
ScrolledList
FileList
Windows and Dialogs
Window
DialogWindow
GenericDialog
FileDialog
Miscellaneous Widgets
ImageMap
ProgressBar
StatusBar
Widget components
ListItem
TextListItem
FileListItem
ListItemCollection
Creating custom widgets
Designing the basis
Implementing the widget basics
Implementing the event handlers
Advanced custom widgets
Changing the widget appearance
How widgets are drawn
Customizing the theme files
Using own drawing routines
A. Widget hierarchy
Example Index
Glossary

Preface

This manual gives an overview about the OcempGUI library and explains how to install, configure and use it. It is meant to be an introduction into the OcempGUI library. This manual is not an API reference. Instead the most classes, methods and functions of the OcempGUI library will be briefly described and examples about how to use them will be provided.

Note

The descriptions, mentioned classes, methods and functions as well as the examples may get out of date and thus using any portion or information of this manual could crash your application or behave in another unexpected way. Although the author tries to keep the manual in synchronization with changes in the library, this can happen from time to time.

If you experience such a misbehaviour or are unsure about a description, refer to the extensive inline documentations of the library. You can accomplish this easily using the pydoc application, for example.

Introduction

The first versions of OcempGUI were created in 2004, when the author of the package started the Ocean Empire project. Ocean Empire is a reimplementation of an old MS-DOS™ game named Ocean Trader.

The author decided to use the pygame library, which is a python wrapper of the SDL library and noted later, that no matching GUI like extensions exist for it. The first task was to implement such a GUI extension to have a suitable graphical environment for the game. The result of this attempt is OcempGUI.

OcempGUI itself is a GUI toolkit based on the Sprite concept of pygame and completely implemented in Python. It can be easily integrated in projects and reduces the development time by providing an own event mangement system and a reliable collection of user interface elements, of which all components are easy to use, extensible and support styling for the look and feel.

Installing OcempGUI

This section describes the process to configure and install OcempGUI.

Dependencies

The following applications and libraries are needed before OcempGUI can be installed:

  • Python, version 2.3 or higher

  • pygame, version 1.6 or higher

Please refer to the documentation of the respective package about how to install it.

Binary packages

OcempGUI is not provided as binary package by the author. However, it might be that someone else set up such a package for your wanted operating system or distribution. Those packages are usually not supported by the author, what means that installation problems or similar issues, which do not target the library directly, should be escalated to the respective supplier of that package.

Building from source

The unpacked package contains two possibilities of building and installing it. Both ways are mostly identical but the one or other user might tend to prefer a specific way. The first is the python way of installing software, the second follows the tradition of the unix environment and uses a Makefile (which actually simply starts the python way). While being in the top source directory, it is possible to type either

make install

for the traditional unix way or

python setup.py install

for the python way.

The package might have some special options, which are described in the README file shipped with it.

Using CVS

Using the latest development sources is possible via CVS. More information about how to use the SourceForge repository can be found on SourceForge.

Warning

It should be noted, that using the development sources can cause higher risks to the environment than the usual releases can do. Thus it is highly recommended to read the ocemp-devel mailing list in which actual development ist discussed.

Integration in projects

The various components of OcempGUI can be used alone without the need to use another module of the package. If only a small event management system is needed, only the ocempgui.events module can be imported. If an event management exists and widgets are needed, only the ocempgui.widgets module needs to be imported and so on.

Modules

The following OcempGUI modules are currently available:

ocempgui.access

Provides various accessibility tools and interfaces for pygame, so that people with disabilities can easily use and access pygame applications. Generic object interfaces for pygame elements are available, which enable them to provide information for access-related technologies like braille keyboards or speech synthesizers. The ocempgui.widgets widget classes integrate the interfaces of this module.

Note

The accessibility module is in an early state at the moment and currently does not offer any outstanding feature.

ocempgui.draw

Provides various drawing primitives, on which the ocempgui.widgets module relies. Usually the methods of this module provide simplified wrappers for the pygame drawing functions.

ocempgui.events

A small and fast event management system. It comes with an EventManager class, which takes care of distributing events to connected objects through signal queues. The event management system can deal with any type of data, which you want to use as event.

ocempgui.object

Abstract object definitions, that allow you to rapidly create own classes, which are event capable through signal slots. Function or method callbacks can be connected to or disconnected from the signals, the object listens to. The BaseObject class is ready to be used with the ocempgui.events module.

ocempgui.widgets

Various GUI elements for the creation and integration of interactive user interfaces. This module contains most commonly used user interface elements as well as abstract core definitions and interfaces to rapidly create own user interface elements. It also provides an own rendering class, which allows you to instantly create your pygame application without the need of taking care about an event and update loop.

Making python objects accessible

To make python objects accessible and usable by the ocempgui.access module and layers, the objects have to inherit from the Accessible class. It provides a single method interface, get_accessible_context(), which has to return a AccessibleContext object for the specific python object.

Dependant on the capabilities of the python object and the information it provides, it has to set up several attributes of the AccessibleContext, it will return.

Note

The accessible module is in an early stage and currently does not perform anything useful.

2D drawing

The ocempgui.draw module contains several wrapper functions around various pygame drawing functions, which are used by the ocempgui.widgets module. It is divided in several submodules, of which each one contains various related functions such as creating rectangle surfaces, drawing strings or loading images. Although not any function defined within the ocempgui.draw module simplifies the usage of the pygame drawing functions, they can reduce the amount of code to write and several of them enable you to simplify specific operations.

To use the drawing routines in your own code, you can simply import the module using:

import ocempgui.draw

Geometric objects

The ocempgui.draw.Draw submodule contains several functions for geometric objects. Although most of them are only wrappers around the respective pygame functions, some of them can be used to create more complex geometric objects. The following list gives an overview about the functions defined within this submodule.

draw_line (surface, color, a, b, width=1)

Draws a line with the given width from a to b on the passed surface. This function simply wraps the pygame.draw.line() function.

You can find the following example as a python script under examples/draw_line.py.

# Draw.draw_line () usage example.
import pygame, pygame.locals
from ocempgui.draw import Draw

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((200, 200))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('Draw.draw_line ()')

# Draw horizontal lines in different colors and sizes.
for i in range (10):
    val = i * 10
    Draw.draw_line (screen, (0 + val, 50 + val, 40 + 2 * val),
                    (5, val), (195, val), i)

# Draw vertical lines in different colors and sizes.
for i in range (10):
    val = i * 8
    Draw.draw_line (screen, (0 + 2 * val, 30 + val, 35 + 2 * val),
                    (5 + i * 10, 100), (5 + i * 10, 195), i)

# Draw a cross.
Draw.draw_line (screen, (0, 0, 0), (120, 100), (195, 195), 3)
Draw.draw_line (screen, (0, 0, 0), (195, 100), (120, 195), 3)

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 1. Draw.draw_line ()

draw_rect (width, height,color=None)

Creates a rectangle surface with a size of width and height, which can be manipulated and blitted on other surfaces. This function simply wraps the pygame.Surface function and calls Surface.fill() on demand.

You can find the following example as a python script under examples/draw_rect.py.

# Draw.draw_rect () usage example.
import random
import pygame, pygame.locals
from ocempgui.draw import Draw

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((200, 200))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('Draw.draw_rect ()')

# Draw rectangles with various colors.
rect = Draw.draw_rect (55, 40, (255, 0, 0))
screen.blit (rect, (5, 5))

rect = Draw.draw_rect (55, 40, (0, 255, 0))
screen.blit (rect, (65, 5))

rect = Draw.draw_rect (55, 40, (0, 0, 255))
screen.blit (rect, (125, 5))

# Draw encapsulated rectangles.
for i in range (30):
    val = i + 3
    rnd = (random.randint (0, 5), random.randint (0, 5), random.randint (0, 5))
    color = (rnd[0] * i + 100,  rnd[1] * i + 100, rnd[2] * i + 100)
    rect = Draw.draw_rect (100 - 2 * val, 100 - 2 * val, color)
    screen.blit (rect, (5 + val, 50 + val))

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 2. Draw.draw_rect ()

draw_triangle (surface, color, a, b, c, width=0)

Draws a triangle using the vertices a, b and c on the passed surface. This function simply wraps the pygame.draw.polygon() function.

You can find the following example as a python script under examples/draw_triangle.py.

# Draw.draw_triangle () usage example.
import pygame, pygame.locals
from ocempgui.draw import Draw

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((200, 200))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('Draw.draw_triangle ()')

# Draw three triangles.
Draw.draw_triangle (screen, (255, 0, 0), (20, 5), (5, 30), (35, 30), 0)
Draw.draw_triangle (screen, (0, 255, 0), (25, 5), (40, 30), (55, 5), 0)
Draw.draw_triangle (screen, (0, 0, 255), (60, 5), (45, 30), (75, 30), 0)

# Draw a 'tunnel effect' of triangles.
for i in range (30):
     val = i + 3
     color = (val * 4, val * 7, val * 5)
     Draw.draw_triangle (screen, color, (5 + 2 * val, 50 + val),
                         (195 - 2 * val, 50 + val), (100, 195 - 2 * val), 1)

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 3. Draw.draw_triangle ()

Image manipulation

The ocempgui.draw.Image submodule contains image related functions, such as loading or saving image data.

load_image (filename, alpha=False, colorkey=None)

Loads an image from the specified filename and automatically converts it to the current display pixel format. If alpha is set to True, the method will try enable alpha transparency by using the pygame.Surface.convert_alpha() method. If the colorkey argument is set to a color value, the method tries to add color based transparency using the pygame.Surface.set_colorkey() method. This function is just a wrapper around pygame.image.load() and additionally calls Surface.convert().

You can find the following example as a python script under examples/load_image.py.

# Image.load_image () usage example.
import pygame, pygame.locals
from ocempgui.draw import Image

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((120, 100))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('Image.load_image ()')

# Load an image and blit it on the screen.
image = Image.load_image ("./image.png")
screen.blit (image, (10, 10))

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 4. Image.load_image ()

Text rendering

The ocempgui.draw.String submodule contains functions, which allow the creation and manipulation of fonts and string surfaces. It includes a simple font caching system, which provides a fast availability of fonts, which were created earlier. Besides this feature, the string surface related functions are mostly wrappers around the respective pygame functions.

create_font (fontfile, size)

Creates and returns a pygame.Font object from the given fontfile using the passed size. The font will be cached internally, so that a second invocation using the same fontfile and size will return the cached font. This function simply wraps the pygame.font.Font initializer.

Note

If you manipulate the returned pygame.Font directly, the manipulation will be applied to the cached font, too. To circumvent this behaviour, create a copy of the return value using the pygame.Font.copy() method and manipulate the copy.

You can find the following example as a python script under examples/create_font.py.

# String.create_font () usage example.
import pygame
from ocempgui.draw import String

def check (font, name):
    bold = "not bold"
    if font.get_bold ():
        bold = "bold"
    print "%s at %s is %s" % (name, font, bold)

# Initialize the pygame engine.
pygame.init ()

# Create a font from the ttf located in the current directory.
font = String.create_font ("tuffy.ttf", 14)
check (font, "font")

# Now create a second font and manipulate it.
# NOTE: Due to the caching we are using the same font object as above!
font_mod = String.create_font ("tuffy.ttf", 14)
font_mod.set_bold (True)

# Output the bold state of both fonts.
check (font, "font")
check (font_mod, "font_mod")

Example 5. String.create_font ()

create_system_font (fontname, size)

Creates and returns a pygame.Font object from the given system font with the specified fontname and the given size. Like the create_font() function, the font will be cached internally, so that a second invocation with the same parameters will return the cached font. This function simply wraps the pygame.font.SysFont initializer.

Note

If you manipulate the returned pygame.Font directly, the manipulation will be applied to the cached font, too. To circumvent this behaviour, create a copy of the return value using the pygame.Font.copy() method and manipulate the copy.

The pygame.SysFont documentation also notes this:

 

This will always return a valid Font object, and will fallback on the builtin pygame font if the given font is not found.

 
 --Pygame documentation

You can find the following example as a python script under examples/create_system_font.py.

# String.create_system_font () usage example.
import pygame
from ocempgui.draw import String

# Initialize the pygame engine.
pygame.init ()

# Create some fonts.
fonts = {}
names = ( "Arial", "Helvetica", "Sans", "Serif", "Times" )
for name in names:
    fonts[name] = String.create_system_font (name, 14)

# Output the fonts as well as their object address.
for name in fonts:
    print "Loaded: %s at %s" % (name, fonts[name])

Example 6. String.create_system_font ()

draw_string (text, font, size, antialias, color)

Creates a transparent surface displaying the text in the given color. If antialias evaluates to True, the text will be rendered using antialiasing (if possible).

Note

The function first tries to resolve font as font file. If that fails, it looks for a system font name, which matches the font name and returns a Font object based on those information (or the fallback font of pygame, see also create_system_font (fontname, size) .

You can find the following example as a python script under examples/draw_string.py.

# String.draw_string () usage example.
import pygame, pygame.locals
from ocempgui.draw import String

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((400, 100))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('String.draw_string ()')

# Create a text using the ttf located in the current directory.
text = String.draw_string ("This is tuffy.ttf", "tuffy.ttf", 16, 1, (0, 0, 0))
screen.blit (text, (5, 5))

# Create a text using the 'Times' system font
text = String.draw_string ("This is Times", "Times", 16, 1, (255, 0, 0))
screen.blit (text, (5, 35))

# Create a text using the fallback python font by specifying a wrong
# font name (hopefully ;-).
text = String.draw_string ("This is the fallback", "invalid_font_name_here",
                           16, 1, (0, 0, 255))
screen.blit (text, (5, 60))

# Now the same again without antialiasing.
text = String.draw_string ("This is tuffy.ttf (no aa)", "tuffy.ttf",
                           16, 0, (0, 0, 0))
screen.blit (text, (200, 5))

text = String.draw_string ("This is Times (no aa)", "Times",
                           16, 0, (255, 0, 0))
screen.blit (text, (200, 35))

text = String.draw_string ("This is the fallback (no aa)",
                           "invalid_font_name_here", 16, 1, (0, 0, 255))
screen.blit (text, (200, 60))

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 7. String.draw_string ()

draw_string_with_bg (text, font, size, antialias, color, bgcolor)

This function is identical to the draw_string (text, font, size, antialias, color) function, except that it provides a background color via the bgcolor parameter.

You can find the following example as a python script under examples/draw_string_with_bg.py.

# String.draw_string_with_bg () usage example.
import pygame, pygame.locals
from ocempgui.draw import String

# Initialize the drawing window.
pygame.init ()
screen = pygame.display.set_mode ((100, 100))
screen.fill ((250, 250, 250))
pygame.display.set_caption ('String.draw_string_with_bg ()')

# Create texts using the 'Times' system font and different background
# colors.
text = String.draw_string_with_bg ("This is Times", "Times", 16, 1, (0, 0, 0),
                                   (200, 200, 200))
screen.blit (text, (5, 5))

text = String.draw_string_with_bg ("This is Times", "Times", 16, 1, (0, 0, 0),
                                   (0, 200, 0))
screen.blit (text, (5, 60))

# Show anything.
pygame.display.flip ()

# Wait for input.
while not pygame.event.get ([pygame.locals.QUIT]):
    pass

Example 8. String.draw_string_with_bg ()

Building event driven systems

The ocempgui.events module provides a small and fast event management system. It is currently separated into three different classes, of which the most important is the EventManager class. Besides the EventManager, an Event class for sending event data and an EventCallback class for connecting functions or methods to signals are available.

To create an own event driven application system or to enhance an existing application, only a few guidelines have to be respected and only a minimal set of changes be made on existing code. You will need to

  • enable objects to receive events,

  • set up the event management system.

To understand, what you are doing and to know the pitfalls of the event management system, you first have to know, how it works. The next subsection will give you a short explanation of it.

How the event management works

The event management system of OcempGUI uses a simple approach using signal slots. This means, that objects will register themselves only for specific event types, of which they want to be notified. Any other event will not be sent to them.This reduces the overhead of events the objects have to deal with (either by dropping or processing them) and improves the performance and scalability of the event management system (especially with many objects).

Event management diagram.

Figure 1. Event management

Enabling objects to receive events

An object, which shall be event aware, has to implement a notify() method, which will receive events distributed by the EventManager. The signature of the method looks like the following:

class OwnObject:
    ...
    def notify (self, event):
        ...

The event argument of the method will be an Event object, which can be used to perform certain actions within the method body then:

class OwnObject:
    ...
    def move (self, coords):
       # Moves the object to the desired coordinates (x, y).
       self.x = coords[0]
       self.y = coords[1]
       print "Moved to %d,%d" % (self.x, self.y)

    def notify (self, event):
        # Check the event signal and run a certain action with its data.
        if event.signal == "clicked":
           print "Something was clicked!"
        elif event.signal == "move":
           # Assuming that the event.data contains a coordinate tuple.
           self.move (event.data)
        

Example 9. Enabling an object to receive events

Setting up the event management

Setting up the main event management is nearly as easy as enhancing the objects. To add objects to the EventManager, the add_object() method has to be invoked. It receives the object to add and a list of signal ids as arguments. The signal ids will cause the object to be registered in specific queues, to which events with matching signal ids then will be sent.

Objects can be removed from the EventManager using the remove_object() method. The method allows you to either remove the object from specific slots or from all slots, it is registered for, at once.

We use the OwnObject class of the previous example and will (un)register it for the signals "move" and "clicked".

# Create an EventManager and OwnObject instance.
manager = EventManager ()
myobj = OwnObject ()

# Add the object to the EventManager.
manager.add_object (myobj, "move", "clicked")

# Remove the object from the 'clicked' slot.
manager.remove_object (myobj, "clicked")

# Remove the object from _all_ slots it still listens on.
manager.remove_object (myobj)
        

Example 10.  Adding and removing an object to the EventManager

Sending events

Now let us proceed to the most important: sending events. To send events to the objects of the EventManager, you can use the emit() method. It receives two arguments, which will become the signal and data of a Event object. The Event will be created by the EventManager and then sent to the matching objects. So all you have to do is to pass the emit() method the correct information for the event.

Both arguments the emit() receives, have no limitations of type, length or whatsoever. It is up to you to to send correct information through the event management system and to check for correct information on the object side.

# Send events to the registered objects via the emit() method.
manager.emit ("clicked", None)
manager.emit ("move", (10, 10))
        

Example 11.  Sending events through the EventManager

Complete event management example

The following example is a complete example based on the excerpts from above. You can find it as python script under examples/eventmanager.py

# EventManager usage example.
from ocempgui.events import EventManager

# Create a new event capable object. This can be acquired by adding a
# 'notify ()' method to the object, which receives a single argument.
class OwnObject:
    def __init__ (self):
        self.x = 0
        self.y = 0
    
    def move (self, coords):
       # Moves the object to the desired coordinates (x, y).
       self.x = coords[0]
       self.y = coords[1]
       print "Moved to %d,%d" % (self.x, self.y)

    def notify (self, event):
        # Check the event signal and run a certain action with its data.
        if event.signal == "clicked":
           print "Something was clicked!"
        elif event.signal == "move":
           # Assuming that the event.data contains a coordinate tuple.
           self.move (event.data)

# Create an EventManager and OwnObject instance.
manager = EventManager ()
myobj = OwnObject ()

# Add the object to the EventManager.
manager.add_object (myobj, "move", "clicked")

# Send events to the registered objects via the emit() method.
manager.emit ("clicked", None)
manager.emit ("move", (10, 10))

# Remove the object from the 'clicked' slot.
manager.remove_object (myobj, "clicked")

# Send the 'clicked' event once more.
manager.emit ("clicked", None)

# Remove the object from _all_ slots it still listens on.
manager.remove_object (myobj)

# Send the 'move' event again.
manager.emit ("move", (40, 40))

Example 12. Complete event management example

Making objects event capable - the better way

The previous section gave you a rough overview about how to use the OcempGUI event system with your own objects. As you might have seen, using only the notify() method and lengthy if-else conditions might not always be the best idea. Also, overriding the notify() method whenever the functionality of an object should change is not the best, especially, if it should be runtime dependant.

You might consider using the BaseObject from the ocempgui.object module instead. It offers ready to use signal slots and methods to bind callbacks at runtime, improving its and your flexibility without big effort.

How the BaseObject works

The BaseObject is event driven, which means, that it acts and reacts upon events it receives and that it can raise events. Hereby you have to distinguish between Signals and Events. Signals are certain identifiers, a BaseObject can listen to, while Events are a combination of a Signal and additional data.

What does that mean in practice? As you already know from the section called “Building event driven systems”, objects will register themselves at an event manager with a specific signal, they listen to. Events in turn carry a specific signal id and additional data. A BaseObject will register itself at an event manager with its signals. If events are passed to the event manager, it will distribute them to the object, if needed. The object in turn will react according to its programming instructions.

To allow your object to react according to the application needs on certain events easily, the BaseObject supports the connection of callbacks to signals you can define for it. A callback is a method or function, which should be invoked, when the object receives a certain event.


              Signal handling of the BaseObject class.

Figure 2.  Signal handling of the BaseObject class

Creating event capable objects

The first thing you should do is to let your existing object or the newly created one inherit from the BaseObject class. Afterwards you can unleash its full power by adding just a minimal set of code.

from ocempgui.object import BaseObject

class OwnObject (BaseObject):
    def __init__ (self):
        BaseObject.__init__ (self)
    
    ...
        

Example 13.  Inheriting from the BaseObject class

That is not all of course. You also have to set up the signals the object has to listen to and create callbacks. Let us create a small ping-pong example, where two objects react upon a 'ping' and 'pong' signal.

The BaseObject has a _signals attribute, which basically is a dictionary with the signal ids it listens to as keys and list for the callbacks as values. To allow your object to listen to the 'ping' or 'pong' signal, you have to add those to this dictionary.

from ocempgui.object import BaseObject

class OwnObject (BaseObject):
    def __init__ (self):
        BaseObject.__init__ (self)
        self._signals["ping"] = []
        self._signals["pong"] = []
    ...
        

Example 14. Adding a signal to the object

Note

The list as value is mandatory to allow callbacks to be connected to those signals. If you are going to supply other types as value, keep in mind, that it is unlikely that connecting or disconnecting callbacks will work as supposed.

Now we just need to make the notify() aware of those signal types and let it invoke the appropriate callbacks, which will be connected to those signals.

class OwnObject (BaseObject):
    ...
    def notify (self, event):
        if event.signal == "ping":
            self.run_signal_handlers ("ping")
        elif event.signal == "pong":
            self.run_signal_handlers ("pong")
        

Example 15. Setting up the notify() method

As you see, the run_signal_handlers() takes care of invoking the connected callbacks. Now you have anything set up to allow the object to listen to specific events, to connect callbacks to it and let it invoke them, when it receives the specific signal.

To connect methods or functions as callbacks to a specific signal, the connect_signal() method of the BaseObject can be used. It allows additional data to be passed to the callback by specifiying the data right after the signal and callback. If the callbacks is not needed anymore, it can be disconnected using the disconnect_signal() method.

class OwnObject (BaseObject):
    ...

my_obj = OwnObject ()
ev_callback1 = my_obj.connect_signal ("ping", ping_callback, data)
ev_callback2 = my_obj.connect_signal ("pong", pong_callback, data1, data2)
...

my_obj.disconnect_signal (ev_callback1)
my_obj.disconnect_signal (ev_callback2)
        

Example 16. Connecting and disconnecting callbacks.

Now it just needs to be connected to an event manager. In contrast to the earlier section, you do not need to register any signal of your BaseObject inheritor manually. When you connect it to an event manager, it will automatically do that for you.

class OwnObject (BaseObject):
    ...


manager = EventManager
my_obj = OwnObject ()

# Any signal of the object will be registered automatically.
my_obj.manager = manager
        

Example 17. Connecting the object to an event manager.

The last important thing to know about the BaseObject is its ability to emit events. If the object is connected to an event manager, you can let it send events through the manager with the object its emit() method. The syntax is the same as if you would emit events on the EventManager directly.

Now let us look at the example we just went through (the following oone is slightly modified only). You can find the example as python script under examples/baseobject.py

# BaseObject usage example.
from ocempgui.object import BaseObject
from ocempgui.events import EventManager

# Callbacks, which should be invoked for the object.
def ping_callback (obj, additional_data):
    print "The object is: %s" % obj.name
    print "Passed data is: %s" % additional_data

def pong_callback ():
    print "Another callback with no arguments."

# Object implementation, which can listen to specific events.
class OwnObject (BaseObject):
    def __init__ (self, name):
        BaseObject.__init__ (self)
        self.name = name
        # The object should be able to listen to 'ping' and 'pong'
        # events.
        self._signals["ping"] = []
        self._signals["pong"] = []

    def notify (self, event):
        # This simple notify method will not be used in this
        # example. Instead, the signals are invoked directly.
        if event.signal == "ping":
            self.run_signal_handlers ("ping")
        elif event.signal == "pong":
            self.run_signal_handlers ("pong")

manager = EventManager ()

# Create an object and connect callbacks to its both events.
my_obj = OwnObject ("First object")
ev1 = my_obj.connect_signal ("ping", ping_callback, my_obj, "data")
ev2 = my_obj.connect_signal ("pong", pong_callback)

# Connect it to the event manager
my_obj.manager = manager

# Invoke the connected signals handlers for a specific event.
manager.emit ("ping", None)
manager.emit ("pong", None)

# After disconnecting a callback, it will not be invoked anymore.
my_obj.disconnect_signal (ev1)
my_obj.disconnect_signal (ev2)
manager.emit ("ping", None)
manager.emit ("pong", None)

Example 18. Ping-Pong with a BaseObject

Events on demand: the ActionListener

The ocempgui.object module includes another event capable object class, the ActionListener, which inherits from the BaseObject class, but allows you to create and delete signals and listening queues as you need them without the necessity to subclass.

The ActionListener creates the signal id and a callback queue, when you connect a callback to it and registers itself for this signal automatically at its event manager. This can be extremeley useful, if a more flexible event capable object type is needed, which does not need to do any sanity checks on the event data. Instead it will send the event data to the callback as well, which then can work with it.

Once more let us create a ping-ping example using the ActionListener class instead of a BaseObject now.

You can find the example as python script under examples/actionlistener.py

# ActionListener usage example.
import sys
from ocempgui.events import EventManager
from ocempgui.object import ActionListener

count = 0

def emit_pong (event, manager):
    print "emit_pong received: [%s] - emitting pong..." % event
    manager.emit ("pong", "pong_event")

def emit_ping (event, manager):
    global count
    if count > 10:
        sys.exit ()
    count += 1
    print "emit_ping received: [%s] - emitting ping..." % event
    manager.emit ("ping", "ping_event")

# Create an event manager and two ping-pong listeners.
manager = EventManager ()

listener1 = ActionListener ()
listener1.connect_signal ("ping", emit_pong, manager)
listener1.manager = manager

listener2 = ActionListener ()
listener2.connect_signal ("pong", emit_ping, manager)
listener2.manager = manager

# start ping-pong actions
print "Starting Ping-Pong"
manager.emit ("ping", "ping_event")

Example 19. Ping-Pong with the ActionListener

Looking at the first interesting line, line eight,

def emit_pong (event, manager):
    print "emit_pong received: [%s] - emitting pong..." % event
    manager.emit ("pong", "pong_event")
        
you will notice, that the signature of the function slightly has changed. Instead of receiving the additional data of a callback connection, it gets the event data of the event here. As second object it receives an EventManager object, on which it emits a pong event with additional data then.

Line twelve and following does the same, but breaks, if it was invoked more than ten times.

The next lines of interest are line twenty-three to twenty-nine,

listener1 = ActionListener ()
listener1.connect_signal ("ping", emit_pong, manager)
listener1.manager = manager
        
in which two ActionLister objects will be created and signal slots and callbacks for the 'ping' and 'pong' events will be set up. In line twenty-five and twenty-nine the objects will be registered at the event manager created earlier in the code.

Note

Although this class is very mighty, you should not use it as base for own event capable classes. When the feature set and code amount of your own classes grow, it easily can happen, that you oversee events or that you do not understand which signals it should deal with anymore.

It is however a good and valuable class type to work as proxy or to delegate events to different functions or methods in a context sensitive manner.

Creating GUI applications

Getting started

The first thing an application using OcempGUI should do is to initialize the renderering system. The Renderer class from the ocempgui.widgets package contains all necessary parts to take care of this. It includes

  • the event mangement

  • a sprite based render engine

  • methods to create the pygame window

The Renderer can be set up with only three lines of code.

from ocempgui.widgets import *
re = Renderer ()
re.create_screen (200, 200) # Creates the pygame window
        

Example 20. Setting up the Renderer

Hello World with OcempGUI

Now that the Renderer is set up, you can start to place widgets on the pygame window by adding them using the Renderer.add_widget() method. Let us do this by building a simple (and well-known) application with a Button widget on it, that displays 'Hello World'.

You can find the example as python script under examples/hello_world.py

# Hello World example.
from ocempgui.widgets import *

# Initialize the drawing window.
re = Renderer ()
re.create_screen (100, 50)
re.title = "Hello World"
re.color = (250, 250, 250)

button = Button ("Hello World")
button.position = (10, 10)
re.add_widget (button)

# Start the main rendering loop.
re.start ()

Example 21. Hello World with OcempGUI

The second line

from ocempgui.widgets import*
        
is the usual directive to import anything from the ocempgui.widgets module you will need to build an application. It is also possible to use a fine grained selection of classes and submodules to import, but mostly the above code will serve well.

The fifth and sixth lines

re = Renderer ()
re.create_screen (100, 50)
        
create a new Renderer object, which takes care of updating the screen and the event management and create a pygame window with a 100x50 size.

The seventh and eight line

re.title = "Hello World"
re.color = (250, 250, 250)
        
set the the title caption of the pygame window and the background color (here as RGB value) for the window. Those are not necessary code, but at least the title will help window managers to display the pygame window correctly within their window lists, etc.

In line ten

button = Button ("Hello World")
the Button widget is created. The constructor can receive an additional argument with the text, the Button should display.

The next line will place the Button at a specific position.

button.position = (10, 10)
The position always refers to the topleft corner of the widget and can be used to place widgets at the desired coordinates on the pygame window.

Line twelve

re.add_widget (button)
adds the button to the Renderer, which will enable the button to be displayed, receive events and send events.

The last line will start the main processing loop of the Renderer.

re.start ()
When the program reaches this line of code, the processing is passed to the Renderer, which will wait for events, draw and update the widgets and so on.

Events and Signals

Every widget of OcempGUI inherits from the ocempgui.object.BaseObject class and its event handling makes heavy usage of the BaseObject features.

To cause a Button to print a message upon a mouse click, you would connect a message printing function to the click signal of the Button. In turn, if this callback is not needed anymore in the later program flow, you would disconnect the function from the Button's signal.

This theoretical model is used in many different toolkits and OcempGUI stays with it. We will enhance our 'Hello world' example application from the previous chapter with a callback now, wich prints a message each time the Button is clicked.

You can find the example as python script under examples/hello_world_signals.py

# Hello World example.
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def print_message  ():
    print "The button was clicked!"

# Initialize the drawing window.
re = Renderer ()
re.create_screen (100, 50)
re.title = "Hello World"
re.color = (250, 250, 250)

button = Button ("Hello World")
button.position = (10, 10)
button.connect_signal (SIG_CLICKED, print_message)
re.add_widget (button)

# Start the main rendering loop.
re.start ()

Example 22. Hello World with callbacks

The first change you note is the new import directive in line three.

from ocempgui.widgets.Constants import *
        
This will import the set of common constants used within the ocempgui.widgets module. The availabe signal identifiers used by the various widgets of OcempGUI area prefixed with SIG_.

Line five and six contain the function, which will be used as the callback for the button. It is indifferent, if the callback is a class or object method or a function. Both cases will work in the same way.

The next notably change was done in line 16

button.connect_signal (SIG_CLICKED, print_message)
        
This tells the Button to invoke the print_message function each time it is clicked. You also could send additional data to the callback as you already know from the section called “Making objects event capable - the better way”. Anything written about the signal handling of the BaseObject class applies to the widgets, too.

Widget overview

The following sections cover the possibilities and capabilities of the different widgets, the ocempgui.widgets module offers. The sections will not cover any method and attribute of the widgets in detail, but just the most important ones. It is strongly recommended, that you read through the doc strings of the widgets, too, to get a complete overview about them.

General settings

The ocempgui.widgets module contains some globally accessed settings, that influence its complete behaviour. Those can be found in the ocempgui.widgets.base part and contain the Style, which is currently in use, the timer rate for double-clicks and a debugging flag. You can adjust those settings easily by simply binding them to new values.

Before you go ahead and change base.GlobalStyle, you should read the section called “Changing the widget appearance”.

The base.debug setting is for additional debugging output and should not be set to True usually (unless you are using a development version).

The last one, base.DoubleClickRate should be set using the base.set_doubleclick_rate() method only and denotes the maximum time to elaps between two click operations to identify them as a double-click.

Common widget operations - the BaseWidget

The BaseWidget is the most basic class of all widgets and contains common attributes and methods, each widget of the ocempui.widgets module has to include. Every widget inherits from it, so that any description and explanation in this section also to the widgets, which are explained later on.

Positioning and sizing

A widget can be set to a specific position on the main screen using the position attribute.

widget.position = 10, 15
        
Using the above code, the topleft corner of the widget will bo positioned at the x-coordinate 10 and the y-coordinate 15 of the main screen, of which 0, 0 denotes the topleft corner.

Note

This will not work as supposed using widgets, that are bound to a Container or Bin widget, which will be explained in one of the following sections.

If you read the current position value of a widget, keep in mind, that it will return a tuple containing the both, x and y, coordinates.

Every widget supports a minimum size, that will be respected by the default drawing methods.

widget.size = 80, 20
        
The minimum size is the guaranteed width and height, the widget should occupy on the screen. It can grow bexond that minimum size, if this is needed to display all of its contents according to its drawing method, but will never be smaller than this value.

The currently used width and height, which can differ from its size can be retrieved using the width and height attributes.

if (widget.width == widget.size[0]) and (widget.height == widget.size[1]):
    print "Widget does not exceed its minimum size."
        

Keyboard support

To allow an easy and logical keyboard navigation, widgets have an index attribute, which influences the navigation order using the keyboard.

widget.index = 3
        
A higher index will cause the widget to receive the input focues earlier. It is highly recommended, that you set this value in your own code to provide the user a better keyboard accessibility.

The input focus mentioned above denotes a state of the widget, in which the user can interact with it using the keyboard only. A Button widget will react upon pressing the space bar with a click while an Entry widget will let the user type text. You can set the input focus of a widget manually with the focus attribute.

widget.focus = True
        
This however will not work with any widget (e.g. layout containers such as the Table). Depedant on the widget the set_focus() method of it will return either True or False, which indicates, that the input focus could be sucessfully set for that widget or not.
frame = VFrame ()
focusok = frame.set_focus (True)
if not focusok:
    print "Focus could not be set."
        

Other features

Widgets can be disabled from user interaction and receiving events, if you set their sensitive attribute to False.

widget.sensitive = False
        
They will be set in a different state then to indicate, that the user cannot interact with them anymore. Of course you can reset the sensitivity at any time.

The renderering system of the ocempgui.widgets module can use different layers, on which widgets are drawn. This is especially useful and often necessary to place widgets above others (e.g. to place a Window widget above another one).

widget.depth = 3
        
The above code causes the widget to be drawn above all widgets, which have a depth value smaller than three, but under any widget, that has a higher depth. You can find an example demonstrating this in the section called “Window”.

Labels

Labels are elements displaying a short amount of text. They are non-interactive widgets and usually are used to display needed information to the user.

To create a Label, you typically will use

my_label = Label (text)
      
The sole argument is the text to display.

To set the text after creation, use the text attribute or set_text() method.

label.text = "New Text"
label.set_text ("New Text")
      

It is possible to have multiline text by setting the multiline attribute to True:

label.multiline = True
label.set_multiline (True)
      
Multiple text lines can be created by the line separator sequence of the operating system (typically a '\n' for unix like systems). The internal line determination however makes use of the os.linesep value of python.

Labels support keyboard accelerators, so called mnemonics, which can activate other widgets. The '#' will cause the directly following to work as mnemonic character. If you want to cause the Label to display a normal '#', use '##' in the text.

label.text = '#Mnemonic'
label.set_text ('A simple hash: ##')
      

If a mnemonic is set up, you usually have to set the widget, which should be activated by the mnemonic as well.

label.widget = another_widget
      

Below you will find an example to illustrate most of the abilities of the Label widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/label.py.

# Label examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame
    
def create_label_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (2, 3)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    for i in xrange (len (states)):
        lbl = Label (states[i])
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            lbl.sensitive = False
        else:
            lbl.state = STATE_TYPES[i]
        frm_states.add_child (lbl)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    for i in xrange (5):
        lbl = Label ("Padding: %dpx" % (i * 2))
        lbl.padding = i * 2
        frm_padding.add_child (lbl)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 1, ALIGN_TOP)

    # Frame with mnemonic support.
    frm_mnemonics = _create_vframe ("Mnemonics")
    strings = ("#Simple Mnemonic", "A ## is displayed using '####'",
               "M#ultiple M#nemonics #have no #effect")
    for i in xrange (len (strings)):
        lbl = Label (strings[i])
        frm_mnemonics.add_child (lbl)
    table.add_child (0, 2, frm_mnemonics)
    table.set_align (0, 2, ALIGN_TOP)

    # Frame with multiline labels.
    frm_multiline = _create_vframe ("Multiline labels")
    strings = ("Single line", "First lines" + os.linesep + "Second line",
               "First line" + os.linesep + "Second line" + os.linesep +
               "Third Line",
               "Two lines with a" + os.linesep + "#mnemonic")
    for i in xrange (len (strings)):
        lbl = Label (strings[i])
        lbl.multiline = True
        frm_multiline.add_child (lbl)
    table.add_child (1, 0, frm_multiline)
    table.set_align (1, 0, ALIGN_TOP)
    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (500, 350)
    re.title = "Label examples"
    re.color = (234, 228, 223)
    re.add_widget (create_label_view ())
    # Start the main rendering loop.
    re.start ()

Example 23. Label example

Buttons

Buttons are interactive user interface elements, which usually react upon mouse events such as clicks or similar events.

Button

You already learned about the Button widget in an earlier section, so let us look at some interesting details of it now.

The Button widget is a interactive widget, which reacts upon mouse input such as clicks. Basically it is a container (which will be explained detailled later on), which holds a Label widget to display its text.

To create a Button, you usually will type

button = Button (text)
        

The usage of mnemonics for the Button is easy to achieve by simply supplying a mnemonic text as described in the section called “Labels”. You can set the text directly through the text attribute or set_text() method.

button.text = "#Mnemonic"
button.set_text ("#Mnemonic")
        
It is not needed to explicitly set the widget attribute of the Button its Label as this already has been done on creation of the Button.

As one of only few widgets the Button supports different border styles to adjust its look and feel without the need to override its drawing methods.

button.border = BORDER_NONE
button.set_border (BORDER_NONE)
        

The Button widget has some default signals, it listens to. Those are

  • SIG_MOUSEDOWN - Invoked, when a mouse button is pressed down on the Button.

  • SIG_MOUSEUP - Invoked, when a mouse button is released on the Button.

  • SIG_MOUSEMOVE - Invoked, when the mouse moves over the Button area.

  • SIG_CLICKED - Invoked, when the left mouse button is pressed and released over the Button.

Below you will find an example to illustrate most of the abilities of the Button widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/button.py.

# Button examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame

def create_button_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (2, 3)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    for i in xrange (len (states)):
        btn = Button (states[i])
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            btn.sensitive = False
        else:
            btn.state = STATE_TYPES[i]
        frm_states.add_child (btn)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    for i in xrange (5):
        btn = Button ("Padding: %dpx" % (i * 2))
        btn.padding = i * 2
        frm_padding.add_child (btn)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 1, ALIGN_TOP)

    # Mnemonics.
    frm_mnemonic = _create_vframe ("Mnemonics")
    btn = Button ("#Simple Mnemonic")
    btn2 = Button ("#Activate using <ALT><Underlined Key>")
    frm_mnemonic.add_child (btn, btn2)
    table.add_child (0, 2, frm_mnemonic)
    table.set_align (0, 2, ALIGN_TOP)

    # Borders.
    frm_borders = _create_vframe ("Borders")
    btn_raised = Button ("Raised border")
    btn_sunken = Button ("Sunken border")
    btn_sunken.border = BORDER_SUNKEN
    btn_flat = Button ("Flat border")
    btn_flat.border = BORDER_FLAT
    btn_none = Button ("No border")
    btn_none.border = BORDER_NONE
    btn_etchedin = Button ("Etched in")
    btn_etchedin.border = BORDER_ETCHED_IN
    btn_etchedout = Button ("Etched out")
    btn_etchedout.border = BORDER_ETCHED_OUT
    frm_borders.add_child (btn_raised, btn_sunken, btn_flat, btn_none,
                           btn_etchedin, btn_etchedout)
    table.add_child (1, 0, frm_borders)
    table.set_align (1, 0, ALIGN_TOP)

    # Multiline labeled buttons
    frm_multiline = _create_vframe ("Multiline labels")
    strings = ("Single lined Button", "Two lines on" + os.linesep + "a Button",
               "Two lines with a" + os.linesep + "#mnemonic")
    for i in xrange (len (strings)):
        button = Button (strings[i])
        button.child.multiline = True
        frm_multiline.add_child (button)
    table.add_child (1, 1, frm_multiline)
    table.set_align (1, 1, ALIGN_TOP)
    
    return table
    
if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (550, 470)
    re.title = "Button examples"
    re.color = (234, 228, 223)
    re.add_widget (create_button_view ())
    # Start the main rendering loop.
    re.start ()

Example 24. Button example

ImageButton

The ImageButton is basically a subclass of the Button, but enhances it by the ability to load and display image data. It supports any image data format, that can be handled by the underlying pygame library. Due to its inheritance, everything said about the Button widget applies to the ImageButton as well.

The creation of an ImageButton is slightly different to the Button. Instead of passing the text to display, you can pass either the name of a file to load (including the full path to it) or a pygame.Surface object to display.

button = ImageButton ("path/to/an/image.png")
button = ImageButton (pygame_surface)
        
Of course it is possible to supply text through the text attribute, too, which then will be displayed right besides the image.
button.text = "Additional text"
        

Below you will find an example to illustrate most of the abilities of the ImageButton widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/imagebutton.py.

# ImageButton examples.
import pygame, os
from ocempgui.draw import Image
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame

def create_button_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    image = Image.load_image ("./image.png")
    table = Table (2, 2)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    for i in xrange (len (states)):
        btn = ImageButton (image)
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            btn.sensitive = False
        else:
            btn.state = STATE_TYPES[i]
        btn.text = states[i]
        frm_states.add_child (btn)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    for i in xrange (4):
        btn = ImageButton (image)
        btn.padding = i * 2
        frm_padding.add_child (btn)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 0, ALIGN_TOP)

    # Mnemonics.
    frm_mnemonic = _create_vframe ("Mnemonics")
    btn = ImageButton (image)
    btn.text = "#Simple Mnemonic"
    btn2 = ImageButton (image)
    btn2.text = "#Activate using <ALT><Underlined Key>"
    frm_mnemonic.add_child (btn, btn2)
    table.add_child (1, 0, frm_mnemonic)
    table.set_align (1, 0, ALIGN_TOP)

    # Multiline labeled ImageButton
    frm_multiline = _create_vframe ("Multiline label")
    button = ImageButton (image)
    button.text = "Multiple lines" + os.linesep + "with a #mnemonic"
    button.child.multiline = True
    frm_multiline.add_child (button)
    table.add_child (1, 1, frm_multiline)
    table.set_align (1, 1, ALIGN_TOP)

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (570, 400)
    re.title = "ImageButton examples"
    re.color = (234, 228, 223)
    re.add_widget (create_button_view ())
    # Start the main rendering loop.
    re.start ()

Example 25. ImageButton example

ToggleButton

Inherited from the Button the ToggleButton widget does not differ from it except that it is always in one state, either active or inactive, which are alternated by a click. By default it is displayed in a depressed state on a click and pops up after clicking it again.

To create a ToggleButton, you can do the same as with the Button class.

button = ToggleButton (text)
        

You can retrieve the current state as boolean value of the ToggleButton through its active attribute and set its state programmatically via this attribute or the set_active() method.

if button.active:
    print "The ToggleButton is currently active!"
button.set_active (False)
        
This also applies to the CheckButton and RadioButton classes in the next sections.

To track changes of this state, the ToggleButton supplies a SIG_TOGGLED signal, which will be raised, if the state is changed via a mouse input or the accelerator action of a Label.

def state_changed (togglebutton):
    state = "active"
    if not togglebutton.active:
       state = "inactive"
    out = "The state of the ToggleButton has been set to %s" % state

button = ToggleButton ("A ToggleButton")
button.connect_signal (SIG_TOGGLED, state_changed, button)
        
The signal will not be raised, if the state is set programmatically.

Below you will find an example to illustrate most of the abilities of the ToggleButton widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/togglebutton.py.

# ToggleButton examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame

def create_button_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (2, 3)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    for i in xrange (len (states)):
        btn = ToggleButton (states[i])
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            btn.sensitive = False
        else:
            btn.state = STATE_TYPES[i]
        frm_states.add_child (btn)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    for i in xrange (5):
        btn = ToggleButton ("Padding: %dpx" % (i * 2))
        btn.padding = i * 2
        frm_padding.add_child (btn)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 0, ALIGN_TOP)

    # Mnemonics.
    frm_mnemonic = _create_vframe ("Mnemonics")
    btn = ToggleButton ("#Simple Mnemonic")
    btn2 = ToggleButton ("#Activate using <ALT><Underlined Key>")
    frm_mnemonic.add_child (btn, btn2)
    table.add_child (0, 2, frm_mnemonic)
    table.set_align (0, 2, ALIGN_TOP)


    # Multiline labeled buttons
    frm_multiline = _create_vframe ("Multiline labels")
    strings = ("Single lined ToggleButton",
               "Two lines on" + os.linesep + "a ToggleButton",
               "Two lines with a" + os.linesep + "#mnemonic")
    for i in xrange (len (strings)):
        button = ToggleButton (strings[i])
        button.child.multiline = True
        frm_multiline.add_child (button)
    table.add_child (1, 0, frm_multiline)
    table.set_align (1, 0, ALIGN_TOP)

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (530, 400)
    re.title = "ToggleButton examples"
    re.color = (234, 228, 223)
    re.add_widget (create_button_view ())
    # Start the main rendering loop.
    re.start ()

Example 26. ToggleButton example

CheckButton

The CheckButton, which inherits from the ToggleButton, does not bring in any new features. Instead it just uses a different look to display its set state. The state of the CheckButton is indicated by a small (usually 10x10 px) square, which is either checked or unchecked.

To create a CheckButton widget, you usually will do the same as with the ToggleButton.

button = CheckButton (text)
        

Below you will find an example to illustrate most of the abilities of the CheckButton widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/checkbutton.py.

# CheckButton examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame

def create_button_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (2, 3)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    for i in xrange (len (states)):
        btn = CheckButton (states[i])
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            btn.sensitive = False
        else:
            btn.state = STATE_TYPES[i]
        frm_states.add_child (btn)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    frm_padding.spacing = 5
    frm_padding.align = ALIGN_LEFT
    for i in xrange (5):
        btn = CheckButton ("Padding: %dpx" % (i * 2))
        btn.padding = i * 2
        frm_padding.add_child (btn)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 0, ALIGN_TOP)

    # Mnemonics.
    frm_mnemonic = _create_vframe ("Mnemonics")
    btn = CheckButton ("#Simple Mnemonic")
    btn2 = CheckButton ("#Activate using <ALT><Underlined Key>")
    frm_mnemonic.add_child (btn, btn2)
    table.add_child (0, 2, frm_mnemonic)
    table.set_align (0, 2, ALIGN_TOP)

    # Multiline labeled buttons
    frm_multiline = _create_vframe ("Multiline labels")
    strings = ("Single lined CheckButton",
               "Two lines on" + os.linesep + "a CheckButton",
               "Two lines with a" + os.linesep + "#mnemonic")
    for i in xrange (len (strings)):
        button = CheckButton (strings[i])
        button.child.multiline = True
        frm_multiline.add_child (button)
    table.add_child (1, 0, frm_multiline)
    table.set_align (1, 0, ALIGN_TOP)

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (550, 350)
    re.title = "CheckButton examples"
    re.color = (234, 228, 223)
    re.add_widget (create_button_view ())
    # Start the main rendering loop.
    re.start ()

Example 27. CheckButton example

RadioButton

RadioButton widgets are similar to the CheckButton widgets, except that they can grouped, so that only one button of a group can be activated at a time. This is especially helpful, if you need to have the user to choose between a small amount of options.

The creation of a RadioButton is done using

button = RadioButton (text, group)
        
The additional group argument can contain another RadioButton object, with which the newly created one should be grouped together.

Alternatively to the group argument of the constructor, a RadioButton can be assigned to a group after its creation with the group attribute or set_group() method.

button.group = other_radio_button
button.set_goup (other_radio_button)
        
It is also possible to go the other way around and to add or remove RadioButton widgets from a group with the add_button() or remove_button() methods of the group.

Given those possibilities a group of four choices can be created like the following example.

group = RadioButton ("Choice 1")
button1 = RadioButton ("Choice 2", group)

button2 = RadioButton ("Choice 3")
button2.group = group

button3 = RadioButton ("Choice 4")
group.add_button (button3)
        

In contrast to its parent classes, activating a RadioButton causes the other buttons in its group to loose their active state.

Below you will find an example to illustrate most of the abilities of the RadioButton widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/radiobutton.py.

# RadioButton examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def _create_vframe (text):
    frame = VFrame (Label (text))
    frame.spacing = 5
    frame.align = ALIGN_LEFT
    return frame

def create_button_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (2, 3)
    table.spacing = 5

    # Frame with the states.
    frm_states = _create_vframe ("States")
    group = None
    for i in xrange (len (states)):
        btn = RadioButton (states[i], group)
        if i == 0:
            group = btn
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            btn.sensitive = False
        else:
            btn.state = STATE_TYPES[i]
        frm_states.add_child (btn)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = _create_vframe ("Padding")
    group = None
    for i in xrange (5):
        btn = RadioButton ("Padding: %dpx" % (i * 2), group)
        if i == 0:
            group = btn
        btn.padding = i * 2
        frm_padding.add_child (btn)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 0, ALIGN_TOP)

    # Mnemonics.
    frm_mnemonic = _create_vframe ("Mnemonics")
    btn = RadioButton ("#Simple Mnemonic")
    btn2 = RadioButton ("#Activate using <ALT><Underlined Key>", btn)
    frm_mnemonic.add_child (btn, btn2)
    table.add_child (0, 2, frm_mnemonic)
    table.set_align (0, 2, ALIGN_TOP)

    # Multiline labeled buttons
    frm_multiline = _create_vframe ("Multiline labels")
    strings = ("Single lined RadioButton",
               "Two lines on" + os.linesep + "a RadioButton",
               "Two lines with a" + os.linesep + "#mnemonic")
    group = None
    for i in xrange (len (strings)):
        btn = RadioButton (strings[i], group)
        if i == 0:
            group = btn
        btn.child.multiline = True
        frm_multiline.add_child (btn)
    table.add_child (1, 0, frm_multiline)
    table.set_align (1, 0, ALIGN_TOP)

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (550, 330)
    re.title = "RadioButton examples"
    re.color = (234, 228, 223)
    re.add_widget (create_button_view ())
    # Start the main rendering loop.
    re.start ()

Example 28. RadioButton example

Entry boxes

Entry boxes support the input of text or numerical values via the keyboard or similar input devices. They usually support the most common editing operations such as character input, deletion, etc.

Editable

The Editable class is an abstract base class, which takes care of dealing with keyboard events and text caret positioning. It is used as backend for the Entry widget class, which will be explained in the next section.

You usually will not create an Editable object directly, but rather use it as parent for your own classes.

It can store and operate on (unicode) text through its text attribute. The text usually will be set via this attribute or the set_text() method.

editable.text = "Text"
editable.set_text ("Text")
        

It also supports a virtual text caret position, which will be used by its internals to determine the position for editing operations. The caret attribute and set_caret() allow you to adjust the caret position.

editable.caret = 7
editable.set_caret (7)
        

Given both the text and caret attribute, the internals of the Editable will modify the text of it upon receiving keyboard events, if it has the input focus. Given an Editable, which contains the text "This is a Test." and its caret is set to 4, the processing of a pressed key ("D") causes:

  1. Check, whether text editing is allowed.

  2. If it is, insert a "D" at the fourth position in the text.

  3. The text of it now is "ThisD is a Test"

The previous list mentioned a check, whether the text of an Editable can be modified. To allow or disallow editing the hold text, you can adjust the value of the editable attribute.

editable.editable = True
editable.set_editable (True)
        
This however only prevents editing operations, which are received as events and not setting the text directly through the text attribute or set_text() method.

The Editable listens by default to the following signals:

  • SIG_KEYDOWN - Invoked, when a key gets pressed.

  • SIG_INPUT - Invoked, when the input is validated or aborted using RETURN or ESC.

Entry

The Entry widget is a single line text input box, that inherits from the Editable class. It fully supports the Editable features and enhances it by mouse event sensitivity.

To create a Entry widget, you usually will type

entry = Entry (text)
        

Entry widgets support a password-like mode, in which any typed character will be displayed as an asterisk ('*').

entry.password = True
        
This however does not enahcens security as the typed characters are stored as plain text. Only the display differs.

To allow a better look for some special fonts, the Entry can place an additional amount of pixels between its border and the text input area via the padding attribute or set_padding() method.

entry.padding = 5
entry.set_padding (10)
        
The gained space will be filled with the background color of the Entry by default.

The Entry supports the SIG_MOUSEDOWN signal to get the input focus upon a left mouse button press.

Below you will find an example to illustrate most of the abilities of the Entry widget class. You do not need to care about other widgets like the Frame class for now as those are explained later on.

You can find the following example as a python script under examples/entry.py.

# Entry examples.
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def create_entry_view ():
    states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE",
              "STATE_INSENSITIVE")

    table = Table (10, 10)
    table.spacing = 5

    # Frame with the states.
    frm_states = VFrame (Label ("States"))
    frm_states.spacing = 5
    frm_states.align = ALIGN_LEFT
    for i in xrange (len (states)):
        entry = Entry (states[i])
        if STATE_TYPES[i] == STATE_INSENSITIVE:
            entry.sensitive = False
        else:
            entry.state = STATE_TYPES[i]
        frm_states.add_child (entry)
    table.add_child (0, 0, frm_states)
    table.set_align (0, 0, ALIGN_TOP)

    # Frame with different padding.
    frm_padding = VFrame (Label ("Padding"))
    frm_padding.spacing = 5
    frm_padding.align = ALIGN_LEFT
    for i in xrange (5):
        entry = Entry ("Padding: %dpx" % (i * 2))
        entry.padding = i * 2
        frm_padding.add_child (entry)
    table.add_child (0, 1, frm_padding)
    table.set_align (0, 0, ALIGN_TOP)

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (400, 400)
    re.title = "Entry examples"
    re.color = (234, 228, 223)
    re.add_widget (create_entry_view ())
    # Start the main rendering loop.
    re.start ()

Example 29. Entry example

Range widgets

Range widgets denote any widget class, that is based on the abstract Range widget class. Those widgets usually support setting a value within a defined value range, such as scaling widgets or scrollbars.

Range

The Range widget class is an abstract class, which enables inheriting classes to set and use value ranges. It can make use of float values, thus providing a high and for most cases exact resolution of values and it supports setting minimum and maximum values as well as stepwise increments.

You usually will not create a Range object directly, but inherit from it in your own widget classes. The constructor of a Range

range = Range (minimum, maximum, step)
        
needs some arguments, which define the minimum range value, the maximum range value and the step range to use for increments or decrements of the Range.

The minimum attribute defines the lower limit of the value range it serves, while the maximum attribute defines the upper limit of it. Both can be set either via the attribute or the set_minimum() or set_maximum() methods.

if range.minimum < 0:
    range.set_minimum (0)
if range.maximum > 100:
    range.set_maximum (100)
        

The step attribute of the Range is useful to inc