The Python code
The complete code is at the end of this document. It must be saved in the
same location of the color.glade file.
A D V E R T I S E M E N T
Including the necessary modules
from math import cos, sin, pi
from whrandom import randint
from gtk import *
from gnome.ui import *
from GDK import *
from libglade import *
|
From the modules math and whrandom, we include non Gnome
specific functions as cos, sin, randint as well the
pi constant. The gnome specific modules are gtk, GDK
and gnome.ui. In C, including the gnome.h includes all the
Gnome headers. In Pyhton, you first need to figure out in which module the
binding for the Gnome function that you want to use is located . For example,
you can search from a terminal window (shell) for the module containing the
string "canvas" with the following command:
cd /usr/local/lib/python1.5/site-packages/gnome
grep canvas *.py
|
The above assumes that the Gnome binding was installed in /usr/local/lib/python1.5/site-packages.
Loading the interface with Libglade
In this Python example, we use the Gnome Canvas to manipulate shapes -
actually stars, circles and squares. A canvas is a placeholder for graphic items
(ellipse, point, line, rectangle), text items and even widgets. In fact, a
canvas can contain several canvas groups. Finally, in a canvas group one can
place canvas items - our shapes. By default a canvas contains one default canvas
group, called root canvas group, we will use this one to place our shapes.
First, some global variables are defined:
- canvas: to hold reference of the canvas, this is a
GnomeCanvas object;
- rootGroup: to hold reference of the root canvas group, this is
a GnomeCanvasGroup object;
- selectedItem: to hold reference of a selected shape (star,
circle or square). These shapes are in fact GnomeCanvasItem
objects.
- colorShape: this list contains reference to shapes (i.e.
GnomeCanvasItem)
The first function to be called - initColor - builds the widgets
from the color.glade file and auto-connects the handlers to the
widgets:
def initColor ():
global rootGroup, canvas
wTree = GladeXML ("color.glade",
"colorApp")
dic = {"on_about_activate": on_about_activate,
"on_exit1_activate": mainquit,
"on_new_activate":on_new_activate}
wTree.signal_autoconnect (dic)
canvas = wTree.get_widget ("canvas")
rootGroup = canvas.root ()
|
Building the widget is done with the GladeXML function. Of
course you need to adjust the path to the color.glade file. This
function builds up and shows up the colorApp Gnome Application Window
we defined with Glade. It returns an object - really a class - with useful
methods.
Next we connect the handlers we have defined in Python - more on that latter
- to the widgets defined in the color.glade file. For that, we need to
build a dictionary which holds keys for the handler names defined in the
color.glade file: on_about_activate, on_exit1_activate
and on_new_activate. The associated values to this keys are the
function names defined in Python.
Finally, the signal_autoconnect method does the rest of the job for us.
Last we fetch the reference of the canvas constructed during the GladeXML
call - a GnomeCanvas object in Python - and the root canvas group - a
GnomeCanvasGroup object.
Useful tips
There is no actual reference manual covering the Gnome binding for Python.
However there is a lot of documentation about Gnome programming in C available
from the Gnome web site. Looking at this documentation can be useful but you
will also need to look a bit inside the Gnome binding for Python to exploit it:
The binding is located at /usr/local/lib/python1.5/site-packages/gnome/
or /usr/lib/python1.5/site-packages/gnome/
Looking in the binding shows several things:
- in the libglade.py binding:
- GladeXML is in fact an object, so we were just calling the
__init__ constructor. Its first argument is filename
as expected - the self is an automatic argument representing a
reference to the current object. The second argument root is
the top level widget name from where we want to build the GUI. This
method then calls a C function glade_xml_new_with_domain. You
can now look at the C documentation of LibGlade to get more information
about the glade_xml_new_with_domain function.
- In the GladeXML object, there is a get_widget
method. This method just call the glade_xml_get_widget C
function.
So when Python runs canvas = wTree.get_widget ("canvas"), it
calls the glade_xml_get_widget C function.
- In the gnome/ui.py binding:
- The GnomeCanvas object, this class has the root
method we use in our Python code. It is connected to the
gnome_canvas_root C function.
For each Gnome use in Python we can do the same to get the related
documentation. I let you read the related Gnome documentation to learn more
about these functions.
Defining the handlers
There are three handlers to auto-connect to the GUI. There are
on_about_activate, on_new_activate and mainquit. The last
one is in fact a Python function to stop and exit from Python.
def on_about_activate(obj):
"display the about dialog"
about = GladeXML ("color.glade", "about").get_widget ("about")
about.show ()
|
This handler opens the about dialog. We first fetch a reference of the
about dialog - in fact LibGlade build it through the GladeXML
object. Remember GladeXML is a Python object with a method - among
other - called get_widget. This method returns a GtkWidget
object which contains the show method.
Tips
Look for the GtkWidget object in the gtk.py binding. You
can see that this object has a show method. The previous handler body
can be written as:
GladeXML("color.glade","about").get_widget("about").show().
def on_new_activate (obj):
global rootGroup, colorShape
for item in colorShape:
item.destroy ()
del colorShape[0:]
buildGameArea (rootGroup)
|
This handler rebuilds a new game area. The existing shapes are first
destroyed. The shapes are GnomeCanvasItem objects derived from
GtkObject objects. The destroy method is located in GtkObject
object. Next a new game area is built.
The GnomeCanvasItem
Defining the shape
The buildGameArea function coordinates the creation of the game area
in the GnomeCanvasGroup group. The shapes - GnomeCanvasItem -
are built from calls to the buildShape function. The shapes can be
circle, square or star.
The shape creation is done with the following code, depending on the created
shape:
item = group.add ("ellipse", x1 = x - r, y1 = y - r,
x2 = x + r, y2 = y + r, fill_color = color,
outline_color = "black", width_units = 2.5)
[...]
item = group.add ("rect", x1 = x - a, y1 = y - a,
x2 = x + a, y2 = y + a, fill_color = color,
outline_color = "black", width_units = 2.5)
[...]
item = group.add ("polygon", points = pts, fill_color = color,
outline_color = "black", width_units = 2.5)
|
The variable group holds a reference to a GnomeCanvasGroup
object. If we look in the ui.py binding, the GnomeCanvasGroup
has an add method. Its first argument, tp expects a string
containing the item type to add. Its next arguments are pairs of keyword
arguments and values, they are matched against a dictionary. To get the full
list of available keywords, look in the GnomeCanvasRect,
GnomeCanvasEllipse and GnomeCanvasPolygon objects in ui.py.
The ellipse and rectangle are quite similar, the two
abscissa and ordinate coordinates define two opposite top points of their
bounding box, respectively top-left and bottom-right. The
origin of the canvas is by default located at the top-left of the
canvas. The polygon expects as value of the points keyword, a
list of pair coordinates defining the points of the polygon. The other arguments
are quite evident to understand.
Attaching the event to the shape
Now, we connect an event to each shape we create. This is done at the end of
the buildShape function:
item.connect ('event', shapeEvent)
colorShape.append (item)
|
We just use the connect method of the GtkObject which is an
ancestor object of GnomeCanvasItem. Its first argument is the signal.
As GnomeCanvasItem has a single signal event to cover all
types of event, we just set it to event. The second argument is the
handler name we wrote, here shapeEvent. Eventually we can pass data in
a third argument, but we don't need. That's all!
The shape event
Now the creation of the handler for the shapes:
def shapeEvent (item, event):
global selectedItem, itemToSelect, colorShape
if event.type == ENTER_NOTIFY and selectedItem != item:
#highligh outline
item.set(outline_color = 'white')
elif event.type == LEAVE_NOTIFY and selectedItem != item:
#unlight outline
item.set(outline_color = 'black')
elif event.type == BUTTON_PRESS:
#select the item
if not selectedItem:
item.set (outline_color = 'white')
selectedItem = item
elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \
and item != selectedItem:
#destroy both item
item.destroy ()
selectedItem.destroy ()
colorShape.remove (item)
colorShape.remove (selectedItem)
selectedItem, itemToSelect = None, itemToSelect - 1
if itemToSelect == 0:
buildGameArea (rootGroup)
return 1
|
When this handler is called, the item variable contains a reference
to the shape where an event occurs, and event contains the event. In
the GdkEvent event we are only interested by three types of event:
- ENTER_NOTIFY: when the mouse pointer enters a shape - and only
if this item is not already selected - we highlight its outline in white;
- LEAVE_NOTIFY: when the mouse pointer leaves a shape - and only
if this item is not selected - we delight its outline in black;
- BUTTON_PRESS: when the user presses one of the mouse buttons
over a shape we alternatively select it, remove it or do nothing.
- If there is no item currently selected, we just select it and copy
its reference in the global variable selectedItem;
- If there is already a selected item we check if their colors match.
To do this we can get their attributes using the overload operator []
over item and selectedItem. The fill_color_gdk
attribute is defined in /usr/include/libgnomeui/gnome-canvas-rect-ellipse.h
or equivalent.
When the selectedItem and the item who raises the
event match, we destroy both using the GtkObject method
destroy. In case there is no more shape, we build another game
area.
Finally, the handler always returns TRUE (1). This means the event signal is not
propagated to other item. We don't want that because our shape never overlays.
Final word
I've left out all Python code not relevant for Gnome, it should not be that
difficult to understand it. My main objective in this simple tutorial was to
show you how to figure out by yourself how the things work: looking in the Gnome
binding for Python or the Gnome C header and reading the Gnome documentation for
C programming. Of course, I also show how easy and powerful is the Gnome Canvas
and Glade/LibGlade are. From now on, there is a lot you can do by extending this
code.
Appendix: The complete source
#!/usr/bin/python
# Couleur - Teo Serie
# Copyright Hilaire Fernandes 2000
# Release under the terms of the GPL licence version 2
# You can get a copy of the license at http://www.gnu.org
#
# Select shapes with same color
#
from math import cos, sin, pi
from whrandom import randint
from gtk import *
from gnome.ui import *
from GDK import *
from libglade import *
width, itemToSelect = 400, 8
selectedItem = rootGroup = canvas = None
# to keep trace of the canvas item
colorShape =[];
def on_about_activate(obj):
"display the about dialog"
about = GladeXML ("color.glade", "about").get_widget ("about")
about.show ()
def on_new_activate (obj):
global rootGroup, colorShape
for item in colorShape:
item.destroy ()
del colorShape[0:]
buildGameArea (rootGroup)
def shapeEvent (item, event):
global selectedItem, itemToSelect, colorShape
if event.type == ENTER_NOTIFY and selectedItem != item:
#highligh outline
item.set(outline_color = 'white')
elif event.type == LEAVE_NOTIFY and selectedItem != item:
#unlight outline
item.set(outline_color = 'black')
elif event.type == BUTTON_PRESS:
#select the item
if not selectedItem:
item.set (outline_color = 'white')
selectedItem = item
elif item['fill_color_gdk'] == selectedItem['fill_color_gdk'] \
and item != selectedItem:
#destroy both item
item.destroy ()
selectedItem.destroy ()
colorShape.remove (item)
colorShape.remove (selectedItem)
selectedItem, itemToSelect = None, itemToSelect - 1
if itemToSelect == 0:
buildGameArea (rootGroup)
return 1
def buildShape (group, number, type, color):
"build a shape of 'type' and 'color'"
global colorShape
w = width / 4
x, y, r = (number % 4) * w + w / 2, (number / 4) * w + w / 2, w / 2 - 2
if type == 'circle':
item = buildCircle (group, x, y, r, color)
elif type == 'squarre':
item = buildSquare (group, x, y, r, color)
elif type == 'star':
item = buildStar (group, x, y, r, 0.4, randint (3, 15), color)
elif type == 'star2':
item = buildStar (group, x, y, r, 0.6, randint (3, 15), color)
item.connect ('event', shapeEvent)
colorShape.append (item)
def buildCircle (group, x, y, r, color):
item = group.add ("ellipse", x1 = x - r, y1 = y - r,
x2 = x + r, y2 = y + r, fill_color = color,
outline_color = "black", width_units = 2.5)
return item
def buildSquare (group, x, y, a, color):
item = group.add ("rect", x1 = x - a, y1 = y - a,
x2 = x + a, y2 = y + a, fill_color = color,
outline_color = "black", width_units = 2.5)
return item
def buildStar (group, x, y, r, k, n, color):
"k: factor to get the internal radius"
"n: number of branch"
angleCenter = 2 * pi / n
pts = []
for i in range (n):
#external points of the star
pts.append (x + r * cos (i * angleCenter))
pts.append (y + r * sin (i * angleCenter))
#internal points of the star
pts.append (x + r * k * cos (i * angleCenter + angleCenter / 2))
pts.append (y + r * k * sin (i * angleCenter + angleCenter / 2))
pts.append (pts[0])
pts.append (pts[1])
item = group.add ("polygon", points = pts, fill_color = color,
outline_color = "black", width_units = 2.5)
return item
def getEmptyCell (l, n):
"get the n-th non null element of l"
length, i = len (l), 0
while i < length:
if l[i] == 0:
n = n - 1
if n < 0:
return i
i = i + 1
return i
def buildGameArea (group):
global itemToSelect, selectedItem
itemColor = ['red', 'yellow', 'green', 'brown', 'blue', 'magenta',
'darkgreen', 'bisque1']
itemShape = ['circle', 'squarre', 'star', 'star2']
emptyCell = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
itemToSelect, i, selectedItem = 8, 15, None
for color in itemColor:
# two items of same color
n = 2
while n > 0:
cellRandom = randint (0, i)
cellNumber = getEmptyCell (emptyCell, cellRandom)
emptyCell[cellNumber] = 1
buildShape (group, cellNumber, itemShape[randint (0, 3)], color)
i, n = i - 1, n - 1
def initColor ():
global rootGroup, canvas
wTree = GladeXML ("color.glade",
"colorApp")
dic = {"on_about_activate": on_about_activate,
"on_exit1_activate": mainquit,
"on_new_activate":on_new_activate}
wTree.signal_autoconnect (dic)
canvas = wTree.get_widget ("canvas")
rootGroup = canvas.root ()
initColor ()
buildGameArea (rootGroup)
mainloop ()
|
|