viernes, 30 de agosto de 2013

Migración de Proyectos de Anjuta 1.x a 2.x

(english version)

Anjuta es una herramienta que durante años me ha sido de gran utilidad. Si bien la herramienta fundamental para el diseño de interfaces que utilizan Gtk+ es Glade, el hecho de que Anjuta provea cierto nivel de integración entre los distintos elementos necesarios para el desarrollo (glade, el editor scintilla) facilita aún más las cosas.

Dicho esto, tenemos claro entonces que nada impide desarrollar los distintos componentes de nuestro programa por separado: podemos mantener una sesión de glade para ir modificando las ventanas de nuestra interfaz; paralelamente utilizamos algún editor de texto para ir modificando nuestro código. Con una terminal adicional podemos ejecutar y depurar las compilaciones parciales de nuestro programa. Anjuta provee todas estas operaciones de manera integrada.

Para facilitar aún más esa integración, Anjuta 1.x proveía un conjunto funciones que facilitaban algunas tareas (lookup_widget(), p. e.). En versiones más recientes de Anjuta ese soporte dejó de existir, porque se prioriza la utilización directa de la librería libglade. Esta librería permite enlazar el programa y la pantalla desarrollada con glade en tiempo de ejecución. En Anjuta 1.x, la relación entre la interfaz y el código se establece durante la compilación, mediante un conjunto de funciones adicionales provistas por el mismo Anjuta (definidas en el archivo support.c).



Familiarizándonos con la interfaz de Anjuta 2.x

Una vez que instalamos la nueva versión de Anjuta, saltan a la vista algunas diferencias con la versión anterior. La más significativa quizás sea que la combinación de teclas "Alt+G" no arranca Glade como antes. A no desesperarse, la forma de interactuar con Glade ahora es mucho más natural: simplemente hay que dar doble click al archivo que define la interfaz de nuestro programa.


Otra diferencia notoria, es que Anjuta dispone ahora de varios perfiles de compilación predefinidos. Esto no es otra cosa que la combinación de flags del compilador necesarias para generar un proyecto optimizado para su ejecución (-O3), optimizado para debug (-g), etc. Al momento de construir nuestro proyecto, elegimos el tipo de perfil que deseamos.

Abriendo un proyecto "viejo" con Anjuta 2.x


De entre todas las opciones que disponemos para abrir un proyecto viejo, personalmente prefiero la opción "Archivo -> Nuevo -> 5. Proyecto desde fuentes existentes".



Una vez seleccionada esa opción, buscamos el directorio que contiene el proyecto "viejo":

El nombre sugerido para el nuevo proyecto, es el mismo que el directorio seleccionado. Podemos modificarlo si así lo deseamos. A continuación, nos consulta por el tipo de backend que queremos para el proyecto que estamos importando. En mi caso, elijo "Backend de autotools".



Finalmente Anjuta genera el nuevo proyecto: 



puede notarse que a la izquierda figuran todos los archivos heredados del proyecto viejo.

Si intentamos la combinación de teclas "Mayus+F7", nos pregunta por el tipo de perfil de compilación que queremos utilizar:



Elegido el tipo de perfil, intenta construir el binario. No lo logra. Intentaremos superar esta situación.



Acomodando el proyecto recién importado

Como comenté anteriormente, muchas funciones de soporte que proveía Anjuta 1.x, ya no son necesarias. Básicamente se puede prescindir de los archivos: "suport.c", "suport.h", "interface.c" e "interface.h". Se pueden eliminar de varias formas. En mi caso, el directorio importado forma parte de un directorio de trabajo de un proyecto almacenado mediante "subversion", así que utilizo la siguiente opción:



de esta forma, eliminamos todos los archivos mencionados. Además, se deben eliminar las referencias a estos archivos que ya no estarán; en particular hay que editar el archivo "src/Makefile.am", para eliminar las apariciones de los archivos arriba mencionados. Luego debemos posicionarnos desde una consola en el directorio, y ejecutamos el comando "./autogen.sh".

Este último comando no solo regenera el archivo "Makefile", para que no tenga más en cuenta los archivos eliminados. Es probable que muchos utilitarios empleados por nuestro proyecto viejo hayan cambiado desde la última vez que lo modificamos, no solo Anjuta y Glade ("automake", "autotools", etc). "./autogen.sh" se encarga de acomodar las versiones de estos programas en el proyecto importado.

Con un poco de suerte, el intento de generación del binario empezará a fallar por el contenido de los archivos fuente, y no por el armado del proyecto en sí. Para empezar, los programas fuente todavía harán referencia a los archivos de cabecera eliminados ("suport.h" e "interface.h"). Podemos eliminar esas líneas en nuestro código.

Por supuesto, no alcanza solo con eliminar estas líneas. Todavía quedan muchas referencias a las funciones incluídas en esos archivos eliminados que deberemos reemplazar. El siguiente punto enumera algunas de ellas.



Modificando el código fuente para una compilación exitosa

El primer intento de compilar nuestro código nos muestra una pantalla como la que sigue:




vemos una seguidilla de mensajes de alarma, que nos avisa sobre ciertas funciones que dejaron de estar definidas para el código, a saber: "add_pixmap_directory", "lookup_widget", "create_window", etc. Deberemos reemplazarlas.

En mi caso, he abusado bastante de la función "lookup_widget" en mis proyectos. Básicamente esta función obtenía el widget a partir del nombre del mismo , y para ello barría todo el árbol de dependencias del widget del programa hasta que lo encontraba, en un mecanismo poco eficiente. En cambio, ahora utilizaré la librería "libglade", que analiza el contenido del archivo ".glade" en tiempo de ejecución y realiza las ligaduras necesarias entre los nombres de los widgets, las funciones de callback asociadas, etc. La forma de utilizar libglade, es mediante el uso de GtkBuilder, que parsea el archivo "xml" en tiempo de ejecución, crea los widgets, configura sus propiedades, y asocia las funciones de callback que están definidas en nuestro código. Además, se crea una estructura con punteros a todos aquellos widgets que deban ser accedidos en las funciones de callback (que no sean aquellos widgets cuya acción está asociada a la función), los cuales se inicializan al momento de crear el builder, y de esa manera pueden ser accedidos en cualquier momento.

La primer corrección que realizaré, es la reformulación de la función genéricamente conocida como "create_window()" ("create_ventana_principal()", en mi ejemplo), que desapareció al eliminarse el archivo "interface.c":

La linea:

ventana_principal = create_window ();

Se convierte en:

item = g_slice_new(ItemStruct);

if (create_window(item) == FALSE) {
      logmsg("Error al crear pantalla");
      return(-1);
}


El argumento de la función "create_window()", es un puntero a la estructura que agrupa los widgets que deban ser accedidos en las funciones de callback. A priori no conozco el contenido de la estructura, sino que la iré armando a medida que avance en el proceso de recompilación exitosa del programa. Por lo pronto, incluye al menos un elemento: el puntero al widget "ventana_principal". Un buen lugar para definir esta estructura es el archivo "callbacks.h":

typedef struct {
   GtkWidget       *ventana_principal;
} ItemStruct;

ItemStruct *item;

Podemos definir la función "create_window()" en el mismo archivo "main.c":

gboolean create_window (ItemStruct *item)
{
   GtkBuilder *builder;
   GError* error = NULL;

   builder = gtk_builder_new ();
   if (!gtk_builder_add_from_file (builder, UI_FILE, &error)) {
      g_warning ("No puedo crear builder a partir del archivo: %s", error->message);
      g_error_free (error);
      return FALSE;
   }

   /* Asocia el widget cuyo nombre es "ventana_principal", con el elemento de la estructura que lo va a representar */
   item->ventana_principal = GTK_WIDGET (gtk_builder_get_object (builder, "ventana_principal"));

   /* Asocia las señales */
   gtk_builder_connect_signals (builder, item);

   g_object_unref (builder);

   return TRUE;
}

Esta función también es el lugar propicio para definir cualquier cuestión relacionada a la definición estética de nuestra interfaz, que no esté ya contemplada por el archivo ".xml" de glade, o en el archivo "rc" que estemos usando. UI_FILE es un macro que define el path al archivo ".xml" de glade.

Al comentar o eliminar aquellas lineas donde utilicé la función "
lookup_widget()", se generan los siguientes mensajes en los sucesivos intentos de compilación:

main.c:XXX: error: 'my_variable' undeclared (first use in this function)

La variable "my_variable", que es un puntero a un widget cuyo valor se completaba con el uso de la función "lookup_widget()", ahora debe ser parte de la estructura mencionada anteriormente, y debe inicializarse durante la invocación de la función "create_window()". La estructura quedaría conformada de esta manera:


typedef struct {
   GtkWidget       *ventana_principal;
   GtkWidget       *my_variable;
} ItemStruct;

ItemStruct *item;



Y en la función "create_window()" agregamos la asociación a este nuevo puntero de widget:

      ...

   /* Asocia el widget cuyo nombre es "ventana_principal", con el elemento de la estructura que lo va a representar */
   item->ventana_principal = GTK_WIDGET (gtk_builder_get_object (builder, "ventana_principal"));



   item->my_variable = GTK_WIDGET (gtk_builder_get_object (builder, "my_variable"));


      ...



En la linea donde el compilador acusó el mensaje de error, se debe reemplazar la aparición de "my_variable" por el elemento de la estructura. Ejemplo:

gtk_widget_modify_font(GTK_WIDGET(my_variable), pfd);

se debe reemplazar por:

gtk_widget_modify_font(GTK_WIDGET(item->my_variable), pfd);

De a poco vamos agregando todos los widgets que deben ser accedidos, y que por ende deberán ser incluidos en la estructura, de tal manera que no se obtengan más esos mensajes del compilador. Este tipo de reemplazos deberemos realizarlos tanto en "main.c" como en "callbacks.c".

En relación a la función "add_pixmap_directory()", en principio podemos evitar su uso, por cuanto la relación a los archivos de imagen está definida en el archivo ".glade". De cualquier manera, si necesitamos referirnos a alguna imagen en particular, podemos utilizar la función "glade_xml_get_widget()".


Con un poco de suerte, nuestro compilador dejará de informarnos que existen errores o faltantes en nuestro código. Igualmente, si queremos ejecutar el código, probablemente aparezcan errores o bien se vea horrible. Esto es porque debemos corregir el archivo ".glade" aún.





Adaptando el archivo de interfaz a la nueva versión de Glade

Existen algunas diferencias entre las versiones de glade 2 y 3, en relación a las marcas de xml que definen los distintos widgets. Aquí van algunas de ellas:


  • La marca <glade-interface> ahora es <interface>, y la marca de cierre pasa de ser </glade-interface> a </interface>
  • La marca <widget class= ahora es <object class=, y la marca de cierre pasa de ser </widget> a </object>
  • Además a los widgets tipo "GtkVBox" y "GtkVPane" hay que agregarles la propiedad <property name="orientation">vertical</property> para que los widgets no se vean apaisados.
  • Los widgets de tipo "GtkComboBox" que muestra una lista sencilla de texto presentan cambios en versiones más nuevas de GTK+. Si bien existe un widget "GtkComboBoxText" que se asemeja bastante al anterior, este no es reconocido por glade 3.12.1, por lo cual si queremos seguir utilizando glade, debemos utilizar el modelo de lista e integrarlo al GtkComboBox. Los pasos se detallan a continuación:
  1. Desde anjuta, se debe asociar el widget "GtkComboBox" a un modelo de ComboBox. Este modelo se define por separado, y en él se establecen cuántas columnas va a tener, y que tipo de datos va a incluir cada una. Cada columna se identifica con un nombre distintivo. Además se pueden precargar valores en el listado. En el caso de las listas sencillas de texto, la cantidad de columnas va a ser 1, de tipo "gchararray". Una vez definido el modelo de lista, con el botón derecho del mouse sobre el widget se debe acceder a la opción "Editar...", -> pestaña "jerarquía" -> "añadir" una nueva jerarquía donde se define el nombre, el tipo, y a que atributo estará relacionado (en principio, el atributo elegido es el correspondiente a la única columna definida previamente en el modelo). 
  2. En lo correspondiente al código del programa, se deben realizar los siguientes reemplazos:
          // Bucle para completar el ComboBox
          while ( ... ) {
              gtk_combo_box_append_text(GTK_COMBO_BOX(comboBox), item);
          }

por


          GtkListStore *listaModelo;
          GtkTreeIter iter;


          // Bucle para completar el ComboBox
          while ( ... ) {
              gtk_list_store_append (listaModelo, &iter);
              gtk_list_store_set (listaModelo, &iter, 0, item, -1);
          }
          // se asocia la lista al ComboBox 
          gtk_combo_box_set_model(comboBox, GTK_TREE_MODEL(listaModelo));
          // "elementoLista" es el widget creado cuando se definió la jerarquía
          gtk_cell_layout_add_attribute (comboBox, elementoLista, "text", 0);

Aún puede aparecer un mensaje de error en tiempo de ejecución, del tipo "Could not find signal handler  ... ". Este mensaje indica que no se encuentra la definición de señales definidas y asociadas a algún widget (típicamente definidas en el archivo "callbacks.c"). Para evitar que se presente este error, se debe compilar el proyecto con el flag adicional "-export-dynamic". Esto se puede definir desde el menú "Construir -> Configurar el proyecto ... -> Opciones de configuración:", agregando 'CFLAGS=-export-dynamic'

Referencias:
http://www.micahcarrick.com/gnome-anjuta-programming-tutorial.html
http://www.micahcarrick.com/libglade-anjuta-tutorial.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-1.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-2.html

http://www.micahcarrick.com/gtk-glade-tutorial-part-3.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-3.html
http://www.micahcarrick.com/gtk-glade-tutorial-part-4.html
http://old.nabble.com/please-help:-bug:-glade2-file-conversion-to-glade3-failed-td26467085.html
http://ubuntuforums.org/showthread.php?t=1377005

"Creating GtkComboBox using Glade":
http://www.youtube.com/watch?v=Z5_F-rW2cL8

"gtk_combo_box_append_text() problem":
http://permalink.gmane.org/gmane.comp.gnome.glade.user/3774