Desenvolvendo Aplica��es para o Gnome com o Python (Parte 3)

ArticleCategory: [Choose a category, do not translate this]

Software Development

AuthorImage:

Hilaire Fernandes

TranslationInfo:[Author + translation history. mailto: or http://homepage, do not translate this]

original in fr Hilaire Fernandes

fr to en Lorne Bailey

en to pt Bruno Sousa

AboutTheAuthor:

O Hilaire Fernandes � o Vice-Presidente da OFSET, uma organiza��o para promover o desenvolvimento de software educacional 'livre' para o ambiente de trabalho Gnome. Ele tamb�m escreveu o Dr. Geo, um programa pioneiro na geometria din�mica e, est� presentemente a trabalhar no Dr. Genius - outro programa educacional para o Gnome.

Abstract:

Esta s�rie de artigos � especialmente escrita para programadores novatos usando o Gnome e GNU/Linux. O Python, a linguagem escolhida para desenvolvimento, evita a habitual complica��o das linguagens compiladas como o C. Para entender este artigo precisa de ter uma compreens�o b�sica da programa��o em Python. Mais informa��o acerca do Python e do Gnome est� dispon�vel em http://www.python.org e http://www.gnome.org.

Artigos anteriores desta s�rie :
- primeiro artigo
- segundo artigo

ArticleIllustration:

Gnome

ArticleBody:

Utilit�rios necess�rios

Para a depend�ncias de software necess�rias para executar o programa descrito neste artigo, refira-se � lista referida na primeira parte da s�rie destes artigos.

Tamb�m precisa de:

Para instala��o e uso do Python-Gnome e LibGlade refira-se � Parte I.

Modele de desenvolvimento para os exerc�cios

No artigo anterior (parte 2), cri�mos a interface do utilizador -- Drill -- que � uma frame para o c�digo dos exerc�cios descritos mais � frente. Agora damos uma vista de olhos ao desenvolvimento de objectos no Python, para adicionar funcionalidades ao Drill. Neste estudo, deixamos de parte os aspectos do desenvolvimento do Python no Gnome.

Assim, comecemos onde deix�mos as coisas, a inser��o de um jogo de cores dentro do Drill como exerc�cio para o leitor. Usaremos isto para ilustrar a nossa presente mat�ria e ao mesmo tempo para dar a solu��o ao exerc�cio.

Desenvolvimento Orientado a Objectos

Brevemente, sem ter de fazer uma an�lise exaustiva, o desenvolvimento por objectos tenta definir e categorizar as coisas atrav�s de rela��es � um, quer existam no mundo real ou n�o. Isto pode ser visto como abstraindo os objectos relacionados com o problema no qual estamos interessados. Podemos encontrar compara��es em dom�nios diferentes, como as categorias de Arist�teles, taxonomias ou ontologias. Em cada caso, devemos entender uma situa��o complexa atrav�s da abstrac��o. Este tipo de desenvolvimento podia ser chamado desenvolvimento orientado �s categorias.

Neste modelo de desenvolvimento, os objectos manipulado pelo programa, ou constituintes do programa s�o chamados classes e a representa��o destes objectos abstractos s�o as inst�ncias. As classes s�o definidas por atributos (contendo valores) e m�todos (fun��es). Falamos de uma rela��o pai-filho quando para uma dada classe uma classe filha herda propriedades do pai. As classes est�o organizadas por uma rela��o � um, onde o filho � ainda um tipo de pai bem como um tipo de filho. As classe podem n�o ser totalmente definidas, sendo neste caso chamadas de classes abstractas. Quando um m�todo � declarado mas n�o definido (o corpo da fun��o n�o � nada) � tamb�m chamado um m�todo virtual. Uma classe abstracta tem um ou mais destes m�todos indefinidos e devido a isto n�o pode ser instanciada. As classes abstractas permitem especifica��o na forma tomada pelas classes derivadas - classes filhas nas quais os m�todos virtuais ser�o definidos.

As diferentes linguagens t�m mais ou menos eleg�ncia em definir os objectos, mas o senso comum parece ser o seguinte:

  1. Heran�a dos atributos e m�todos da classe pai pelas classes filhas.
  2. Possibilidade das classes filhas sobreporem os m�todos herdados da classe pai.
  3. Polimorfismo, onde uma classe pode ter v�rias classes pais.


Python e o Desenvolvimento Orientado a Objectos

No caso do Python foi escolhido o denominador menos comum. Este permite aprender o desenvolvimento orientado a objectos sem ter de entrar nos detalhes desta metodologia.

No Python todos os m�todos dos objectos s�o sempre m�todos virtuais. Isto quer dizer que podem sempre ser sobrepostos por uma classe filha -- o que geralmente � o que pretendemos quando usamos o desenvolvimento orientado por objectos -- e simplifica, ligeiramente, a sintaxe. Mas n�o � f�cil distinguir entre m�todos que podem e que n�o podem ser sobrepostos. Para al�m disto � imposs�vel criar um objecto opaco por conseguinte negar o acesso a atributos e m�todos do exterior do objecto. Em conclus�o, os atributos de um objecto do Python podem ser lidos e escritos a partir do exterior do objecto.

Exercicio Classe Pai

No nosso exemplo, (veja o ficheiro templateExercice.py), gostar�amos de definir muitos objectos do tipo exercice. Definimos um objecto do tipo exercice para servir como uma classe base abstracta, para serem derivados outros exerc�cios que criaremos mais tarde. O objecto exemple � a classe pai de todos os outros tipo de exerc�cios criados. Pelo menos, os tipos de classes herdadas ter�o os mesmos atributos e m�todos que a classe exemple visto que derivam dela. Isto permitir-nos-� manipular todos os tipos de objecto independentemente do objecto pelo qual foram instanciados.

Por exemplo, para criar uma inst�ncia da classe exercice podemos escrever:

from templateExercice import exercice

monExercice = exercice ()
monExercice.activate (ceWidget)


De facto, n�o existe necessidade de criar uma inst�ncia da classe exercice porque s� � um modelo a partir do qual outras classes s�o derivadas.

Atributos

Se estivermos interessados noutros aspectos do exercise podemos adicionar atributos, por exemplo, a pontua��o obtida ou o n�mero de vezes que correu.

M�todos

Em termos de c�digo Python :

class exercice:
    "A template exercice"
    exerciceWidget = None
    exerciceName = "No Name"
    def __init__ (self):
        "Create the exericice widget"
    def activate (self, area):
        "Set the exercice on the area container"
        area.add (self.exerciceWidget)
    def unactivate (self, area):
        "Remove the exercice fromt the container"
        area.remove (self.exerciceWidget)
    def reset (self):
        "Reset the exercice"

Este c�digo � inclu�do num ficheiro pr�prio templateFichier.py, o que nos permite clarificar as ideias espec�ficas de cada objecto. Os m�todos s�o declarados dentro da classe exercice, e no fundo s�o fun��es.

Veremos que o argumento area � uma refer�ncia para uma widget GTK+ constru�da pela LibGlade, � uma janela com sliders.



Neste Objecto, os m�todos __init__ e reset est�o vazios e ser�o sobrepostos pelos das classes filhas se necess�rio.

labelExercice, Primeiro Exemplo de Heran�a

Isto �, praticamente um exerc�cio vazio. S� faz uma coisa, p�e o nome do exerc�cio na zona Drill do exercise. Serve como princ�pio para os exerc�cios que preencheram a parte direita da �rvore do Drill, mas ainda n�o a cri�mos.

Do mesmo que o objecto exercice, o objecto labelExercice � posto num ficheiro pr�prio, labelExercice.py . De seguida, como este objecto � um filho do objecto exercice, precisamos de dizer como � que o pai � definido. Isto � feito atrav�s de um simples import:

from templateExercice import exercice

Isto literalmente, significa que a defini��o da classe exercice no ficheiro templateExercice � importada para o currente c�digo.

Chegamos agora, ao aspecto mais importante, a declara��o da classe labelExercice como classe filha da classe exercice.
A ClasselabelExercice � declarada da seguinte forma :

class labelExercice(exercice):

Voilà, e � o suficiente para que a labelExercice herde os atributos e m�todos da exercice.

Claro que ainda temos trabalho a fazer, em particular, precisamos de inicializar a widget do exerc�cio. Fazemos isto sobrepondo o m�todo __init__ (isto �, redefinindo-o na classe labelExercice), este �ltimo � chamado quando uma inst�ncia � criada. Esta widget, tamb�m, deve ser referenciada no atributo exerciceWidget assim n�o precisaremos de sobrepor os m�todos activate e unactivate da classe pai exercice.

  def __init__ (self, name):
      self.exerciceName = "Un exercice vide" (Trans. note: an empty exercise)
      self.exerciceWidget = GtkLabel (name)
      self.exerciceWidget.show ()

Este � o �nico m�todo que sobrepomos. Para criar uma inst�ncia do labelExercice, s� precisamos de chamar :

monExercice = labelExercice ("Um exerc�cio que nada faz")

Para aceder aos seus atributos ou m�todos :

# O nome do exerc�cio
print monExercice.exerciceName

# Colocar o widget do exercice no contentor "area"
monExerice.activate (area)

colorExercice, Segundo Exemplo de Heran�a

Aqui come�amos a transforma��o da cor do jogo, visto no primeiro artigo deste s�rie, numa classe do tipo exercice que se chamar� colorExercice. Coloc�-la-emos no seu pr�prio ficheiro, colorExercice.py , que ser� concatenado a este artigo com o c�digo fonte completo.

As altera��es requeridas ao c�digo fonte inicial consistem, basicamente, na redistribui��o das fun��es e vari�veis em m�todos e atributos na classe colorExercice.

As vari�veis globais s�o transformadas em atributos declarados no princ�pio da classe :

class colorExercice(exercice):
    width, itemToSelect = 200, 8
    selectedItem = rootGroup = None
    # to keep trace of the canvas item
    colorShape = []

Como para a classe labelExercice, o m�todo __init__ � sobreposto para acomodar a constru��o das widgets do exercice :

def __init__ (self):
    self.exerciceName = "Le jeu de couleur" # Translator Note: the color game
    self.exerciceWidget = GnomeCanvas ()
    self.rootGroup = self.exerciceWidget.root ()
    self.buildGameArea ()
    self.exerciceWidget.set_usize (self.width,self.width)
    self.exerciceWidget.set_scroll_region (0, 0, self.width, self.width)
    self.exerciceWidget.show ()

Nada de novo comparativamente ao c�digo inicial, somente o GnomeCanvas referenciado no atributo exerciceWidget.

O outro m�todo sobreposto � o reset. Visto que faz um reset do jogo para zero, deve ser alterado para o jogo colorido :

    def reset (self):
        for item in self.colorShape:
            item.destroy ()
        del self.colorShape[0:]
        self.buildGameArea ()

Os outros m�todos s�o c�pias directas das fun��es originais, com a adi��o da vari�vel self para permitir acesso aos atributos e m�todos da inst�ncia. Existe uma excep��o nos m�todos buildStar e buildShape onde um par�metro decimal k � substitu�do por um n�mero inteiro. Eu notei um comportamento estranho no documento colorExercice.py onde os n�meros decimais declarados no c�digo s�o truncados. O problema parece estar no m�dulo gnome.ui e no French locale (onde os n�meros decimais usam a v�rgula como separador em vez do ponto). Trabalharei para encontrar o c�digo do problema antes do pr�ximo artigo.

Ajustamentos Finais no Drill

Definimos agora dois tipos de exerc�cios labelExercice e colorExercice. Criamos inst�ncias deles com as fun��es addXXXXExercice no c�digo drill1.py. As inst�ncias s�o referenciadas num dicion�rio exerciceList no qual as chaves tamb�m s�o argumentos para as p�ginas de cada exerc�cio na �rvore da direita :

def addExercice (category, title, id):
    item = GtkTreeItem (title)
    item.set_data ("id", id)
    category.append (item)
    item.show ()
    item.connect ("select", selectTreeItem)
    item.connect ("deselect", deselectTreeItem)
[...]    
def addGameExercice ():
    global exerciceList
    subtree = addSubtree ("Jeux")
    addExercice (subtree, "Couleur", "Games/Color")
    exerciceList ["Games/Color"] = colorExercice ()

A fun��o addGameExercice cria uma folha na �rvore com o atributo id="Games/Color" ao chamar a fun��o addExercice. Este atributo � usado como chave para a inst�ncia do jogo colorido, criado pelo comando colorExercice() no dicion�rio exerciceList.

De seguida, devido � eleg�ncia do polimorfismo num desenvolvimento orientado a objectos, podemos correr os exerc�cios ao usar as mesmas fun��es que agem diferentemente para cada objecto, sem termos de nos preocupar com a sua implementa��o interna. S� chamamos m�todos definidos na classe base abstracta exercice e elas fazem coisas diferentes quer na classe colorExercice ou quer na classe labelExercice. O programador "fala" para todos os exerc�cios do mesmo modo, mesmo que a "resposta" de cada exerc�cio seja um pouco diferente. Para fazer isto combinamos o uso de cada atributo id das p�ginas da �rvore com o dicion�rio exerciceList ou a vari�vel exoSelected que se refere ao exerc�cio em uso. Dado isto todos os exerc�cios s�o filhos da classe exercice, usamos os seus da mesma maneira para controlar os exerc�cios em toda a sua variedade.

def on_new_activate (obj):
    global exoSelected
    if exoSelected != None:
        exoSelected.reset ()

def selectTreeItem (item):
    global exoArea, exoSelected, exerciceList
    exoSelected = exerciceList [item.get_data ("id")]
    exoSelected.activate (exoArea)

def deselectTreeItem (item):
    global exoArea, exerciceList
    exerciceList [item.get_data ("id")].unactivate (exoArea)


[Main window of Drill]
Fig. 1 - Janela Principal do Drill, com o exerc�cio colorido

Assim termina o nosso artigo. Descobrimos as atrac��es do desenvolvimento orientado a objectos no Python ainda com o aditivo de uma interface de utilizador gr�fica. No pr�ximo artigo continuaremos a descobrir as widgets do Gnome atrav�s da codifica��o de novos exerc�cios que vamos inserir no Drill.

Apendice: C�digo Fonte Completo

drill1.py

#!/usr/bin/python
# Drill - Teo Serie
# Copyright Hilaire Fernandes 2002
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org


from gnome.ui import *
from libglade import *

# Import the exercice class
from colorExercice import *
from labelExercice import *

exerciceTree = currentExercice = None
# The exercice holder
exoArea = None
exoSelected = None
exerciceList = {}
 
def on_about_activate(obj):
    "display the about dialog"
    about = GladeXML ("drill.glade", "about").get_widget ("about")
    about.show ()
    
def on_new_activate (obj):
    global exoSelected
    if exoSelected != None:
        exoSelected.reset ()

def selectTreeItem (item):
    global exoArea, exoSelected, exerciceList
    exoSelected = exerciceList [item.get_data ("id")]
    exoSelected.activate (exoArea)

def deselectTreeItem (item):
    global exoArea, exerciceList
    exerciceList [item.get_data ("id")].unactivate (exoArea)

def addSubtree (name):
    global exerciceTree
    subTree = GtkTree ()
    item = GtkTreeItem (name)
    exerciceTree.append (item)
    item.set_subtree (subTree)
    item.show ()
    return subTree

def addExercice (category, title, id):
    item = GtkTreeItem (title)
    item.set_data ("id", id)
    category.append (item)
    item.show ()
    item.connect ("select", selectTreeItem)
    item.connect ("deselect", deselectTreeItem)
    

def addMathExercice ():
    global exerciceList
    subtree = addSubtree ("Mathématiques")
    addExercice (subtree, "Exercice 1", "Math/Ex1")
    exerciceList ["Math/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Math. Ex2")
    exerciceList ["Math/Ex2"] = labelExercice ("Exercice 2")

def addFrenchExercice ():
    global exerciceList
    subtree = addSubtree ("Français")
    addExercice (subtree, "Exercice 1", "French/Ex1")
    exerciceList ["French/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "French/Ex2")
    exerciceList ["French/Ex2"] = labelExercice ("Exercice 2")

def addHistoryExercice ():
    global exerciceList
    subtree = addSubtree ("Histoire")
    addExercice (subtree, "Exercice 1", "Histoiry/Ex1")
    exerciceList ["History/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Histoiry/Ex2")
    exerciceList ["History/Ex2"] = labelExercice ("Exercice 2")

def addGeographyExercice ():
    global exerciceList
    subtree = addSubtree ("Géographie")
    addExercice (subtree, "Exercice 1", "Geography/Ex1")
    exerciceList ["Geography/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Geography/Ex2")
    exerciceList ["Geography/Ex2"] = labelExercice ("Exercice 2")

def addGameExercice ():
    global exerciceList
    subtree = addSubtree ("Jeux")
    addExercice (subtree, "Couleur", "Games/Color")
    exerciceList ["Games/Color"] = colorExercice ()

    
def initDrill ():
    global exerciceTree, label, exoArea
    wTree = GladeXML ("drill.glade", "drillApp")
    dic = {"on_about_activate": on_about_activate,
           "on_exit_activate": mainquit,
           "on_new_activate": on_new_activate}
    wTree.signal_autoconnect (dic)           
    exerciceTree = wTree.get_widget ("exerciceTree")
    # Temporary until we implement real exercice
    exoArea = wTree.get_widget ("exoArea")
    # Free the GladeXML tree
    wTree.destroy ()
    # Add the exercice
    addMathExercice ()
    addFrenchExercice ()
    addHistoryExercice ()
    addGeographyExercice ()
    addGameExercice ()
    
initDrill ()
mainloop ()


templateExercice.py

# Exercice pure virtual class
# exercice class methods should be override
# when exercice class is derived
class exercice:
    "A template exercice"
    exerciceWidget = None
    exerciceName = "No Name"
    def __init__ (self):
        "Create the exericice widget"
    def activate (self, area):
        "Set the exercice on the area container"
        area.add (self.exerciceWidget)
    def unactivate (self, area):
        "Remove the exercice fromt the container"
        area.remove (self.exerciceWidget)
    def reset (self):
        "Reset the exercice"


labelExercice.py

# Dummy Exercice - Teo Serie
# Copyright Hilaire Fernandes 2001
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org

from gtk import *
from templateExercice import exercice

class labelExercice(exercice):
    "A dummy exercie, it just prints a label in the exercice area"
    def __init__ (self, name):
        self.exerciceName = "Un exercice vide"
        self.exerciceWidget = GtkLabel (name)
        self.exerciceWidget.show ()


colorExercice.py

# Color Exercice - Teo Serie
# Copyright Hilaire Fernandes 2001
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org

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

from templateExercice import exercice
       

# Exercice 1 : color game

class colorExercice(exercice):
    width, itemToSelect = 200, 8
    selectedItem = rootGroup = None
    # to keep trace of the canvas item
    colorShape = []
    def __init__ (self):
        self.exerciceName = "Le jeu de couleur"
        self.exerciceWidget = GnomeCanvas ()
        self.rootGroup = self.exerciceWidget.root ()
        self.buildGameArea ()
        self.exerciceWidget.set_usize (self.width,self.width)
        self.exerciceWidget.set_scroll_region (0, 0, self.width, self.width)
        self.exerciceWidget.show ()
    def reset (self):
        for item in self.colorShape:
            item.destroy ()
        del self.colorShape[0:]
        self.buildGameArea ()
    def shapeEvent (self, item, event):
        if event.type == ENTER_NOTIFY and self.selectedItem != item:        
            item.set(outline_color = 'white') #highligh outline
        elif event.type == LEAVE_NOTIFY and self.selectedItem != item:
            item.set(outline_color = 'black') #unlight outline
        elif event.type == BUTTON_PRESS:
            if not self.selectedItem:
                item.set (outline_color = 'white')
                self.selectedItem = item
            elif item['fill_color_gdk'] == self.selectedItem['fill_color_gdk'] \
                 and item != self.selectedItem:
                item.destroy ()
                self.selectedItem.destroy ()
                self.colorShape.remove (item)
                self.colorShape.remove (self.selectedItem)
                self.selectedItem, self.itemToSelect = None, \
                 self.itemToSelect - 1
                if self.itemToSelect == 0:
                    self.buildGameArea ()
        return 1    

    def buildShape (self,group, number, type, color):
        "build a shape of 'type' and 'color'"
        w = self.width / 4
        x, y, r = (number % 4) * w + w / 2, (number / 4) * w + w / 2, w / 2 - 2
        if type == 'circle':
            item = self.buildCircle (group, x, y, r, color)
        elif type == 'squarre':
            item = self.buildSquare (group, x, y, r, color)
        elif type == 'star':
            item = self.buildStar (group, x, y, r, 2, randint (3, 15), color)
        elif type == 'star2':
            item = self.buildStar (group, x, y, r, 3, randint (3, 15), color)
        item.connect ('event', self.shapeEvent)
        self.colorShape.append (item)

    def buildCircle (self,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 (self,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 (self,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):            
            pts.append (x + r * cos (i * angleCenter))
            pts.append (y + r * sin (i * angleCenter))
            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 (self,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 (self):
        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]
        self.itemToSelect, i, self.selectedItem = 8, 15, None
        for color in itemColor:
            # two items of same color
            n = 2
            while n > 0:
                cellRandom = randint (0, i)
                cellNumber = self.getEmptyCell (emptyCell, cellRandom)
                emptyCell[cellNumber] = 1
                self.buildShape (self.rootGroup, cellNumber, \
                 itemShape[randint (0, 3)], color)
                i, n = i - 1, n - 1