Copyright © 2005-2007 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:
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.
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.
Abstract
A GUI library for Pygame
Table of Contents
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.
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.
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.
OcempGUIs focus lies on a GUI toolkit implementation based on the Sprite concept of pygame and is 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.
Besides GUI functionality OcempGUI offers modules for other needs such as accessibility and a fast event management system. Both parts do not need rely on pygame and thus can be used in any Python project.
This section describes the process to configure and install OcempGUI.
The following applications and libraries are needed before OcempGUI can be installed:
Python, version 2.3 or higher
pygame, version 1.7.1 or higher
ATK, version 1.12.1 or higher (optional)
Please refer to the documentation of the respective package about how to install it.
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.
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 the latest development sources is possible via CVS. More information about how to use the SourceForge repository can be found on SourceForge.
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 on which actual development ist discussed.
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.
The following OcempGUI modules are currently available:
ocempgui.access
Provides various accessibility tools and interfaces for
python, so that people with disabilities can easily use
and access python applications. Generic interfaces for
objects are available, which enable them to provide
information for access-related technologies like braille
keyboards or speech synthesizers. The
ocempgui.widgets
widget classes
will integrate the interfaces of this module in future
versions.
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 as
well as some more complex drawing object types.
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.
The ocempgui.access
module provides a
wrapper around the ATK accessibility library and several classes
to ease the development of accessible applications. To serve
different ability ranges, objects can be aware, the ATK
developers use an C interface system, that has to be implemented
for each toolkit. OcempGUI implements those interfaces using the
AtkObject
only and flags, that will
enable or disable certain interfaces within that instance.
To use accessibility features in your code, you have to import the module using:
import ocempgui.access
import ocempgui.access.papi
To make python objects accessible and usable by
accessibility-aware applications and hardware they should
implement IAccessible
interface
class. It provides a single method interface,
get_accessible()
, which has to return
an AtkObject
object for the specific
python instances.
class A11yObject (IAccessible): def __init__ (self): IAccessible.__init__ (self) ... def get_accessible (self): obj = AtkObject (...) ... return obj
Dependant on the capabilities of the python object and the
information it provides, it has to set up several attributes
and/or implement various interfaces of the
AtkObject
, which will be returned.
TODO: provide more details
You can find the following example as a python script
under examples/a11y_test.py
.
# papi test example. import ocempgui.access.papi as papi import atexit # Main object - somewhat similar to the GailTopLevel object. application = papi.AtkObject () application.name = "Application object" application.description = "Application description" application.role = papi.ATK_ROLE_APPLICATION application.parent = None def get_application (): global application return application # Register the interfaces and initialize the atk-bridge. papi.set_atk_root (get_application) atexit.register (papi.shutdown) papi.init () class SimpleA11y (papi.AtkObject): def __init__ (self): ifaces = papi.ATK_IFACE_COMPONENT | papi.ATK_IFACE_ACTION papi.AtkObject.__init__ (self, ifaces) # Implement some interfaces of ATK_IFACE_ACTION and # ATK_IFACE_COMPONENT. self.action_get_n_actions = self.__get_n_actions self.action_get_description = self.__get_description self.action_get_name = self.__get_name self.component_get_extents = self.__get_pos def __get_pos (self, coords): return 10, 10, 99, 99 def __get_n_actions (self): return 1 def __get_description (self, i): return "Example action." def __get_name (self, i): return "Example action name" # Window dummy as child of the toplevel application object. window = SimpleA11y () # Retrieve the state set, so we can set it active. set = window.ref_state_set () set.add_state (papi.ATK_STATE_ACTIVE) # Set some necessary information for accessibility applications. window.role = papi.ATK_ROLE_WINDOW window.name = "Window A11y Object" window.description = "Window Description" # Link it with the application object window.parent = application # Signal testing - window:create will cause accessibility applications # to note, that a new window was created for the application. window.emit ("window:create") print "Keeping myself alive. Press CTRL-C to exit the application." while True: # Iterate the main processing loop of the ATK wrapper internals, # so that external applications can interact with the objects. papi.iterate ()
Example 1. ocempgui.access.papi test example
The Magnifier
class of the
ocempgui.access
module is a screen
magnification tool for pygame screens. It allows users to zoom
portions of the current pygame screen, which are determined by
the mouse cursor position. It allows different magnification
factors and a variable sizing of the area to zoom.
The Magnifier
can be integrated easily
into any pygame mainloop and adjusted with minimal effort. To
create a new instance of it, you simply can invoke its
constructor with no arguments.
magnifier = Magnifier ()
# Use a magnification area of 100, 100 and a factor of 3. magnifier = Magnifier (100, 100, 3)
The zoom factor can be adjusted using the
factor attribute and
set_factor()
method.
magnifier.factor = 5.2 magnifier.set_factor (0.5)
Values smaller than 1 will zoom out the affected area.
The size of the area around the mouse cursor, which should be
magnified, can be adjusted using the size
attribute and set_size()
method.
magnifier.size = 50, 50 magnifier.set_size (40, 70)
Integrating the Magnifier
in a pygame
application is done by just adding one or two lines of code.
The most important line is to notify it about the available
pygame events.
while True: events = pygame.event.get () magnifier.notify (*events) ...
pygame.MOUSEMOTION
events. Any other event
will cause it to update its area only.
The update is done only once per
notify()
invocation. If multiple
pygame.MOUSEMOTION
events are in the
passed list, only the last on will be used for repositioning.
If the pygame display is manipulated directly and thus needs to
contain its original surface information (without the magnified
area), the restore()
method should be
invoked before the manipulation occurs.
while True: events = pygame.event.get () magnifier.restore () # Display manipulation .... magnifier.notify (*events) ...
Magnifier
will be suspended) before
any manipulation takes place. Afterwards the
Magnifier
will be enabled again by
passing the current events to it.
The Magnifier
allows you to setup your
own zoom function for best results when zooming parts of your
application screen. The zoom_func attribute
and set_zoom_func()
method allow you to
provide an own zoom function, which has to return the zoomed
surface. It receives additional arguments, which are explained
in detail in the inline documentation of the
Magnifier
class.
Implementing the Magnifier
's default zoom
function could be achieved using the following code.
def own_zoom_func (screen, mousepos, resultsize, size, factor): offset = mousepos[0] - size[0] / 2, mousepos[1] - size[1] / 2 # Create zoomable surface. surface = pygame.Surface ((size[0], size[1])) surface.blit (screen, (0, 0), (offset[0], offset[1], size[0], size[1])) # Zoom and blit. return pygame.transform.scale (surface, (resultsize[0], resultsize[1])) # Assign the new zoom function. magnifier.zoom_func = own_zoom_func
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
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 2. 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 3. 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 4. Draw.draw_triangle ()
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 5. Image.load_image ()
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, style)
Creates and returns a pygame.Font
object from the given fontfile
using the passed size
and
style
. The font will be cached
internally, so that a second invocation using the same
fontfile
and
size
will return the cached font.
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 6. String.create_font ()
create_system_font (fontname, size, style)
Creates and returns a pygame.Font
object from the given system font with the specified
fontname
and the given
size
and
style
. 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.
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 7. String.create_system_font ()
draw_string (text, font, size, antialias, color, style)
Creates a transparent surface displaying the
text
in the given
color
with the specified
style
applied. If
antialias
evaluates to True, the
text will be rendered using antialiasing (if possible).
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, style)
.
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 8. String.draw_string ()
draw_string_with_bg (text, font, size, antialias, color,
bgcolor, style)
This function is identical to the
draw_string (text, font, size, antialias, color, style)
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 9. String.draw_string_with_bg ()
The ocempgui.draw.Complex
module contains
more advanced drawing objects, that implement interesting
features for 2D graphics.
The FaderSurface
class enhances the
pygame.Surface
class by some additional
attributes and methods to support fade-in/fade-out operations.
You can adjust them to fit its behaviour to your needs, such as
setting the current transparency value using the
alpha attribute
surface.alpha = 0 # Make it completely transparent
surface.step = 5
You can find the following example as a python script
under examples/fader_surface.py
.
# Complex.FaderSurface usage example. import pygame, pygame.locals from ocempgui.draw import Complex, Image # Initialize the drawing window. pygame.init () screen = pygame.display.set_mode ((120, 100)) screen.fill ((180, 180, 180)) pygame.display.set_caption ('Complex.FaderSurface') # Create a surface we can use to display. image = Image.load_image ("./image.png") r = image.get_rect () # Create a new FaderSurface with the same dimensions and an initial # transparency of 1. surface = Complex.FaderSurface (r.width, r.height, 1) # Blit the original on the FaderSurface. surface.blit (image, (0, 0)) # The default step value is -1, but we want to fade the image in. surface.step = 1 # Loop until the FaderSurface reached the maximum or minimum alpha # transparency value. while surface.update (): # Clean up and blit the surface.. screen.fill ((180, 180, 180)) screen.blit (surface, (10, 10)) pygame.display.flip () # Check the bounds. We have to check the maximum values - 1, because # 255 and 0 cause the surface to return False and we would exit the # loop. if surface.alpha == 254 or surface.alpha == 1: surface.step = -surface.step pygame.time.delay (50 / 4) # Wait for input. if pygame.event.get ([pygame.locals.QUIT]): break
Example 10. FaderSurface example
OcempGUI provides a minimalistic observer pattern implementation
in the ocempgui.events
module, which
enables objects to track changes ('observe') from others. Two
classes, the Subject
and
IObserver
, are used for this.
An object, which should expose changes is usually called a subject
to which other objects, the observers, subscribe for notification
about those changes. To create a subject, the class just needs to
inherit from the Subject
class, which
provides a minimum set of methods and attributes to become
observable.
class MyObject (Subject): def __init__ (self): Subject.__init__ (self, "UniqueSubjectName") ...
Subject
receives a
string argument, the name, which identifies the object instance
for possible observers.
The object now features all attributes and methods to register
observers and to notify them about state changes. State changes
however have to be emitted manually, so that the object should
invoke its notify()
method, whenever this
is necessary.
class MyObject (Subject): def __init__ (self): Subject.__init__ (self, "UniqueSubjectName") self._value = None def set_value (self, value): # Preserve old value. oldval = self._value # Set new value. self._value = value # Notify observers self.notify ('value', old, value) ...
set_value()
is invoked now, any
registered observer will be notified about
The object, that changed its state (UniqueSubjectName).
The name of the object part that changed (here the attribute value).
The old and new value of the object part that changed.
An object that shall act as an observer should inherit from the
IObserver
class and implement its
update()
method, which will receive the
state change notification from the Subject
.
The signature of the method looks like the following:
class ObserverObject (IObserver): ... def update (self, subject, prop, oldval, newval): ...
Your classes do not need to explicitly inherit from
IObserver
, but have to implement the
update()
method with its correct
signature.
The subject
argument is the unique name of
the Subject
that just
changed. prop
identifies the detail that
changed and oldval
and
newval
contain the old and new value of the
detail.
The following example is a complete example based on the
excerpts from above. You can find it as python script under
examples/observer.py
# Subject/Observer usage example. from ocempgui.events import Subject, IObserver # The subject that should notify observers about state changes. class MyObject (Subject): def __init__ (self): Subject.__init__ (self, "MyObject") self._x = "Simple Attribute" self._y = 1234567890 self._z = None def get_x (self): return self._x def set_x (self, value): # Preserve old value. old = self._x self._x = value # Notify about change. self.notify ("x", old, value) def get_y (self): return self._y def set_y (self, value): # Preserve old value. old = self._y self._y = value # Notify about change. self.notify ("y", old, value) def get_z (self): return self._z def set_z (self, value): # Preserve old value. old = self._z self._z = value # Notify about change. self.notify ("z", old, value) x = property (get_x, set_x) y = property (get_y, set_y) z = property (get_z, set_z) class OwnObserver (IObserver): def __init__ (self): pass def update (self, subject, prop, oldval, newval): if subject == "MyObject": # A MyObject instance, check details. if prop == "x": # Its x value changed. print "The x value of a MyObject instance changed from " \ "%s to %s" % (str (oldval), str (newval)) elif prop == "y": # Its y value changed. print "The y value of a MyObject instance changed from " \ "%s to %s" % (str (oldval), str (newval)) else: # Another value changed. print "The %s value of a MyObject instance changed from" \ "%s to %s" % (str (prop), str (oldval), str (newval)) class AnotherObserver (IObserver): def __init__ (self): pass def update (self, subject, prop, oldval, newval): print "Detail %s of %s changed from %s to %s" % (str (prop), subject, str (oldval), str (newval)) subject = MyObject () # Add tow observers doing observer1 = OwnObserver () observer2 = AnotherObserver () subject.add (observer1, observer2) subject.x = "FooBarBaz" subject.y = subject.x * 3 subject.z = 100
Example 11. Observer example
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.
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).
An object, which shall be event aware, should inherit from the
INotifyable
class and implement its
notify()
method, which will receive
events distributed by the EventManager
.
The signature of the method looks like the following:
class OwnObject (INotifyable): ... def notify (self, event): ...
Your classes do not need to explicitly inherit from
INotifyable
, but have to implement the
notify()
method with its correct
signature.
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 (INotifyable): ... 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 12. Enabling an object to receive events
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 13.
Adding and removing an object to the
EventManager
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 14.
Sending events through the EventManager
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, INotifyable # 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 (INotifyable): 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 15. Complete event management example
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.
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.
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 16.
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 17. Adding a signal to the object
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 18. 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 19. 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 20. 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 one 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 21. Ping-Pong with a BaseObject
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 22. 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")
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
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.
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.
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 23. Setting up the Renderer
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.topleft = (10, 10) re.add_widget (button) # Start the main rendering loop. re.start ()
Example 24. Hello World with OcempGUI
The second line
from ocempgui.widgets import*
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)
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)
In line ten
button = Button ("Hello World")
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.topleft = (10, 10)
Line twelve
re.add_widget (button)
The last line will start the main processing loop of the
Renderer
.
re.start ()
Renderer
, which will wait
for events, draw and update the widgets and so on.
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.topleft = (10, 10) button.connect_signal (SIG_CLICKED, print_message) re.add_widget (button) # Start the main rendering loop. re.start ()
Example 25. Hello World with callbacks
The first change you note is the new import directive in line three.
from ocempgui.widgets.Constants import *
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)
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.
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.
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.
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.
A widget can be set to a specific position on the main screen using the topleft attribute.
widget.topleft = 10, 15
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 topleft 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.minsize = 80, 20
The currently used width and height, which can differ from its minsize can be retrieved using the width and height attributes or as tuple using the size.
if (widget.width == widget.minsize[0]) and (widget.height == widget.minsize[1]): print "Widget does not exceed its minimum size."
The counterpart to the minsize attribute is maxsize. A widget will never grow beyond the values of it, if set.
The BaseWidget
class exports all
pygame.Rect
attributes, so you are not
limited to the topleft only, but can also
use the pygame.Rect
attributes you are
used to. See the pygame.Rect documentation for more
details.
To allow an easy and logical keyboard navigation, widgets have an index attribute, which influences the navigation order using the keyboard.
widget.index = 3
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
Table
). Dependant
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."
Widgets can be disabled from user interaction and receiving events, if you set their sensitive attribute to False.
widget.sensitive = False
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
Widgets support transparency using the alpha color channel. To set it for a widget use the opacity attribute of it. The opacity attribute accepts values betwee 0 (fully transparent) and 255 (not transparent, default).
widget.opacity = 100
To change the appearance of a single widget instance, the
create_style()
method and
style attribute of a widget can be used.
While create_style()
will generate an
instance specific style dictionary for the widget,
style can be used to have quick access to
it, once it is created.
# Create an instance specific style dictionary for the widget and return a # reference to it. style = widget.create_style() ... # Access the created style dictionary of the widget. widget.style[...] = ...
create_style()
.
Changing instance specific styles is explained in detail in the section called “Changing the appearance of single instances”.
Labels are non-interactive decorative user interface elements, which provide certain information to the user.
The Label
class can display a short amount
of text and allows you to control interaction with other widgets
using keyboard mnemonics.
To create a Label
, you typically will use
my_label = Label (text)
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)
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (states): lbl = Label (s) 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) # 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) # Frame with mnemonic support. frm_mnemonics = _create_vframe ("Mnemonics") strings = ("#Simple Mnemonic", "A ## is displayed using '####'", "M#ultiple M#nemonics #have no #effect") for s in strings: lbl = Label (s) frm_mnemonics.add_child (lbl) table.add_child (0, 2, frm_mnemonics) # Frame with multiline labels. frm_multiline = _create_vframe ("Multiline labels") frm_multiline.align = ALIGN_NONE strings = ("Single line", "First lines\nSecond line", "First line\nSecond line\nThird Line", "Two lines with a\n#mnemonic") for s in strings: lbl = Label (s) lbl.multiline = True frm_multiline.add_child (lbl) table.add_child (1, 0, frm_multiline) 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 26. Label example
The ImageLabel
is a decorative widget
holding an image and not much functionality besides that. It
does not support mnemonics nor text and is mainly used to
display images in a GUI, whenever they should fit smoothly into
the layout.
To create an ImageLabel
, you have pass
either the name of a file to load (including the full path to
it) or a pygame.Surface
object to
display.
imagelabel = ImageLabel ("path/to/an/image.png") imagelabel = ImageLabel (pygame_surface)
set_picture()
method.
In contrast to the Label
class, the
ImageLabel
supports different border
styles to adjust its look and feel without the need to override
its drawing methods.
imagelabel.border = BORDER_NONE imagelabel.set_border (BORDER_NONE)
Below you will find an example to illustrate most of the
abilities of the ImageLabel
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/imagelabel.py
.
# ImageLabel examples. import 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_imagelabel_view (): image = Image.load_image ("./image.png") table = Table (1, 3) table.spacing = 5 table.set_row_align (0, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (STATE_TYPES): lbl = ImageLabel (image) if s == STATE_INSENSITIVE: lbl.sensitive = False else: lbl.state = s frm_states.add_child (lbl) table.add_child (0, 0, frm_states) # Frame with different padding. frm_padding = _create_vframe ("Padding") for i in xrange (5): lbl = ImageLabel (image) lbl.border = BORDER_FLAT lbl.padding = i * 2 frm_padding.add_child (lbl) table.add_child (0, 1, frm_padding) # Borders. frm_borders = _create_vframe ("Borders") for border in BORDER_TYPES: lbl = ImageLabel (image) lbl.border = border frm_borders.add_child (lbl) table.add_child (0, 2, frm_borders) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (500, 350) re.title = "ImageLabel examples" re.color = (234, 228, 223) re.add_widget (create_imagelabel_view ()) # Start the main rendering loop. re.start ()
Example 27. ImageLabel example
Buttons are interactive user interface elements, which usually react upon mouse events such as clicks or similar events.
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 “Label”. You can set the text directly through
the text attribute or
set_text()
method.
button.text = "#Mnemonic" button.set_text ("#Mnemonic")
Button
its
Label
as this already has been done on
creation of the Button
.
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (states): btn = Button (s) 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) # 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) # 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) # 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) # Multiline labeled buttons frm_multiline = _create_vframe ("Multiline labels") strings = ("Single lined Button", "Two lines on\na Button", "Two lines with a\n#mnemonic") for s in strings: button = Button (s) button.child.multiline = True frm_multiline.add_child (button) table.add_child (1, 1, frm_multiline) # Empty buttons with different minimum sizes frm_empty = _create_vframe ("Empty Buttons") for i in xrange (5): button = Button () button.minsize = (20 * i, 10 * i) frm_empty.add_child (button) table.add_child (1, 2, frm_empty) 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 28. Button example
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, just
like the ImageLabel
class.
button = ImageButton ("path/to/an/image.png") button = ImageButton (pygame_surface)
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (states): btn = ImageButton (image) if STATE_TYPES[i] == STATE_INSENSITIVE: btn.sensitive = False else: btn.state = STATE_TYPES[i] btn.text = s frm_states.add_child (btn) table.add_child (0, 0, frm_states) # 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) # 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) # Multiline labeled ImageButton frm_multiline = _create_vframe ("Multiline label") button = ImageButton (image) button.text = "Multiple lines\nwith a #mnemonic" button.child.multiline = True frm_multiline.add_child (button) table.add_child (1, 1, frm_multiline) 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 29. ImageButton example
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)
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)
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (states): btn = ToggleButton (s) 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) # 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) # 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) # Multiline labeled buttons frm_multiline = _create_vframe ("Multiline labels") strings = ("Single lined ToggleButton", "Two lines on\na ToggleButton", "Two lines with a\n#mnemonic") for s in strings: button = ToggleButton (s) button.child.multiline = True frm_multiline.add_child (button) table.add_child (1, 0, frm_multiline) # Empty buttons with different minimum sizes frm_empty = _create_vframe ("Empty Buttons") for i in xrange (5): button = ToggleButton () button.minsize = (20 * i, 10 * i) frm_empty.add_child (button) table.add_child (1, 2, frm_empty) 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 30. ToggleButton example
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") for i, s in enumerate (states): btn = CheckButton (s) 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) # 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) # 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) # Multiline labeled buttons frm_multiline = _create_vframe ("Multiline labels") strings = ("Single lined CheckButton", "Two lines on\na CheckButton", "Two lines with a\n#mnemonic") for s in strings: button = CheckButton (s) button.child.multiline = True frm_multiline.add_child (button) table.add_child (1, 0, frm_multiline) # Empty buttons with different minimum sizes frm_empty = _create_vframe ("Empty Buttons") for i in xrange (5): button = CheckButton () button.minsize = (20 * i, 10 * i) frm_empty.add_child (button) table.add_child (1, 2, frm_empty) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (550, 380) re.title = "CheckButton examples" re.color = (234, 228, 223) re.add_widget (create_button_view ()) # Start the main rendering loop. re.start ()
Example 31. CheckButton example
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)
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)
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 table.set_row_align (0, ALIGN_TOP) table.set_row_align (1, ALIGN_TOP) # Frame with the states. frm_states = _create_vframe ("States") group = None for i, s in enumerate (states): btn = RadioButton (s, 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) # 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) # 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) # Multiline labeled buttons frm_multiline = _create_vframe ("Multiline labels") strings = ("Single lined RadioButton", "Two lines on\na RadioButton", "Two lines with a\n#mnemonic") group = None for i, s in enumerate (strings): btn = RadioButton (s, group) if i == 0: group = btn btn.child.multiline = True frm_multiline.add_child (btn) table.add_child (1, 0, frm_multiline) # Empty buttons with different minimum sizes group = None frm_empty = _create_vframe ("Empty Buttons") for i in xrange (5): button = RadioButton (None, group) button.minsize = (20 * i, 10 * i) if i == 0: group = button frm_empty.add_child (button) table.add_child (1, 2, frm_empty) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (550, 380) re.title = "RadioButton examples" re.color = (234, 228, 223) re.add_widget (create_button_view ()) # Start the main rendering loop. re.start ()
Example 32. RadioButton example
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.
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:
Check, whether text editing is allowed.
If it is, insert a "D" at the fourth position in the text.
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)
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.
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
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)
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 _toggle_password (button, entry): entry.password = button.active def create_entry_view (): states = ("STATE_NORMAL", "STATE_ENTERED", "STATE_ACTIVE", "STATE_INSENSITIVE") table = Table (2, 3) table.spacing = 5 table.set_row_align (0, ALIGN_TOP) # 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) # 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) # Password Mode frm_password = VFrame (Label ("Password support")) frm_padding.spacing = 5 entry = Entry ("A password") button = ToggleButton ("Password mode") button.connect_signal (SIG_TOGGLED, _toggle_password, button, entry) frm_password.add_child (entry, button) table.add_child (0, 2, frm_password) 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 33. Entry example
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.
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)
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 increment or decrement
the value of it in a constant manner. By default it is set to
1.0.
range.step = 10.0 range.set_step (-3.2)
The value of the Range
can be read and
set via the value attribute and
set_value()
method. It can not grow
beyond the upper or lower limit of the
Range
. Assigning a value not within those
limitations will let it raise an exception.
range.value = 4.59 range.set_value (4.59)
For an efficient usage of the Range
within loops, etc. the increase()
and
decrease()
are supplied by it.
range.increase () range.decrease ()
The Range
raises a SIG_VALCHANGED event,
whenever its value changed. This means a
real value change, reassignments of the same value are ignored.
The following example code shows this behaviour.
def val_changed (range): print "The value changed to: %d" % range.value range.connect_signal (SIG_VALCHANGED, val_changed, range) if range.value != 10.0: range.value = 10.0 # Signal handler is invoked. range.value = 10.0 # Nothing happens.
Scale widgets separate in an abstract
Scale
base class, which mainly contains
internal code needed by its both subclasses, the
VScale
and HScale
widget. A Scale
, inherited from the
Range
, is a widget that lets you pick a
value from a range using a vertical
(VScale
) or horizontal
(HScale
) slider. Those both widgets only
differ in their appearance.
To create a HScale
or
VScale
widget, you typically will use the
same constructor syntax as for the Range
widget.
hscale = HScale (minimum, maximum, step) vscale = VScale (minimum, maximum, step)
Range
, the
minimum
and
maximum
arguments set the upper an lower
limit of the value range, while the step
argument sets the step range and defaults to 1.0.
The most important attributes and methods are already explained in the section called “Range”, so they will not be explained here again.
The Scale
widgets listen to the three
available mouse events,
SIG_MOUSEDOWN - Invoked, when a mouse button is pressed
down on the Scale
.
SIG_MOUSEUP - Invoked, when a mouse button is released on
the Scale
.
SIG_MOUSEMOVE - Invoked, when the mouse moves over the
Scale
.
Similar to the Scale
widget, the
ScrollBar
lets you pick a value from a
range, but additionally contains two buttons at its ends, which
allow the user to increment or decrement the value stepwise.
The ScrollBar
is usually not used as
standalone widget, but instead serves as control in more complex
widgets, which need scrolling abilities.
In contrast to the Scale
, the
ScrollBar
uses a slider with a variable
size, which depends on the length of the
ScrollBar
and the
maximum value of it. The slider however has
a defined minimum size, so that it will not shrink to an
unusable size by default.
The procedure to create a ScrollBar
is
similar to the Scale
and
Range
.
hscrollbar = HScrollBar (minimum, maximum, step) vscrollbar = VScrollBar (minimum, maximum, step)
The most important attributes and methods are already explained in the section called “Scale” and the section called “Range”, so they will not be explained here again.
The Scrollbar
widgets listen to the three
available mouse events,
SIG_MOUSEDOWN - Invoked, when a mouse button is pressed
down on the ScrollBar
.
SIG_MOUSEUP - Invoked, when a mouse button is released on
the ScrollBar
.
SIG_MOUSEMOVE - Invoked, when the mouse moves over the
ScrollBar
.
Container widgets are able to hold other widgets and take care of
drawing them on their own surface. They are mostly used for layout
purposes or complex widgets, which consist of several other
widgets or which need to add additional functionality to different
widget types (like the ScrolledWindow
widget). They allow to bind one or more widgets as child(ren) to
themselves and take over the role as parent widget.
The abstract Bin
class is a container,
that is able to hold exactly one child. It allows to bind und
unbind a child widget and supports setting an additional padding
between its surface borders and the child surface.
You usually will not create a Bin
object directly, but inherit from it in your own widget classes.
The child of a Bin
can be set with the
child attribute or
set_child()
method. It is not necessary
to register the child at an event manager after binding it to
the Bin
as this will set the event
manager of the child to its own one.
label = Label ("Label for a bin") bin.set_child (label) # No need to set an event manager explicitly for the label. bin.manager = eventmanager
For layout purposes the Bin
can make use
of additional pixels to place between its outer surface edges
and the child surface. This pixel amount can be modified and
used using the padding attribute of the
Bin
. Various inheritors within the
widgets module of OcempGUI make heavy use of this attribute to
adjust the look of themselves. The following example
demonstrates this.
from ocempgui.widgets import Button, Renderer renderer = Renderer () renderer.create_screen (200, 120) button_5px = Button ("Button with 5px padding") button_5px.topleft = 5, 5 button_5px.padding = 5 button_10px = Button ("Button with 10px padding") button_10px.topleft = 5, 60 button_10px.padding = 10 renderer.add_widget (button_5px, button_10px) renderer.start ()
Example 34. Bin.padding example
The following example provides a complete
Bin
implementation which can rotate the
visible of its child (and only the surface).
You can find the following example as a python script under
examples/bin.py
.
# Bin examples. import pygame from ocempgui.widgets import * from ocempgui.widgets.Constants import * class PivotBin (Bin): """PivotBin (widget) -> OwnBin A Bin implementation example class. This class does not support real rotations of widgets. Instead it simply rotates their image surface and displays it. Any other behaviour and information of the widget stay the same. Thus event capable widgets will not work correctly. """ def __init__ (self): Bin.__init__ (self) self._orientation = ORIENTATION_HORIZONTAL def set_orientation (self, orientation=ORIENTATION_HORIZONTAL): """P.set_orientation (...) -> None Sets the orientation of the attached child. """ if orientation not in ORIENTATION_TYPES: raise ValueError("orientation must be a value of ORIENATION_TYPES") self._orientation = orientation self.dirty = True def draw_bg (self): width, height = self.padding, self.padding cls = self.__class__ if self.child: width += self.child.width height += self.child.height if self.orientation == ORIENTATION_VERTICAL: # Swap width and height on demand width, height = height, width # Guarantee the set minimum and maximum sizes. width, height = self.check_sizes (width, height) surface = base.GlobalStyle.engine.draw_rect (width, height, self.state, cls, self.style) return surface def draw (self): """Draws the PivotBin and its child according to the set orientation.""" Bin.draw (self) if self.child: rect = self.image.get_rect () self.child.center = rect.center if self.orientation == ORIENTATION_VERTICAL: # Rotate the child image on demand. image = pygame.transform.rotate (self.child.image, 90) rotate_rect = image.get_rect () rotate_rect.center = rect.center self.image.blit (image, rotate_rect) else: self.image.blit (self.child.image, self.child.rect) orientation = property (lambda self: self._orientation, lambda self, var: self.set_orientation (var), doc = "The orientation of the child.") def rotate_bin (bin, button): # Set the Bin orientation and replace the button. if bin.orientation == ORIENTATION_HORIZONTAL: bin.orientation = ORIENTATION_VERTICAL else: bin.orientation = ORIENTATION_HORIZONTAL button.topleft = bin.left, bin.bottom + 10 if __name__ == "__main__": bin = PivotBin () bin.topleft = 10, 10 bin.child = Label ("Simple label in PivotBin") button = Button ("Switch orientation") button.topleft = bin.left, bin.bottom button.connect_signal (SIG_CLICKED, rotate_bin, bin, button) # Initialize the drawing window. re = Renderer () re.create_screen (300, 300) re.title = "Bin implementation example" re.color = (234, 228, 223) re.add_widget (bin, button) # Start the main rendering loop. re.start ()
Example 35. Bin example
The abstract Container
widget class is
similar to the Bin
class, except that it
can hold more than one widget.
As well as with the Bin
, you usually will
not create a Container
object directly,
but inherit from it in your own class.
The children can be added using
add_child()
, which allows to add
multiple children at once, or
the insert_child()
method.
# Add a single child. container.add_child (label1) # Add multiple children at once. container.add_child (label1, button1, label2, entry1) # Insert a child at a specific position container.insert_child (1, entry2)
Container
implementation, insert_child()
can
cause the inserted child to appear at a specific position.
The removal of children can be done with one widget at a time only
using the remove_child()
method of the
Container
.
# Only one child can be removed at a time. container.remove_child (button1)
You also can set the children directly using the
children attribute. The
Container
will remove all its children
first and then add the new list of widgets. Simply setting the
children to None will remove all the
widgets of the Container
.
# Set a list of widgets as children of a container. container.children = [label1, button1, entry1] # Remove all children of the container. container.children = None
Besides the padding attribute, which was
already explained in the section called “Bin” the
Container
has a
spacing attribute, which indicates the
pixel amount to place between its children.
container.spacing = 10
Frame
examples in the later section
will show you possible concrete results.
TODO: provide implementation examples
The Box
widgets are
Container
s which allow an absolute
placement of their attached children. This enables you to let
widgets overlap or place them next o each other according to
your needs while keeping them relative to other widgets without
extensive position calculations. You could e.g. place a
Box
into a Frame
so that the contents of the Box
are
aligned relative to other widgets of the
Frame
while being positioned exactly as
you need.
To create a Box
you have to call its
constructor with the size it should occupy.
box = Box (100, 100)
Container
methods. The topleft
coordinates of the attached widgets are used to position them
within the Box. Thus a Button
with the
topleft value of 10, 10 will be placed 10
pixels from the topleft corner of the Box
.
box = Box (100, 100) button = Button ("A Button") button.topleft = 10, 10 box.add_child (button)
The Box
widget class does not make use
of the padding and
spacing attributes of its
Container
parent class. It also does
not resize itself, when a widget leaves its visible area or
occupies more space than the Box
.
Below you will find an example to illustrate most of the
abilities of the Box
widget
classes. You do not need to care about other widgets like the
VFrame
class for now as those are
explained later on.
You can find the following example as a python script under
examples/box.py
.
# Box examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * def create_box_view (): frame = VFrame (Label ("Box example")) frame.topleft = 10, 10 # The Box with 200x200 pixels in size. box = Box (200, 200) # Widgets to place into it. label = ImageLabel ("image.png") label.topleft = 10, 10 button = Button ("A Button") button.topleft = 30, 30 frame1 = VFrame (Label ("A VFrame")) frame1.add_child (Label ("Label in the VFrame")) frame1.topleft = 60, 80 chk = CheckButton ("A CheckButton") chk.topleft = 130, 110 box.children = label, button, frame1, chk frame.add_child (box) return frame if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (300, 300) re.title = "Box examples" re.color = (234, 228, 223) re.show_layer_info = True re.add_widget (create_box_view ()) # Start the main rendering loop. re.start ()
Example 36. Box example
Frame
widgets are
Container
s, that support drawing a
decorative border and title widget around their children. The
basic Frame
class is an abstract class,
which defines the common attributes and methods for its
inheritors, while the HFrame
and
VFrame
widgets are concrete
implementations, that place their children horizontally or
vertically. Both subclasses only differ in the placing behaviour
of their widgets and the basic aligning possibilities, so that,
if not stated otherwise, the following examples always apply to
both widget types, although the HFrame
is
used.
The creation of a HFrame
is done using
frame = HFrame () frame = HFrame (widget)
Frame
. The title widget can be any valid
BaseWidget
subclass and typically be
placed at the topleft corner of the
Frame
. If no title widget is supplied, a
complete border will be drawn around the
Frame
, while a set title widget will
discontinue that border at the topleft corner. You can change
or set the title widget at any later time. A few possibilities
should be shown here.
frame = HFrame (Label ("A title label")) frame.widget = Button ("Button as frame title") frame.set_widget (VFrame (Label ("Frame in a frame")))
To adjust the look of the Frame
, it is
possible to change its border style using the
border attribute
frame.border = BORDER_NONE frame.set_border (BORDER_SUNKEN)
frame.align = ALIGN_TOP frame.align = ALIGN_LEFT
HFrame
and
VFrame
has to be made, because each one
only supports a subset of the alignment possibilities. The
HFrame
widget natively supports aligning
its children at the top or bottom only
hframe.align = ALIGN_TOP hframe.set_align (ALIGN_BOTTOM)
VFrame
supports only the left
and right alignment.
vframe.align = ALIGN_LEFT vframe.set_align (ALIGN_RIGHT)
Below you will find an example to illustrate most of the
abilities of the Frame
widget
classes. You do not need to care about other widgets like the
Table
class for now as those are
explained later on.
You can find the following example as a python script under
examples/frame.py
.
# Frame examples. import os from ocempgui.widgets import * from ocempgui.widgets.Constants import * def create_frame_view (): table = Table (2, 3) table.topleft = 5, 5 table.spacing = 5 # Create and display two 'standard' frames. hframe = HFrame (Label ("Horizontal Frame")) table.add_child (0, 0, hframe) lbl = Label ("Vertical Frame" + os.linesep + "with 5 px spacing") lbl.multiline = True vframe = VFrame (lbl) vframe.spacing = 5 table.add_child (0, 1, vframe) for i in xrange(3): btn = Button ("Button %d" % i) hframe.add_child (btn) btn2 = Button ("Button %d" % i) vframe.add_child (btn2) # Create framed frames. framed1 = VFrame (Label ("VFrame")) framed2 = HFrame () framed3 = VFrame (Label ("VFrame as HFrame.widget")) framed3.add_child (Label ("Child of a VFrame")) framed2.widget = framed3 framed2.add_child (Button ("Button 1"), Button ("Button 2")) button = Button ("Simple Button") framed1.add_child (framed2, button) table.add_child (1, 0, framed1) # Create a Frame with alignment. frame_align = VFrame (Label ("VFrame with right alignment")) frame_align.align = ALIGN_RIGHT label1 = Label ("Label") label2 = Label ("Even longer label") button = CheckButton ("A CheckButton") frame_align.add_child (label1, label2, button) table.add_child (1, 1, frame_align) # Add insensitive frames. hframe = HFrame (Label ("Insensitive HFrame")) hframe.sensitive = False table.add_child (0, 2, hframe) vframe = VFrame (Label ("Insensitive VFrame")) vframe.sensitive = False table.add_child (1, 2, vframe) for i in xrange(3): btn = Button ("Button %d" % i) hframe.add_child (btn) btn2 = Button ("Button %d" % i) vframe.add_child (btn2) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (600, 300) re.title = "Frame examples" re.color = (234, 228, 223) re.add_widget (create_frame_view ()) # Start the main rendering loop. re.start ()
Example 37. Frame example
Table
widgets, which inherit from the
Container
class, allow a table-like
placement of their children in rows and columns. The children
can be placed in the table cells and each cell supports an
individual alignment.
The Table
constructor expects the row and
column dimensions to set up for the table, so that
table = Table (2, 2) table2 = Table (3, 5)
Adding children is different from the usual
Container
methods. The
Table
needs the additional information,
in which cell to place the child. Thus instead of using
table.add_child (child_widget1, child_widget2) # This does not work!
table.add_child (row, column, child_widget1) table.add_child (row1, column1, child_widget2)
Table
in
contrast works in the same way you already learned about in
the section called “Container”.
Assigning the children attribute of the
Table
with a list of widgets adds the
children row for row to it.
As said above, each cell of the Table
supports using an own alignment, which can be set with the
set_align()
method.
table.set_align (0, 1, ALIGN_TOP) table.set_align (1, 1, ALIGN_LEFT)
Table
documentation.
# Aligning the child at the topleft. table.set_align (0, 1, ALIGN_TOP | ALIGN_LEFT) # Conflicting alignments, thus using the higher priority of ALIGN_TOP. table.set_align (0, 1, ALIGN_TOP | ALIGN_BOTTOM) # Again a conflict. ALIGN_NONE has the lowest priority, thus it will be # left aligned. table.set_align (0, 1, ALIGN_LEFT | ALIGN_NONE)
table.set_row_align (row, alignment) table.set_column_align (column, alignment)
Below you will find an example to illustrate most of the
abilities of the Table
widget class.
You can find the following example as a python script under
examples/table.py
.
# Table examples. from ocempgui.widgets import Renderer, Table, Label, Button from ocempgui.widgets.Constants import * def create_table_view (): # Crate and display a Table. table = Table (9, 2) table.spacing = 5 table.topleft = 5, 5 label = Label ("Nonaligned wide Label") table.add_child (0, 0, label) table.add_child (0, 1, Button ("Simple Button")) label = Label ("Top align") table.add_child (1, 0, label) table.set_align (1, 0, ALIGN_TOP) table.add_child (1, 1, Button ("Simple Button")) label = Label ("Bottom align") table.add_child (2, 0, label) table.set_align (2, 0, ALIGN_BOTTOM) table.add_child (2, 1, Button ("Simple Button")) label = Label ("Left align") table.add_child (3, 0, label) table.set_align (3, 0, ALIGN_LEFT) table.add_child (3, 1, Button ("Simple Button")) label = Label ("Right align") table.add_child (4, 0, label) table.set_align (4, 0, ALIGN_RIGHT) table.add_child (4, 1, Button ("Simple Button")) label = Label ("Topleft align") table.add_child (5, 0, label) table.set_align (5, 0, ALIGN_TOP | ALIGN_LEFT) table.add_child (5, 1, Button ("Simple Button")) label = Label ("Topright align") table.add_child (6, 0, label) table.set_align (6, 0, ALIGN_TOP | ALIGN_RIGHT) table.add_child (6, 1, Button ("Simple Button")) label = Label ("Bottomleft align") table.add_child (7, 0, label) table.set_align (7, 0, ALIGN_BOTTOM |ALIGN_LEFT) table.add_child (7, 1, Button ("Simple Button")) label = Label ("Bottomright align") table.add_child (8, 0, label) table.set_align (8, 0, ALIGN_BOTTOM |ALIGN_RIGHT) table.add_child (8, 1, Button ("Simple Button")) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (250, 350) re.title = "Table examples" re.color = (234, 228, 223) re.add_widget (create_table_view ()) # Start the main rendering loop. re.start ()
Example 38. Table example
ScrolledWindow
widgets are
Bin
widgets that add scrolling abilities
to their attached child. This is extremely useful for
situations, in which the widget to scroll should not exceed a
specific size, but has to display all of its data. Putting such
a widget in a ScrolledWindow
allows you
to respect this specific size, while the widget can grow as it
wants.
You create a ScrolledWindow
using
window = ScrolledWindow (width, height)
width
and
height
denote values for the
size attribute of the
ScrolledWindow
. The newly created
ScrolledWindow
will not exceed this size
by default.
The widget, which needs to be scrolled is packed into the
ScrolledWindow
the usual
Bin
way.
window.child = widget_to_scroll
To allow a flexibly scrolling behaviour, you can adjust the
scrolling attribute to make the
ScrolledWindow
automatic scrolling,
always scrolling or never scrolling.
window.scrolling = SCROLL_ALWAYS window.scrolling = SCROLL_NEVER window.scrolling = SCROLL_AUTO
ScrollBar
controls attached to the window
and is described in details in the
set_scrolling()
method documentation.
You also can influence the scrollbars of the
ScrolledWindow
programmatically.
window.vscrollbar.value = 0 window.hscrollbar.value = window.vscrollbar.maximum
ScrolledWindow
to scroll to the topright
of its attached child.
The ScrolledWindow
widget natively
listens to two signals to support better navigation
possibilities. Those are
SIG_MOUSEDOWN - Invoked, when a mouse button is pressed down on the ScrolledWindow.
SIG_KEYDOWN - Invoked, when a key gets pressed.
The ScrolledList
widget class can be used
to display and manage collections in a list-style look and
feel. Technically it is a ScrolledWindow
,
which holdes a ListViewPort
that takes
care of displaying the list contents.
As the ScrolledWindow
the
ScrolledList
supports different scrolling
types and anything else, while the keyboard and mouse behaviour
differ slightly to match the needs of list browsing.
To create a ScrolledList
you have to
supply the sizing dimensions and an optional collection, which
makes up the contents.
scrolledlist = ScrolledList (width, height, collection=None)
ListItemCollection
(explained later)
object is automatically bound to it. The
ScrolledList
however only accepts
collections, which inherit from the
ListItemCollection
.
Items can be added and removed dynamically to the list using the items property.
for no in xrange (10): scrolledlist.items.append (TextListItem ("Item no. %d" % no)) # Last one was too much. scrolledlist.items.remove (scrolledlist.items[-1])
ListItemCollection
wraps a list
and fully supports all important operations of, including
slicing, indexed item access and sorting. More details about the
ListItemCollection
can be found in the section called “ListItemCollection”.
The FileList
widget is useful to display
filesystem contents in a list-style manner. It supports the
distinction between different filetypes through the
FileListItem
item type and allows you to
browse filesystem contents.
As the ScrolledList
, from which the
FileList
inherits, you need to pass the
size dimensions it should occupy and optionally the initial
starting directory to list the contents of.
filelist = FileList (200, 400, "/usr/home")
FileList
will list the contents of the
current directory as set in os.curdir
.
It allows to list directories programmatically through the
directory attribute and
set_directory()
method.
filelist.directory = "C:\" filelist.set_directory ("/tmp")
FileList
will not
list it, but instead provide an acoustic signal using
print "\a"
.
The FileList
additionally supports the
SIG_DOUBLECLICKED signal to allow changing directories
interactively. Of course own methods can be bound to it.
The windows of ocempgui.widgets
are
containers, that have an own caption bar, can be moved within the
main pygame window and allow the user to collapse them to their
caption bar. They can be drawn on top of other widgets by
adjusting their depth attribute as already
explained in the section called “Common widget operations - the BaseWidget”.
The base for all the different window types is the
Window
class. It is a
Bin
container with the additional
ability to collapse itself to its caption bar.
To create a Window
you can simply call
its constructor and provide an optional title text, that will be
used as caption.
window = Window ("My new window")
The title can be retrieved or set at any later time with the title attribute.
window.title = "A new title!"
As already mentioned above, the Window
allows the user to minimize it (and of course restore it), which
is also possible in your own code.
if not window.minimized: window.minimize (True)
minimize()
method you
can prevent the user from doing so, if you do not want it for a
certain window.
You can align the child of the window using the align attribute. Different alignments can be combined, although not any case makes sense and some alignment combinations conflict with each other.
# Align the child at the top. window.align = ALIGN_TOP # Align the child at the bottom left. window.align = ALIGN_BOTTOM | ALIGN_LEFT # Invalid alignment, ALIGN_BOTTOM has a higher priority, thus # the child will be aligned at the bottom. window.align = ALIGN_TOP | ALIGN_BOTTOM
Window
uses an aligning
priority, which is explained in detail in the
Window
documentation.
The Window
widget by default listens to
the following signals:
SIG_MOUSEDOWN - Invoked, when a mouse button is pressed down on the Window.
SIG_MOUSEUP - Invoked, when a mouse button is released on the Window.
SIG_MOUSEMOVE - Invoked, when the mouse moves over the Window area.
Below you will find an example of the
Window
widget class. You do not need to
care about the DialogWindow
class for now
as this is explained in the next section.
You can find the following example as a python script under
examples/window.py
.
# Window examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _destroy_dialog (window): window.child.text = "#Close" def create_window_view (): # Create and display a simple window. window = Window ("Simple window") window.child = Button ("#Not clickable") window.child.connect_signal (SIG_CLICKED, window.destroy) window.topleft = 5, 5 window.depth = 1 # Create dialog window. dialog = DialogWindow ("Modal dialog window") dialog.child = Button ("#Close first to use other widgets") dialog.child.connect_signal (SIG_CLICKED, _destroy_dialog, window) dialog.child.connect_signal (SIG_CLICKED, dialog.destroy) dialog.topleft = 100, 5 dialog.depth = 2 return window, dialog if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (400, 200) re.title = "Window examples" re.color = (234, 228, 223) re.show_layer_info = True re.add_widget (*create_window_view ()) # Start the main rendering loop. re.start ()
Example 39. Window example
The DialogWindow
is a modal
Window
widget, that grabs all events that
are sent through the event manager it is attached to. The user
will not be able to interact with other event capable elements
until the DialogWindow
is
destroyed. Widgets, that are attached to it of course will still
be notified.
The DialogWindow
does not offer any new
features besides those it incorporates from its parent
class(es). To create a new DialogWindow
you use the same constructor syntax as for the
Window
.
window = DialogWindow ("A new DialogWindow")
You can find an example of the
DialogWindow
in Example 39, “Window example”.
It is often needed to know about how the user interacted with a
certain dialog. Creating various Buttons
with different callbacks or one callback, in which the buttons
are distinguished is of course possible, but finding a safe
distinction mode is a more complex task. You might argue, that
you could check the text of the Button
,
but that only will work, if you do not want to offer localized
version of you program.
To circumvent this and other issues the
GenericDialog
widget class was created.
It allows you to associate different states with the buttons you
want to place on it.
To create a GenericDialog
you have to
pass two lists to its constructor, one containing the
Button
widgets you want to display and one
with the result states to associate with.
list1 = [Button ("OK"), Button("Cancel")] list2 = [DLGRESULT_OK, DLGRESULT_CANCEL] dialog = GenericDialog ("Title", list1, list2)
influence the order of the buttons from left to right,
influence the results of the buttons.
This means, that the first Button
in the
list will be displayed at the leftmost of all other
Buttons
, while it will be associated with
the first item of the result list.
To allow a more flexible button and result behaviour (as needed in wizard dialogs for example), you can place new buttons and results at runtime on the dialog.
newlist1 = [Button ("Close"), Button ("Help")] newlist2 = [DLGRESULT_CLOSE, DLGRESULT_USER] dialog.set_buttons (newlist1, newlist2)
Now all you have to do is to connect your
GenericDialog
with a callback for the
SIG_DIALOGRESPONSE signal, it listens to.
def my_callback (result, dlg): if result == DLGRESULT_CLOSE: dlg.destroy () elif result == DLGRESULT_USER: ... dialog,connect_signal (SIG_DIALOGRESPONSE, my_callback, dialog)
To place your own widgets and contents in the dialog, you can
use the content attribute, which is a
VFrame
.
label1 = Label ("Label 1") entry = Entry ("Hello world") dialog.content.add_child (label1, entry) ...
Frame
widget type
applies to this one, too.
Below you will find an example of the
GenericDialog
widget class.
You can find the following example as a python script under
examples/genericdialog.py
.
# GenericDialog examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _close (result, dialog, label): if result == DLGRESULT_OK: label.text = "You pressed OK!" elif result == DLGRESULT_CANCEL: label.text = "You pressed Cancel!" elif result == DLGRESULT_CLOSE: dialog.destroy () def create_dialog_view (): buttons = [Button ("#OK"), Button ("#Cancel"), Button ("Clo#se")] results = [DLGRESULT_OK, DLGRESULT_CANCEL, DLGRESULT_CLOSE] dialog = GenericDialog ("Generic dialog", buttons, results) lbl = Label ("Press the buttons to see the action.") dialog.content.add_child (lbl) dialog.connect_signal (SIG_DIALOGRESPONSE, _close, dialog, lbl) dialog.topleft = 30, 30 dialog.depth = 1 return dialog if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (300, 300) re.title = "GenericDialog examples" re.color = (234, 228, 223) re.add_widget (create_dialog_view ()) # Start the main rendering loop. re.start ()
Example 40. GenericDialog example
The FileDialog
widget allows the user to
choose files and directories from a
FileList
and can return those easily by a
corresponding method. It also includes an
Entry
widget at the top, which keeps
track of the current location and lets the user change the
directory path quickly.
To create a FileDialog
you have would
type something like
dialog = FileDialog ("Select a file...", [Button ("OK")], [DLGRESULT_OK])
The selected file and directory entries can be received easily
using the get_filenames()
method.
selection = dialog.get_filenames()
Below you will find an example of the
FileDialog
widget class.
You can find the following example as a python script under
examples/filedialog.py
.
# FileDialog examples. import os from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _set_files (result, dialog, entry): string = "" if result == DLGRESULT_OK: string = "".join(["\"%s\" " % f for f in dialog.get_filenames ()]) else: string = "Nothing selected" dialog.destroy () entry.text = string def _open_filedialog (renderer, entry): buttons = [Button ("#OK"), Button ("#Cancel")] buttons[0].minsize = 80, buttons[0].minsize[1] buttons[1].minsize = 80, buttons[1].minsize[1] results = [DLGRESULT_OK, DLGRESULT_CANCEL] dialog = FileDialog ("Select your file(s)", buttons, results) dialog.depth = 1 # Make it the top window. dialog.topleft = 100, 20 dialog.filelist.selectionmode = SELECTION_MULTIPLE dialog.connect_signal (SIG_DIALOGRESPONSE, _set_files, dialog, entry) renderer.add_widget (dialog) def create_file_view (renderer): table = Table (1, 2) hframe = HFrame (Label ("FileList")) hframe.add_child (FileList (200, 200)) table.add_child (0, 0, hframe) hframe2 = HFrame (Label ("FileDialog")) label = Label ("Selection:") entry = Entry () entry.minsize = 200, entry.minsize[1] button = Button ("#Browse") button.connect_signal (SIG_CLICKED, _open_filedialog, renderer, entry) hframe2.add_child (label, entry, button) table.add_child (0, 1, hframe2) table.set_row_align (0, ALIGN_TOP) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (550, 300) re.title = "FileDialog examples" re.color = (234, 228, 223) re.add_widget (create_file_view (re)) # Start the main rendering loop. re.start ()
Example 41. FileDialog example
The ImageMap
is a widget, that can
display any type of image, pygame can handle, and supports
different mouse events.
In contrast to the ImageButton
it does
not react visually upon those events, but can store the last
event, that occured as well as providing the relative position
coordinates of the event.
To create an ImageMap
, you have to
provide an image surface or filename similar to the
ImageButton
constructor.
imagemap = ImageMap ("path/to/an/image.png") imagemap = ImageMap (pygame_surface)
set_picture()
method.
Below you will find an example of the
ImageMap
widget class.
You can find the following example as a python script under
examples/imagemap.py
.
# ImageMap examples. import os from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _get_type (eventtype): if eventtype == SIG_MOUSEDOWN: return "SIG_MOUSEDOWN" elif eventtype == SIG_MOUSEUP: return "SIG_MOUSEUP" elif eventtype == SIG_MOUSEMOVE: return "SIG_MOUSEMOVE" else: return "Unknown signal" def _got_mouseevent (event, imagemap, labels): labels[0].text = "Signal: %s" % _get_type (imagemap.last_event.signal) if imagemap.last_event.signal != SIG_MOUSEMOVE: labels[1].text = "Button: %d" % imagemap.last_event.data.button else: labels[1].text = "Button: None" labels[2].text = "Event pos: %s" % str (imagemap.last_event.data.pos) labels[3].text = "Rel. pos: %s" % str (imagemap.relative_position) def _create_vframe (text): frame = VFrame (Label (text)) frame.spacing = 5 frame.align = ALIGN_LEFT return frame def create_imagemap_view (): frm_map = _create_vframe ("ImageMap") imagemap = ImageMap ("image.png") lbl_desc = Label ("Move the mouse over the ImageMap and" + os.linesep + "press the mouse buttons to interact with it.") lbl_desc.multiline = True lbl_results = [Label ("Signal:"), Label ("Button:"), Label ("Event pos:"), Label ("Rel. pos:")] for label in lbl_results: label.create_style()["fgcolor"][STATE_NORMAL] = (255, 0, 0) imagemap.connect_signal (SIG_MOUSEDOWN, _got_mouseevent, imagemap, lbl_results) imagemap.connect_signal (SIG_MOUSEMOVE, _got_mouseevent, imagemap, lbl_results) imagemap.connect_signal (SIG_MOUSEUP, _got_mouseevent, imagemap, lbl_results) frm_map.add_child (imagemap, lbl_desc, *lbl_results) return frm_map if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (250, 220) re.title = "ImageMap examples" re.color = (234, 228, 223) re.add_widget (create_imagemap_view ()) # Start the main rendering loop. re.start ()
Example 42. ImageMap example
The ProgressBar
is a non-interactive
widget class, that uses a horizontal bar to display operation
efforts. Its value range reaches from 0.0 to 100.0.
To create a ProgressBar
widget, you
simply invoke its constructor with no arguments.
bar = ProgressBar ()
Similar to the Range
widget class, the
ProgressBar
supports the
step and
set_step()
methods to set the step
range for continouos increments/decrements
bar.step = 1.4 bar.set_step (10) # Increment/decrement the bar value by the currently set step range. while process_runs: bar.increment () # or bar.decrement () ...
bar.value = 50.0
You can optionally display a short amount of text centered on it,
which can be set with the text attribute or
set_text()
method.
bar.text = "Processing data..." bar.set_text ("Please wait...")
You can find the following example as a python script under
examples/progressbar.py
.
# ProgressBar examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _update_bar (bar, button): if bar.value == 0: while bar.value < 100: bar.increase () button.text = "Clean the progressbar" else: while bar.value > 0: bar.decrease () button.text = "Fill the progressbar" def _update_text (bar): bar.text = "%.2f" % bar.value + "%" def _create_vframe (text): frame = VFrame (Label (text)) frame.spacing = 5 frame.align = ALIGN_LEFT return frame def create_progressbar_view (): table = Table (1, 3) table.spacing = 5 # Create and display a simple ProgressBar. frame = _create_vframe ("ProgressBar") progress = ProgressBar () progress.step = 0.5 # Create a button to start filling. btn = Button ("#Fill the ProgressBar") btn.connect_signal ("clicked", _update_bar, progress, btn) frame.add_child (progress, btn) table.add_child (0, 0, frame) # ProgressBar with text. frame = _create_vframe ("Progressbar with text") progress = ProgressBar () progress.text = "0.00%" progress.step = 0.5 progress.connect_signal (SIG_VALCHANGED, _update_text, progress) # Create a button to start filling. btn = Button ("Fill the ProgressBar") btn.connect_signal ("clicked", _update_bar, progress, btn) frame.add_child (progress, btn) table.add_child (0, 1, frame) # Insensitive progressbar. frame = _create_vframe ("Insensitive Progressbar") progress = ProgressBar () progress.value = 50.0 progress.text = "50.00%" progress.sensitive = False frame.add_child (progress) table.add_child (0, 2, frame) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (450, 150) re.title = "ProgressBar examples" re.color = (234, 228, 223) re.add_widget (create_progressbar_view ()) # Start the main rendering loop. re.start ()
Example 43. ProgressBar example
The Diagram
class is an abstract base
class for Diagram like widgets such as the
Graph2D
. It provides interfaces to create
statistical diagrams easily, be it graphs, bar and pie charts or
whatever else.
You usually will not create an Diagram
object directly, but rather use it as parent for your own
classes.
TODO
The Graph2D
class allows you to plot
two-diemensional graphs using a cartesian coordinate plane.
You can choose the names of the axes, scale units and units to
display and set up the evaluation function for plotting the graph
freely.
To create a Graph2D
widget, you simply invoke
its constructor with the minimum size to occupy by the widget.
graph = Graph2D (400, 400)
The default names of the axes are "x" and "y". They can be changed
using the axes attribute and
set_axes
method.
graph.axes = "t", "v" # Set velocity related to time. graph.set_axes ("t", "v")
To set the scale units to display for the horizontal and vertical axis,
the scale_units attribute and
set_scale_units()
method can be used.
The first value is used for the horizontal, the second for the vertical
axis.
graph.scale_units = ("cm", "kp") graph.set_scale_units ("cm", "kp")
To show or hide the scale units and axes names on the graph, the
show_names attribute and
set_show_names
method are used.
The names and units are usually diplayed on the right of the axes.
graph.show_names = True # Show the names graph.set_show_names (False) # Do not display them.
The distance from one unit to another can be set using the
units and set_units
method. The distance is given in pixels and defaults to 10 to 10.
graph.units = 10, 25 # Set 10 px for the horizontal units, 25 for vertical graph.set_units (10, 25)
You can rotate the graph clockwise by 90 degrees by adjusting the orientation attribute. The default is a horizontal orientation.
graph.orientation = ORIENTATION_VERTICAL # Rotate the graph grap.set_orientation (ORIENTATION_HORIZONTAL)
You can find the following example as a python script under
examples/graph2d.py
.
# Graph2D examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * import Numeric, math __function = "func_1" def change(graph): global __function if __function == "func_1": graph.eval_func = lambda x: x / ((- 3 * x**2.0 + 2) * math.e**x) __function = "func_2" else: graph.eval_func = lambda x: x**4.0 - 3 * x**2.0 + 2 * x __function = "func_1" def create_graph2d_view (): frame = VFrame (Label ("Graph2D")) frame.topleft = 10, 10 # Create the graph. graph = Graph2D (400, 400) # Lock it, because we set some necessary information and want to # avoid excessive update() calls. graph.lock () # Scale units for the x and y axis. graph.scale_units = ("cm", "kp") # Point of origin. graph.origin = 200, 200 # We want to see negative values. graph.negative = True # The evaluation function and data to use. graph.eval_func = lambda x: x**4.0 - 3 * x**2.0 + 2 * x graph.data = Numeric.arrayrange (-10, 10, .001).tolist() # Done, unlock. graph.unlock () button = Button ("Change function") button.connect_signal (SIG_CLICKED, change, graph) frame.add_child (graph, button) return frame if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (450, 500) re.title = "Graph2D examples" re.color = (234, 228, 223) re.add_widget (create_graph2d_view ()) # Start the main rendering loop. re.start ()
Example 44. Graph2D example
The StatusBar
widget class can display various
text information using a stack system as well as the current date and
time. It also supports the placement of widgets on its area.
To create a new StatusBar
widget, you simply
invoke its constructor with no arguments.
statusbar = StatusBar ()
To set set the current information to display, the
push_tip()
method has to be used.
statusbar.push_tip ("Display this tip")
pop_tip()
method
has to be used.
statusbar.pop_tip ()
get_current_tip()
method.
info = statusbar.current_tip info = statusbar.get_current_tip()
To show or hide the information on the StatusBar
the tips attribute and
show_tips
method are used.
statusbar.tips = True statusbar.show_tips (False)
show_date
method,
statusbar.date = True statusbar.show_date (False)
You can supply your own date and time format string to customize
the date output. The date_format attribute and
set_date_format()
method allow you to use
python specific date format strings, which can be understood by
python's datetime.strftime()
method.
statusbar.date_format = "%x" statusbar.set_date_format ("%H:%M")
To get the currently displayed date and time, use the current_date attribute. Its value matches the set date_format attribute.
date = statusbar.current_date
You can customize the size occupied by the displayed information
and date using the tip_width and
date_width properties and
set_tip_width
and
set_date_width
methods.
statusbar.tip_width = 50 # Set the information display width to 50 px. statusbar.set_tip_width (50) statusbar.date_width = 50 # Set the date display width tto 50 px. statusbar.set_date_width (50)
You can find the following example as a python script under
examples/statusbar.py
.
# StatusBar examples. from ocempgui.widgets import * from ocempgui.widgets.Constants import * def _update_tip (statusbar, button): statusbar.pop_tip () statusbar.push_tip ("Button '%s' was clicked" % button.text) def _create_vframe (text): frame = VFrame (Label (text)) frame.spacing = 5 frame.align = ALIGN_LEFT return frame def create_statusbar_view (): table = Table (1, 3) table.spacing = 5 # Create and display a simple StatusBar with some children. frame = _create_vframe ("StatusBar") status = StatusBar () button1 = Button ("Button") button1.connect_signal (SIG_CLICKED, _update_tip, status, button1) button2 = CheckButton ("Empty") button2.connect_signal (SIG_CLICKED, _update_tip, status, button2) status.add_child (button1, button2) button = Button ("Testbutton") button.connect_signal (SIG_CLICKED, _update_tip, status, button) frame.add_child (button, status) table.add_child (0, 0, frame) return table if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (450, 150) re.title = "StatusBar examples" re.color = (234, 228, 223) re.add_widget (create_statusbar_view ()) # Start the main rendering loop. re.start ()
Example 45. StatusBar example
The TooltipWindow
is a non-interactive
widget that can display a short to medium amount of text and can
be used to display additional information and descriptions. It
uses a certain background color to be easily distinguished from
other widgets.
To create a TooltipWindow
widget, you
simply invoke its constructor with the text to display.
window = TooltipWindow ("A message to display")
To set the text after creation, use the text
attribute or set_text()
method.
window.text = "New Text" window.set_text ("New Text")
You can find the following example as a python script under
examples/tooltipwindow.py
.
# TooltipWindow examples import pygame.mouse from ocempgui.widgets import * from ocempgui.widgets.Constants import * __tooltip = None def _make_tooltip (tip, renderer): global __tooltip # Destroy the tooltip, if it exists. if __tooltip: __tooltip.destroy () # Create a new tooltip. __tooltip = TooltipWindow (tip) x, y = pygame.mouse.get_pos () __tooltip.topleft = x + 8, y - 5 __tooltip.depth = 99 # Make it the topmost widget. renderer.add_widget (__tooltip) def _destroy_tooltip (): # Destroy the tooltip, if it exists. global __tooltip if __tooltip: __tooltip.destroy () __tooltip = None def create_tooltip_view (renderer): frame = VFrame () frame.border = BORDER_NONE frame.spacing = 20 button1 = Button ("Move the mouse over me") button1.tooltip = "An enhanced description" button1.connect_signal (SIG_ENTER, _make_tooltip, button1.tooltip, renderer) button1.connect_signal (SIG_LEAVE, _destroy_tooltip) button2 = Button ("And over me, too") button2.tooltip = "Another description" button2.connect_signal (SIG_ENTER, _make_tooltip, button2.tooltip, renderer) button2.connect_signal (SIG_LEAVE, _destroy_tooltip) frame.children = button1, button2 return frame if __name__ == "__main__": # Initialize the drawing window. re = Renderer () re.create_screen (200, 150) re.title = "TooltipWindow examples" re.color = (234, 228, 223) re.add_widget (create_tooltip_view (re)) # Start the main rendering loop. re.start ()
Example 46. TooltipWindow example
The following section covers the usage of the components used by
the ocempgui.widgets
module. Components
denote elements, which are strongly wired with or used by a
widget, but do not inherit from the
BaseWidget
in any way. The components can
be found in the ocempgui.widgets.components
submodule.
The ListItem
component is an abstract
base class for creating own item implementations, which are
suitable for the usage in list or tree widgets. It contains a
set of attributes and methods, which make it ready to be used in
the ListItemCollection
collection class.
You usually will not create ListItem
objects directly, but instead subclass them, whenever you need a
more advanced or specialized item type, than the
TextListItem
and
FileListItem
offer.
Similar to the widgets inheriting from the
BaseWidget
class, the
ListItem
offers a
style attribute, that lets you set up an
instance specific look and feel for it.
# Create the instance specific style information. listitem.create_style() listitem.style['font']['size'] = 16
The collection, the item should be or is bound to, can be read and set by accessing its collection attribute.
if listitem.collection != my_collection: listitem.set_collection (my_collection)
To make the ListItem
suitable for tree or
list widgets, it contains a selected
attribute that, as the name says, can be used by the according
widget implementation to set or determine the selection state of
the item.
if not listitem.selected: listitem.selected = True
For optimization purposes in drawing the
ListItem
contains, similar to the
BaseWidget
, a dirty
attribute and set_dirty()
method.
listitem.dirty = True
For concrete implementations and examples, the author recommends
to look at the source code of the
TextListItem
and
FileListItem
implementations, which will
be explained hereafter.
The TextListItem
- as its name suggests -
is a ListItem
, that is able to store and
show text. Besides this it does not offer anything oustanding so
that its only improvements are to set and get text.
A TextListItem
is created by passing the
text it should store to its constructor.
listitem = TextListItem ("This is a TextListItem")
listitem.text = "I contain changed text."
The FileListItem
is an advanced
TextListItem
, which contains additional
attributes to make it suitable for storing different file
related information.
The FileListItem
requires besides a text
the file mode information (those are explained in detail in the
stat documentation).
# Retrieve the stats for a file. st = os.stat ("myfile") # Create the item with the file modes. listitem = ("myfile", st.st_mode)
Dependant on the passed file mode, which will be stored in the filetype attribute,
if stat.S_ISDIR (listitem.filetype): print "Item %s is a directory, hooray!" % listitem.text
FileListItem
will set load a matching
icon into its icon attribute (at
construction time). This icon will be taken from the
Icons16x16
icon constants of the
ocempgui.widgets.images
submodule by
default.
The ListItemCollection
is a wrapper
around the python builtin type list. It
supports a set of most important list operations, such as
iterators, slicing and sorting. Several methods, especially the
arithmetic overloads (addition, mulitplication, etc.) are not
available. For a complete list of supported operations, browse
the documentation strings of the
ListItemCollection
.
The ListItemCollection
is created by
simply invoking its constructor.
collection = ListItemCollection ()
insert()
,
append()
or
extend()
methods for example.
collection.append (item1) collection.insert (9, item2)
The specialities of the
ListItemCollection
are, that it only
accepts ListItem
objects as valid items
on the one hand and supports two notification slots on the
other. Those notification slots are implemented as attributes,
item_changed and
list_changed. Any method or function can be
bound to those to get informed about item changes in the list
(e.g. an item has changed its contents) and list changes in
general (items were added, removed, etc.). The
item_changed attribute additionally passes
the affected item to the notification handler, while
list_changed() will pass the
ListItemCollection
.
You can find the following example as a python script under
examples/listitemcollection.py
.
# ListItemCollection example. from ocempgui.widgets.components import ListItemCollection, TextListItem # Item change handler. def item_has_changed (item): print "Item '%s' has changed" % item.text # List change handler. def list_has_changed (l): print "List now contains %d item(s)" % l.length collection = ListItemCollection () # Set up a notification handler for item changes. collection.item_changed = item_has_changed # Set up a notification handler for list changes collection.list_changed = list_has_changed for i in xrange (5): collection.append (TextListItem ("Item no. %d" % i)) collection[2].text = "New text in item 3" # Use a tuple as constructor argument. items = (TextListItem ("String 1"), TextListItem ("String 2"), TextListItem ("String 3")) collection = ListItemCollection (items) print "New collection:" print collection
Example 47. ListItemCollection example
Sometimes it is necessary to create an own, specialized widget type, that matches your exact needs. The following section will give you an rough overview about what you have to keep in mind and about what you have to take care, when creating custom widgets. You are encouraged to browse the existing widget sourcecode in order to see, how the one or other is implemented.
In this section, you will create an own widget, that lets the user play the famous game Tic Tac Toe and is able to deal with a custom SIG_TICTACTOE event.
First of all you should make yourself a picture about how the widget should look like and what it should be able to do. In our case, this would be an area consisting of 3x3 squares, of which each is clickable exactly one time and should fill - dependant on whose turn it is - the square with either a cross or a circle. When the game is ended, the widget should note that by sending a corresponding event, so that other widgets can update themselves according to that.
After you know about how it should look like and what it should be able to do, its time to collect the things you need. Whenever it is possible, you should use existing code or classes, so that you do not need to reinvent anything.
Luckily our example can be completed by using a
Table
widget with 3 rows and 3 columns
and nine ImageButton
widgets, that can
display the circle or cross. The only things, that do not exist
are the signal and graphics for a circle and cross.
We start by creating a custom class, that inherits directly from
the Table
.
from ocempgui.draw import Image from ocempgui.widgets import Table from ocempgui.widgets.Constants import * class TicTacToe (Table): """The famous game as widget. """ def __init__ (self): Table.__init__ (self, 3, 3)
ImageButton
widgets into the table. We
will size them to 60x60 px, so that they will not resize
later, when we set their images, which have a size of 48x48 px.
class TicTacToe (Table): ... def __init__ (self): Table.__init__ (self, 3, 3) for i in xrange (3): for j in xrange (3): button = ImageButton ("") button.minsize = 60, 60 self.add_child (i, j, button)
class TicTacToe (Table): ... def __init__ (self): Table.__init__ (self, 3, 3) self._curplayer = "Player 1" for i in xrange (3): for j in xrange (3): button = ImageButton ("") button.minsize = 60, 60 self.add_child (i, j, button) button.connect_signal (SIG_CLICKED, self._clicked, button, i, j)
_clicked()
method, which we now
implement:
class TicTacToe (Table): ... def __init__ (self): ... def _clicked (self, button, i, j): """Sets the image of the button, if not already done. """ button = self.grid[(i, j)] # Check, if it is not already set. if button.picture == None: if self._curplayer == "Player 1": # Use the cross. button.picture = "cross.png" self._curplayer = "Player 2" else: button.picture = "circle.png" self._curplayer = "Player 1"
The widget is fairly usable now, but still lacks the event mechanism, which will be explained in the next section.
To allow a better interactivity with the TicTacToe widget, it will invoke its event handlers, whenever a player clicked on a square. Dependant on what happens, the event mechanism should indicate this. Thus it will have to pass additional data to the event handlers. We want it to send the following data:
TICTACTOE_VALIDSQUARE, if the player clicked on an unoccupied button,
TICTACTOE_INVALIDSQUARE, if the player clicked on an occupied button,
TICTACTOE_WIN, if the one of the players wins. Further input should not be allowed afterwards.
Implementing the first and second event is easy and we only have to add four lines of code to our existing class. The first line will define the SIG_TICTACTOE signal, the next three lines our signal data we want to send.
from ocempgui.widgets import Table from ocempgui.widgets.Constants import * SIG_TICTACTOE = "tictactoe" TICTACTOE_WIN = 1 TICTACTOE_VALIDSQUARE = 0 TICTACTOE_INVALIDSQUARE = -1 class TicTacToe (Table): """The famous game as widget. """ ...
class TicTacToe (Table): """The famous game as widget. """ def __init__ (self): ... self._signals[SIG_TICTACTOE] = []
class TicTacToe (Table): ... def _clicked (self, button, i, j): """Sets the image of the button, if not already done. """ button = self.grid[(i, j)] # Check, if it is not already set. if button.picture == None: if self._curplayer == "Player 1": button.picture = "cross.png" self._curplayer = "Player 2" else: button.picture = "circle.png" self._curplayer = "Player 1" self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_VALIDSQUARE) else: self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_INVALIDSQUARE)
The third event handler requires a bit more work as we have to check, if there are three buttons displaying the same picture in a row. The most simple (and unoptimized) algorithm for that would be:
Check all columns for the first, second and third line.
Check all lines for the first, second and third column.
Check diagonal the squares located at (0, 0), (1, 1) and (2, 2).
Check diagonal the squares located at (0, 2), (1, 1) and (2, 0).
class TicTacToe (Table): ... def _check_input (self): """Checks for three in a row. """ three = False image = None # Check the columns for i in xrange (3): if three: break image = self.grid[(i, 0)].path if image: three = (self.grid[(i, 1)].path == image) and \ (self.grid[(i, 2)].path == image) if not three: # Check the rows. for i in xrange (3): if three: break image = self.grid[(0, i)].path if image: three = (self.grid[(1, i)].path == image) and \ (self.grid[(2, i)].path == image) if not three: # Diagonal left to right image = self.grid[(0, 0)].path if image: three = (self.grid[(1, 1)].path == image) and \ (self.grid[(2, 2)].path == image) if not three: # Diagonal right to left image = self.grid[(2, 0)].path if image: three = (self.grid[(1, 1)].path == image) and \ (self.grid[(0, 2)].path == image) if three: self._finished = True self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_WIN)
class TicTacToe (Table): """The famous game as widget. """ def __init__ (self): ... self._finished = False
def _clicked (self, button, i, j): """Sets the image of the button, if not already done. """ if self._finished: return button = self.grid[(i, j)] # Check, if it is not already set. if button.picture == None: if self._curplayer == "Player 1": # Use the cross. button.picture = "cross.png" else: button.picture = "circle.png" self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_VALIDSQUARE) self._check_input () # Set it after the check, so we can get the correct player name. if self._curplayer == "Player 1": self._curplayer = "Player 2" else: self._curplayer = "Player 1" else: self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_INVALIDSQUARE)
As you see, switching the current player is done right after running signal handler and input check now, so that clicks onto an already occupied buttons do not cause a player change.
So far we are done. You can play a basic Tic Tac Toe game with your newly created widget and the widget can react upon the user input. It is however neither very usable nor nice to look at. All those things will be explained in the next section, but first let us look at the complete code.
Note that an additional curplayer attribute is at the bottom of the code, so that it is possible to find out, who's turn it is.
You can find the code as a python script under
examples/tictactoe/TicTacToeSimple.py
.
There is also a small starting test script called
tictactosimple.py
as well as the both
needed graphics.
from ocempgui.widgets import * from ocempgui.widgets.Constants import * SIG_TICTACTOE = "tictactoe" TICTACTOE_WIN = 1 TICTACTOE_VALIDSQUARE = 0 TICTACTOE_INVALIDSQUARE = -1 class TicTacToe (Table): """The famous game as widget. """ def __init__ (self): Table.__init__ (self, 3, 3) self._curplayer = "Player 1" self._finished = False for i in xrange (3): for j in xrange (3): button = ImageButton ("") button.minsize = 60, 60 self.add_child (i, j, button) button.connect_signal (SIG_CLICKED, self._clicked, button, i, j) self._signals[SIG_TICTACTOE] = [] def _clicked (self, button, i, j): """Sets the image of the button, if not already done. """ if self._finished: return button = self.grid[(i, j)] # Check, if it is not already set. if button.picture == None: if self._curplayer == "Player 1": # Use the cross. button.picture = "cross.png" else: button.picture = "circle.png" self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_VALIDSQUARE) self._check_input () # Set it after the check, so we can get the correct player name. if self._curplayer == "Player 1": self._curplayer = "Player 2" else: self._curplayer = "Player 1" else: self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_INVALIDSQUARE) def _check_input (self): """Checks for three in a row. """ three = False image = None # Check the columns for i in xrange (3): if three: break image = self.grid[(i, 0)].path if image: three = (self.grid[(i, 1)].path == image) and \ (self.grid[(i, 2)].path == image) if not three: # Check the rows. for i in xrange (3): if three: break image = self.grid[(0, i)].path if image: three = (self.grid[(1, i)].path == image) and \ (self.grid[(2, i)].path == image) if not three: # Diagonal left to right image = self.grid[(0, 0)].path if image: three = (self.grid[(1, 1)].path == image) and \ (self.grid[(2, 2)].path == image) if not three: # Diagonal right to left image = self.grid[(2, 0)].path if image: three = (self.grid[(1, 1)].path == image) and \ (self.grid[(0, 2)].path == image) if three: self._finished = True self.run_signal_handlers (SIG_TICTACTOE, TICTACTOE_WIN) curplayer = property (lambda self: self._curplayer)
Example 48. Simple TicTacToe widget
The ocepgui.widgets
module offers two
possibilities to change the provided widget appearance. This
section covers the necessary tasks, which have to be performed to
change create an own look and feel to match your personal needs.
The first part will give you a rough overview about how the
styling and drawing system of the
ocempgui.widgets
module words, while the
second will explain the style files and syntax, that deal with
basic attributes such as background colors or fonts. The last part
then gives an in-depth explanation, how to provide own drawing
methods for a widget to spend it a completely different look
and feel (such as rounded borders for buttons for example).
The rendering engine contains several methods to draw each widget and some generalized methods for some commonly used parts such as the basic widget surface. The widget to draw is passed to its matching drawing method, which will look up its style settings and then draw it.
The Style
supports cascading the widget
style settings. This means, that it will, when it retrieves the
widget style and does not find the style setting it needs, walk
through the inherited classes (the __mro__
attribute) of the widget until it finds the setting. The last
instance it looks the setting up in is the
"default" entry of its
styles attribute.
Theme files for OcempGUI define the most basic settings for the widgets, such as the background color or the font to use. They however do not cause the widget to look completely different. The basic widget layout will stay the same.
The theme files are read by the currently active
Style
class (which is set in
base.GlobalStyle
) and their information
will be stored in its styles dictionary.
The currently active theme information for a specific widget
class are stored in the styles attribute by
using its lowercase classname. This means that the specific
theme for a Button
widget is stored in
Style.styles["button"]
. For a
Label
widget it would be
Style.styles["label"]
and so on.
Such a theme entry is a dictionary, that contains various
information about the background and foreground color for the
different supported widget states, the font settings for that
widget and more. The complete list of possible settings can be
found in the documentation of the Style
,
so it will not be covered in detail here.
Setting up individual styles should be done using an own file for them, so you can easily change them without touching your program code. Let us create a separate theme file, that will cause the buttons to use a blue background color and the labels a different font and foreground color.
After creating the file for that (the author recommends using
the suffix ".rc" for theme files), we use
the python syntax for the contents. First we have to let the
file and its content know about the supported widget states,
thus we will import the Constants
of the
ocempgui.widgets
module.
from ocempgui.widgets import Constants
Do not use any from ... import *
within the theme
files to avoid a bloated Style
class.
The theme loader give you much freedom including the
possibility to make many mistakes. You also should not import
anything except the Constants
module
into the theme file, too.
Now we will set up our theme dictionary for the
Button
widget.
from ocempgui.widgets import Constants button = { "bgcolor" : { Constants.STATE_NORMAL : (18, 12, 135), Constants.STATE_ENTERED : (32, 25, 164), Constants.STATE_ACTIVE : (18, 12, 135), Constants.STATE_INSENSITIVE : (54, 51, 106) } }
Button
to
different types of a dark blue color. It also will affect any
inherited widget, that does not
implement and own style explicitly,
define the style setting in its own style.
You can easily see this by placing a
Button
and a
CheckButton
side by side.
Let us now set the styles for the Label
widget. The first part sets up the font, its size and if
antialiasing should be used, the second one sets up different
colors to use for the text according to the widget its state.
from ocempgui.widgets import Constants ... label = { "font" : { "name" : "Helvetica", "size" : 16, "alias" : True }, "fgcolor" : { Constants.STATE_NORMAL : (250, 250, 250), Constants.STATE_ENTERED : (150, 150, 150), Constants.STATE_ACTIVE : (0, 0, 0), Constants.STATE_INSENSITIVE : (235, 235, 235) } }
Typing in those Constants...
entries can
become a lot of work, especially with complex themes. Thus
OcempGUI allows you to define variables to ease your life. Any
entry, that is prefixed with an underscore,
"_", will be handled as variable, ignored
by the loading mechanisms and thus does not go into the
style dictionary.
With this in mind, let's substitute those lengthy constants with some variables.
from ocempgui.widgets import Constants # Variables, that will be used to ease the life. _normal = Constants.STATE_NORMAL _entered = Constants.STATE_ENTERED _active = Constants.STATE_ACTIVE _insensitive = Constants.STATE_INSENSITIVE button = { "bgcolor" : { _normal : (18, 12, 135), _entered : (32, 25, 164), _active : (18, 12, 135), _insensitive : (54, 51, 106) } } ...
_red = (255, 0, 0) __myfont = "path/to/font.ttf" ____________a_pretty_long_variable_that_is_completely_useless = None
As you see, setting up own themes for widgets is not a complex task. Let us now examine the complete code and a small example, that will make use of the just created theme.
You can find the code and theme as python scripts under
examples/theme_example.rc
and
examples/theme.py
.
from ocempgui.widgets import Constants # Variables, that will be used to ease the life. _normal = Constants.STATE_NORMAL _entered = Constants.STATE_ENTERED _active = Constants.STATE_ACTIVE _insensitive = Constants.STATE_INSENSITIVE button = { "bgcolor" : { _normal : (18, 12, 135), _entered : (32, 25, 164), _active : (18, 12, 135), _insensitive : (54, 51, 106) } } label = { "font" : { "name" : "Helvetica", "size" : 16, "alias" : True }, "fgcolor" : { _normal : (250, 250, 250), _entered : (150, 150, 150), _active : (0, 0, 0), _insensitive : (235, 235, 235) } }
# Theme usage example. from ocempgui.widgets import base, Renderer, Button, Label # Load the theme. base.GlobalStyle.load ("theme_example.rc") # Create screen. re = Renderer () re.create_screen (200, 100) re.title = "Theme example" re.color = (250, 250, 250) # Create widgets. button = Button ("Button") button.topleft = 5, 5 label = Label ("Label") label.topleft = 100, 5 re.add_widget (button, label) re.start ()
Example 49. Customizing the themes
Whenever you need an completely different layout, the creation of own theme files will not be enough. This section explains how to modify and/or override the drawing methods for the widgets.
First you should make yourself clear about if you need to provide an own layout only for specific instances of a widget or if those widgets always should be drawn that way. Dependant on your needs, you have several possibilites.
Subclass the widget and override its
draw()
and
draw_bg()
methods.
Bind the draw()
and
draw_bg()
methods of the instance
to your own methods or functions at runtime.
Provide an own drawing method for the widget type and bind it to the set drawing engine.
Implement an own drawing engine and bind it to the
Style
instance.
The first both entries should be self-explanatory. The only
thing you have to keep in mind when overriding the widget's
drawing methods are, that you must invoke
at least the BaseWidget.draw()
method.
This method updates the internals of the widget after drawing
the background, does surface conversions and more, so it is an
absolutely necessary method to be invoked.
Of course you can also invoke the parent's
draw()
method, if you (just) want to
add something to the widget's look instead of implementing all
drawing yourself.
class OwnButton (Button): ... def draw_bg (self): ... # Own background drawing implementation. def draw (self): # We want the drawing routines of the Button to be executed and # just add something to them. Button.draw (self) ... class OwnButton2 (Button): ... def draw_bg (self): ... # Own drawing implementation. def draw (self): # Do not process any drawing of the Button, but use the BaseWidget # directly to update the widget internals, because we do anything # ourselves BaseWidget.draw (self) ...
So far for the first both entries of the above list. Now let's look at manipulating the drawing engine by overriding it (binding a new method should not be any problem for you, not?).
We will create an own drawing engine subclass using the provided
DefaultEngine
and use an own drawing
method for Label
widgets, which adds a
dropshadow to them by default. After creating this subclass we
will use an instance of it for our own small test application.
The first we have to do is to inherit from the
DefaultEngine
subclass. The class is not
located in the module directly, but installed as additional data
and can be imported with help of the
DEFAULTDATADIR constant.
# Drawing engine usage example. from ocempgui.widgets import * from ocempgui.widgets.Constants import DEFAULTDATADIR # Get the path where the theme engines are usually installed. import sys sys.path.append (DEFAULTDATADIR) from themes import default # Override the DefaultEngine with its drawing definitions so that we can # implement our own draw_label() method. class DropShadowEngine (default.DefaultEngine): def __init__ (self, style): default.DefaultEngine.__init__ (self, style)
DefaultEngine
class contains. To provide
our own drawing method for the Label
, we
have to override the matching method, which is
draw_label()
.
As you can see from its signature, it receives one argument, the
Label
object, for which the surface
should be created.
All widget drawing methods follow the same naming scheme, which is draw_WIDGETTYPE (self, widget) with widgettype being the all-lowercase classname.
def draw_label (self, label): """Overrides the label drawing method and adds a dropshadow.""" # We do not care about multiline labels. Thus pass those to the # parent. if label.multiline: return DefaultEngine.draw_label (self, label)
Now we shorten some attributes, which we will need in our method more often.
def draw_label (self, label): ... cls = label.__class__ state = label.state # Peek the style of the label so we can get the colors later. st = label.style or self.create_style (cls)
DefaultEngine
class to look up the
theme information of the widget. The st
variable will be used to get and set the matching color for the
dropshadow of our Label
.
Now we have to get the foreground color to use for the text and
store it temporarily as we will modify it in the
st
variable later.
def draw_label (self, label): ... # Save the label color temporarily as we will change it for the # dropshadow. Because we are using references, not plain style # copies, we have to do this. fgcolor = self.get_style_entry (cls, st, "fgcolor", state) # The 2px added here are used for the dropshadow. width = 2 * label.padding + 2 height = 2 * label.padding + 2
Label
surface will occupy. Because the
Label
offers a
padding attribute, we have to take it into
account for the surface size.
The basics are set up, so that we can proceed with drawing the surfaces for our widget.
def draw_label (self, label): ... if label.mnemonic[0] != -1: front = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) # Swap colors. st["fgcolor"] = { state : (100, 100, 100) } drop = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, st) # Swap colors. st["fgcolor"] = { state : (100, 100, 100) } drop = self.draw_string (label.text, state, cls, st)
Label
and then use either the mnemonic
string drawing method or the simple string drawing method. We do
that twice, for the normal text and its dropshadow. In line
eleven and nineteen we swap the foreground color of the
st
variable with a bright grey one.
So we have our both surfaces now and are nearly done. But we
have to respect some definitions of the
BaseWidget
and we have to reset the
swapped colors.
def draw_label (self, label): ... # Surface creation done. Restore the colors. st["fgcolor"][state] = fgcolor # Get the size of the surface(s) and add it to the complete # width and height, the label will occupy. rect = front.get_rect () width += rect.width height += rect.height # Guarantee size. width, height = label.check_sizes (width, height)
def draw_label (self, label): ... # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, st) surface.blit (drop, (label.padding + 2, label.padding + 2)) surface.blit (front, (label.padding, label.padding)) return surface
So far so good. We now have our own drawing engine, that will
draw Label
widgets with a dropshadow by
default. The very last thing to do is to make the
omcepgui.widgets
module use our own class
instead of the default. This is done by assigning the
base.GlobalStyle.engine attributes an
instance of our own class in the code. The drawing methods will
then make use of it.
# Set out own drawing engine in the Style class. base.GlobalStyle.engine = DropShadowEngine (base.GlobalStyle)
Label
widgets will use the just
created draw_label()
method.
The complete code of the previous example follows. It includes some test code, so you can easily evaluate the results.
You can find the code as python script under
examples/drawing_engine.py
.
# Drawing engine usage example. from ocempgui.widgets import * from ocempgui.widgets.Constants import DEFAULTDATADIR # Get the path where the theme engines are usually installed. import sys sys.path.append (DEFAULTDATADIR) from themes import default # Override the DefaultEngine with its drawing definitions so that we can # implement our own draw_label() method. class DropShadowEngine (default.DefaultEngine): def __init__ (self, style): default.DefaultEngine.__init__ (self, style) def draw_label (self, label): """Overrides the label drawing method and adds a dropshadow.""" # We do not care about multiline labels. Thus pass those to the # parent. if label.multiline: return DefaultEngine.draw_label (self, label) cls = label.__class__ state = label.state # Peek the style of the label so we can get the colors later. st = label.style or self.style.get_style (cls) # Save the label colour temporarily as we will change it for the # dropshadow. Because we are using references, not plain style # copies, we have to do this. fgcolor = self.style.get_style_entry (cls, st, "fgcolor", state) # The 2px added here are used for the dropshadow. width = 2 * label.padding + 2 height = 2 * label.padding + 2 # Backup the color. tmp = None if st.has_key ("fgcolor"): # Is fgcolor defined? tmp = st["fgcolor"] # Draw the text. front = None drop = None if label.mnemonic[0] != -1: front = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) # Swap colors. st["fgcolor"] = WidgetStyle ({ state : (100, 100, 100) }) drop = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, st) # Swap colors. st["fgcolor"] = WidgetStyle ({ state : (100, 100, 100) }) drop = self.draw_string (label.text, state, cls, st) # Surface creation done. Restore the colors. if not tmp: del st["fgcolor"] else: st["fgcolor"] = tmp # Get the size of the surface(s) and add it to the complete # width and height, the label will occupy. rect = front.get_rect () width += rect.width height += rect.height # Guarantee size. width, height = label.check_sizes (width, height) # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, st) surface.blit (drop, (label.padding + 2, label.padding + 2)) surface.blit (front, (label.padding, label.padding)) return surface if __name__ == "__main__": re = Renderer () re.create_screen (200, 200) re.title = "Style example." re.color = (234, 228, 223) # Set out own drawing engine in the Style class. base.GlobalStyle.engine = DropShadowEngine (base.GlobalStyle) label = Label ("Example label") label.create_style ()["font"]["size"] = 30 label.topleft = 10, 10 label2 = Label ("#Mnemonic") label2.create_style ()["font"]["size"] = 30 label2.topleft = 10, 60 button = CheckButton ("Dropshadow") button.topleft = 10, 100 re.add_widget (label, label2, button) re.start ()
Example 50. Customizing the widget drawing routines
The Renderer
class supports z-axis
layering by drawing and keeping widgets on top of others as
shown in the figure beneath. Layers are set up automatically,
whenever a widget with a certain depth does not match the
existing layers. Thus you usually will not have to deal with the
layer internals of the Renderer
class,
but instead adjust the layers using the different
widgets. Sometimes it might be necessary to manually adjust
portions of the layer behaviour, especially if layers should be
activated automatically on certain actions.
The currently active layer, which has the input focus, can be
get and set using the active_layer
attribute or set_active_layer()
method.
layer = renderer.active_layer renderer.set_active_layer (2)
an list containing the indices of the attached widgets for keyboard navigation,
a list containing the widgets attached to the layer,
an EventManager
subclass, which
deals with the events sent to the layer.
You should not modify the contents nor the objects directly but
instead use the respective Renderer
methods instead.
The set_active_layer()
methods accepts
both, a tuple as returned by active_layer
as well as an integer, which denotes the layer to activate
(similarily to the depth attribute of the
BaseWidget
class).
Both, the method and attribute, allow you to manipulate the active layer directly, so that you can enforce the focus on a certain dialog window for example.
It is also possible to cycle through the layers
programmatically,using the
switch_layer()
method. The figure
beaneath shows, what happens on each invocation.
renderer.switch_layer () # Step 1 renderer.switch_layer () # Step 2 renderer.switch_layer () # Step 3
To enhance the accessibility of widgets on the screen, the
Renderer
implements the
IIndexable
interface class to let the
user navigate through widgets using the keyboard. Each installed
layer manages its own indexing list, so that the navigation by
default always happens on the currently active layer.
The Renderer
determines the order,
widgets are navigated through, using their
index attribute. The invocation of
switch_index()
will set the keyboard
focus to the widget with the next higher
index value. If the last widget is reached,
the first widget will receive the focus again as shown in the
figure below.
Below you will find the Widget hierarchy of the
ocempgui.widget
module.
BaseObject +-ActionListener `-BaseWidget +-Label +-ImageLabel +-Bin | +-Alignment | +-ButtonBase | | +-Button | | +-ImageButton | | +-ToggleButton | | +-CheckButton | | `-RadioButton | | | +-ScrolledWindow | | `-ScrolledList | | `-FileList | | | +-ViewPort | | `-ListViewPort | | | `-Window | `-DialogWindow | `-GenericDialog | `-FileDialog +-Container | +-Box | +-Frame | | +-HFrame | | `-VFrame | `-Table | +-Diagram | `-Graph2D | +-Editable | `-Entry | +-Range | +-Scale | | +-HScale | | `-VScale | | | `-ScrollBar | +-HscrolBar | `-VScrollBar | +-ImageMap +-ProgressBar +-StatusBar `-TooltipWindow
The following section will describe the most notably changes between the version series 0.1.x and 0.2.x, which might break the compatibility of applications. If you plan to migrate your OcempGUI application to 0.2.x, read this section carefully.
The AccessibleContext
class was
completely removed. Because it had no practical use, this should
not affect anyone.
The Accessible
class was renamed to
IAccessible
. Make sure, that you replace
any occurance of it with the new name.
The Indexable
class was renamed to
IIndexable
. Make sure, that you replace
any occurance of it with the new name.
The EventManager
class expects
INotifyable
objects for addition to its
queues. Although it does not force objects to inherit from
INotifyable
, it will print out a warning,
if an object to add does not inherit from this class. Newly
created classes thus should inherit from INotifyable, if they do
not already inherit from it (by e.g. inheriting from
BaseObject
).
The eventgrabber attribute of the
EventManager
was renamed to
event_grabber
. Make sure, that you
replace all occurances of it accordingly, because there is no
compatibility support.
The EventManager
is a full class by now
and does not implement __slots__ anymore.
If your classes inherit from it, make sure, that they deal with
that change correctly.
Widgets are not placed using absolute coordinates
anymore. Instead they are placed relative to their parent
coordinates, which applies especially to boxed widgets being
put in Bins
or
Containers
. Their position attributes
such as top,
topleft, rect,
... thus refer to their relative
position. To retrieve the absolute coordinates, you should use
their rect_to_client()
method.
The position attribute and
set_position
method of widgets are
deprecated and will be removed completely in a later
version. The new topleft attribute will
replace them. Make sure to change your code accordingly over
time.
The size attribute of widgets is no
longer writeable and its function was replaced by the new
minsize attribute. The
set_size()
method however is still
available but deprecated. Make sure, that your code respects
that change.
The eventarea attribute and
set_event_area()
method of widgets
are deprecated, do not have any functionality anymore and
will be removed completely in a later version. The new method
rect_to_client()
provides a read-only
replacement for them in a slightly different intention.
The drawing code and behaviour heavily changed in 0.2.x, so
that the widget method draw_bg()
now
will take care of drawing a widget background (usually
invoking Style.engine.draw_WIDGET()
),
while draw()
will do the rest. This
should only affect user-defined widgets or inheritors, which
override the default drawing code. For more details see the section called “Changing the widget appearance”.
Most Button
widget implementations now
inherit from the newly added ButtonBase
class. This should not harm the most code, but inheritance
checks might have to change accordingly. Make sure to recheck
your code.
The ScrolledWindow
class now
encapsulates widgets using the ViewPort
class. Code that relies upon the child
attribute of it, should be changed accordingly.
The get_visible_area()
method of the
ScrolledWindow
class is no longer
publicly available.
The get_scrollable_area()
if the
ScrolledWindow
class was removed
without a replacement.
The get_coords_from_value()
and
get_value_from_coords()
methods of
the Scale
and
ScrollBar
classes are no longer
publicly available.
The get_slider_size()
and
get_button_coords()
methods of the
ScrollBar
classes are no longer
publicly available.
The RenderLayer
class was removed
without a replacement.
The Renderer
class does not directly
inherit from the EventManager
and
RenderLayer
classes anymore. A
replacement for the EventManager
functionality is the new managers
attribute. Code, that added user-defined objects to the
Renderer
should use the attribute and
its layered behaviour from now on.
The force_update()
method of the
Renderer
class was removed. User code
should use the update()
and/or
refresh()
methods now.
The colour for flat borders (BORDER_FLAT) is no longer retrieved using the 'fgcolor' entry, but instead uses the new 'bordercolor' style entry. Code, that made use of this should be changed to use 'bordercolor' from now on.
The Style
no longer directly defines
drawing routines. Instead it uses the drawing engine bound to
its engine attribute. User code, that
made use of this, has to be changed accordingly. See the section called “Changing the widget appearance” for more details.
Application programming interface
A library, which provides layers to accessibility solutions such as alternate input devices or online keyboards.
A revision control system.
Graphical user interface.
A set of python modules for writing games, based on the SDL.
Python accessibility programming interface - a wrapper around the ATK toolkit of the GNOME accessibility project, shipped with OcempGUI.
A cross-platform scripting language.
See Simple DirectMedia Layer .
A cross-platform multimedia library.
A user interface element on a screen, with which the user can interact in a passive or active way.