Python kullanarak Gnome için Uygulama Geliştirme(Bölüm 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 tr Esen Fahri AKIN

AboutTheAuthor:

Hilaire Fernandes Gnome masaüstü için "ücretsiz" eğitsel yazılımlar geliştiren OFSET adlı organizasyonun ikinci başkanıdır. Aynı zamanda bir dinamik geometri programı olan Dr.Geo'yu yazdı. Halen Gnome için bir başka eğitim programı olan Dr.Genius üzerinde çalışmaktadır.

Abstract:

Bu makale serisi özellikle Gnome ve GNU/Linux kullanan amatör programcılar için yazılmiştır. Geliştirme için seçilen dil, Python, C gibi derlenmiş dillerin alışageldiğimiz genel hatlarını kullanmaz. Bu makaleyi anlamanız ic cin temel Python programlama bilgisine sahip olmanız gerekir. Python ve Gnome hakkında ayrıntılı bilgiyi http://www.python.org ve http://www.gnome.org.

adreslerinden edinebilirsiniz.

Serideki diğer makaleler :
- ilk makale
- ikinci makale

ArticleIllustration:

Gnome

ArticleBody:

Gereken araçlar

Bu makalede tanıtılan programı çalıştırmak için gereken yazılımlar için bu serinin 1.bölümüne bakınız.

Diğer gereksinimler:

Python-Gnome ve LibGlade kurulumu ve kullanımı için Bölüm 1'e bakınız.

Alıştırmalar için Geliştirme Modeli

Önceki makalede (Bölüm 2), ileride tanımlanacak alıştırmaların kodlaması için bir çerçeve olan -- Drill -- adlı kullanıcı arayüzünü oluşturduk. Şu aşamada, Drill'e yararlık kazandırmak açısından Python kullanarak nesne yönelimli geliştirmeyi daha yakından incelemeliyiz. Bu çalışmada, Gnome'da Python geliştirmeyi bir kenara bırakacağız.

O halde bıraktığımız yerden devam edelim, okuyucu için bir alıştırma olması bakımından Drill içine bir renk oyununun eklenmesine bakalım. Bunu konumuzu görselleştirmek, aynı zamanda da bu alıştırma için bir çözüm sunmak için yapacağız.

Nesne Yönelimli Geliştirme

Kısaca, ayrıntılı bir çözümleme yapmaksızın, nesne yönelimli geliştirmenin olayların gerçekte olup olmadığını Bu budur ilişkileri ile tanımlamaya çalıştığını söyleyebiliriz. Bu ilgilendiğimiz problemdeki nesneleri özetlemek olarak da görülebilir.Değişik alanlarda karşılıklar bulabiliriz, Aristoteles'in kategorileri, taksonomiler, ontolojiler gibi. Bunların hepsinde, kişi karmaşık bir durumu özetleme yoluyla anlamak durumundadır. Bu tür bir geliştirme pekala kategori yönelimli geliştirme olarak adlandırılabilir.

Bu geliştirme modelinde, program tarafından değiştirilen yada programı oluşturan nesneler sınıflar bu özet nesnelerin temsilcileri de örnekler olarak anılır. Sınıflar nitelikler (değerler içeren) ve yöntemler (fonksiyonlar) ile tanımlanırlar. Aslında çocuk sınıfın özelliklerini ebeveyn sınıftan aldığıbir ebeveyn-çocuk ilişkisinden söz ediyoruz. Sınıflar bu budurilişkisiyle organize olmuşlardır ve bir çocuk hem bir aile tipidir hem de bir çocuk. Sınıflar tamamen tanımlanamayabilirler, böyle bir durumda özet sınıflar olarak adlandırılırlar. Bir yöntem ifade edilmiş fakat tanımlanmamışsa (fonksiyonun gövdesi boşsa) buna da sanal yöntem denir. Bir özet sınıf bu tanımlanmamış yöntemlerden bir veya birden fazla içerir ve bu nedenle örneklenemez. Özet sınıflar türetilmiş sınıfların - saf sanal yöntemlerin tanımlanacağı çocuk sınıflar - aldıkları formları tanımlamaya izin verir.

Çeşitli dillerin nesne tanımlamadaki zariflikleri farklıdır fakat ortak paydanın şunlar olduğunu söyleyebiliriz:

  1. Çocuk sınıf tarafından niteliklerin ve yöntemlerin ebeveyn sınıftan alınması.
  2. Çocuk sınıfın ebeveynden gelen yöntemleri geçersiz kılması ve fazla yüklenmesi.
  3. Polimorfluk, bir sınıfın birden fazla ebeveyninin olması durumu.


Python ve Nesne Yönelimli Geliştirme

Python için bu seçilen en küçük ortak paydadır. Bu metodolojinin içinde kaybolmadan nesne yönelimli geliştirme öğrenmeye olanak sağlar.

Python'da, bir nesnenin yöntemleri her zaman sanal yöntemlerdir. Bu her zaman bir çocuk sınıf tarafından geçersiz kılınabileceği anlamına gelir - ki nesne yönelimli gelıştirmede biz bunu istiyoruz - ve söz dizimini biraz basitleştirir. Fakat bir yöntemin geçersiz kılınıp kılınmadığının ayırdına varmak kolay değildir. Bunun da ötesinde, bir nesneyi donuk kılmak, bunun sonucunda da nesne dışından niteliklerine ve yöntemlerine erişimi reddetmek olanaksızdır. Sonuç olarak, bir Python nesnesinin nitelikleri dışarıdan okunabilir ve yazılabilirdir.

Ebeveyn sınıf alıştırması

Örneğimizde, (templateExercice.py dosyasına bakın), exercice tipinde nesneler tanımlamak istiyoruz. exercice tipi nesneyi tanımlayarak daha sonra oluşturacağımız exercice'ler için bir özet temel sınıf oluşturmuş olduk. exemple nesnesi oluşturulacak tüm diğer tip nesnelerin ebeveyn sınıfıdır. Bu türetilmiş tipler en azından exercice ile aynı nitelik ve yöntemlere sahiptirler. Bu bize her türden nesneye, nereden türetildiğine bakmaksızın, müdahale etme imkanı verecektir.

exercice sınıfına örnek olarak şunu yazabiliriz:

from templateExercice import exercice

monExercice = exercice ()
monExercice.activate (ceWidget)


Aslında exercice sınıfına örnek oluşturmak gereksizdir çünkü zaten diğer sınıfların türetildiği bir kalıptır.

Nitelikler

Eğer alıştırmanın başka yönleriyle de ilgileniyorsak, edinilen skoru yada kaç kez çalıştırıldığını belirten nitelikler ekleyebiliriz.

Yöntemler

Bu Python kodunda bize şu sonucu verir :

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"

Bu kodu kendi dosyası olan templateFichier.py içermektedir, bu da bizim her nesnenin kendine özgü görevlerini ayırt etmemizi sağlar. Yöntemler, aslında fonksiyonlar demek daha doğru olur, exercice sınıfının içinde ifade edilmiştir.

area argümanının LibGlade tarafından oluşturulan GTK+ widget'ına referans olduğunu göreceğiz.



Bu yöntemde __init__ ve reset boştur ve gerektiğinde çocuk sınıflar tarafından geçersiz kılınacaktır.

labelExercice, Kalıtımın ilk örneği

Bu neredeyse boş bir alıştırma. Yalnızca bir tek şey yapıyor, o da Drill'in alıştırma bölgesine alıştırmanın adını koymak. Henüz oluşturmadığımız ama Drill'in sol tarafını dolduracak olan alıştırmalar için bir başlatıcı görevi görüyor.

exercice'da olduğu gibi, labelExercice da kendi dosyası olan labelExercice.py'a konuyor. Bundan sonra nesne exercice'in çocuğu için ebeveyninin nasıl tanımlandığını anlatmamız gerek. Bu basitçe import'la yapılıyor :

from templateExercice import exercice

Bunun anlamı, exercice sınıfının templateExercice.py dosyasındaki tanımı bu koda getirtiliyor(import).

Şimdi en önemli noktaya geldik, labelExercice sınıfının exercice'in çocuğu olarak ifade edilmesi.
labelExercice aşağıdaki şekilde ifade edilir :

class labelExercice(exercice):

İşte, bu labelExercice'in exercice.

'in tüm nitelik ve yöntemlerini alması için yeterli.

Tabii ki, hala yapacak işimiz var, alıştırmanın widget'ını başlatmamız(initialize) gerekiyor. Bunu __init__ yöntemini geçersiz sayarak yapıyoruz. Bu widget'a exerciceWidget niteliğinde de atıfta bulunulmalıdır ki activate ve unactivate yöntemlerini geçersiz kılmamız gerekmesin.

  def __init__ (self, name):
      self.exerciceName = "Un exercice vide" (Çevirmenin notu: boş bir alıştırma)
      self.exerciceWidget = GtkLabel (name)
      self.exerciceWidget.show ()

Bu geçersiz kıldığımız tek yöntem. labelExercice örneğini oluşturmak için yapmamiz gereken:

monExercice = labelExercice ("Un exercice qui ne fait rien")
(Çevirmenin Notu: "Un exercice qui ne fait rien" "Hiçbir şey yapmayan alıstırma" demektir)

Niteliklerine ve yöntemlerine erişmek için:

# Le nom de l'exercice (Çevirmenin notu: alıştırmanın adı)
print monExercice.exerciceName

# Placer le widget de l'exercice dans le container "area"
# (Çevirmenin notu: Alıştırmanın
widget'ını konteyner alanına yerleştir)
monExerice.activate (area)

colorExercice, Kalıtımın ikinci örneği

Burada ilk makalede görülen renk oyununu adı colorExercice.py olan exercice tipi bir sınıfa dönüştürmeye başlıyoruz. Kendi dosyası olan colorExercice.py'ye koyuyoruz(kaynak kodu bu belgeye eklidir).

Baştaki kaynak kodda yapılması gereken değişiklikler çoğunlukla fonksiyonların ve değişkenlerin colorExercice sınıfındaki yöntem ve niteliklere yeniden dağıtımını içerir.

Küresel değişkenler sınıfın başında ifade edilerek niteliklere dönüştürülür:

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

labelExercice sınıfında olduğu gibi, __init__ yöntemi alıştırmanın widget'larının oluşturulması için geçersiz sayılmıştır:

def __init__ (self):
    self.exerciceName = "Le jeu de couleur" # Çevirmenin Notu: Renk Oyunu 
    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 ()

exerciceWidget'de atıfta bulunulan yalnız GnomeCanvas ise başlangıç koduna kıyasla yeni bir şey yok.

Geçersiz sayılan yöntem reset'dir. Oyunu sıfıra eşitlediği için renk oyununa uyarlanması gerekir:

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

Diğer yöntemler, eklenen değişken self dışında orjinal fonksiyonun doğrudan kopyasıdır. Yalnız buildStar ve buildShape yöntemlerinde istisnalar vardır, ondalık parametre k bir tam sayı ile değiştirilmiştir. colorExercice.py dökümanında garip bir davranışı fark ettim. Kaynak kod tarafından alınan ondalık sayılar kesiliyordu(truncation). Sorun gnome.ui modülünden Fransız notasyonundan kaynaklanıyor gibi görünüyor(ondalık kıs mı nokta yerine virgülle ayırırlar).Bir sonraki makaleye kadar sorunun kaynağını bulma üzerine çalışacağım.

Drill'de son düzeltmeler

Şimdi iki tip alıştırmamız var -- labelExercice ve colorExercice. drill1.py kodunda addXXXXExercice fonksiyonları ile örnekler oluşturabiliriz. exerciceList sözlüğünde bu örneklere atıfta bulunuluyor. Bu sözlükte anahtarlar aynı zamanda soldaki ağaçtaki her alıştırmanın sayfalarının argümanları:

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 ()

addGameExercice fonksiyonu addExercice fonksiyonunu çağırarak ağaçta niteliği id="Games/Color" olan bir yaprak oluşturur. Bu nitelik colorExercice() tarafından exerciceList sözlüğünde oluşturulan renk oyunu örneğinde anahtar olarak kullanılır.

Polimorfluğun nesne yönelimli geliştirmedeki sıklığı dolayısıyla alıştırmalarıher nesne için farklı davranış gösteren aynı fonksiyonlarla yürütebiliriz. Yöntemleri yalnızca özet temel sınıfta exercice'da çağırıyoruz ve onlar colorExercice sınıfında ve labelExercice sınıfında farklı şeyler yapıyorlar. Programcı tüm alıştırmalara aynı şeyi "söylüyor", her alıştırmanın "yanıtı" biraz farklı olsa da. Bunu yapmak için ağacın yapraklarının id niteliği ile exerciceList sözlüğünün yada exoSelected değişkeninin kullanımını birleştiriyoruz. Bütün alıştırmaların exercice'ın çocuğu olduğu bilgisini kullanarak, onun yöntemlerini her türlü alıştırmayı kontrol etmek için kullanıyoruz.

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 - Main window of Drill, with the color exercise

Makalemiz burada bitiyor. Python'da nesne yönelimli geliştirmenin çekiciliğini grafik kullanıcı arayüzü ortamında keşfettik. İlerideki makalelerde Drill'e ekleyeceğimiz yeni alıştırmalar kodlayarak Gnome widget'larını keşfetmeye devam edeceğiz.

Appendix:Tam Kaynak Kodu

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