[Hoofd-balk]
[Voet-balk]
Dit document is beschikbaar in: Castellano  Deutsch  English  Francais  Nederlands  Turkce  

Hilaire Fernandes
door Hilaire Fernandes
<hfernandes /at/ april.org>

Over de auteur:

Hilaire Fernandes is vice president van OFSET, een organisatie om educatieve vrije software voor het Gnome project te promoten en ontwikkelen. Hij is ook de auteur van Dr. Geo, een programma voor dynamische geometrie. Op het moment werkt hij aan Dr. Genius, een ander wiskundig programma voor onderwijsdoeleinden voor het Gnome project.



Vertaald naar het Nederlands door:
Guus Snijders <ghs /at/ linuxfocus.org>

Inhoud:

 

Gnome applicaties ontwikkelen met Python (Deel 1)

Gnome

Kort:

Deze serie artikelen is vooral bedoeld voor beginnende programmeurs op het gebied van Gnome en GNU/Linux. Als programmeertaal is Python gekozen omdat beginners hier vaak sneller mee leren werken dan met gecompileerde talen zoals C. Om dit artikel te volgen is enige basiskennis van Python nodig.


_________________ _________________ _________________

 

Benodigde tools

Om het programma te gebruiken dat in dit artikel wordt beschreven, heb je op z'n minst nodig:

Om Python-Gnome en LibGlade te installeren uit de broncode:

./configure
make
make install

doet de truuk (Meer uitleg kun je vinden op
http://www.linuxgazette.com/issue38/pollman.html ]). Controleer ook of de Python omgevingsvariabele PYTHONPATH het het pad naar de Python-Gnome modules bevat. Dit kan /usr/local/lib/python1.5/site-packages of /usr/lib/python1.5/site-packages/ zijn. Hier kun je alle nodige bindingen vinden voor Gnome en LibGlade, zo kun je daar bijvoorbeeld de libglade.py module vinden. Om PYTHONPATH te zetten, voeg je gewoon het volgende toe aan je .bash_profile:

PYTHONPATH=/usr/local/lib/python1.5/site-packages
export PYTHONPATH

Vergeet niet om je Python code in een terminal te starten, zodat deze variabele wordt ingesteld.


 

Glade, LibGlade & Python interactie

Glade is een interface-bouwer en is ontwikkeld door Damon Chaplin. Hiermee kun je interactieve grafische Gnome/GTK gebruikersinterfaces construeren. In Glade kun je de gegenereerde interface opslaan in een XML bestand of direct naar C code exporteren en opnemen in een C code tree. Glade staat je ook toe om de namen van de handlers -functies- definiëren en aan verschillende gebeurtenissen van de interface toe te wijzen. Zo wordt bijvoorbeeld de functie (name) aangeroepen als er een specifiek menu item wordt aangeklikt.

LibGlade is een library die is geschreven door James Henstridge om on-the-fly een interface te genereren aan de hand van een Glade XML bestand. De applicatie hoeft alleen maar van het XML bestand te weten -gewoonlijk met de extensie .glade- en LibGlade kan de interface genereren. James Henstridge heeft ook de LibGlade Python binding geschreven die -naast andere- gevonden kan worden in het Gnome-Python pakket. LibGlade staat je ook toe om de handlers -bijna in Python- die zijn gedefinieerd in het .glade bestand automatisch aan Python functies te koppelen.

Het diagram hieronder geeft het algemene mechanisme weer. Om te begrijpen hoe de Python binding is geïplementeerd, is het soms nodig om naar de Gtk, Gnome en LibGlade Python modules in PYTHONPATH te kijken en ze te vergelijken met de C Gtk/Gnome ontwikkelaars documentatie.

 

Een eerste voorbeeld, couleur

Als een eerste introductie tot Gnome-Python programmeren, stel ik een simpel kleurenspel voor, waarbij de kinderen vormen van dezelde kleur moeten herkennen. Dit voorbeeld is erg grafisch georiënteerd en presenteerd aardige functies van Gnome, zoals de Gnome Canvas en het Gnome Application Window. De regels van dit spel zijn eenvoudig: het spelbord is gevuld met 16 vormen - cirkels, sterren en vierkanten - met verschillende kleuren. Deze 16 vormen zijn verdeeld in 8 paren met dezelfde kleur. Eventueel kun je eerst aan het einde van dit document de code bekijken om zo een idee te krijgen en van daar uit verdergaan.

 

Een interface bouwen met Glade

De widgets

Na het starten van Glade krijg je twee vensters. Een is een widget toolbox, genaamd Palette. Hierin kun je widgets kiezen in de categoriën GTK+ Basic, GTK+ Additional en Gnome. Als de Gnome widget ontbreekt, kan het zijn dat Glade is gecompileerd zonder Gnome ondersteuning. Controleer de configure in de Glade broncode, configure --help legt de configuratie opties uit.

Het andere venster geeft in zijn hoofdruimte een lijst van de gemaakte widgets.

Met Glade maken we eerst een Gnome Application Window. Deze widget is een venster met een menu- & tool bar. Onderin het Gnome Applicatie Venster is ook reeds een status bar. Na het maken van een Gnome Applicatie Venster open je de Widget Tree dialoog (te vinden in het view menu van Glade). Nu kun je verkennen wat er precies in deze widget zit.

Vervolgens voeg je een canvas toe in de hoofdruimte van het Gnome Applicatie widget. In de properties dialoog verander je de maximale hoogte en breedte in 400.


En nu maak je een Gnome About Dialoog. De inhoud ervan kun je veranderen via de properties dialoog in het Widget sheet.

Al deze widgets zijn in de Gnome categorie van het Palette.

Vervolgens verwijder je de knoppen en menu items die je niet gebruikt. In de toolbar verwijder je de Open en Save knoppen. Vervolgens bewerk je de menubalk (rechts-klik er op en kies edit menu) en verwijder alle menus en menu-items behalve File->New, File->Exit, Settings->Preferences en Help->About.

De widget en handler namen opzetten

Geef deze widgets de volgende namen, zodat we ze kunnen gebruiken in Python:

Gnome Application Window:
colorApp
Gnome About Dialog:
about

De handler namen zijn functie namen die worden aangeroepen bij gebeurtenissen bij een bepaalde widget. Dit betekend dat we functies in Python zullen definiëren met deze namen - bijna zoals je later zult zien. Als de gebruiker bijvoorbeeld op de "new" knop drukt, roepen we een functie aan om het spel te resetten. Om dit op te zetten vanuit Glade, selecteer je eerst de widget en pas je daarna het Signals sheet in de properties dialoog.

In ons voorbeeld is het signaal clicked en de handler is de functie naam. De volgende arrays geven de gebruikte signalen en handlers:

In de about dialoog:

Widget naam Signaal Handler
about clicked gtk_widget_destroy
about close gtk_widget_destroy
about destroy gtk_widget_destroy

De gtk_widget_destroy handler is gedefinieerd in GTK. Deze vernietigd gewoon de widget.

Het colorApp venster. Eerst kiest Glade automatisch de signalen/handlers voor het items menu. Je kunt de namen controleren. Ik voeg ze teo aan het eind van deze array. Je zult zien dat het nieuwe menu item en de nieuwe knop dezelfde handler hebben, normaal gesproken hebben ze ook hetzelfde doel:

Widget naam Signael Handler
button1 (nieuwe knop
op de toolbar
clicked on_new_activate
new activate on_new_activate
colorApp destroy on_exit1_activate
exit1 activate on_exit1_activate
about1 activate on_about_activate

De afwerking

Ga naar de Project Options via de Options knop in de Glade toolbar. In het General sheet, pas je de project entries als volgt aan:

Het bestand dat de widgets representeerd is color.glade. Verander het pad naar je eigen directory.

Sla nu het bestand op via het File menu. Bouw de broncode niet, die feature gebruiken we neit.
We zijn nu klaar met Glade en kunnen beginnen met Python.

 

De Python code

De complete code bevindt zich aan het eind van dit artikel. Sla het op in dezelfde locatie als het bestand color.glade.

 

De nodige modules opnemen

from math import cos, sin, pi
from whrandom import randint
from gtk import *
from gnome.ui import *
from GDK import *
from libglade import *

Van de modules math en whrandom, nemen we niet Gnome specifieke functies als cos, sin, randint op en de pi constante. De gnome-specifieke modules zijn gtk, GDK en gnome.ui. In C gebruik je gnome.h, deze bevat alle Gnome headers. In python moet je eerst uitzoeken in welke module de binding zich bevindt voor de Gnome functie die je wilt gebruiken. Zo kun je bijvoorbeeld in terminal venster (shell) zoeken welke module de string "canvas" bevat met het volgende commando:

cd /usr/local/lib/python1.5/site-packages/gnome
grep canvas *.py

Het bovenstaande commando neemt aan de Gnome binding in /usr/local/lib/python1.5/site-packages is geïnstalleerd.

 

De interface laden met Libglade

In dit Python voorbeeld gebruiken we de Gnome Canvas om vormen te manipuleren - eigenlijk sterren, cirkels en vierkanten. Een Canvas is een plaatshouder voor grafische items (ellips, punt, lijn, driehoek), tekst items en zelfs widgets. Een canvas kan zelfs meerdere canvas groepen bevatten. Ten slotte kun je items in een canvas plaatsen - onze vormen. Standaard bevat een canvas een standaard canvas groep, de zogenaamde root canvas. Deze zullen we gebruiken om onze vormen in te plaatsen.

Eerst definiëren we enkele globale variabelen:

De eerste functie die we gebruiken - initColor bouwt de widgets in het color.glade bestand en verbindt automatisch handlers aan de 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 ()

Het bouwen van de widget gaat met de GladeXML functie. Uiteraard moet je het pad naar het color.glade bestand aanpassen. Deze functie bouwt en geeft het colorApp Gnome Applicatie Venster dat we hebben gedefinieerd met Glade. Het geeft object - eigenlijk een class - met handige methodes.

Vervolgens verbinden we de handlers die we hebben gedefinieerd in Python - later meer hierover - aan de widgets in het color.glade bestand. Daarvoor moeten we een lijst ("dictionary") maken met de sleutels voor de handler namen in het color.glade bestand: on_about_activate, on_exit1_activate en on_new_activate. De waarden voor deze sleutels zijn de functie namen in Python.
Tenslotte doet de methode signal_autoconnect de rest van het werk voor ons.

Ten slotte gebruiken we de referentie van de canvas die was gemaakt tijdens de GladeXML call - een GnomeCanvas object in Python - en de root canvas groep - een GnomeCanvasGroup object.

Handige tips

Er is geen echte referentie handleiding voor de Gnome binding van Python. er is echter een hoop documentatie beschikbaar over Gnome programmeren in C beschikbaar op de Gnome website. Deze documentatie kan nuttig zijn, maar je zult ook wat in de Gnome binding voor Python moeten lezen om deze goed te gebruiken:

De binding is te vinden in /usr/local/lib/python1.5/site-packages/gnome/ of /usr/lib/python1.5/site-packages/gnome/

Even lezen in de binding levert verschillende dingen op:

  1. in de libglade.py binding:
  2. In de gnome/ui.py binding:
    • Het GnomeCanvas object, deze class heeft de root methode die we in onze Python code gebruiken. Het is verbonden met de gnome_canvas_root C functie.

Voor ieder Gnome gebruik in Python kunnen we hetzelfde doen om de gerelateerde documentaite krijgen. Ik laat je de gerelateerde Gnome documentatie lezen om meer te leren over het gebruik van deze fucnties.

 

De handlers definiëren

Er zijn drie handlers de automatisch worden verbonden met de GUI. Dit zijn on_about_activate, on_new_activate en mainquit. De laatste is in feite een Python functie om Python te stoppen en af te sluiten.

def on_about_activate(obj):
    "display the about dialog"
    about = GladeXML ("color.glade", "about").get_widget ("about")
    about.show ()

Deze handler opent het "about" venster. Eerst halen we een referentie op voor de about dialoog - LibGlade bouwde deze via het GladeXML object. Denk eraan dat GladeXML een python object met een methode is - onder meer - genaamd get_widget. Deze methode geeft een GtkWidget object dat de show methode bevat.

Tips

Kijk naar het GtkWidget ojbect in de gtk.py binding. Je kunt zien dat dit object een show methode heeft. De vorige hanlder body kun je schrijven als: 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)

Deze handler bouwt een nieuwe spelruimte. Eerst worden de bestaande vormen vernietigd. De vormen zijn GnomeCanvasItem objecten, verkregen uit GtkObject objecten. De destroy methode bevindt zich in het GtkObject object. Vervolgens wordt er een nieuwe spelruimte gebouwd.

 

Het GnomeCanvasItem

De vorm definiëren

De buildGameArea functie coördineerd het maken van de spelruimte in de GnomeCanvasGroup groep. De vormen - GnomeCanvasItem - worden gebouwd met aanroepen naar buildShape functie. De vormen kunnen cirkels, vierkanten of sterren zijn.

Het make van de vormen gebeurd met de volgende code, afhankelijk van de te maken vorm:

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)

De variabele groep bevat een referentie naar een GnomeCanvasGroup object. Als we in de ui.py binding kijken, zien we dat GnomeCanvasGroup een add methode heeft. Het eerste argument, tp verwacht een string met het type item om toe te voegen. De volgende argumenten zijn argumenten en hun waarden, deze worden opgezocht in een dictionary (woordenboek). Voor de volledige lijst van beschikbare trefwoorden, zie de GnomeCanvasRect, GnomeCanvasEllipse en GnomeCanvasPolygon objecten in ui.py.

De ellips en rechthoek zijn regelijk vergelijkbaar, de coördinaten definiëren twee tegenovergestelde punten van de box, links-boven en rechtsonder. De plaatsing van de canvas is standaard op linksboven van de canvas. De polygoon verwacht een waarde voor het points sleutelwoord, een lijst van paar-coördinaten die de punten van de polygoon definiëren. De andere argumenten zijn redelijk rechttoe-rechtaan.

De gebeurtenis met de vorm verbinden

Nu verbinden we een gebeurtenis met iedere vorm die we maken. Dit gebeurd aan het eind van de buildShape functie:

item.connect ('event', shapeEvent)
colorShape.append (item)

We gebruiken de connect methode van GtkObject die een voorvader object is van GnomeCanvasItem. Diens eerste argument is het signaal. Daar GnomeCanvasItem maar een signaal event heeft voor alle soorten events, geven we hier event op. Het tweede argument is de naam van de handler die we schreven, hier is dat shapeEvent. Ten slotte kunnen we in een derde argument nog data meegeven, maar dat hebben we hier niet nodig. Dat is alles!

 

De shape gebeurtenis

Het bouwen van de handler voor de vormen(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

Als deze handler wordt gebruikt, bevat de variabele item een referentie naar de vorm waar een gebeurtenis optrad, en event bevat de gebeurtenis. Bij een GdkEvent gebeurtenis zijn we alleen geïnteresseerd in drie soorten gebeurtenissen:

Tenslotte, de handler geeft altijd TRUE (1) terug. Dit betekend dat het signaal niet wordt doorgezet naar andere items. Dit willen we ook niet omdat onze vormen nooit overlappen.  

Laatste woord

Ik heb alle Python code die niet relevant was voor Gnome achterwege gelaten, het zou niet zo moeilijk moeten zijn om die te begrijpen. Het doel van deze eenvoudige tutorial was om je te laten zien hoe je zelf kunt uitzoeken hoe de dingen werken: kijk in de Gnome binding voor Python of de Gnome C header en lees de Gnome documentatie voor programmeren in C. Natuurlijk kan ik laten zien hoe makkelijk en krachtig de Gnome Canvas en Glade/LibGlade zijn. Vanaf nu kun je veel doen door deze code uit te breiden (de broncode voor dit artikel kun je hier vinden).

 

Bijlage: De complete broncode

#!/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 ()
 

Talkback voor dit artikel

Elk artikel heeft zijn eigen talkback pagina. Daar kan je commentaar geven of commentaar van anderen lezen:




Site onderhouden door het LinuxFocus editors team
© Hilaire Fernandes
"some rights reserved" see linuxfocus.org/license/
http://www.LinuxFocus.org
Vertaling info:
fr --> -- : Hilaire Fernandes <hfernandes /at/ april.org>
fr --> de: Harald Radke <harryrat /at/ postnuklear.de>
en --> nl: Guus Snijders <ghs /at/ linuxfocus.org>

2006-03-12, generated by lfparser version 2.54