Open source repositories at: github and bitbucket. Current project: erp4all

Sunday, May 29, 2011

Γνωριμία με το Glade (γ' μέρος)


Στα δύο προηγούμενα άρθρα είδαμε πως μπορούμε να φτιάξουμε το γραφικό περιβάλλον του προγράμματος μας χρησιμοποιώντας το Glade. Σε αυτό θα το παρουσιάσουμε και θα δούμε πως γίνεται η διασύνδεση μεταξύ τους. Η περιγραφή θα γίνει τμηματικά, ανά μέθοδο, για να βοηθηθούν και όσοι δεν έχουν μεγάλη εμπειρία με την python.


Στην αρχή ενός αρχείου python καλό είναι να συμπεριλαμβάνουμε τα εξής:


#!/usr/bin/env python
# -*- coding: utf-8 -*-


Η πρώτη γραμμή λέει στο shell του linux με τι πρόγραμμα θα προσπαθήσει να εκτελέσει αυτό το αρχείο, αν έχει δικαιώματα εκτέλεσης.
Η δεύτερη είναι μια οδηγία για τον python interpreter ότι το αρχείο είναι κωδικοποιημένο σε utf-8. Αυτό το χρειαζόμαστε αν έχουμε γράψει ελληνικά στο κώδικά μας (όπως σε κάποιο string), αλλιώς μας βγάζει μήνυμα λάθους.


Στην συνέχεια τα καθιερωμένα import. Εδώ θα χρειαστούμε τα:


import pygtk 
pygtk.require('2.0') 
import gtk 
from datetime import datetime


Φορτώνουμε pygtk και gtk, οι βασικές μας βιβλιοθήκες και datetime για να έχουμε πρόσβαση στην ώρα και ημερομηνία του συστήματος. 


class BackgroundXML(object):     


    DIALOG_TYPE_ERROR = 0
    DIALOG_TYPE_INFO = 1 


    CHOOSER_CANCEL = 0 
    CHOOSER_OK = 1


Η δήλωση της κύριας κλάσης μας και μερικές σταθερές που θα χρησιμοποιήσουμε παρακάτω.


    def __init__(self): 
        builder = gtk.Builder()
        builder.add_from_file("backgroundxml.glade")
        builder.connect_signals(self) 
        self.backgroundxml = builder.get_object("backgroundxml") 
        self.picchooser = builder.get_object("picchooser") 
        self.picbuffer = builder.get_object("picbuffer") 
        self.xmlchooser = builder.get_object("xmlchooser") 
        self.xmlfile = builder.get_object("xmlfile") 
        self.duration = builder.get_object("duration") 
        self.change = builder.get_object("change") 
        self.xmlfilter = builder.get_object("xmlfilter") 
        self.xmlfilter.add_pattern('*.xml') 
        self.picfilter = builder.get_object("picfilter") 
        self.picfilter.add_pattern('*.jpg') 
        self.picfilter.add_pattern('*.png') 
        self.picfilter.add_pattern('*.JPG') 
        self.picfilter.add_pattern('*.PNG')


Η μέθοδος __init__ η οποία φτιάχνει το κυρίως αντικείμενο μας από την κύρια κλάση μας.
Ορίζουμε από ποιο αρχείο θα βρει ο builder τις οδηγίες κατασκευής των παραθύρων. Στην συνέχεια συνδέουμε τα σήματα με τις μεθόδους μας. Ορίζουμε μεταβλητές για το κυρίως παράθυρο και για τα παράθυρα επιλογής αρχείων, μεταβλητές για το buffer και τα πεδία εισόδου και στο τέλος ορίζουμε τα δύο φίλτρα αρχείων μας και ορίζουμε σε καθένα από αυτά τις τιμές που θέλουμε να φιλτράρουν.


    def close_window(self, widget, data=None): 
        gtk.main_quit()


    def delete_chooser(self, widget, data=None): 
        return True  


Το GTK όταν καλεί μία μέθοδο ως αποτέλεσμα ενός σήματος του παραθύρου στέλνει και ένα ή δύο ορίσματα. Το πρώτο είναι το αντικείμενο (widget) από το οποίο προήλθε το σήμα και σε ορισμένες περιπτώσεις στέλνει και ορισμένα στοιχεία σχετικά με το αντικείμενο (data). Σε ένα drop-down για παράδειγμα όταν ο χρήστης επιλέξει μια τιμή από αυτό στέλνει το drop-down αντικείμενο και τον αριθμό της επιλογής ξεκινώντας από το 0 για την πρώτη κατά σειρά επιλογή του drop-down. Γι' αυτό και όλες οι μέθοδοί μας που έχουν να κάνουν με σήματα ορίζονται με αυτό τον τρόπο:
def onoma_hanlder(self, widget, data=None):
Η close_window παραπάνω είναι υπεύθυνη για τον σωστό κλείσιμο του προγράμματος και η delete_chooser αποτρέπει την καταστροφή των παραθύρων επιλογής αρχείων.


    def xmlbutton_click(self,widget,data=None):
        response=self.xmlchooser.run()
        if response == self.CHOOSER_OK: 
            self.xmlfile.set_text(self.xmlchooser.get_filename())
        self.xmlchooser.hide()


Η xmlbutton_click καλείται όταν ο χρήστης πατήσει το αντίστοιχο κουμπί επιλογής αρχείου XML. Το παράθυρο επιλογής εμφανίζεται καλώντας την μέθοδο run() του διαλόγου (το gtk.Filechooser αντικείμενο κληρονομεί από το gtk.Dialog). Ο έλεγχος πηγαίνει στο καινούργιο παράθυρο και επιστρέφει στο κυρίως όταν καταστραφεί ή καλέσει την μέθοδο response() η οποία επιστρέφει και τιμή. CHOOSER_OK είναι μία από τις δύο σταθερές μας που έχουμε ορίσει στην αρχή. Την επιστρέφει η responce όταν ο χρήστης πατήσει “Ένταξει”, αν διαλέξει “Άκυρο” επιστρέφει CHOOSER_CANCEL.  Αν είναι ΟΚ βάζουμε στο αντίστοιχο πεδίο (xmlfile) αυτό που έχει διαλέξει. Αν έχει πατήσει το “Άκυρο” δεν κάνουμε τίποτα, και στις δύο περιπτώσεις κρύβουμε το παράθυρο. Μετά από αυτό ο έλεγχος έχει επιστρέψει στο κυρίως παράθυρο. 


    def picbutton_click(self,widget,data=None): 
        response = self.picchooser.run() 
        if response == self.CHOOSER_OK:
            buffer1=self.picbuffer.get_text(self.picbuffer.get_start_iter(), self.picbuffer.get_end_iter())
            for fn in self.picchooser.get_filenames(): 
                buffer1=buffer1+fn+'\n' 
            self.picbuffer.set_text(buffer1)
        self.picchooser.hide()


H picbutton_click είναι ίδια με την προηγούμενη. Η μόνη της διαφορά είναι το ότι έχουμε να γεμίσουμε ένα buffer με πολλές γραμμές και όχι μόνο με μία τιμή. Γι' αυτό και βάζουμε στην μεταβλητή buffer1 ότι έχει η picbuffer μέχρι εκείνη την στιγμή και μετά προσθέτουμε κάθε αρχείο που έχει διαλέξει ο χρήστης με ένα “\n” στο τέλος. Όταν ολοκληρωθούν όλα τα αρχεία βάζουμε την buffer1 πίσω στην picbuffer προς εμφάνιση.


    def clearbutton_click(self,widget,data=None):
        self.picbuffer.set_text(u'')  


Η clearbutton_click καλείται όταν ο χρήστης πατήσει “Εκκαθάριση”. Διαγράφει ότι έχει η picbuffer βάζοντας ένα άδειο string.


    def picchooser_cancel_clicked(self, widget, data=None):
        self.picchooser.response(self.CHOOSER_CANCEL)


    def picchooser_ok_clicked(self, widget, data=None):
        self.picchooser.response(self.CHOOSER_OK)  


    def xmlchooser_ok_clicked(self,widget,data=None):
        self.xmlchooser.response(self.CHOOSER_OK)


    def xmlchooser_cancel_clicked(self,widget,date=None): 
        self.xmlchooser.response(self.CHOOSER_CANCEL)  


Αυτές οι τέσσερις μέθοδοι είναι ταυτόσημες. Είναι υπεύθυνες για την επιστροφή της σωστής τιμής από τα δύο παράθυρα επιλογής αρχείων. Στο “Εντάξει” επιστρέφουν CHOOSER_OK και στο “Ακύρωση” CHOOSER_CANCEL. Χρησιμοποιούμε την response() μέθοδο για την επιστροφή η οποία κλείνει και το παράθυρο. Μπορούμε να τις αντικαταστήσουμε με μία η οποία να κάνει ακριβώς το ίδιο και για τα δύο παράθυρα;


    def dialog(self, text, dialogtype): 
        if dialogtype == self.DIALOG_TYPE_ERROR: 
            dialog=gtk.MessageDialog(parent=None,flags=gtk.DIALOG_MODAL & \
             gtk.DIALOG_DESTROY_WITH_PARENT, \
             type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
            dialog.set_title(u"Σφάλμα") 
            dialog.format_secondary_text(u"Διορθώστε τα παραπάνω προβλήματα.")
        elif dialogtype == self.DIALOG_TYPE_INFO:
            dialog=gtk.MessageDialog(parent=None,flags=gtk.DIALOG_MODAL & \
             gtk.DIALOG_DESTROY_WITH_PARENT,\
             type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK)
            dialog.set_title(u"Ενημέρωση")
        dialog.set_markup(u"<b>"+text+"</b>")  
        response=dialog.run()
        if response == gtk.RESPONSE_DELETE_EVENT or response == gtk.RESPONSE_OK:
            dialog.destroy()  


Η dialog είναι μια δική μας βοηθητική μέθοδος η οποία δημιουργεί ένα παράθυρο διαλόγου. Παίρνει δύο ορίσματα: text και dialogtype. Το text είναι το κείμενο που θα εμφανίσει και dialogtype είναι το είδος του διαλόγου. Αν θέλουμε να εμφανίσουμε μήνυμα λάθους την καλούμε με DIALOG_TYPE_ERROR, αν θέλουμε να εμφανίσουμε κάποια άλλη πληροφορία την καλούμε με DIALOG_TYPE_INFO. Ο διάλογος δημιουργείται με την gtk.MessageDIalog η οποία παίρνει σαν ορίσματα τα parent, flags,type, buttons. Η parent χρησιμοποιείται για να δηλώσουμε κάποιο πατρικό παράθυρο, στην flags δηλώνουμε αν θέλουμε να είναι modal το παράθυρο μας και αν θα καταστραφεί μαζί με το πατρικό. (Modal είναι όταν το παράθυρο έχει αποκλειστικά τον έλεγχο και ο χρήστης δεν μπορεί να γυρίσει στο πατρικό αν πρώτα δεν κλείσει το modal παράθυρο).Η type παίρνει το είδος του διαλόγου και η buttons ποια κουμπιά θα εμφανιστούν με τον διάλογο. Όταν θέλουμε να εμφανίσουμε λάθος δημιουργούμε ένα gtk.MESSAGE_ERROR διάλογο με ένα μόνο κουμπί, το “Εντάξει”. Για πληροφορία το δημιουργούμε σαν gtl.MESSAGE_INFO με ένα μόνο κουμπί πάλι. Το μήνυμα το έχουμε συμπεριλάβει μέσα σε <b></b> για να φαίνεται έντονο, και συμπληρωματικά αν είναι μήνυμα λάθους εμφανίζουμε και μια προτροπή, secondary_text. Εμφανίζουμε τον διάλογο με run() και περιμένουμε την απάντηση του χρήστη. Η απάντηση μπορεί να είναι είτε OK είτε DELETE (αν έχει πατήσει το X). Και στις δύο περιπτώσεις καταστρέφουμε το παράθυρο.


    def clicked_ok(self, widget, data=None):
        picfiles = self.picbuffer.get_text(self.picbuffer.get_start_iter(), self.picbuffer.get_end_iter())
        filelist=picfiles.split("\n") 
        filelist.remove('') 
        errortxt='' 
        now = datetime.now() 
        if not self.xmlfile.get_text().endswith('.xml'): 
            errortxt+=u'Το όνομα του XML αρχείου πρέπει να τελειώνει σε .xml\n'
        if not filelist :
            errortxt+=u'Πρέπει να διαλέξετε φωτογραφίες\n' 
        if not self.duration.get_text().isdigit() :
            errortxt+=u'Πρέπει να δώσετε την διάρκεια εμφάνισης (αριθμητικά)\n' 
        if not self.change.get_text().isdigit() :
            errortxt+=u'Πρέπει να δώσετε την διάρκεια εναλλαγής (αριθμητικά)\n'
        if errortxt:
            self.dialog(errortxt, self.DIALOG_TYPE_ERROR)
        else:
            with open(self.xmlfile.get_text(),'w') as f:
                try:
                    f.write("<background>\n") 
                    f.write("  <starttime>\n")
                    f.write("    <year>"+str(now.year)+"</year>\n")
                    f.write("    <month>"+str(now.month)+"</month>\n")
                    f.write("    <day>"+str(now.day)+"</day>\n")
                    f.write("    <hour>"+str(now.hour)+"</hour>\n")
                    f.write("    <minute>"+str(now.minute)+"</minute>\n")
                    f.write("    <second>"+str(now.second)+"</second>\n")
                    f.write("  </starttime>\n")
                    first=filelist[0]
                    for ln in range(len(filelist)):
                        f.write("  <static>\n")
                        f.write("    <duration>"+self.duration.get_text()+"</duration>\n")
                        f.write("    <file>"+filelist[ln]+"</file>\n")
                        f.write("  </static>\n")
                        f.write("  <transition>\n")
                        f.write("    <duration>"+self.change.get_text()+"</duration>\n")
                        f.write("    <from>"+filelist[ln]+"</from>\n")
                        if ln == len(filelist)-1:
                            f.write("    <to>"+first+"</to>\n")
                        else:
                            f.write("    <to>"+filelist[ln+1]+"</to>\n")
                        f.write("  </transition>\n")
                        f.write("</background>\n")
                    self.dialog(u"Επιτυχής δημιουργία αρχείου XML",self.DIALOG_TYPE_INFO) 
                except:
                    self.dialog(u"Πρόβλημα στην δημιουργία του αρχείου XML",self.DIALOG_TYPE_ERROR) 


Η clicked_ok είναι μέθοδος όπου δημιουργείται το αρχείο XML. Στην αρχή της φτιάχνουμε έναν πίνακα με τα περιεχόμενα του buffer και βάζουμε σε μια μεταβλητή την ώρα και ημερομηνία του συστήματος. Στην συνέχεια διενεργούμε ελέγχους για να επαληθεύσουμε τις εισόδους του χρήστη. Πρέπει να έχει δώσει αρχείο XML και αυτό να τελειώνει σε .xml, πρέπει να έχει διαλέξει εικόνες προς εμφάνιση και να έχει δώσει αριθμητικές τιμές στα πεδία με τους χρόνους. Για καθένα από αυτά, σε περίπτωση λάθους, προσθέτουμε και το αντίστοιχο λεκτικό σε μια μεταβλητή όπου και την εμφανίζουμε, αν έχει τιμή, με την dialog μέθοδό μας. Αν όλα είναι καλά ανοίγουμε το αρχείο XML προς εγγραφή και αρχίζουμε να γράφουμε σε αυτό τα δεδομένα όπως έχουμε περιγράψει εδώ. Με την επιτυχή δημιουργία του αρχείου εμφανίζουμε πληροφοριακό διάλογο ότι όλα τελείωσαν ομαλά, αλλιώς ένα μήνυμα λάθους.


    def main(self): 
        self.backgroundxml.show()
        gtk.main()
        
if __name__ == "__main__":
    app = BackgroundXML() 
    app.main() 


Το πρόγραμμα τελειώνει με τον κλασικό τρόπο ενός pygtk προγράμματος, την main μέθοδο και τον τρόπο που την καλούμε στην έναρξη.


Σώζουμε το αρχείο σαν backgroundxml.py στον ίδιο φάκελο με το glade αρχείο και το τρέχουμε με python backgroundxml.py. 



Εδώ ολοκληρώνεται μια πρώτη γνωριμία με το Glade, το GTK περιβάλλον και την python. Με λίγες γραμμές κώδικα σε python έχουμε έτοιμο το πρόγραμμα μας για την δημιουργία εναλλασσόμενων εικόνων στο φόντο του Gnome. Από εδώ και περά μπορεί ο καθένας να πειραματιστεί τόσο με το Glade και το γραφικό περιβάλλον όσο και με την python και την λογική του προγράμματος. Για παράδειγμα είναι χρήσιμο να προσθέσουμε έναν έλεγχο αν το αρχείο XML που έγραψε ο χρήστης υπάρχει ήδη ή όχι, να εμφανίζει ένα μήνυμα διαλόγου “Το αρχείο υπάρχει. Να το αντικαταστήσω;” και ανάλογα με την επιλογή του χρήστη να κάνει την αντικατάσταση ή όχι.
Σε επόμενο άρθρο θα ασχοληθούμε με την κατασκευή ενός προγράμματος σε gtk για την μαζική μετατροπή αρχείων text μεταξύ των διαφόρων τύπων κωδικοποίησης που υπάρχουν σήμερα, windows-1253, utf-8 κλπ

Το τελικό πρόγραμμα καθώς και αυτό το άρθρο σε pdf μορφή θα τα βρείτε εδώ.
Ορισμένα χρήσιμα links για περαιτέρω εξερεύνηση:
Glade
GTK
PyGTK
Python

No comments:

Post a Comment