# Φωτογραφικός τοίχος (C)

Ένας προβολέας εικόνων σε Clutter
Chris Kühl
chrisk@openismus.com
Johannes Schmid
jhs@gnome.org
Marta Maria Casetti
mmcasetti@gmail.com 2013
Ελληνική μεταφραστική ομάδα GNOME
team@gnome.gr 2012-2015
Δημήτρης Σπίγγος
dmtrs32@gmail.com 2012, 2013
Μαρία Θουκιδίδου
marablack3@gmail.com 2014
Θάνος Τρυφωνίδης
tomtryf@gmail.com 2014, 2015

# Φωτογραφικός τοίχος

      Για αυτό το παράδειγμα θα κατασκευάσουμε έναν απλό προβολέα εικόνων χρησιμοποιώντας το Clutter. Θα μάθετε:

        Αυξομείωση και τοποθέτηση του ClutterActors
        Πώς να τοποθετήσετε μια εικόνα σε ένα ClutterActor
        Πώς να κάνετε απλές μεταβάσεις χρησιμοποιώντας το σκελετό κίνησης του Clutter
        Πώς να κάνετε το ClutterActors να απαντήσει στα συμβάντα του ποντικιού
        Πώς να πάρετε ονόματα αρχείου από έναν κατάλογο

# Εισαγωγή

Το Clutter είναι μια βιβλιοθήκη για δημιουργία δυναμικών διεπαφών χρήστη χρησιμοποιώντας το OpenGL για επιτάχυνση υλικού. Αυτό το παράδειγμα δείχνει ένα μικρό, αλλά κεντρικό, τμήμα της Clutter για δημιουργία ενός απλού αλλά ελκυστικού προγράμματος προβολής εικόνας.

Για να μας βοηθήσετε να φτάσουμε τον στόχο μας θα χρησιμοποιήσουμε λίγα άλλα κοινά κομμάτια του GLib επίσης. Το πιο σημαντικό, θα χρησιμοποιήσουμε ένα `GPtrArray`, έναν δυναμικό πίνακα δεικτών, για να κρατήσουμε τα ονόματα διαδρομής αρχείου. Θα χρησιμοποιήσουμε επίσης `GDir`, ένα βοήθημα για εργασία με καταλόγους, για προσπέλαση του καταλόγου της εικόνας μας και να συγκεντρώσουμε τις διαδρομές του αρχείου.

# Δημιουργία έργου με το Anjuta

Πριν ξεκινήσετε να προγραμματίζετε, πρέπει να δημιουργήσετε ένα καινούργιο έργο στο Anjuta. Έτσι θα δημιουργηθούν όλα τα απαραίτητα αρχεία που χρειάζονται για την εκτέλεση του κώδικα αργότερα. Επίσης θα ήταν χρήσιμο να τα κρατάτε όλα μαζί.

Ξεκινήστε το Anjuta και πατήστε αρχείονέοέργο για να ανοίξετε τον οδηγό του έργου.

Επιλέξτε GTK+ (απλό) από την καρτέλα C, πατήστε συνέχεια και συμπληρώστε τις λεπτομέρειές σας στις επόμενες λίγες σελίδες. Χρησιμοποιήστε το photo-wall ως όνομα του έργου και του καταλόγου.

Σιγουρευτείτε ότι η χρήση του GtkBuilder για διεπαφή χρήστη είναι ανενεργή επειδή θα φτιάξουμε το UI χειροκίνητα σε αυτό το μάθημα. Σημειώστε το μάθημα ρυθμιστής κιθάρας χρησιμοποιώντας τον κατασκευαστή διεπαφής.

Ενεργοποιήστε ρύθμιση εξωτερικών πακέτων. Στην επόμενη σελίδα, επιλέξτε clutter-1.0 από τη λίστα για να συμπεριλάβετε τη βιβλιοθήκη Clutter στο έργο σας.

Πατήστε εφαρμογή και το έργο θα δημιουργηθεί για σας. Ανοίξτε το src/main.c από τις καρτέλες έργο ή αρχείο. Θα πρέπει να δείτε κάποιο κώδικα που ξεκινάει με τις γραμμές:

#include <config.h> #include <gtk/gtk.h>

# Μια ματιά στο φωτογραφικό πίνακα

Ο προβολέας εικόνας μας παρουσιάζει τον χρήστη με έναν τοίχο εικόνων.

Όταν κάνουμε κλικ σε μια εικόνα, κινείται για να γεμίσει την περιοχή προβολής. Όταν κάνουμε κλικ σε μια εστιασμένη εικόνα επιστρέφεται στην αρχική της θέση χρησιμοποιώντας μια κίνηση με την ίδια διάρκεια των 500 ms.

# Αρχική ρύθμιση

Το παρακάτω τμήμα κώδικα περιέχει πολλούς ορισμούς και μεταβλητές που θα χρησιμοποιήσουμε στις παρακάτω ενότητες. Χρησιμοποιήστε αυτό ως αναφορά για άλλες ενότητες. Αντιγράψτε αυτόν τον κώδικα στην αρχή του src/main.c:

#include <gdk-pixbuf/gdk-pixbuf.h> #include <clutter/clutter.h>

#define STAGE_WIDTH 800 #define STAGE_HEIGHT 600

#define THUMBNAIL_SIZE 200 #define ROW_COUNT (STAGE_HEIGHT / THUMBNAIL_SIZE) #define COL_COUNT (STAGE_WIDTH / THUMBNAIL_SIZE) #define THUMBNAIL_COUNT (ROW_COUNT * COL_COUNT)

#define ANIMATION_DURATION_MS 500

#define IMAGE_DIR_PATH "./berlin_images/"

static GPtrArray *img_paths·

static ClutterPoint unfocused_pos·

# Μετάβαση στον κώδικα

Θα ξεκινήσουμε ρίχνοντας μια ματιά στη συνάρτηση `main()` ως σύνολο. Έπειτα θα συζητήσουμε τις άλλες ενότητες κώδικα λεπτομερώς. Αλλάξτε το src/main.c για να περιέχει αυτήν την συνάρτηση `main()`. Μπορείτε να διαγράψετε τη συνάρτηση `create_window()` καθώς δεν τη χρειαζόμαστε σε αυτό το παράδειγμα.

int main(int argc, char *argv[]) { ClutterColor stage_color = { 16, 16, 16, 255 }· ClutterActor *stage = NULL·

if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1·

stage = clutter_stage_new()· clutter_actor_set_size(stage, STAGE_WIDTH, STAGE_HEIGHT)· clutter_actor_set_background_color(stage, &stage_color)· clutter_stage_set_title(CLUTTER_STAGE (stage), "Photo Wall")· g_signal_connect(stage, "destroy", G_CALLBACK(clutter_main_quit), NULL)·

load_image_path_names()·

guint row = 0· guint col = 0· for(row=0· row < ROW_COUNT· ++row) { for(col=0· col < COL_COUNT· ++col) { const char *img_path = g_ptr_array_index(img_paths, (row * COL_COUNT) + col)· GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(img_path, STAGE_HEIGHT, STAGE_HEIGHT, NULL)· ClutterContent *image = clutter_image_new ()· ClutterActor *actor = clutter_actor_new ()·

if (pixbuf != NULL) { clutter_image_set_data(CLUTTER_IMAGE(image), gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_has_alpha(pixbuf) ; COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_rowstride(pixbuf), NULL)· }

clutter_actor_set_content(actor, image)· g_object_unref(image)· g_object_unref(pixbuf)·

initialize_actor(actor, row, col)· clutter_actor_add_child(stage, actor)· } }

/* Show the stage. */ clutter_actor_show(stage)·

/* Start the clutter main loop. */ clutter_main()·

g_ptr_array_unref(img_paths)·

return 0· }

Γραμμή 4: `ClutterColor` ορίστηκε ορίζοντας τις τιμές κόκκινου, πράσινου, γαλάζιου και διαφάνειας (άλφα). Το εύρος τιμών από 0-255. Για διαφάνεια τιμή 255 είναι αδιαφανής.

Γραμμή 7: Πρέπει να αρχικοποιήσετε το Clutter. Εάν το ξεχάσετε, θα πάρετε πολύ περίεργα σφάλματα. Προειδοποιηθήκατε.

Γραμμές 10-14: Εδώ δημιουργούμε ένα νέο `ClutterStage`. Έπειτα ορίζουμε το μέγεθος χρησιμοποιώντας τους ορισμούς από την προηγούμενη ενότητα και τη διεύθυνση του `ClutterColor` που μόλις ορίσαμε.

Ένα `ClutterStage` είναι ανώτατου επιπέδου `ClutterActor` στο οποίο τοποθετούνται άλλοι `ClutterActor`.

Γραμμή 16: Εδώ καλούμε τη συνάρτηση μας για λήψη των διαδρομών αρχείου εικόνας. Θα το δούμε σε λίγο.

Γραμμές 18-49: Εδώ ρυθμίζουμε το `ClutterActor`s, φόρτωση των εικόνων και τοποθέτησή τους στο σημείο τους στον τοίχο εικόνας. Θα το δούμε λεπτομερώς στην επόμενη ενότητα.

Γραμμή 52: εμφάνιση του σταδίου και όλων των θυγατρικών του, που σημαίνει των εικόνων μας.

Γραμμή 55: Έναρξη του κύριου βρόχου του Clutter.

# Εγκατάσταση των δραστών εικόνας

Στο Clutter, ένας δράστης είναι το πιο βασικό οπτικό στοιχείο. Βασικά, οτιδήποτε βλέπετε είναι ένας δράστης.

Σε αυτήν την ενότητα, πρόκειται να κοιτάξουμε στενότερα το χρησιμοποιούμενο βρόχο για εγκατάσταση του `ClutterActor`s που θα εμφανίσει τις εικόνες μας.

guint row = 0· guint col = 0· for(row=0· row < ROW_COUNT· ++row) { for(col=0· col < COL_COUNT· ++col) { const char *img_path = g_ptr_array_index(img_paths, (row * COL_COUNT) + col)· GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(img_path, STAGE_HEIGHT, STAGE_HEIGHT, NULL)· ClutterContent *image = clutter_image_new ()· ClutterActor *actor = clutter_actor_new ()·

if (pixbuf != NULL) { clutter_image_set_data(CLUTTER_IMAGE(image), gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_has_alpha(pixbuf) ; COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_rowstride(pixbuf), NULL)· }

clutter_actor_set_content(actor, image)· g_object_unref(image)· g_object_unref(pixbuf)·

initialize_actor(actor, row, col)· clutter_actor_add_child(stage, actor)· } }

Γραμμή 7: Εδώ θέλουμε να πάρουμε τη διαδρομή στη θέση n στο `GSList` που κρατά τα ονόματα διαδρομής εικόνας. Η nστη θέση υπολογίζεται βασισμένη στη `row` και `col`.

Γραμμές 8-23: Εδώ δημιουργείται στην πραγματικότητα ο `ClutterActor` και τοποθετείται η εικόνα στον δράστη. Το πρώτο όρισμα είναι η διαδρομή προσπέλασης μέσα από τον κόμβο `GSList`. Το δεύτερο όρισμα είναι για αναφορά σφάλματος, αλλά το αγνοούμε για λόγους συντομίας.

Γραμμή 47: Αυτή προσθέτει τον `ClutterActor` στη σκηνή, που είναι ένας περιέκτης. Επίσης υποθέτει ιδιοκτησία του `ClutterActor` που είναι κάτι που θα θελήσετε να κοιτάξετε καθώς πηγαίνετε βαθύτερα στην ανάπτυξη του GNOME. Δείτε την `GObject`τεκμηρίωση για τις φρικτές λεπτομέρειες.

# Φόρτωση των εικόνων

Ας κάνουμε μια σύντομη διακοπή από το Clutter για να δούμε πώς μπορούμε να πάρουμε τα ονόματα αρχείου από τον κατάλογό μας των εικόνων.

static void load_image_path_names() { /* Επιβεβαιώνουμε ότι μπορούμε να προσπελάσουμε τον κατάλογο. */ GError *error = NULL· GDir *dir = g_dir_open(IMAGE_DIR_PATH, 0, &error)· if(error) { g_warning("g_dir_open() failed with error: %s\n", error->message)· g_clear_error(&error)· return· }

img_paths = g_ptr_array_new_with_free_func (g_free)·

const gchar *filename = g_dir_read_name(dir)· while(filename) { if(g_str_has_suffix(filename, ".jpg") || g_str_has_suffix(filename, ".png")) { gchar *path = g_build_filename(IMAGE_DIR_PATH, filename, NULL)· g_ptr_array_add (img_paths, path)· } filename = g_dir_read_name(dir)· } }

Γραμμές 5 και 12: Αυτό ανοίγει τον κατάλογό μας ή, εάν συμβεί σφάλμα, επιστρέφει μετά την εκτύπωση ένα μήνυμα σφάλματος.

Γραμμές 16-25: Η πρώτη γραμμή παίρνει ένα άλλο όνομα αρχείου από το `GDir` που ανοίξαμε πιο νωρίς. Εάν υπήρχε ένα αρχείο εικόνας (που ελέγχουμε κοιτώντας την επέκτασή του, ".png" ή ".jpg") στον κατάλογο συνεχίζουμε να προτάσσουμε τη διαδρομή καταλόγου εικόνας στο όνομα αρχείου και το προτάσσουμε στη λίστα που εγκαταστήσαμε νωρίτερα. Τελευταία προσπαθούμε να πάρουμε το επόμενο όνομα διαδρομής και ξαναεισάγουμε τον βρόχο εάν βρεθεί κάποιο άλλο αρχείο.

# Εγκατάσταση των δραστών

Ας ρίξουμε μια ματιά τώρα στο μέγεθος και την τοποθέτηση του `ClutterActor` και επίσης να ετοιμάσουμε το `ClutterActor` για αλληλεπίδραση χρήστη.

/* Αυτή η συνάρτηση χειρίζεται την ρύθμιση και τοποθέτηση των ορθογωνίων. */ static void initialize_actor(ClutterActor *actor, guint row, guint col) { clutter_actor_set_size(actor, THUMBNAIL_SIZE, THUMBNAIL_SIZE); clutter_actor_set_position(actor, col * THUMBNAIL_SIZE, row * THUMBNAIL_SIZE); clutter_actor_set_reactive(actor, TRUE);

g_signal_connect(actor, "button-press-event", G_CALLBACK(actor_clicked_cb), NULL); }

Γραμμή 7: ρύθμιση αντίδρασης δράστη σημαίνει ότι αντιδρά σε συμβάντα, τέτοια όπως `button-press-event` στην περίπτωσή μας. Για φωτογραφικό τοίχο, όλοι οι `ClutterActor` στον τοίχο θα πρέπει αρχικά να είναι αντιδραστικοί.

Γραμμή 9-12: Τώρα συνδέουμε το `button-press-event` στην επανάκληση `actor_clicked_cb` που θα δούμε στη συνέχεια.

Σε αυτό το σημείο πήραμε έναν τοίχο εικόνων που είναι έτοιμες να προβληθούν.

# Αντίδραση στα πατήματα

static gboolean actor_clicked_cb(ClutterActor *actor, ClutterEvent *event, gpointer user_data) { /* Σημαία για να κρατήσουμε ίχνος της κατάστασής μας. */ static gboolean is_focused = FALSE· ClutterActorIter iter· ClutterActor *child·

/* Επαναφορά της κατάστασης εστίασης σε όλες τις εικόνες */ clutter_actor_iter_init (&iter, clutter_actor_get_parent(actor))· while (clutter_actor_iter_next(&iter, &child)) clutter_actor_set_reactive(child, is_focused)·

clutter_actor_save_easing_state(actor)· clutter_actor_set_easing_duration(actor, ANIMATION_DURATION_MS)·

if(is_focused) { /* Επαναφορά της παλιάς τοποθεσίας και μεγέθους. */ clutter_actor_set_position(actor, unfocused_pos.x, unfocused_pos.y)· clutter_actor_set_size(actor, THUMBNAIL_SIZE, THUMBNAIL_SIZE)· } else { /* Αποθήκευση της τρέχουσας τοποθεσίας πριν την κίνηση. */ clutter_actor_get_position(actor, &unfocused_pos.x, &unfocused_pos.y)· /* Μόνο η τρέχουσα εστιασμένη εικόνα πρέπει να δεχτεί συμβάντα. */ clutter_actor_set_reactive(actor, TRUE)·

/* Βάλτε την εστιασμένη εικόνα στην κορυφή. */ clutter_actor_set_child_above_sibling(clutter_actor_get_parent(actor), actor, NULL)·

clutter_actor_set_position(actor, (STAGE_WIDTH - STAGE_HEIGHT) / 2.0, 0)· clutter_actor_set_size(actor, STAGE_HEIGHT, STAGE_HEIGHT)· }

clutter_actor_restore_easing_state(actor)·

/* Εναλλαγή της σημαίας μας. */ is_focused = !is_focused·

return TRUE· }

Γραμμές 1-4: Πρέπει να βεβαιωθούμε ότι η συνάρτηση επανάκλησής μας ταιριάζει με την απαιτούμενη υπογραφή για το σήμα `button_clicked_event`. Για το παράδειγμά μας, θα χρησιμοποιήσουμε μόνο το πρώτο όρισμα, το `ClutterActor` που στην πραγματικότητα πατήθηκε.

Λίγες λέξεις για τα ορίσματα που δεν χρησιμοποιούμε σε αυτό το παράδειγμα. Ο `ClutterEvent` είναι διαφορετικός ανάλογα με τι συμβάν επεξεργάζεται. Για παράδειγμα, ένα συμβάν πλήκτρου παράγει ένα `ClutterKeyEvent` από το οποίο μπορείτε να πάρετε το πλήκτρο που πατήθηκε μεταξύ άλλων πληροφοριών. Για τα συμβάντα κλικ του ποντικιού παίρνετε `ClutterButtonEvent` από το οποίο μπορείτε να πάρετε τις τιμές `x` και `y`. Δείτε την τεκμηρίωση του Clutter για άλλους τύπους `ClutterEvent`.

Ο `user_data` είναι τι χρησιμοποιεί κάποιος για να περάσει δεδομένα στη συνάρτηση. Ένας δείκτης σε οποιοδήποτε τύπο δεδομένων μπορεί να περαστεί. Εάν χρειαζόσαστε πολλαπλά δεδομένα να περαστούν στην επανάκληση, μπορείτε να τοποθετήσετε τα δεδομένα σε μια δομή και να περάσετε τη διεύθυνσή του.

Γραμμή 7: Προδιατάσσουμε μια στατική σημαία για εντοπισμό της κατάστασης στην οποία είμαστε: κατάσταση τοίχου ή κατάσταση εστίασης. Ξεκινάμε σε κατάσταση τοίχου, έτσι καμιά εικόνα δεν έχει εστίαση. Έτσι, ορίζουμε τη σημαία σε `FALSE` αρχικά.

Γραμμές 12-14: Αυτές βάζουν τους δράστες εικόνας να δέχονται συμβάντα εάν εστιαστούν.

Γραμμές 16-17: Εδώ ορίζουμε τη διάρκεια κίνησης και αποθηκεύουμε την τρέχουσα κατάσταση.

Γραμμές 21-23: Φτάνοντας αυτόν τον κώδικα σημαίνει ότι μια εικόνα έχει την τρέχουσα εστίαση και θέλουμε να επιστρέψει σε κατάσταση τοίχου. Ορίζοντας μια θέση σε έναν `ClutterActor` ξεκινά μια κίνηση με τη διάρκεια που ορίζουμε στη γραμμή 17.

Γραμμή 24: φτάνοντας αυτή τη γραμμή του κώδικα σημαίνει ότι είμαστε προς το παρόν στην κατάσταση τοίχου και πρόκειται να δώσουμε μια `ClutterActor` εστίαση. Εδώ αποθηκεύουμε την αρχική θέση, έτσι ώστε να μπορούμε να επιστρέψουμε αργότερα.

Γραμμή 25: ορίζοντας την ιδιότητα `reactive` του `ClutterActor` σε `TRUE` κάνει το `ClutterActor` να αντιδράσει σε συμβάντα. Σε αυτή την εστιασμένη κατάσταση ο μόνος `ClutterActor` που θέλουμε να δεχθεί συμβάντα θα είναι ο προβαλλόμενος `ClutterActor`. Κλικ στο `ClutterActor` θα επιστρέψει στην αρχική του θέση.

Γραμμές 27-36: Εδώ αποθηκεύουμε την τρέχουσα θέση της εικόνας, την ορίζουμε να δέχεται συμβάντα και έπειτα την κάνουμε να εμφανιστεί πάνω από τις άλλες εικόνες και ξεκινάμε την κίνησή της για να γεμίσει τη σκηνή.

Γραμμή 39: Εδώ επαναφέρουμε την κατάσταση απάλυνσης σε ό,τι ορίστηκε πριν την αλλάξουμε στη γραμμή 16.

Γραμμή 42: Εδώ εναλλάσσουμε τη σημαία `is_focused` στην τρέχουσα κατάσταση.

Όπως αναφέρθηκε προηγουμένως, ο `ClutterActor` με υψηλότερες τιμές `depth` δέχεται συμβάντα αλλά μπορούν να επιτρέψουν `ClutterActor` από κάτω τους να δεχθούν επίσης συμβάντα. Επιστροφή σε `TRUE` θα σταματήσει τα συμβάντα από το να συνεχίσουν, ενώ `FALSE` θα συνεχίσει τα συμβάντα.

Να θυμόσαστε, όμως, ότι για αποδοχή συμβάντων το `ClutterActor` πρέπει να οριστεί σε `reactive`.

# Κατασκευή και εκτέλεση της εφαρμογής

Όλος ο κώδικας πρέπει τώρα να είναι έτοιμος. Τώρα χρειαζόσαστε μερικές εικόνες για φόρτωση. Από προεπιλογή, οι εικόνες φορτώνονται από έναν κατάλογο berlin_images. Εάν θέλετε, μπορείτε να αλλάξετε τη γραμμή `#define IMAGE_DIR_PATH` κοντά στην κορυφή για να αναφερθείτε στον κατάλογο φωτογραφιών, ή να δημιουργήσετε ένα κατάλογο berlin_images με κλικ στο έργονέος κατάλογος... και δημιουργία ενός καταλόγου berlin_images ως υποκαταλόγου του καταλόγου photo-wall. Βεβαιωθείτε ότι βάλατε τουλάχιστον δώδεκα εικόνες στον κατάλογο!

Όταν τελειώσετε, κλικ κατασκευήκατασκευή έργου για να κατασκευάσετε οτιδήποτε ξανά, έπειτα τρέξιμοεκτέλεση για έναρξη της εφαρμογής.

Εάν δεν το έχετε ήδη κάνει, επιλέξτε την εφαρμογή Debug/src/photo-wall στον διάλογο που εμφανίζεται. Τελικά πατήστε εκτέλεση και απολαύστε το!

# Υλοποίηση αναφοράς

Αν αντιμετωπίσετε πρόβλημα με το μάθημα, συγκρίνετε τον κώδικά σας με αυτόν τον κώδικας αναφοράς.