Προγραμματισμός με GTK(...): Βασικές έννοιες προγραμματισμού σε gtk
Description: This is issue 20 of Magaz, the Hellenic LinuxMagazine
Keywords: magaz hellenic greek hellas greece hellug linux magazine

Magaz, The Greek Linux Magazine
Τεύχος #20
Περιληπτικά Περιεχόμενα Αναλυτικά Περιεχόμενα Mirrors Links Πληροφορίες Συντάκτες Αναζήτηση Επικοινωνία
Επόμενο Προηγούμενο Περιεχόμενα ### 4. Βασικές έννοιες προγραμματισμού σε gtk Ας ξεκινήσουμε με τις αρχικές έννοιες, που στη συνέχεια θα μας βοηθήσουν στην κατανόηση του προγραμματισμού σε gtk. ### 4.1 Βασικά χαρακτηριστικά Κάθε πρόγραμμα που θα φτιάχνουμε, πρέπει να περιέχει, μεταξύ άλλων, και τα εξής: Πρώτα από όλα, πρέπει να περιέχει τις βιβλιοθήκες που θα κάνουμε import. Για την gtk, αυτή είναι ή `gtk.h`, που βρίσκεται μέσα στο directory gtk
    #include <gtk/gtk.h>
Φυσικά, όπως όλα τα προγράμματα, περιέχει την πολύ γνωστή `main`. Αυτή είναι η πρώτη συνάρτηση που καλείται, και πρέπει οπωσδήποτε να υπάρχει, είναι int, γιατί πρέπει να επιστρέφει μια τιμή τύπου int στο shell (το γνωστό σε όλους μας exit status).
    int main(int argc, char *argv[]){
      ...
      ...
      }
Μέσα σε αυτήν τώρα, πρέπει να υπάρχει μια άλλη, η `gtk_init` που καλείται από όλα τα προγράμματα που είναι γραμμένα σε gtk για την αρχικοποίηση.
    gtk_init(&argc, &argv);
Επίσης, σε κάποιο σημείο πρέπει να καλείται η `gtk_main` η οποία δεν είναι τίποτα άλλο από την συνάρτηση η οποία περιμένει για ενέργειες του χρήστη, όπως το πάτημα ενός κουμπιού στο ποντίκι, ή το πάτημα ενός πλήκτρου.
    gtk_main();
Φυσικά δεν πρέπει να ξεχάσουμε την τιμή που θα επιστρέφει το όλο πρόγραμμά μας (είναι είπαμε int), και αυτό γίνεται με την γνωστή `return`
    return(0);
Αυτά είναι τα βασικά και απαραίτητα που πρέπει να περιέχει ένα πρόγραμμα σε gtk. Φυσικά, για να γραφεί ένα ολοκληρωμένο και λειτουργικό πρόγραμμα απαιτούνται πολύ περισσότερα που θα δούμε στη συνέχεια, γιαύτο μη βιάζεστε... Συνεχίστε το διάβασμα για την βουτά στα βαθιά... ))) ### 4.2 widgets, signals, κ.λπ. First things first... #### Widgets Πριν αρχίσουμε, να δούμε μερικά πράγματα, όπως πχ. τι είναι το gtk_widget που θα συναντάμε κατ κόρον. Είναι μια δομή, που μέσω αυτής μπορούμε να έχουμε πρόσβαση σε όλα τα widgets της gtk. Αυτά μπορεί να είναι buttons, radio buttons, check buttons, lists, combo boxes, boxes, toolbars, και γενικότερα οτιδήποτε βλέπετε στα παραθυράκια των προγραμμάτων gtk. #### signals Ο έλεγχος σε ένα πρόγραμμα gtk δίδεται χρησιμοποιώντας τα signals. Ας σας εξηγήσω όμως με ένα παράδειγμα. Για να συνδέσουμε ένα συμβάν με μια λειτουργία, μπορούμε να χρησιμοποιούμε μια συνάρτηση όπως η `gtk_signal_connect`. Αυτή, συντάσσεται όπως βλέπουμε παρακάτω, και επιστρέφει μια τιμή τύπου gint (Μια μορφή ακεραίου που χρησιμοποιεί η gtk).
         gint gtk_signal_connect(GtkObject *object, gchar name, GtkSignalFunc func, gpointer func_data);
Παρακάτω δίνονται οι απαραίτητες εξηγήσεις για να καταλάβετε τι κάνει το κάθε όρισμα: `GtkObject *object` Το widget που θα παρακολουθούμε για signal `gchar name` Το signal για το οποίο παρακολουθούμε `GtkSignalFunc func` Η συνάρτηση που θα κληθεί όταν γίνει trap στο σινιάλο που παρακολουθούμε `gpointer func_data` Το όρισμα που θα περάσει στην καλούμενη συνάρτηση (μπορεί να είναι πχ, το πλήκτρο που πατήθηκε) Το τρίτο όρισμα, είναι μια συνάρτηση που δέχεται σαν ορίσματα ένα δείκτη (pointer) στο widget από το οποίο προκλήθηκε το signal, και ένα δείκτη που αναφέρεται στο τέταρτο όρισμα της καλούσας συνάρτησης (το func_data δηλαδή). ώστε να ξέρει τι να κάνει με τα δεδομένα που της εστάλησαν (ΜΠΕΡΔΕΥΤΗΚΑΤΕ;) ### 4.3 Γεια σας μάγκες! Το πρώτο πρόγραμμα σε gtk Επειδή ήδη αρχίσαμε να κολυμπάμε σε βαθύτερα νερά, ας φτιάξουμε ένα μικρό προγραμματάκι, και ας το εξηγήσουμε στη συνέχεια. Κάντε copy-paste, σε ένα αρχείο το παρακάτω:
    #include <gtk/gtk.h>
    void hello(GtkWidget *widget, gpointer data){
        g_print("Hello Magez!");
        }

    gint del_eve(GtkWidget *widget, GdkEvent *event, gpointer data){
        g_print("close pressed\n");
        return(TRUE);
        }

    void dest(GtkWidget *widget, gpointer data){
        gtk_main_quit();
        }

    int main(int argc, char *argv[]){
        GtkWidget *window, *button;

        gtk_init(&argc, &argv);

        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

        gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(del_eve), NULL);
        gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(dest), NULL);

        gtk_container_set_border_width(GTK_CONTAINER(window), 10);

        button = gtk_button_new_with_label("Hello Magez");

        gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(hello), NULL);
        gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window));

        gtk_container_add(GTK_CONTAINER(window), button);

        gtk_widget_show(button);
        gtk_widget_show(window);

        gtk_main();

        return(0);
        }
Ονομάστε το αρχείο HelloMagez.c και αποθηκεύστε το κάπου που έχετε δικαιώματα (πχ. στο home directory σας). Παρακάτω θα δούμε πως μπορούμε να δημιουργήσουμε το εκτελέσιμο αρχείο. ### 4.4 Εξήγηση του Hello Magez Ας δούμε τώρα πως δουλεύει. Θα εξηγούμε το πρόγραμμα με μικρά βήματα. Αλλά ας μην αρχίσουμε από την αρχή, πάμε κατευθείαν στην main. `GtkWidget *window, *button;` Εδώ ορίζουμε ότι θα χρησιμοποιήσουμε δύο widgets, με ονόματα `window` και `button` `gtk_init(&argc, &argv);` Η γνωστή gtk_init. Την αναφέραμε προηγουμένως. `window = gtk_window_new(GTK_WINDOW_TOPLEVEL);` Ορισμός στο widget window ενός νέου παραθύρου τύπου `GTK_WINDOW_TOPLEVEL`, δηλαδή κανονικού παραθύρου προγράμματος. `gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(del_eve), NULL);` Εδώ αρχίζουν τα δύσκολα. Μη φοβηθείτε όμως, θα τα εξηγήσουμε όλα. Αυτό που κάνουμε, είναι να συνδέσουμε το συμβάν `delete_event` με την συνάρτηση `del_eve`. To `delete_event` το στέλνει ο window manager που χρησιμοποιούμε όταν πατήσουμε το close, ή αντίστοιχα το κουμπί close στην bar του προγράμματος. Γιατί το παγιδεύουμε αυτό; Μα γιατί ίσως να θέλουμε να κάνουμε κάποιες εργασίες πριν να κλείσουμε το παράθυρο, πχ. να αποθηκεύσουμε ένα αρχείο ρυθμίσεων, ή να εμφανίσουμε ένα μήνυμα του τύπου ``Είστε σίγουρος;'' To `GTK_OBJECT` και το `GTK_SIGNAL_FUNC` είναι ουσιαστικά μακροεντολές, που ελέγχουν αν είναι σωστές οι παράμετροι που περνάμε στην `gtk_signal_connect` και (σύμφωνα με μερικούς-μερικούς) βοηθάει να είναι ο κώδικας πιο ευανάγνωστος και πιο κατανοητός. `gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(dest), NULL);` `Aλλη μια σύνδεση. Σε αυτή συνδέουμε το συμβάν `destroy` με τη συνάρτηση `dest`. Το συμβάν `destroy` συμβαίνει όταν δίνουμε στο `delete_event` την τιμή FALSE, ή όταν καλούμε το `gtk_widget_destroy()` που είναι μια συνάρτηση στην οποία περνάμε σαν παράμετρο το όνομα του παραθύρου που θέλουμε να καταστρέψουμε (κλείσουμε). Με αυτό τον τρόπο, με μια συνάρτηση, ελέγχουμε και τις δύο περιπτώσεις. `gtk_container_set_border_width(GTK_CONTAINER(window), 10);` Αυτή η εντολή, απλά θέτει μια ιδιότητα για ένα αντικείμενο. Συγκεκριμένα, την ιδιότητα border στο widget `window`, που είναι ο χώρος γύρω από το παράθυρο που μένει ανεκμετάλλευτος και δεν μπορεί να χρησιμοποιηθεί από άλλα widgets. Αυτό το κάνουμε για αισθητικούς λόγους. Το `GTK_CONTAINER` είναι και αυτό μια μακροεντολή, για type casting, όπως τα `GTK_OBJECT` και `GTK_SIGNAL_FUNC`. `button = gtk_button_new_with_label("Hello Magez");` Συνάρτηση για τη δημιουργία ενός κουμπιού που γράφει "Hello Magez". Φυσικά, κουμπιά μπορούν να δημιουργηθούν και αλλιώς, χωρίς να είμαστε αναγκασμένοι να τους δώσουμε ένα κείμενο, σε περίπτωση πχ. που το κείμενο θα εξαρτάται από κάποιες μεταβλητές, ή θα πρέπει να αλλάξει μετά από λίγο. `gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(hello), NULL);` Συνδέουμε το click στο κουμπί, με τη συνάρτηση `hello`. Αυτό είναι πολύ απλό, και εύκολο στην κατανόηση. `gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window));` Να και κάτι καινούριο. Με αυτή την εντολή, παρατηρούμε ότι μπορούμε να συνδέσουμε πολλές συναρτήσεις με ένα event (στην περίπτωση αυτή με το click του ποντικιού στο κουμπί `button`. Αυτό που θα συμβεί είναι ότι πρώτα θα καλέσουμε τη συνάρτηση `hello` και αμέσως μετά τη συνάρτηση `gtk_widget_destroy` που είναι μια συνάρτηση που ``καταστρέφει'' το widget που της δίνεται σαν όρισμα, στη συγκεκριμένη περίπτωση το `window`, που είναι το παράθυρο του προγράμματός μας. Θα παρατηρήσατε ότι εδώ χρησιμοποιείται η `gtk_signal_connect_object` αντί της `gtk_signal_connect`. Αυτό συμβαίνει γιατί πρέπει να περάσουμε σαν όρισμα το widget που πρέπει να καταστραφεί. Περισσότερα για τα signals αργότερα. `gtk_container_add(GTK_CONTAINER(window), button);` Κι άλλα καινούρια! η `gtk_container_add` είναι μια συνάρτηση που προσθέτει ένα widget σε ένα container. Εδώ, widget=`button` και container=`window`. Σημειώστε ότι ένα `gtk_container` μπορεί να περιέχει μόνο ένα widget. Υπάρχουν όμως άλλα widgets, που πάνω τους μπορούν να φιλοξενούν πολλά άλλα widgets. Αυτό, στην ενότητα των widgets θα αναλυθεί πολύ καλύτερα. Προς το παρών αρκεστείτε σε αυτό, και τυχόν απορίες σας θα λυθούν παρακάτω. `gtk_widget_show(button);` Αυτή η συνάρτηση εμφανίζει το widget που δέχεται σαν όρισμα. Παρατηρείστε ότι τα widgets, δεν εμφανίζονται μόνα τους. Δεν φτάνει δηλαδή η συνάρτηση `gtk_container_add` για να εμφανιστεί. Πρέπει πρώτα να προστεθούν όλα, και μετά να εμφανιστούν. Αυτό, μας προστατεύει από συμπεριφορές του τύπου να γίνονται όλα render στην οθόνη ένα-ένα το οποίο οπτικά είναι πολύ άσχημο, πιστέψτε με. Εμφανίζουμε λοιπόν με την συγκεκριμένη εντολή το κουμπάκι. `gtk_widget_show(window);` `Οπως και το παραπάνω, μόνο που εδώ εμφανίζουμε το παράθυρο. `gtk_main();` Περνάμε τον έλεγχο στην `gtk_main` όπως περιγράψαμε προηγουμένως. `retutn(0);` Είναι το exit status του προγράμματός μας. Θα μπορούσαμε με κάποιο έλεγχο να είχαμε διαφορετικό exit status, ανάλογα με το αν το πρόγραμμα απέτυχε, αν το πρόγραμμα δεν ολοκληρώθηκε, κ.λπ. Και ας ρίξουμε μια γρήγορη ματιά στις συναρτήσεις που περιέχει το πρόγραμμα. Αν και είναι πολύ εύκολες στην κατανόηση, υπάρχουν κάνα-δυο σημεία που χρειάζονται εξήγηση. `void hello(GtkWidget *widget, gpointer data)` `Οπως βλέπουμε, είναι μια συνάρτηση που απλά καλείται όταν πατήσει ο χρήστης το πλήκτρο, και το μόνο που κάνει είναι να τυπώνει Hello Magez και να προσθέτει μια νέα γραμμή. `gint del_eve(GtkWidget *widget, GdkEvent *event, gpointer data)` Η συνάρτηση που καλείται όταν πατηθεί το κουμπί που τερματίζει το πρόγραμμα. Επιστρέφει τιμή ακεραίου, και όπως βλέπουμε (στο συγκεκριμένο παράδειγμα τουλάχιστον) επιστρέφει TRUE. Αυτός είναι και ο λόγος που δεν έχουμε έξοδο από το πρόγραμμα όταν -θεωρητικά- το κλείνουμε με το κουμπί κλεισίματος. Αν η τιμή που επιστρέφει αλλαχθεί σε FALSE, τότε θα έχουμε έξοδο από το πρόγραμμα (δοκιμάστε το). Αυτό συμβαίνει, γιατί η συνάρτηση που καλείται από την `delete_event` εξ ορισμού από τo gtk επιστρέφει μια τιμή που αν είναι FALSE, καλεί το event `destroy` (θυμηθείτε ότι το έχουμε συνδέσει με τη συνάρτηση `dest`) `void dest(GtkWidget *widget, gpointer data)` Με τη συνάρτηση αυτή έχουμε έξοδο από το πρόγραμμα, μέσω μιας ενσωματωμένης στο gtk συνάρτησης, της `gtk_main_quit` ### 4.5 Πως θα το τρέξετε. Για να κάνετε compile to πρόγραμμα, χρησιμοποιήστε την εντολή:
    gcc -Wall -g HelloMagez.c -o hellomagez `gtk-config --cflags` `gtk-config --libs`
`Οσοι δεν ξέρουν τι είναι αυτό, man gcc. Μόλις ολοκληρωθεί η παραπάνω εντολή, στο τρέχον directory θα έχει δημιουργηθεί το αρχείο hellomagez, που είναι και το εκτελέσιμο. Τρέξτε το! Επόμενο Προηγούμενο Περιεχόμενα