+++ title = 'Προγραμματισμός με GTK(\...)' date = '1999-10-01T00:00:00Z' description = '' author = 'Παπαδογιαννάκης Βαγγέλης για το Magaz ( magaz.hellug.gr(http://magaz.hellug.gr) )' issue = ['Magaz 20'] issue_weight = 4 +++ ---------------------------------------------------------------------------------------------------------------------------------------------------------------- *GTK\... Gimp ToolΚit. Αυτό το πρόγραμμα τα άρχισε όλα. Από τη στιγμή που βγήκε το gimp, δημιουργήθηκαν ένα σωρό εφαρμογές που χρησιμοποιούν τις βιβλιοθήκες απεικόνισής του, οι οποίες σημειωτέον είναι από τις καλύτερες που υπάρχουν. Πάνω σε αυτή βασίζεται ολόκληρο το gnome, και είναι από τα προτιμούμενα της REDHAT και άλλων φυσικά διανομών. \`Ελα όμως που ο προγραμματισμός σε GTK είναι σχετικά δύσκολος\... Μη μασάτε, δεν είναι και τόσο όσο νομίζετε\... Είναι ευκολούτσικος, και δεν θα σας πάρει πάνω από 10 λεπτά διάβασμα για να καταφέρετε να στήσετε το πρώτο σας προγραμματάκι σε GTK. Πολλά από τα σημεία αυτού του άρθρου, έχουν βασιστεί στα tutorials της gtk. όπως αυτά έρχονται με το gtk-devel.rpm πακέτο. Για περισσότερες πληροφορίες σε σημεία του κειμένου, παρακαλώ ρίξτε τους μια ματιά.* ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **1. Περί αυτού του άρθρου. (the boring stuff)** ---------------------------------------------------------------------- **2. Που θα βρείτε το GTK** ------------------------------------------------- **3. Προϋποθέσεις** ----------------------------------------- **4. Βασικές έννοιες προγραμματισμού σε gtk** ------------------------------------------------------------------- - [4.1 Βασικά χαρακτηριστικά](#ss4.1) - [4.2 widgets, signals, κ.λπ.](#ss4.2) - [4.3 Γεια σας μάγκες! Το πρώτο πρόγραμμα σε gtk](#ss4.3) - [4.4 Εξήγηση του Hello Magez](#ss4.4) - [4.5 Πως θα το τρέξετε.](#ss4.5) **5. Tοποθέτηση των widgets** --------------------------------------------------- - [5.1 Boxes, ή αλλιώς κουτιά :)](#ss5.1) - [5.2 Tοποθέτηση των widgets, partII (λέγε με tables)](#ss5.2) - [5.3 Η συνέχεια](#ss5.3) ### [1. Περί αυτού του άρθρου. (the boring stuff)]{#s1} Αυτό το άρθρο, δεν έχει σκοπό να σας μάθει gtk, αλλά να σας εξοικειώσει με τις δυσκολότερες έννοιες στο toolkit αυτό, ώστε να αρχίσετε μόνοι σας να προγραμματίζετε. Σε καμία περίπτωση δεν είμαι υπεύθυνος εγώ ειδικά και το magaz γενικότερα αν κάνετε μπάχαλο τον υπολογιστή σας. Στο δικό μου δουλεύουν όλα μια χαρά, και δεν έπαθε τίποτα από όσα γράφω σε αυτό το άρθρο. \`Εχω προγραμματίσει να ολοκληρωθεί σε τρεις συνέχειες. Πολύ πιθανόν και 4. Αυτό δεν σημαίνει ότι είμαι υποχρεωμένος να συνεχίσω και με τα άλλα, αλλά δεσμεύομαι να προσπαθήσω. Και αυτό το λεω, γιατί στο παρελθόν είχα κάνει κάτι αντίστοιχό που δεν ολοκληρώθηκε (bash, enlightenment) αλλά είχα δικαιολογία και για τα δύο. Μπορεί κάτι να συμβεί και να μη συνεχιστεί. Σε μια τέτοια περίπτωση, παρακαλώ όποιος έχει την όρεξη να συνεχίσει. Επίσης, δεν υπάρχει ούτε ΜΙΑ πιθανότητα να μην έχω κάνει εκφραστικά -και ίσως νοηματικά- λαθάκια. Είναι η δυσκολία που υπάρχει στη μετάφραση αυτού που θέλουμε να εκφράσουμε, καθώς και η μεταφορά του σε λέξεις. \`Οσοι από εσάς προγραμματίζουν, ή έχουν τέλος πάντων μια εξοικείωση με την αγγλική ορολογία, καταλαβαίνετε τι θέλω να πω. Να είσαστε λοιπόν ελαστικοί στην κρίση σας. \`Οσοι από εσάς ασχολούνται με gtk και βρουν τίποτα ανακρίβειες (πράγμα πολύ πιθανό γιατί ναι μεν προγραμματίζω σε gtk αλλά δεν χρησιμοποιώ όλα όσα εξηγώ σε αυτό το άρθρο), παρακαλώ να μου στείλετε με [mail στο papas\@hellug.gr](mailto:papas@hellug.gr) τις παρατηρήσεις σας, και θα προσπαθήσω να διορθώσω τα λάθη σε ένα από τα επόμενα άρθρα. Καλό διάβασμα, και happy programming! ### [2. Που θα βρείτε το GTK]{#s2} Κατά πάσα πιθανότητα, το έχετε ήδη εγκατεστημένο στον υπολογιστή σας. Για όσους από εσάς αρνούνται να εγκαταστήσουν το GTK, επιμένοντας σε υποκατάστατα του τύπου QT - καλά, μη βαράτε :) - σας παραπέμπω στη διεύθυνση να το κατεβάσετε σε ότι μορφή θέλετε. Αν θέλετε **rpm**, μην ξεχάσετε να πάρετε και το **devel** πακέτο, γιατί αυτό ουσιαστικά θα μας χρειαστεί ώστε να κάνουμε τα προγράμματά μας να τρέχουν. ### [3. Προϋποθέσεις]{#s3} Δεν υπάρχουν ουσιαστικές προϋποθέσεις. Απλά θα πρέπει να ξέρετε τα βασικά του προγραμματισμού, λίγη C, και να έχετε πολύ όρεξη. Επίσης, θα πρέπει να έχετε στο PATH σας το script **gtk-config**, για να αποφύγουμε να βρίσκουμε λεπτομέρειες που είναι διαφορετικές για κάθε μηχάνημα. Περισσότερες πληροφορίες θα βρείτε στο **man page** του gtk-config (`man gtk-config`) ### [4. Βασικές έννοιες προγραμματισμού σε gtk]{#s4} Ας ξεκινήσουμε με τις αρχικές έννοιες, που στη συνέχεια θα μας βοηθήσουν στην κατανόηση του προγραμματισμού σε gtk. ### [4.1 Βασικά χαρακτηριστικά]{#ss4.1} Κάθε πρόγραμμα που θα φτιάχνουμε, πρέπει να περιέχει, μεταξύ άλλων, και τα εξής: Πρώτα από όλα, πρέπει να περιέχει τις βιβλιοθήκες που θα κάνουμε import. Για την gtk, αυτή είναι ή `gtk.h`, που βρίσκεται μέσα στο directory **gtk** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- #include ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Φυσικά, όπως όλα τα προγράμματα, περιέχει την πολύ γνωστή `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, κ.λπ.]{#ss4.2} 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]{#ss4.3} Επειδή ήδη αρχίσαμε να κολυμπάμε σε βαθύτερα νερά, ας φτιάξουμε ένα μικρό προγραμματάκι, και ας το εξηγήσουμε στη συνέχεια. Κάντε copy-paste, σε ένα αρχείο το παρακάτω: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- #include 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]{#ss4.4} Ας δούμε τώρα πως δουλεύει. Θα εξηγούμε το πρόγραμμα με μικρά βήματα. Αλλά ας μην αρχίσουμε από την αρχή, πάμε κατευθείαν στην 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 Πως θα το τρέξετε.]{#ss4.5} Για να κάνετε compile to πρόγραμμα, χρησιμοποιήστε την εντολή: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- gcc -Wall -g HelloMagez.c -o hellomagez `gtk-config --cflags` `gtk-config --libs` ---------------------------------------------------------------------------------------------------------------------------------------------------------------- \`Οσοι δεν ξέρουν τι είναι αυτό, **man gcc**. Μόλις ολοκληρωθεί η παραπάνω εντολή, στο τρέχον directory θα έχει δημιουργηθεί το αρχείο **hellomagez**, που είναι και το εκτελέσιμο. Τρέξτε το! ### [5. Tοποθέτηση των widgets]{#s5} Το πιο πιθανό είναι να μην δημιουργήσουμε ποτέ μια εφαρμογή που θα έχει μονάχα ένα *widget*, όπως στην περίπτωση του **HelloMagez**. Μάλλον, αυτό που θα θέλουμε να κάνουμε θα αποτελείται από περισσότερα από ένα *widget*. Ας δούμε πως μπορούμε να τα τοποθετήσουμε στο παράθυρο του προγράμματός μας. ### [5.1 Boxes, ή αλλιώς κουτιά :)]{#ss5.1} Τα *boxes* θα γίνουν οι καλύτεροι φίλοι μας στη διαδικασία σύνταξης εφαρμογών για **gtk**, αλλά - πιστέψτε με - και οι χειρότεροί μας εχθροί. Θα μάθουμε να ζούμε με τα *boxes*, και να είστε σίγουροι ότι δεν θα τα γλιτώσουμε. Τι είναι όμως αυτά τα *boxes*;\ Είναι widgets, όπως και όλα τα άλλα. Απλά, μπορούν να περιέχουν περισσότερα από 1 widgets πάνω τους. Δεν είναι ορατά, και χρησιμοποιούνται μονάχα για την ομαδοποίηση αντικειμένων. Τα boxes μπορεί να είναι δύο ειδών, ανάλογα με τον προσανατολισμό που θέλουμε να πάρουν τα προς τοποθέτηση σε αυτό *widgets*: - **HORIZONTAL BOXES**\ Τα τοποθετημένα σε αυτό *widgets* θα μπουν στη σειρά από αριστερά προς τα δεξιά ή από τα δεξιά προς τα αριστερά όπως θα δούμε παρακάτω στην εξήγηση των συναρτήσεων **`gtk_box_pack_start`** και **`gtk_box_pack_end`**.\ H δημιουργία ενός τέτοιου *box* επιτυγχάνεται με τη χρήση της συνάρτησης **`gtk_hbox_new`**. - **VERTICAL BOXES**\ Τα τοποθετημένα σε αυτό *widgets* θα μπουν στη σειρά από πάνω προς τα κάτω ή από κάτω προς τα πάνω, πάλι με χρήση των **`gtk_box_pack_start`** και **`gtk_box_pack_end`**.\ Σε γενικές γραμμές, η δημιουργία ενός τέτοιου *box* επιτυγχάνεται με τη χρήση της συνάρτησης **`gtk_hbox_new`**. #### Δημιουργία, σύνταξη και παραμέτροι. Για τη δημιουργία ενός box, χρησιμοποιούμε τις **`gtk_hbox_new`** και **`gtk_vbox_new`** όπως αναφέραμε και παραπάνω.\ Η σύνταξη και ο τύπος των συναρτήσεων αυτών είναι: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- GtkWidget *gtk_hbox_new(gint homogeneous, gint spacing); GtkWidget *gtk_vbox_new(gint homogeneous, gint spacing); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Στη συνέχεια θα αναφέρομαι μονάχα στην εντολή `gtk_hbox_new` και ότι ισχύει για αυτήν ισχύει και για την `gtk_vbox_new`. \`Οπως παρατηρούμε, επιστρέφει τιμή τύπου `GtkWidget`, και παίρνει δύο τιμές, τύπου *gint*, τις **homogenous** και **spacing**. Η **homogenous** μπορεί να είναι TRUE ή FALSE (και φυσικά οποιαδήποτε λογική έκφραση) και μας δίνει τη δυνατότητα να ελέγξουμε αν τα *widgets* που θα τοποθετηθούν στο box θα έχουν το ίδιο πλάτος όλα ή όσο χρειάζεται το κάθε ένα. Η παράμετρος **spacing** καθορίζει πόσα pixels θα απέχουν μεταξύ τους τα *widgets*. #### Εργασία με τα boxes Δεν πιστεύω να σας τρόμαξα με τα όσα έγραψα για τα boxes στην προηγούμενη ενότητα\... \`Οπως θα δείτε, η εργασία με τα boxes είναι μια εύκολη υπόθεση, αν ξέρετε να δουλεύετε τις συναρτήσεις **`gtk_box_pack_start`** και **`gtk_box_pack_end`**. Στη συνέχεια θα δούμε πόσο εύκολη είναι η εργασία με αυτές τις συναρτήσεις. **`gtk_box_pack_start`**\ **`gtk_box_pack_end`** Η μορφή αυτών των συναρτήσεων είναι: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- void gtk_box_pack_start(GtkBox *box, GtkWidget *child, gint expand, gint fill, gint padding ); void gtk_box_pack_end(GtkBox *box, GtkWidget *child, gint expand, gint fill, gint padding ); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Δεν επιστρέφουν καμία τιμή (είναι void απ\' ότι βλέπετε) και τα ορίσματα που δέχονται εξηγούνται παρακάτω: - **`GtkBox *box`**\ Το όνομα του *box* στο οποίο αναφερόμαστε. - **`GtkWidget *child`**\ Το όνομα του *widget* το οποίο προσθέτουμε. - **`gint expand`**\ Είναι μορφής *gint*, και αυτό που περιγράφει είναι αν το *widget* που τοποθετούμε θα πιάνει όλη την περιοχή του *box*. Μπορούμε να του αποδώσουμε τις τιμές TRUE και FALSE. TRUE στην περίπτωση που θέλουμε να πιάνει όλη την διαθέσιμη περιοχή, και FALSE αν θέλουμε να πιάνει μόνο όση περιοχή του είναι απαραίτητη. Δίδοντας την τιμή FALSE μπορούμε να επιτύχουμε (αριστερή στην περίπτωση της **`gtk_box_pack_start`** ή δεξιά στην περίπτωση της **`gtk_box_pack_end`**) στοίχιση.\ \`Οπως καταλαβαίνετε, αν δοθεί η τιμή TRUE, δεν έχει σημασία πια από τις **`gtk_box_pack_start`** ή **`gtk_box_pack_end`** χρησιμοποιήσουμε. - **`gint fill`**\ Η τιμή αυτής της παραμέτρου έχει σημασία μόνο αν η τιμή της **`expand`** είναι TRUE. Και αυτό γιατί περιγράφει αν ο επιπλέον χώρος που μένει μετά τη δημιουργία των *widgets* θα ανήκει σε αυτά (Δηλ. θα τα μεγαλώσει) στην περίπτωση που πάρει την τιμή TRUE ή αν θα ανήκει στο *box* σαν αδιάθετος χώρος στην περίπτωση που της δώσουμε την τιμή FALSE - **`gint padding`**\ Αριθμός τύπου *gint*, που καθορίζει πόσος χώρος (σε pixels) γύρω από το κάθε *widget* δεν θα χρησιμοποιείται. ### [5.2 Tοποθέτηση των widgets, partII (λέγε με tables)]{#ss5.2} Υπάρχει και άλλος ένας τρόπος για να στοιχίσουμε τα widgets στο παράθυρο της εφαρμογής μας. Αυτός επιτυγχάνεται με τη χρήση της συνάρτησης **`gtk_table_new`** που δημιουργεί έναν πίνακα στο παράθυρο της εφαρμογής μας. Φυσικά, και αυτή η συνάρτηση χρησιμοποιείται μόνο για την τοποθέτηση των διαφόρων *widgets* στο παράθυρο, και δεν σχεδιάζεται κανένας πίνακας στην εφαρμογή μας.\ Η σύνταξή της είναι η εξής: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- GtkWidget *gtk_table_new(gint rows, gint columns, gint homogeneous ); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Είναι προφανές ότι rows είναι ο αριθμός των γραμμών του πίνακά μας και columns ο αριθμός των στηλών του. Το homogenous περιγράφει τον τρόπο που τα κελιά του πίνακα θα διανέμονται και θα τοποθετούνται πάνω στο παράθυρό μας. Αν είναι TRUE, τότε όλα τα κελιά του πίνακα μεγαλώνουν ή μικραίνουν, σύμφωνα με το μεγαλύτερο *widget* στον πίνακα. Αν πάρει την τιμή FALSE, το μέγεθος ορίζεται από το πιο μακρύ *widget* στην κάθε στήλη και το πιο ψηλό *widget* στην σειρά. Αυτό που ουσιαστικά συμβαίνει είναι ότι αν το HOMOGENEOUS είναι TRUE, όλα τα *widgets* έχουν το ίδιο μέγεθος, και είναι ομοιόμορφα τοποθετημένα πάνω στο box. Η διάταξη των κελιών του πίνακα, διαμορφώνεται από το 0 μέχρι τις τιμές *rows* και *columns*. Μoιάζει δηλαδή με το παρακάτω. Η αρίθμηση όπως βλέπετε, ξεκινάει από την πάνω αριστερή γωνία. ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 0 1 2 0+----------+----------+ | | | 1+----------+----------+ | | | 2+----------+----------+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Για την τοποθέτηση ενός widget σε ένα πίνακα, χρησιμοποιούμε την συνάρτηση **`gtk_table_attach`**, της οποίας η σύνταξη είναι η παρακάτω ---------------------------------------------------------------------------------------------------------------------------------------------------------------- void gtk_table_attach(GtkTable *table, GtkWidget *child, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach, gint xoptions, gint yoptions, gint xpadding, gint ypadding); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Δεν επιστρέφei καμία τιμή και τα ορίσματα που δέχεται είναι - **`GtkTable *table`**\ Το Table στο οποίο αναφερόμαστε - **`GtkWidget *child`**\ Το widget που θέλουμε να τοποθετήσουμε - **`gint left_attach`**\ Η αριστερή συντεταγμένη από όπου θα αρχίζει το widget - **`gint right_attach`**\ Η δεξιά συντεταγμένη από όπου θα τελειώνει το widget - **`gint top_attach`**\ Η πάνω συντεταγμένη από όπου θα αρχίζει το widget - **`gint bottom_attach`**\ Η κάτω συντεταγμένη που θα τελειώνει το widget - **`gint xoptions`**\ χρησιμοποιείται για τον καθορισμό του τρόπου πακεταρίσματος, και θα το δούμε στη συνέχεια πιο αναλυτικά - **`gint yoptions`**\ \`Οπως και το παραπάνω, και οι τιμές που μπορεί να πάρει τόσο αυτό όσο και το `xoptions` είναι οι παρακάτω: - **`GTK_FILL`**\ Αν η περιοχή (συνήθως το κουτί του πίνακα) είναι μεγαλύτερο από το widget που τοποθετούμε, το widget θα μεγαλώσει τόσο ώστε να καλύψει όλο τον χώρο που υπάρχει. - **`GTK_SHRINK`**\ Αν κατά την απόδοση χώρου σε ένα κουτί ο χώρος που δίνεται σε ένα widget είναι μικρότερος από το μέγεθός του ίδιου του widget, τότε αυτό θα μικράνει ώστε να χωράει. Κάτι τέτοιο συμβαίνει όταν ο χρήστης κάνει resize σε ένα παράθυρο. Αν δεν είναι ορισμένο το **`GTK_SHRINK`** είναι πολύ πιθανό σε μια τέτοια περίπτωση να μην εμφανίζονται τα widgets μέσα στον χώρο του παραθύρου μας. - **`GTK_EXPAND`**\ Με αυτό τον τρόπο μπορούμε να αποδώσουμε στο table μας όλο τον χώρο που απομένει στο παράθυρο μετά τη δημιουργία του. Οι παραπάνω τιμές που μπορούν να πάρουν, είναι δυνατό να συνδυαστούν με το **OR** για να καλύψουμε περισσότερες από μια περιπτώσεις. - **`gint xpadding`**\ Το γνωστό και πολλάκις εξηγημένο padding στον οριζόντιο άξονα - **`gint ypadding`**\ \`Οτι και το παραπάνω, αναφέρεται όμως στον κατακόρυφο άξονα Για όλους εσάς που όλα αυτά τα ορίσματα στην παραπάνω συνάρτηση σας φάνηκαν πολλά, υπάρχει και μια άλλη, που κάνει την ίδια (περίπου) δουλειά απλούστερα. ---------------------------------------------------------------------------------------------------------------------------------------------------------------- void gtk_table_attach_defaults(GtkTable *table, GtkWidget *widget, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- \`Οπως παρατηρούμε, μόνο οι βασικές παραμέτροι περνάνε στη συνάρτηση. Ουσιαστικά δηλαδή, μόνο τα controls θέσης και μεγέθους. Οι παραμέτροι που δεν αναφέρονται, παίρνουν κάποιες προκαθορισμένες τιμές. Αυτές είναι οι πλέον χρησιμοποιούμενες, και συγκεκριμένα τα **X** και **Y options** γίνονται **`GTK_FILL | GTK_EXPAND`**, (ελπίζω να ξέρετε ότι το \`\`\|\'\' είναι το **OR**) και στα **X** και **Y padding** δίδεται η τιμή 0. Για να καθορίσουμε το spacing ανάμεσα σε συγκεκριμένες γραμμές και στήλες ενός πίνακα, χρησιμοποιούμε τις παρακάτω συναρτήσεις: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- void gtk_table_set_row_spacing(GtkTable *table, gint row, gint spacing); void gtk_table_set_col_spacing(GtkTable *table, gint col, gint spacing); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- όπου row ή col η γραμμή ή η στήλη στην οποία αναφερόμαστε, και spacing η απόσταση που θέλουμε να ορίσουμε σαν spacing. Για τις στήλες το spacing προστίθεται στα ΔΕΞΙΑ και για τις γραμμές ΚΑΤΩ. Είναι επίσης δυνατό να καθορίσουμε για ΟΛΕΣ τις στήλες ή γραμμές το spacing, χρησιμοποιώντας τις συναρτήσεις: ---------------------------------------------------------------------------------------------------------------------------------------------------------------- void gtk_table_set_row_spacings(GtkTable *table, gint spacing); void gtk_table_set_col_spacings(GtkTable *table, gint spacing); ---------------------------------------------------------------------------------------------------------------------------------------------------------------- \`Οπως πολύ καλά καταλάβατε, η τελευταία στήλη και η τελευταία σειρά δεν λαμβάνουν αυτή την τιμή, γιατί θα δημιουργούσε ένα κενό στα αριστερά και ένα κενό κάτω. Είναι φανερό ότι το να δουλεύει κανείς με tables είναι εύκολο. Εγώ προσωπικά δεν το προτιμώ, παρόλο που είναι βασικά η μόνη λύση στην περίπτωση που θέλουμε απόλυτα στοιχισμένα κουτάκια και κουμπάκια και widgets. Δοκιμάστε το πάντως, και πολλές φορές θα σας λύσει τα χέρια ### [5.3 Η συνέχεια]{#ss5.3} Τον επόμενο μήνα θα συνεχίσουμε αυτό το άρθρο και θα ασχοληθούμε περισσότερο με τα widgets (buttons, radio/check/toggle buttons, text boxes, κ.λπ.) και τις ιδιότητές τους. Μέχρι τότε, πολλά φιλάκια.