<?xml version="1.0" encoding="utf-8"?>
<page xmlns="http://projectmallard.org/1.0/" type="topic" id="photo-wall.c" xml:lang="gl">
  <info>
    <link type="guide" xref="index#c"/>

    <desc>Un visor de imaxes Clutter</desc>

    <revision pkgversion="0.1" version="0.1" date="2011-03-22" status="review"/>
    <credit type="author">
      <name>Chris Kühl</name>
      <email>chrisk@openismus.com</email>
    </credit>
    <credit type="author">
      <name>Johannes Schmid</name>
      <email>jhs@gnome.org</email>
    </credit>
  </info>

<title>Mural de fotos</title>

<synopsis>
  <p>Para este exemplo construirase un sinxelo visor de imaxes usando Clutter. Aprenderá:</p>
  <list>
    <item><p>Como dimensionar e posicionar varios <code>ClutterActor</code></p></item>
    <item><p>Como posicionar unha imaxe nun <code>ClutterActor</code></p></item>
    <item><p>Como facer transicións sinxelas usando o framework de animacións Clutter.</p></item>
    <item><p>Como facer que os <code>ClutterActor</code> respondan a eventos do rato</p></item>
    <item><p>Como obter nomes de ficheiros dun cartafol</p></item>
  </list>
</synopsis>

<section id="intro">
  <title>Introdución</title>
  <p>Clutter é unha biblioteca para crear interfaces de usuario dinámicas usando OpenGL para a aceleración gráfica. Este exemplo demostra unha parte pequena, porén central, da biblioteca Clutter para crear un sinxelo, máis atractivo, programa de visualización de imaxes.</p>
  <p>Para axudarlle a alcanzar o seu destino usaremos tamén algunhas pezas comúns de GLib. A máis importante, usaremos <code>GSList</code>, unha lista ligada para manter os nosos <code>ClutterActor</code>s e os nomes de ruta de ficheiro.</p>
</section>

<section id="anjuta">
  <title>Cree un proxecto de Anjuta</title>
  <p>Antes de comezar a programar, deberá configurar un proxecto novo en Anjuta. Isto creará todos os ficheiros que precise para construír e executar o código máis adiante. Tamén é útil para manter todo ordenado.</p>
  <steps>
    <item>
    <p>Inicie Anjuta e prema <guiseq><gui>Ficheiro</gui><gui>Novo</gui><gui>Proxecto</gui></guiseq> para abrir o asistente de proxectos.</p>
    </item>
    <item>
    <p>Choose <gui>Gtk+ (simple)</gui> from the <gui>C</gui> tab, click <gui>Continue</gui>, and fill out your details on the next few pages. Use <file>photo-wall</file> as project name and directory.</p>
   	</item>
    <item>
    <p>Asegúrese que <gui>Usar GtkBuilder para a interface de usuario</gui> está desactivado xa que crearemos a UI manualmente neste titorial. Comprobe o titorial <link xref="guitar-tuner.c">Guitar-Tuner</link> usando o construtor de interface.</p>
    </item>
    <item>
    <p>Active <gui>Configurar paquetes externos</gui>. Na seguinte páxina seleccione <em>clutter-1.0</em> desde a lista para incluír a biblioteca Clutter no seu proxecto.</p>
    </item>
    <item>
    <p>Prema <gui>Aplicar</gui> para crear o proxecto. Abra <file>src/main.c</file> desde as lapelas <gui>Proxecto</gui> ou <gui>Ficheiro</gui>. Debería ver algún código que comeza coas liñas:</p>
    <code mime="text/x-csrc"><![CDATA[
#include <config.h>
#include <gtk/gtk.h>]]></code>
    </item>
  </steps>
</section>

<section id="look">
  <title>Unha ollada ao Muro de fotos</title>
  <p>O novo visor de imaxes móstralle ao usuario un muro de imaxes.</p>
  <media type="image" mime="image/png" src="media/photo-wall.png"/>
  <p>Cando se fai clic sobre unha imaxe, esta anímase para ocupar todo o área de visualización. Cando se fai clic sobre unha imaxe que ten o foco volverá á súa posición orixinal usando unha animación da mesma duración de 500 milisegundos.</p>
  <media type="image" mime="image/png" src="media/photo-wall-focused.png"/>
</section>

<section id="setup">
  <title>Configuración inicial</title>
  <p>O seguinte fragmento de código contén moitas definicións e variábeis que se usarán nas seguintes seccións. Úseo como referencia para as próximas seccións. Copie este código ao principio de <file>src/main.c</file>:</p>
<code mime="text/x-csrc" style="numbered"><![CDATA[
#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 FOCUS_DEPTH 100.0
#define UNFOCUS_DEPTH 0.0

#define IMAGE_DIR_PATH "./berlin_images/"

static GSList *actor_list = NULL;
static GSList *img_path_list = NULL;

typedef struct Position
{
    float x;
    float y;
}
Position;

static Position origin = {0, 0};
]]>
</code>
</section>

<section id="code">
  <title>Saltando ao código</title>
  <p>Comezaremos ollando a función <code>main()</code>. Logo discutiremos as outras seccións de código en detalle. Cambie o ficheiro <file>src/main.c</file> para que conte;a a función <code>main()</code>. Pode elminar a función <code>create_window()</code> xa que non a necesitamos neste exemplo.</p>
  <code mime="text/x-csrc" style="numbered"><![CDATA[
int
main(int argc, char *argv[])
{
    ClutterColor stage_color = { 16, 16, 16, 255 };
    ClutterActor *stage = NULL;

    clutter_init(&argc, &argv);

    stage = clutter_stage_get_default();
    clutter_actor_set_size(stage, STAGE_WIDTH, STAGE_HEIGHT);
    clutter_stage_set_color(CLUTTER_STAGE (stage), &stage_color);

    load_image_path_names();

    guint row = 0;
    guint col = 0;
    for(row=0; row < ROW_COUNT; ++row)
    {
        for(col=0; col < COL_COUNT; ++col)
        {
            GSList *img_path_node = g_slist_nth(img_path_list, (row * COL_COUNT) + col);
            ClutterActor *actor = clutter_texture_new_from_file((gchar *)(img_path_node->data), NULL);
            initialize_actor(actor, row, col);
            clutter_container_add_actor(CLUTTER_CONTAINER(stage), actor);
            actor_list = g_slist_prepend(actor_list, actor);
        }
    }

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

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

    return 0;
}]]></code>
  <list>
    <item><p>Liña 4: <code>ClutterColor</code> é definido estabelecendo o valor a vermello, verde, azul ou transparente (alfa). O rango de valores están entre 0-255. Para a transparencia 255 é opaco.</p></item>
    <item><p>Liña 7: Debe inicializar Clutter. Se esqueceu isto, haberá moitos erros estraños. Está avisado.</p></item>
    <item><p>Liñas 9-11: aquí é onde se obtén o <code>ClutterStage</code> predeterminado que forneceu <code>clutter_init</code>. Entón estabelécese o tamaño usando o definido na sección anterior e os enderezos do <code>ClutterColor</code> que xa están definidas.</p>
      <note><p>Un <code>CluterStage</code> é o nivel superior dun <code>ClutterActor</code> no que se localizan outros <code>ClutterActor</code>.</p></note>
</item>
    <item><p>Liña 12: aquí chámase á función para obter as rutas das imaxes. Isto verase en breve.</p></item>
    <item><p>Liña 14-26: aquí é onde se configuran os <code>ClutterActor</code>, cárganse as imaxes e colócanse no seu sitio no mural de imaxes. Isto verase con máis detalle na seguinte sección.</p></item>
    <item><p>Liña 29: mostra o escenario e <em>todos os seus fillos</em>, é dicir, as imaxes.</p></item>
    <item><p>Liña 32: inicia o bucle principal de Clutter.</p></item>
  </list>
</section>

<section id="actors">
  <title>Configurar os nosos actores de imaxe</title>
 <note><p>En Clutter, un actor é o elemento visual máis básico. Basicamente, todo o que ve é un actor.</p></note>
<p>Nesta sección, imos botar unha ollada máis polo miúdo ao búcle usado para configurar os <code>ClutterActor</code>s que mostrarán as nosas imaxes.</p>
  <code mime="text/x-csrc" style="numbered"><![CDATA[
for(row=0; row < ROW_COUNT; ++row)
{
    for(col=0; col < COL_COUNT; ++col)
    {
        GSList *img_path_node = g_slist_nth(img_path_list, (row * COL_COUNT) + col);
        ClutterActor *actor = clutter_texture_new_from_file((gchar *)(img_path_node->data), NULL);
        initialize_actor(actor, row, col);
        clutter_container_add_actor(CLUTTER_CONTAINER(stage), actor);
        actor_list = g_slist_prepend(actor_list, actor);
    }
}
]]>
</code>
<list>
  <item><p>Liña 5: aquí quérese obter a ruta á localización <var>n</var>-ésima na <code>GSList</code> que contén os nomes das rutas das imaxes. A <var>n</var>-ésima posición calcúlase baseándose en <code>row</code> e <code>col</code>. O valor de retorno é un punteiro a <code>GSList</code> que é simplemente un nodo na lista. Isto usarase para obter a ruta actual na seguinte liña. O primeiro parámetro é un punteiro á cabeceira da lista.</p>
  </item>
  <item><p>Liña 6: aquí é onde actualmente se crea o <code>ClutterActor</code> e localízase a imaxe no actor. O primeiro argumento é a ruta coa que se acceder ao nodo da <code>GSList</code>. O seguindo argumento é para informar dun error, pero ignórase para simplificar.</p>
  </item>
  <item><p>Liña 7: esta función verase nunha sección posterior.</p>
  </item>
  <item><p>Liña 8: isto engade o <code>ClutterActor</code> ao escenario, que é un contedor. Asume o propietario do <code>ClutterActor</code>, que é algo que quererá cando profundice no desenvolvemento de GNOME. Para obter máis detalles, consulte a <link href="http://library.gnome.org/devel/gobject/stable/gobject-memory.html">documentación de <code>GObject</code></link>.</p>
  </item>
  <item><p>Liña 9: isto engade o <code>ClutterActor</code> a unha <code>GSList</code> que logo se poderá iterar sobre os <code>ClutterActor</code>.</p>
<note><p>É interesante ter en conta que prefírese antepoñer os <code>ClutterActor</code> no lugar de anexalos, o que evita atravesar a lista en cada inserción. A miúdo verá <code>g_slist_prepend</code> seguido de <code>g_slist_reverse</code>, xa que é máis rápido que inserir varios obxectos ao final da lista.</p></note>
  </item>
</list>
</section>

<section id="load">
  <title>Cargar as imaxes</title>
  <p>Botemos unha pequena ollada a Clutter para ver como obter os nomes dos ficheiros desde o noso cartafol de imaxes.</p>
  <code mime="text/x-csrc" style="numbered"><![CDATA[
static void
load_image_path_names()
{
    /* Ensure we can access the directory. */
    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;
    }

    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);
            img_path_list = g_slist_prepend(img_path_list, path);
        }
        filename = g_dir_read_name(dir);
    }
}]]></code>
  <list>
    <item><p>Liñas 5 e 12: Isto abre o noso cartafol ou, se se produce un erro, devolve despois de imprimir unha mensaxe de erro.</p></item>
    <item><p>Liñas 14‒23: la primeira liña obtén outro nome de ficheiro do <code>GDir</code> aberto anteriormente. Se hai un ficheiro de imaxe (compróbase mirando se a extensión é «.png» o «.jpg») no cartafol, procédese a antepoñer a ruta do cartafol da imaxe ao nome do ficheiro e anteponse na lista creada anteriormente. Por último, inténtase obter a seguinte ruta e vólvese entrar no bucle se se atopa outro ficheiro.</p></item>
  </list>
</section>

<section id="actors2">
  <title>Configurar os actores</title>
  <p>Vote unha ollada ao tamaño e ao posicionamento dos <code>ClutterActor</code> e a como se deixa listo o <code>ClutterActor</code> para a interacción do usuario.</p>
  <code mime="text/x-csrc" style="numbered"><![CDATA[
/* This function handles setting up and placing the rectangles. */
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);
}]]></code>
  <list>
    <item>
      <p>Liña 7: configurar un actor como «reactivo» significa que reacciona aos eventos tales como <code>button-press-event</code> no noso caso. Para o mural de fotos, todos os <code>ClutterActor</code> do mural deben ser inicialmente reactivos.</p>
    </item>
    <item>
      <p>Liñas 9-12: agora conéctase o evento <code>button-press-evento</code> á chamada <code>actor_clicked_cb</code> que veremos máis adiante.</p>
    </item>
  </list>
  <p>Neste punto obteremos un muro de imaxes que están listas para ser mostradas.</p>
</section>

<section id="click">
  <title>Reaccionar aos clics</title>
  <p>

  </p>
  <code mime="text/x-csrc" style="numbered"><![CDATA[
static gboolean
actor_clicked_cb(ClutterActor *actor,
                 ClutterEvent *event,
                 gpointer      user_data)
{
    /* Flag to keep track of our state. */
    static gboolean is_focused = FALSE;

    g_slist_foreach(actor_list, foreach_set_focus_state, &is_focused);

    if(is_focused)
    {
        clutter_actor_animate(actor, CLUTTER_LINEAR, ANIMATION_DURATION_MS,
                              "x",      origin.x,
                              "y",      origin.y,
                              "depth",  UNFOCUS_DEPTH,
                              "width",  (float) THUMBNAIL_SIZE,
                              "height", (float) THUMBNAIL_SIZE,
                              NULL);
    }
    else
    {
        /*Save the current location before animating. */
        clutter_actor_get_position(actor, &origin.x, &origin.y);
        clutter_actor_set_reactive(actor, TRUE);
        clutter_actor_animate(actor, CLUTTER_LINEAR, ANIMATION_DURATION_MS,
                              "x",      (STAGE_WIDTH - STAGE_HEIGHT) / 2.0,
                              "y",      0.0,
                              "depth",  FOCUS_DEPTH,
                              "width",  (float) STAGE_HEIGHT,
                              "height", (float) STAGE_HEIGHT,
                              NULL);
    }

    /* Toggle our flag. */
    is_focused = !is_focused;

    return TRUE;
}]]></code>
  <list>
    <item><p>Lines 1‒4: We have to make sure our callback function matches the signature required for the <code>button_clicked_event</code> signal. For our example, we will only use the first argument, the <code>ClutterActor</code> that is actually clicked.</p>
<note>
  <p>A few words on the arguments we are not using in this example. The <code>ClutterEvent</code> is different depending on what event is being handled. For example, a key event produces a <code>ClutterKeyEvent</code> from which you can get the key being pressed among other information. For mouse click events you get a <code>ClutterButtonEvent</code> from which you can get the <code>x</code> and <code>y</code> values. See the Clutter documentation for other <code>ClutterEvent</code> types.</p>
  <p>
    The <code>user_data</code> is what one uses to pass data into the the function. A pointer to any data type can be passed in. If you need multiple data to be passed into the callback, you can place the data into a struct and pass its address in.
  </p>
</note></item>
    <item><p>Line 7: We set up a static flag to track which state we are in: wall mode or focus mode. We start out in wall mode so no image has focus. Thus, we set the flag to <code>FALSE</code> initially.</p></item>
    <item><p>Line 9: This line of code runs a custom function, <code>foreach_set_focus_state</code>, for each element in our <code>actor_list</code>, passing it the address to the <code>is_focused</code> flag. We'll see the definition of the <code>foreach_set_focus_state</code> function in the next section.</p></item>
    <item><p>Lines 13‒19: Reaching this code means that one image currently has focus and we want to return to wall mode. The <code>clutter_actor_animate</code> function is used to animate a <code>ClutterActor</code>'s property or properties from the current state(s) to the specified state(s). The arguments are as follows:</p>
<list type="numbered">
  <item><p>O enderezo do <code>ClutterActor</code> a animar</p></item>
  <item><p>The animation mode to use. Here we use <code>CLUTTER_LINEAR</code> so that we have a constant speed for animation.</p></item>
  <item><p>The duration of the animation in milliseconds. I've chosen 500 ms for this example.</p></item>
  <item><p>The remaining arguments are property/value pairs. Here we want to set the <code>x</code> value to the starting <code>x</code> value this <code>ClutterActor</code> was at before being brought into focus.</p></item>
  <item><p>The last argument must always be <code>NULL</code> to indicate that there are no more properties to be set.</p></item>
</list>
<note><p>The <code>depth</code> property needs a little more explaining. We need to raise the focused image so that it doesn't slide behind other <code>ClutterActor</code>s. In this section we are returning it to the same depth as the others on the wall.</p>
<p>Depth also determines which <code>ClutterActor</code>s receive events. A <code>ClutterActor</code> with a higher depth value receives the click events and can choose whether the event gets sent to <code>ClutterActor</code>s under it. We'll see how that works in a few steps.</p></note>
    </item>
    <item><p>Line 24: Reaching this line of code means we are currently in the wall state and are about to give a <code>ClutterActor</code> focus. Here we save the starting position so that we can return to it later.</p></item>
    <item><p>Line 25: Setting the <code>ClutterActor</code>'s <code>reactive</code> property to <code>TRUE</code> makes this <code>ClutterActor</code> react to events. In this focused state the only <code>ClutterActor</code> that we want to receive events will be the <code>ClutterActor</code> being viewed. Clicking on the <code>ClutterActor</code> will return it to its starting position. </p></item>
    <item><p>Lines 27‒33: This is similar to the above block of code. Notice that we are setting the the depth to raise it above the other images.</p></item>
    <item><p>Line 37: Here we toggle the <code>is_focused</code> flag to the current state.</p></item>
<item><p>As mentioned previously, the <code>ClutterActor</code>s with higher <code>depth</code> values receive events but can allow <code>ClutterActor</code>s below them to also receive events. Returning <code>TRUE</code> will stop events from being passed down, while <code>FALSE</code> will pass events down.</p>
 <note>
   <p>Remember, however, that to receive events the <code>ClutterActor</code>s must be set <code>reactive</code>.</p>
 </note>
</item>
 </list>
 <p>
   The following is the convenience function passed to <code>g_slist_foreach</code>.
 </p>
 <code mime="text/x-csrc" style="numbered"><![CDATA[
static void
foreach_set_focus_state(gpointer data, gpointer user_data)
{
    ClutterActor *actor = CLUTTER_ACTOR(data);
    gboolean is_reactive = *((gboolean*)user_data);

    clutter_actor_set_reactive(actor, is_reactive);
}]]></code>
<list>
  <item><p>Lines 2‒5: The signature of this function requires two <code>gpointer</code>s. The first is a pointer to the <code>ClutterActor</code> that our <code>GSList</code> holds and the other is the <code>is_focused</code> flag that we've passed in the previous section. We want to cast these and store them for easy use.</p></item>
  <item><p>Line 7: Depending on which boolean value is passed in, the <code>ClutterActor</code> will be set to respond to events or not.</p></item>
</list>
</section>

<section id="run">
  <title>Construír e executar o aplicativo</title>
  <p>Todo o código debería estar listo para executarse. Todo o que necesita son algunhas imaxes para cargar. De maneira predeterminada, as imaxes cárganse desde o cartafol <file>berlin_images</file>. Se quere, pode cambiar a liña <code>#define IMAGE_DIR_PATH</code> do principio para que faga referencia ao seu cartafol de fotos, ou crear un cartafol <file>berlin_images</file> premendo en <guiseq><gui>Proxecto</gui><gui>Cartafol novo…</gui></guiseq> e creando un cartafol <file>berlin_images</file> como subcartafol do cartafol <file>mural-fotos</file>. Asegúrese de poñer cando menos doce imaxes no cartafol.</p>
  <p>Cando teña que facer iso, prema <guiseq><gui>Construír</gui><gui>Construír proxecto</gui></guiseq> para construír todo de novo, logo <guiseq><gui>Executar</gui><gui>Executar</gui></guiseq> para iniciar o aplicativo.</p>
  <p>Se non o fixo aínda, seleccione o aplicativo <file>Debug/src/photo-wall</file> no diálogo que aparece. Finalmente, prema <gui>Executar</gui> e desfrute!</p>
</section>

<section id="impl">
 <title>Implementación de referencia</title>
 <p>Se ten problemas ao executar este titorial compare o seu código con este <link href="photo-wall/photo-wall.c">código de referencia</link>.</p>
</section>

</page>
