Programación de Aplicaciones Usando las Bibliotecas GNOMEGeorgeLeblEl Proyecto GNOMEjirka@5z.com1999George Lebl
En este tutorial, usted recibirá una primera impresión de las
bibliotecas de GNOME. Aprenderá como desarrollar rápidamente
aplicaciones usando dichas bibliotecas y cómo conseguir interfaces de
usuario consistentes usando los widgets propios de GNOME. Otro punto
en el que se centrará este documento es en el desarrollo de
aplicaciones en lenguaje C, usando el conjunto de widgets GTK+.Créditos, Copyrights y Otras Aclaraciones Adicionales
Todo el código contenido en este tutorial se acoge a la Licencia
Pública General GNU (GPL) o a la Licencia Pública General para
Bibliotecas GNU (LGPL). El código fue escrito por los integrantes del
equipo de desarrollo de GNOME y por el autor mismo.
El autor expresa sus disculpas acerca de errores gramaticales o de
ortografía que pudieran aparecer en el texto. El inglés no es su
lengua nativa y espera que su código sea lo suficientemente explícito
como para suplir sus carencias con el idioma.Sobre la Traducción
La presente traducción es fruto del trabajo de un grupo de personas
pertenecientes a LILO [Asociación de Informática de la Universidad de
Alcalá de Henares]. Este trabajo ha sido realizado con la esperanza de
acercar el software libre y más concretamente la biblioteca de
desarrollo de aplicaciones gráficas GNOME, a la comunidad
hispanohablante.
Durante el proceso de elaboración de esta traducción ha colaborado un
grupo heterogéneo de personas, por lo tanto, y aunque no hemos
escatimado esfuerzos para presentar la traducción más coherente
posible, es probable que el lector se encuentre con algunos
fallos. Agradeceríamos en gran medida que si detecta alguna errata o
tiene alguna idea de cómo mejorar a la traducción nos la haga saber
para incluir sus aportaciones en próximas revisiones.
Quisiéramos también expresar nuestro agradecimiento a todos los
miembros de la lista de correo de LILO, a Gnome-es, a la Escuela
Politécnica de Alcalá de Henares y a todas las personas que directa o
indirectamente han colaborado con LILO, haciendo posible la presente
traducción.
Los autores de esta traducción son, por riguroso orden alfabético, los
siguientes:David Fernández Barrero dfb60332@alu.uah.esJavier Ballesteros Plaza heraclit0@wanadoo.esJorge García González gar_gon@teleline.es>Jorge Rodríguez jorginius@interlap.com.arPablo Marín Tomás pablo.tmarin@uah.esRaúl Ocaña Rodríguez ror@retemail.es
La revisión del texto ha sido realizada por Sergio de la Hoz Pérez
purificalo@hotmail.es.Por último, si
desea contactar con el grupo LILO, puede hacerlo a través de
http://lilo.sourceforge.netVistazo a las Bibliotecas GNOMEDónde Encajan las Bibliotecas GNOME
Antes de entrar en describir las especificaciones de las bibliotecas
GNOME, es importante ver dónde encajan en el conjunto de todas las
diferentes bibliotecas que son usadas en una aplicación GNOME. Las
bibliotecas GNOME están en el nivel más alto. GTK+ con sus dos partes,
GTK y GDK forman el siguiente nivel. GTK proporciona un modelo de
objetos para C y un conjunto de herramientas de interfaz de usuario
con los widgets básicos que dan la base genérica para una GUI. GTK
depende de GDK, el cual es una envoltura alrededor de Xlib, la
biblioteca que habla directamente con el servidor X. Todo (excepto
Xlib) depende de GLib, que es una biblioteca de C muy práctica con
muchas utilidades y portabilidad así como un surtido de contenedores
para C fáciles de usar.
Jerarquía de Bibliotecas Enlazadas con una Aplicación GNOMEEstructura de las Bibliotecas GNOME
Ahora miramos a la estructura de las bibliotecas GNOME para ver
qué nos pueden ofrecer. Aquí hay un listado de las diferentes
bibliotecas que están presentes en el paquete gnome-libs:
libgnomeBiblioteca de utilidades independiente del conjunto de
herramientaslibgnomeuiBiblioteca dependiente del conjunto de herramientaslibgnorbaBiblioteca para usar la implementación de CORBA ORBit con
GNOMEgtk-xmhtmlwidget xmhtml portado de GTK, usado para el visualizador de
ayudazvtwidget de emulación de terminallibvfsUna biblioteca de un sistema de archivos virtual usado en
Midnight Commanderlibart_lgplUna biblioteca usada para mostrar bonitos gráficos con suavizado
(N.T: anti-aliased)
No cubriremos gtk-xmhtml, zvt, libvfs, libart_lgpl ni libgnorba porque son
mayoritariamente bibliotecas especializadas y algunas, como libvfs y gtk-xmhtml
probablemente serán remplazadas en un futuro cercano.
Podemos ver una clara división entre libgnome y
libgnomeui. La primera es usada de forma
independiente al conjunto de herramientas e incluso puede ser
utilizada en programa de línea de órdenes que nunca usan X. La última
es la biblioteca que proporciona los widget estándar y un armazón para
aplicaciones escritas usando GTK+. Puede concebirse escribir programas
con otros conjuntos de herramientas, pero nadie ha escrito todavía una
libgnomeui con un conjunto de herramientas
diferente, y dudo que esto pase alguna vez porque GTK+ es realmente un
gran conjunto de herramientas.Programando con GTK+Introducción
GTK+ es una biblioteca escrita en C, que aglutina a un conjunto de
widgets orientados hacia la programación de aplicaciones gráficas en X
Window. Está altamente orientada a objetos y se acopla con los
lenguajes más populares, como C++, Objective C, Perl, TOM, Guile,
Python, etc... GTK+ además usa GLib, que es una biblioteca en C muy
útil, incluye ayudas para portar los programas a diferentes
arquitecturas y contenedores como listas enlazadas o tablas de claves
(N.T: hash tables). Si ya está familiarizado con GTK+, entonces se
aburrirá leyendo esta sección.GLibAcuerdos en la Nomenclatura
GLib es una biblioteca muy usada en GTK+ y en casi todo GNOME. Las
funciones de GLib empiezan con g_ (como
g_strdup), la definición de tipos de GLib para la
mayoría de ellos tienen simplemente como prefijo una
g (como gint32), y las
estructuras de GLib están en mayúsculas y empiezan con una
G (como
GHashTable).Definición de Tipos
GLib proporciona algunos tipos predefinidos para facilitar la
portabilidad, simplificar y clarificar el código y para mantener
cierta consistencia. La siguiente tabla muestra estos tipos. Si el
campo Equivalente está en blanco, significa que
no hay representación equivalente en el C estandar.
Tipos en GLibNombreEquivalenteDescripcióngint8Entero con signo de 8 bitsguint8Entero sin signo de 8 bitsgint16Entero con signo de 16 bitsguint16Entero sin signo de 16 bitsgint32Entero con signo de 32 bitsguint32Entero sin signo de 32 bitsgint64Entero con signo de 64 bits (vea nota al margen)guint64Entero sin signo de 64 bits (vea nota al margen)gcharcharCaráctergucharunsigned charCarácter sin signogshortshortEntero cortogushortunsigned shortEntero corto sin signoglonglongEntero largogulongunsigned longEntero largo sin signogintintEnteroguintunsigned intEntero sin signogfloatfloatNúmero realgdoubledoubleNúmero real de doble precisióngbooleanintTipo para almacenar valores VERDADERO/FALSOgpointervoid *Tipo para almacenar punteros a distintos objetosgconstpointerconst void *Tipo para almacenar punteros a distintos objetos inmutables
Se debe tener en cuenta que gint64 y
guint64 pueden no estar disponibles en todas las
plataformas. Puede comprobar esto en su código viendo si la macro
G_HAVE_GINT64 está definida
Como puede ver, algunos de los tipos como
gint parecen no tener otro sentido en la vida que
tener el prefijo 'g' (Son idénticos a los ofrecidos por C
estandar). La razón de su existencia es la de hacer el código mas
consistente y limpio. Aunque no sea un crimen no usar estos tipos,
debería emplearlos para facilitar la portabilidad de su
código. Algunos de los tipos como gboolean están
sólo para mejorar la claridad del código y podría usar también
int para hacer exactamente lo mismo, pero el
anterior método indica claramente que se esta hablando de un valor que
solo puede tomar los valores TRUE/FALSE (VERDADERO/FALSO).Portabilidad y Funciones de Utilidad
Hay funciones que o bien no se comportan exactamente igual en todos
los sistemas, o bien son inseguras o bien no existen en alguna
plataforma (o en ninguna), así que GLib proporciona sus propias
implementaciones o recubrimientos que tienen un comportamiento
constante y que normalmente comprueban los argumentos.
Aquí tiene algunas de las funciones más útiles que encontrará en
esta categoría. Tenga en cuenta que el encabezado "Prototipo" es a
título informativo, ya que algunas de estas funciones pueden ser
realmente macros.
Algunas Funciones Portables de GLibPrototipoDescripcióngchar * g_strdup (const gchar *)Devuelve
la localización de una nueva cadena que es una copia del argumento, si el
argumento es NULL, se devuelve NULLgpointer g_malloc (int tam)Devuelve una
nueva región de memoria de 'tam' bytes, si no se puede reservar la
memoria solicitada, el programa abortará invocando
g_errorvoid g_free (gpointer p)Libera la memoria
apuntada por 'p', si 'p' es NULL no realiza ninguna
operacióngint g_snprintf (gchar *cadena, gulong n, gchar const
*formato, ...) Funciona simplemente como sprintf
escribiendo los argumentos de acuerdo con 'formato' en 'cadena', sin
embargo solo usará 'n' bytes de la 'cadena', así que truncará el
resultado si éste necesitase más. Devuelve el numero de bytes que son en
realidad escritos en 'cadena'.void g_usleep (gulong cont)Suspende la
ejecución durante al menos 'cont' microsegundosgint g_strcasecmp (const gchar *c1, const gchar *c2)Compara la cadena 'c1' y 'c2' sin tener en cuenta las
mayúsculasgint g_strncasecmp (const gchar *c1, const gchar *c2, guint n)
Compara los 'n' primeros caracteres de la cadena 'c1' y 'c2' sin
tener en cuenta las mayúsculas
También hay algunas funciones de utilidad y macros que realmente no se
encuentran en las bibliotecas normales de C. Aquí hay una pequeña lista de
algunas de las más útiles e importantes.
Algunas Funciones Útiles de GLibPrototipoDescripcióng_new (tipo, cont)Una macro que asignará memoria nueva para 'cont' elementos del tipo
'tipo' y devuelve el resultado como 'tipo'. Es equivalente a
'(tipo) g_malloc(cont * sizeof(tipo))'g_new0 (tipo,cont)La misma semántica que g_new, excepto que la memoria devuelta estará a
cero. Tenga en cuenta que no se debería asumir que poner la memoria a cero
pondrá a cero los números reales (Del tipo 'float')gchar * g_strconcat (const gchar *cad, ...)Cuando se le pasa cualquier numero de argumentos del tipo (const char *)
y un NULL despues del último argumento, devolverá una nueva cadena que
proviene de la concatenación de todos los argumentos.gchar * g_strdup_printf (const gchar *formato, ...)Una función como printf que devolverá una nueva cadena con el resultado
de la operación printfgchar * g_strstrip (gchar *cadena)
Eliminará los espacios en blanco del principio y final de una
cadena. No reserva memoria nueva, pero modifica la cadena original y
devuelve un puntero a ella. Si desea reservar memoria nueva use una
construcción como esta: 'cadena2 = g_strstrip(g_strdup(cadena1));'
Hay otros utilísimos métodos en GLib, y le insto a estudiar la
documentación de GLib y el archivo de cabecera de GLib
(glib.h): si lo hace ahorrará gran cantidad de
tiempo no reimplementando funcionalidades básicas.Contenedores
Probablemente la mejor parte de GLib sean sus contenedores. Aquí tenemos una
lista de los contenedores de GLib.
Contenedores de GLib ComunesNombreDescripciónGListLista doblemente enlazadaGSListLista enlazada simpleGHashTableTabla de claves (N.T: hash)GCacheCacheGTreeÁrbol binario balanceadoGNodeÁrbol n-simoGStringCadena de tamaño dinámicoGArrayVector (N.T: Array) de tamaño dinámicoGPtrArrayVector de punteros de tamaño dinámicoGByteArrayVector de bytes (guint8) de tamaño dinámico
GList, Lista Doblemente Enlazada
La forma más fácil de usar listas o pilas en su código es con
GList. La estructura básica de
GList representa por un lado una lista doblemente
enlazada y por otro un único nodo de la lista. Podrá añadir sus datos
dentro del puntero de datos de GList (El miembro
'data' perteneciente a la estructura). Aquí tenemos una enumeración de
las funciones que actúan sobre GList. Las funciones normalmente
recogen un puntero y devuelven la lista modificada (Un puntero a
ella). Tenga en cuenta que el primer nodo puede ser ahora diferente.
Funciones Más Importantes de GListPrototipoDescripciónGList* g_list_append (GList *lista, gpointer
datos)Añade al final de la lista un nuevo nodo que
contiene el puntero 'datos'. Si 'lista' es NULL creará una lista
nueva.GList* g_list_prepend (GList *lista, gpointer
datos)Añade en el inicio de la lista un nodo que
contiene 'datos', si 'lista' es NULL creará una lista
nueva.GList* g_list_remove (GList *lista, gpointer
datos)Elimina el nodo que contiene el parametro
'datos' de la lista. Tenga en cuenta que sólo se comparan punteros (Y
no contenidos).GList* g_list_find (GList *lista, gpointer datos)Encuentra el nodo de GList que contiene el parametro
'datos'. De nuevo, téngase en cuenta que se comparan únicamente los
punteros.GList* g_list_next (GList *lista)Una macro que devuelve un puntero al nodo posterior.GList* g_list_previous (GList *list)Una macro que devuelve un puntero al nodo anterior.void g_list_free(GList *list)Libera la
lista entera. NO libera la memoria asignada a sus
datos, esto debe hacerlo usted mismo antes antes de invocar a esta
función.
Para acceder a los datos de un nodo particular de
GList lea el miembro data en
la estructura GList. Así, el código que crearía
una lista doblemente enlazada de dos elementos con dos cadenas
duplicadas, y después liberara esa lista y las cadenas, quedaría así:
GList *list = NULL; /*el actual puntero a la lista*/
GList *li; /*simplemente un puntero temporal a un nodo usado para interaccionar
con la lista*/
...
/*aquí añadimos dos cadenas a la lista*/
list = g_list_append(list,g_strdup("Cadena 1"));
list = g_list_append(list,g_strdup("Cadena 2"));
...
/*aquí nos movemos por la lista, liberando todas las cadenas y al final
/* liberamos la lista en sí*/
for(li = list; li!= NULL; li = g_list_next(li)) {
char *string = li->data;
g_free(string);
}
g_list_free(list);
GString, Cadenas de Tamaño DinámicoOtro contenedor simple de usar y muy útil es el contenedor
GString. Es un contenedor para cadenas de tamaño
dinámico, ideales en las ocasiones en las que no sabe que longitud
tendrá la cadena que va a necesitar. Aquí tenemos una lista de las
funciones más importantes.
Funciones Más Importantes de GString
PrototipoDescripciónGString* g_string_new (const gchar *cad)Crea una GString nueva con el valor 'cad'void g_string_free (GString *cadena, int
borra_contenido)Libera la estructura GString y
opcionalmente también el segmento de datos de la cadenaGString* g_string_append(GString *cadena, const gchar
*val)Añade al final 'val' a 'cadena'GString* g_string_prepend (GString *cadena, const gchar *val)Añade al principio 'val' a 'cadena'void g_string_sprintf (GString *cadena, const gchar *formato, ...)
Una función como sprintf para GStringvoid g_string_sprintfa (GString *cadena, const gchar *formato, ...)
Una función como sprintf para GString, pero añade la cadena en lugar de
sobreescribirla
Para acceder a los datos de la cadena para usarlos como un
char *, simplemente acceda al elemento
str de la estructura GString. En
realidad es posible liberar la estructura GString sin
liberar el segmento de datos. Esto es útil si quiere crear una cadena en C
normal. El siguiente ejemplo es una función que coge un vector de enteros y los
escribe dentro de una cadena y devuelve un char *.
char *
create_number_list(int array[], int array_len)
{
int i; /* el contador del vector */
GString *string; /* la variable GString */
char *ret; /* al valor de retorno */
/* crear una nueva GString vacía */
string = g_string_new("");
/* recorrer el vector de enteros */
for(i=0; i<array_len; i++) {
/* añadir el número a la cadena en paréntesis */
g_string_sprintfa(string, "(%d)", array[i]);
}
/* poner el valor de retorno */
ret = string->str;
/* liberar la estructura GString, pero no los datos */
g_string_free(string,FALSE);
/* devolver la cadena */
return ret;
}
GHashTable
Aunque usado con menor frecuencia que GList y GString, el contenedor
para tablas de claves es muy útil también. Una tabla de claves se
podría entender como un conjunto de objetos (en GLib el término objeto
se refiere a un gpointer) y sendas llaves, estás
últimas nos permitirán acceder a cada objeto posteriormente. GLib
lleva este hecho más allá, haciendo que la llave sea un
gpointer también, y dandonos la posibilidad de
que le proporcionemos una función de comparación y de acceso (N.T:
hashing). Aunque esto hace a GHashTable mucho
más flexible, puede llevarnos a alguna confusión con respecto a la
asignación de la memoria para las llaves. Vamos a enunciar algunas
funciones importantes y nos ocuparemos de los detalles posteriormente:
Funciones Más Importantes de GHashTable
PrototipoDescripciónGHashTable* g_hash_table_new (GHashFunc func_hash,
GCompareFunc func_clave_cmp)Crea una tabla de claves
nueva usando la función de generación de claves y la de comparación
especificadasvoid g_hash_table_destroy (GHashTable *tabla_hash)Destruye la tabla de claves y libera la memoria. Esto no implica que libere
ningún dato o las claves, esto lo tendrá que hacer usted mismo
void g_hash_table_insert (GHashTable *tabla_hash, gpointer
clave, gpointer valor)Introduce un nuevo 'valor' con
una clave 'clave'void g_hash_table_remove (GHashTable *tabla_hash,
gconstpointer clave)Elimina el valor con la clave 'clave' de la tabla. No libera ni el valor
ni la clave.gpointer g_hash_table_lookup (GHashTable *tabla_hash,
gconstpointer clave)Busca el valor del puntero con clave 'clave'. Devuelve NULL si no lo
encuentragboolean g_hash_table_lookup_extended (GHashTable
*tabla_hash, gconstpointer lookup_key, gpointer *orig_key, gpointer
*value)Consulta si existe el dato con clave
'clave_valor', almacena la clave del puntero original en 'clave_orig'
y el valor en 'valor'. Devuelve TRUE si la consulta tuvo éxito, FALSE
en caso contrario. Debe usar esta función al eliminar un elemento de
la memoria para deshacerse de la clave original. void g_hash_table_foreach (GHashTable *tabla_hash, GHFunc
func, gpointer datos)Ejecuta 'func' sobre cada dato
almacenado en la tabla de claves. El parámetro 'datos' se le pasará a
la función como último argumento. El prototipo de GHFunc es el
siguiente.void (*GHFunc) (gpointer clave, gpointer valor, gpointer
datos)Esta es la función prototipo que usará con la
función que se pasa a g_hash_table_foreach. Coge la 'clave', el 'valor' y
los 'datos', especificado este último campo en la llamada a
g_hash_table_foreach.guint g_str_hash (gconstpointer v)Una función para generar claves a partir de cadenasgint g_str_equal (gconstpointer v, gconstpointer v2)Una función de comparación de cadenas para tablas de clavesguint g_int_hash (gconstpointer v)Una función para generar claves a partir de enterosgint g_int_equal (gconstpointer v, gconstpointer v2)Una función de comparación de enteros para tablas de claves
Para crear una tabla de claves, pase las funciones de acceso y de comparación
de claves a g_hash_table_new. Hay funciones genéricas
definidas para cadenas (g_str_hash y
g_str_equal) y otras. Sin embargo si le pasa
NULL como funciones de acceso y de comparación, obtendrá un puntero
directo: los punteros serán en realidad utilizados como claves.
El problema de la reserva de memoria se vuelve aparente cuando
empezamos a usar cadenas como claves.GHashTable
no almacena la cadena, todo lo que almacena es un puntero. Por tanto,
cuando insertamos un valor dentro de la tabla, tenemos que crear una
copia nueva de la clave para ese valor (un otro caso, habrá elementos
que compartan la clave). Esto es importante recordarlo pues sino las
cosas no funcionarán como esperamos. El otro problema es como liberar
entonces la clave. Si usa g_hash_table_remove,
dará como clave una cadena con el mismo contenido que la clave
original pero no en la misma zona de memoria (un puntero distinto).
Entonces, el puntero a la clave original se habrá perdido y a
menos que haya almacenado la dirección en algún sitio se habrá creado
una laguna en la memoria. Lo que tiene que hacer en su lugar es usar
g_hash_table_lookup_extended para obtener primero
los punteros a la clave y al valor y luego hacer uso de
g_hash_table_remove.
El siguiente ejemplo creará una nueva tabla de claves, insertará una
pareja de cadenas, las recuperaremos, y entonces destruiremos la tabla
y los valores almacenados en ella:
/* función que usaremos para liberar las claves y los datos en la tabla antes
de que la destruyamos */
static void
free_key_value(gpointer key, gpointer value, gpointer user_data)
{
g_free(key);
g_free(value);
}
...
/* en algún lugar del código */
GHashTable *ht;
/* crea una nueva tabla de claves cadenas como claves*/
ht = g_hash_table_new(g_str_hash, g_str_equal);
/* introduce una pareja de cadenas (es decir, colores marcados por la forma) */
g_hash_table_insert(ht, g_strdup("triangulo"), g_strdup("verde"));
g_hash_table_insert(ht, g_strdup("cuadrado"), g_strdup("rojo"));
g_hash_table_insert(ht, g_strdup("círculo"), g_strdup("azul"));
/* de nuevo, en algún lugar del código */
...
/* ahora aquí deseamos imprimir el color de un cuadrado */
char *color;
/* coger el color del cuadrado*/
color = g_hash_table_lookup(ht, "cuadrado");
printf("El color del cuadrado es: %s\n",color);
/* de nuevo en algún lugar del código */
...
/* Ahora simplemente queremos destruir la tabla de claves y liberar toda la
* memoria asociada a ella. Usaremos la función free_key_value y la usaremos
* sobre todos los valores de la tabla. */
g_hash_foreach(ht, free_key_value, NULL);
/* ahora podemos destruir la tabla de claves actual */
g_hash_table_destroy(ht);
Más Información Sobre GLib
Para mas información mirad el archivo de cabecera glib.h y
la documentación el sitio web
www.gtk.org.
GTK+GUIs Básicos
Escribir un GUI (Graphic User Interface: Interfáz gráfico de Usuario)
basado en GTK+ es en esencia extremadamente simple, y nosotros
usaremos el ejemplo de Hola Mundo del excelente tutorial de GTK+ de
Ian Main, el cual es una utilísima guía para escribir aplicaciones
para GNOME. Pero primero hablaremos sobre la filosofía oculta de
GTK+.
GTK+ es una herramienta basada en contenedores, queriendo esto decir
que no especifica dónde está el widget, sino en que contenedor está.
Algunos widget, como una ventana, un marco o un botón, son
contenedores que sólo pueden almacenar otro único widget. Por ejemplo
un botón con una etiqueta es realmente un botón dentro del cual hemos
añadido un widget etiqueta. Si necesita poner más widgets dentro de
ese contenedor, necesitará añadir otro contenedor dentro de él.
De hecho la mayoría de capas de una ventana normalmente están formadas
con contenedores como cajas horizontales, cajas verticales y tablas,
estos son los más importantes de aprender. Una caja horizontal es un
widget al que puede añadir varios widgets dentro y estos serán
añadidos en una fila horizontal. La altura de la caja horizontal es la
altura del widget añadido más alto y su longitud es la suma de la
longitud de todos los widgets. Las cajas verticales se comportan
exactamente igual, excepto que es vertical en lugar de horizontal.
Una tabla puede incluir widget en diferentes filas y columnas.
Ejemplo de la Jerarquía de la VentanaModelo de Objetos en GTK+
El modelo de objetos de Gtk es un marco de trabajo orientado a objetos
para C. Incluye una singular herencia de objetos, metodos virtuales,
señales, modificación de objetos en tiempo de ejecución, comprobación
de tipos en tiempo de ejecución y otras bondades. Aunque escribir un
objeto GTK+ es más enrevesado que escribirlo en algo como Java, tiene
muchas ventajas. GTK+ es un modelo de objetos que no requiere herencia
para la mayoría de las cosas que se hacen con objetos. Por ejemplo,
como los métodos son simplemente funciones que cogen un puntero al
objeto como primer argumento, es muy fácil escribir más metodos en su
propio código, que no existían en el objeto original.
Los objetos en GTK+ son simplemente estructuras en C y la herencia se
logra simplemente incluyendo la estructura padre como primer elemento
en la estructura hija. Esto significa que podemos hacer conversiones
explícitas entre tipos sin problemas. Además de la estructura del
objeto que es una instancia, hay también una estructura de clase para
cada clase que contenga cosas como punteros a funciones virtuales y
manejadores de señales.
Tipos de Metodos en GTK+
El modelo de objetos en GTK+ usa 3 tipos de métodos, un método puede
ser una simple función C que espera un puntero a un objeto como primer
argumento. Este tipo de método es el más rápido en ejecutarse, pero el
programador no dispone de ningún mecanismo para modificar su
funcionamiento o para reemplazarlo por su propio método.
El segundo tipo es el método virtual. Usted puede reemplazar estos
métodos por los suyos propios en clases derivadas (N.T: lo que
comúnmente se llama "polimorfismo" en los términos de la programación
orientada a objetos), pero hasta aquí llegan sus ventajas. Un método
virtual se implementa como un puntero a una función en la estructura
de la clase, normalmente con una función de recubrimiento que se
encarga de llamar al método virtual.
El tercer tipo de método y el más flexible (y también el de llamada
más lenta) es el método de señal. El tipo señal es parecido al
virtual, el usuario puede modificar el funcionamiento del método pero
esto lo consigue conectando manejadores que se pueden ejecutar antes o
despues del método por defecto. Algunas veces los objetos tienen una
señal con el único propósito de tener manejadores del programador
conectados a ella. Estas señales tienen su cuerpo de método vacío (No
aportan método por defecto alguno).
Datos en Objetos
Hay una forma de almacenar datos arbitrarios en los objetos para extender el
objeto. Esto se hace con el método, gtk_object_set_data
(o gtk_object_set_user_data para un puntero simple sin
nombre). Para escoger el dato, usamos gtk_object_get_data.
Ejemplo:
GtkObject *obj;
void *some_pointer;
...
/* aquí ponemos el dato "some_data" en obj para que apunte a
some_pointer */
gtk_object_set_data(obj,"some_data",some_pointer);
...
/* recuperamos el puntero de some_data desde obj y lo guardamos en
some_pointer */
some_pointer = gtk_object_get_data(obj,"some_data");
El puntero puede ser de un tipo desconocido (Un (void *))
puesto que es manipulado como tal.
Convención de Nombres en GTK+/GNOME
GTK+ y GNOME usan la misma norma para los nombres de objetos y
funciones. GTK+ usa el prefijo gtk_ para las
funciones, y Gtk para los objetos, y GNOME usa
gnome_ y Gnome. Cuando una
función es un método de un objeto, el nombre (en minúsculas) se añade
al prefijo. Por ejemplo, el objeto botón se llama
GtkButton (que es el nombre de la estructura en C
que sustenta los datos del objeto), y llama al método
"new" (El constructor) para
GtkButton como
gtk_button_new. Las macros asociadas con objetos
usan la misma norma que las funciones, pero todas están en
mayúsculas. Por ejemplo una macro que convierte explícitamente un
objeto a GtkButton se llama
GTK_BUTTON. Hay excepciones importantes, la macro
que comprueba el tipo para GtkButton, se llama
GTK_IS_BUTTON.Usando Métodos en GTK+
Puesto que GTK+ es orientado a objetos, usa herencia para los
widgets. Por ejemplo GtkHBox y
GtkVBox derivan de GtkBox. Y
así se puede usar cualquier método GtkBox sobre
GtkVBox o GtkHBox. Sin
embargo necesitamos convertir explícitamente el objeto
GtkVBox a GtkBox antes de
que poder llamar a la función. Esto se podría hacer en C estándar de
esta forma:
GtkVBox *vbox;
...
gtk_box_pack_start((GtkBox *)vbox, ...);
...
Aunque esto funcionaría, es
inseguro (No verifica en ningún momento que vbox sea un objeto
perteneciente a una clase derivada de GtkBox). GTK+ proporciona un
mecanismo de comprobación de tipos, de forma que pueda prevenirle si
esta haciendo un conversión a un objeto que no deriva del objeto al
que estas convirtiendo, o si intenta hacer una conversión a NULL. La
macro es el nombre del widget en mayúsculas. Por ejemplo el código
anterior debería escribirse de la siguiente manera:
GtkVBox *vbox;
...
gtk_box_pack_start(GTK_BOX(vbox), ...);
...
GNOME usa exactamente la misma forma así que cualquier concimiento que
haya adquirido sobre GTK+ puede ser usado para los widget de GNOME,
simplemente cambie el prefijo GTK por GNOME.Programa de Ejemplo Hola Mundo
Aquí esta el código de ejemplo prometido para el programa 'Hola
mundo'. No usa ningún contenedor avanzado, solamente una ventana y un
botón dentro del cual se le ha añadido una etiqueta. Ilustra el
funcionamiento básico de un GUI escrito en GTK+. No se asuste de su
tamaño, casi todo son comentarios.
/* ejemplo holamundo holamundo.c */
#include <gtk/gtk.h>
/* esto es una función de retrollamada. los argumentos son ignorados en este
* ejemplo... Mas retrollamadas más adelante. */
void
hello (GtkWidget *widget, gpointer data)
{
g_print ("Hola Mundo\n");
}
gint
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("Evento delete\n");
/* si devuelve FALSE en el manejador de la
* señal "delete_event", GTK emitirá la señal "destroy".
* Devolviendo TRUE da a entender que no quiere
* que la ventana sea destruida. Esto es útil para
* que aparezcan los diálogos típicos de '¿está seguro
* de que desea salir?'. */
/* Cambiando TRUE a FALSE la ventana principal
* sera destruida con un "delete_event". */
return (TRUE);
}
/* otra retrollamada */
void
destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int
main (int argc, char *argv[])
{
/* GtkWidget es el tipo para el almacenamiento de los widgets */
GtkWidget *window;
GtkWidget *button;
/* Esta función se debe llamar en todas la aplicaciones GTK
* antes de ninguna otra los argumentos son pasados desde la
* línea de órdenes y devueltos a la aplicación. */
gtk_init (&argc, &argv);
/* crear una nueva ventana */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* cuando a la ventana se le pasa la señal "delete_event"
* (la envía el usuario a través del gestor de ventanas), le
* pedimos que llame a la función delete_event () tal y como
* está definida arriba. El dato pasado a la función de
* retrollamada es NULL y es ignorado en la función de
* retrollamada. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* aquí conectamos el evento "destroy" al manejador
* de la señal. Este evento sucede cuando llamamos a
* gtk_widget_destroy() en la ventana, o si devolvemos
* 'FALSE' en la retrollamada "delete_event". */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* configura el ancho del borde de la ventana. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* crea un nuevo botón con la etiqueta "Hola Mundo". */
button = gtk_button_new_with_label ("Hola Mundo");
/* Cuando el botón recibe la señal "clicked", llamará
* a la función hello() pasándole NULL como argumento.
* La función hello() esta definida arriba. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* Esto causará que la ventana se destruya por la llamada
* a gtk_widget_destroy(window) cuando se emita "clicked".
* De nuevo, la señal "destroy" podría venir desde aquí o
* desde el gestor de ventanas. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* esto pone el botón dentro de una ventana
* (un contenedor gtk). */
gtk_container_add (GTK_CONTAINER (window), button);
/* el paso final es ver este recién creado widget... */
gtk_widget_show (button);
/* y la ventana */
gtk_widget_show (window);
/* Todas las aplicaciones GTK deben tener un gtk_main().
* Aquí termina el control y se espera hasta que suceda un evento
* (como una pulsación del teclado o un evento del ratón). */
gtk_main ();
return 0;
}
/* Fin del ejemplo */
Para más información mire el archivo de cabecera en
<prefijo>/include/gtk/ y <prefijo>/include/gdk/ ('prefijo'
será el directorio donde haya instalado los archivos de cabecera de
GTK+) y la documentación en el sitio web www.gtk.org.
Programación en GNOMEIntroducciónQué es un programa GNOME
Un programa GNOME es una aplicación GUI (interfaz gráfico de usuario)
para GTK+ que hace uso de las bibliotecas GNOME. Las bibliotecas
GNOME hacen posible tener un aspecto similar entre aplicaciones y
hacen las cosas más sencillas y fáciles de programar. Además las
bibliotecas GNOME añaden una completa gama de widgets que extiende el
conjunto de widgets que ofrece GTK+.
Un Programa GNOME sencillo
El siguiente programa crea una ventana GNOME básica y añade una caja
horizontal dentro de la cual se incluyen dos botones, los cuales
(cuando son presionados) imprimen una cadena hacia la salida estándar
del terminal desde el que comenzó la aplicación. La semántica y la
estructura de un programa GNOME es muy similar a la programación pura
en GTK+.
Necesitará compilar este código, así que debería mirar el capítulo
Construyendo Aplicaciones
GNOME para obtener mas información sobre como realizar este
paso. Aquí tiene un sencillo Makefile que compilará buttons.c para
usted. Para compilar otros ejemplos, sólo cambie 'buttons' por el nombre
apropiado.
CFLAGS=-g -Wall `gnome-config ––cflags gnome gnomeui`
LDFLAGS=`gnome-config ––libs gnome gnomeui`
all: buttons
Y aquí esta buttons.c:
/*
* Sencillo programa GNOME, no integrado en el árbol GNOME, sin usar i18n
* buttons.c
*/
/* La cabecera GNOME más sencilla */
#include <gnome.h>
/* una llamada para los botones */
static void
button_clicked(GtkWidget *button, gpointer data)
{
/* la cadena que se imprimirá es pasada por el campo 'data' */
char *string = data;
/* imprime una cadena en la salida estándar*/
g_print(string);
}
/* llamada cuando el usuario cierra la ventana*/
static gint
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
/* señal al bucle principal para salir*/
gtk_main_quit();
/* devuelve FALSE para continuar cerrando la ventana */
return FALSE;
}
int main(int argc, char *argv[])
{
GtkWidget *app;
GtkWidget *button;
GtkWidget *hbox;
/* Inicializa GNOME, esto es muy parecido a gtk_init */
gnome_init ("buttons-basic-example", "0.1", argc, argv);
/* Crea un widget GNOME, el cual establece una ventana básica
para su aplicación*/
app = gnome_app_new ("buttons-basic-example",
"Aplicación GNOME Básica");
/* conecta "delete_event", el cual es el evento que se obtiene
cuando el usuario cierra la ventana desde el gestor de
ventanas, con gtk_main_quit (La función que
provoca que el bucle gtk_main salga, y consecuentemente
termine la aplicación)*/
gtk_signal_connect (GTK_OBJECT (app), "delete_event",
GTK_SIGNAL_FUNC (delete_event),
NULL);
/* crea una caja horizontal para los botones y la añade dentro
de la aplicación del widget*/
hbox = gtk_hbox_new (FALSE,5);
gnome_app_set_contents (GNOME_APP (app), hbox);
/* crea un botón y lo añade a la caja horizontal */
button = gtk_button_new_with_label("Botón 1");
gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (button_clicked),
"Botón 1\n");
/* y otro botón*/
button = gtk_button_new_with_label("Botón 2");
gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (button_clicked),
"Botón 2\n");
/* muestra todo dentro de este widget y el widget mismo */
gtk_widget_show_all(app);
/* entra en el bucle principal */
gtk_main ();
return 0;
}
Por favor, observe el uso de gnome_init en lugar
de gtk_init, y del widget
GnomeApp en lugar del
GtkWindow habitual. Entraremos en detalle sobre
esto mas tarde.
Descripción
Ahora veremos las diferentes bibliotecas que vamos a cubrir. Primero
echaremos un vistazo a la biblioteca libgnome y a
sus funcionalidades, después describiremos
libgnomeui para completar todas las
funcionalidades básicas de un programa GNOME. Veremos también
gnome-canvas en detalle: un widget extremadamente
potente y útil.
Usando la Biblioteca libgnome
La biblioteca libgnome es la biblioteca de
utilidades para aplicaciones GNOME no específica del conjunto de
herramientas (GTK+). Incluye aspectos como lectura de archivos de
configuración, manejo del archivo .desktop, funciones útiles
especiales similares a las contenidas en GLib, obtención de la
localización de los archivos estándar para GNOME, manejo de tipos
MIME, manejo de meta-datos en archivos, sonido, "triggers", y otros
aspectos útiles que uno querría usar en cualquier aplicación. Por
ejemplo, si usted está escribiendo una aplicación en, digamos Motif, y
quiere que su aplicación sea compatible con GNOME, entonces podría
hacer uso de esta biblioteca para conseguirlo.Archivos de Configuración
Las funciones gnome-config proporcionan una forma
sencilla de almacenar información de configuración dentro de
archivos. Para ver una lista completa de las funciones, mire en el
archivo de cabecera
libgnome/gnome-config.h.
Todas las funciones trabajan con un camino o ruta (N.T: path). El
camino es semejante a un camino de Unix, y representa la ruta al
archivo de configuración (empezando ésta desde el directorio
~/.gnome/) y la clave que estamos buscando
dentro. Así,
/some/config/path/file/sectionA/keyB, se refiere
al archivo ~/.gnome/some/config/path/file, y
dentro del archivo usando la sección sectionA y
la clave keyB.Leyendo Información de Configuración
Para leer información de los archivos de configuración se usan las
funciones gnome_config_get_*. El
* se cambia por el tipo de dato, el cual puede
ser int, float,
string, bool y
vector. Las funciones int
trabajan con gint, las funciones
float trabajan con gdouble,
las funciones string trabajan con gchar
*, las funciones booltrabajan con
gboolean y las vector
manejan un entero con un número de elementos y un vector de cadenas que
contiene dichos elementos (gint y
gchar**). Las funciones
gnome_config_get_*, pueden aceptar valores por
defecto si usted los añade a la ruta después de un signo '='. Si
necesita saber si el valor por omisión fue usado, puede añadir un
_with_default al nombre de la función y pasarle
un gboolean *, a través del cual la función
devuelve si se empleó ese valor por defecto o si realmente
se encontró otro valor válido en el archivo de configuración. A
continuación se muestra un ejemplo:
gint contador;
gchar *texto;
gboolean def;
...
contador = gnome_config_get_int_with_default("/ejemplo/seccion/contador=1",
&def);
if(def) g_print("Usamos el contador por defecto\n");
texto = gnome_config_get_string("/ejemplo/seccion/texto=TEXT");
...
g_free(text);
Observe que la cadena devuelta por
gnome_config_get_string debería ser liberada con
g_free, el vector de
gnome_config_get_vector debería ser también
liberado con g_free.
Escribiendo la Configuración
Para escribir la configuración en un archivo, se usan las funciones
gnome_config_set_*. Su uso es muy parecido a las
anteriores gnome_config_get_*. Los tipos se usan
exactamente igual. Excepto con las funciones "set", se pasan los datos
que se quieren almacenar después de la ruta o camino y no hay valor
por omisión dentro de ella. Si el directorio en la ruta no existe,
será creado cuando las funciones escriban en el disco. Después de
configurar todos sus datos, necesitará llamar a
gnome_config_sync para escribir sus datos a un
archivo. La biblioteca no escribirá los datos a un archivo
inmediatamente por razones de eficiencia. A continuación se muestra un
ejemplo:
char *text;
int counter;
...
/*después de haber establecido un texto y un contador a algún valor,
podemos escribirlos a nuestro lugar de configuración*/
gnome_config_set_int("/example/section/counter",counter);
gnome_config_set_string("/example/section/text",text);
gnome_config_sync();
Funciones Privadas
Si quiere almacenar información comprometedora, que otros usuarios no
deberían leer, use las funciones
gnome_config_private_*: éstas se comportan de
igual modo que las anteriores, con la excepción de que estas
escriben en un directorio privado llamado
~/.gnome_private sobre el que se fuerzan los
permisos 0700. Esto no es extremadamente seguro, pero debido a las
descerebradas restricciones de exportación de los EEUU, no se
puede usar encriptación. La función
gnome_config_sync (y algunas otras) no tiene una
equivalencia privada, ya que funciona para ambos tipos de funciones.
Usando Gnome-Config para Archivos Arbitrarios
Si desea usar gnome-config para lectura y
escritura de archivos arbitrarios en el sistema de archivos (estos
archivos estarán en formato gnome-config), usted
puede añadir '=' al principio de la ruta y otro
'=' al final del nombre del archivo. A
continuación se muestra un ejemplo:
char buf[256];
...
/* escribe algunos datos en un archivo temporal */
g_snprintf(buf,256,"=%s=/seccion/clave",tmpnam(tmpnam));
gnome_config_set_int(buf,999);
gnome_config_sync();
Observe que realmente no tiene sentido usar las versiones privadas
cuando se usa una ruta absoluta arbitraria, ya que no habrá ninguna
diferencia entre privadas y normales.Prefijos Automáticos
Algunas veces, especialmente si tiene una ruta larga, será útil tener
una cadena dada que se pase de forma automática como prefijo a la ruta.
Esto es para lo que están gnome_config_push_prefix y
gnome_config_pop_prefix. Se pasa la cadena que ha
de servir como prefijo a gnome_config_push_prefix y
se llama a gnome_config_pop_prefix cuando haya
acabado de usarlo. Observe que estas funciones son comunes a las funciones de
configuración privadas y normales. Ejemplo:
gnome_config_push_prefix("/archivo/seccion/");
gnome_config_set_int("clave1",1);
gnome_config_set_int("clave2",2);
gnome_config_set_int("clave3",-88);
gnome_config_pop_prefix();
Miscelánea Gnome-Config
Si necesita borrar su archivo de configuración, debe usar
gnome_config_clean_file. Esta función planificará
qué archivo sera borrado en el próximo
gnome_config_sync. Usted puede hacer un
gnome_config_clean_file y después usar el archivo
y hacer un gnome_config_sync, y esto tendrá el
comportamiento esperado.
Si ha escrito o leído de un archivo y quiere sacarlo de memoria (los
cambios no se guardarán en disco hasta hacer 'sync') use
gnome_config_drop_file. Esto se puede usar para
deshacer los cambios hechos a este archivo, o simplemente ahorrar
recursos, ya que de otra forma gnome-config
conservará una copia de los datos en memoria para un acceso más
rápido.
Archivos .desktop
Los archivos .desktop son los que contienen
información sobre programas. Estos archivos estan en formato
gnome-config y son leídos internamente usando
gnome-config. Su aplicación necesitará uno de
estos archivos si quiere que sea accesible en el menú de GNOME.
Puede usar las funciones gnome_desktop_entry_*
para manipular esos archivos. Estas funciones trabajan con una
estructura llamada GnomeDesktopEntry y debería de
mirar al archivo de cabecera
libgnome/gnome-dentry.h para ver el formato de
esta estructura.
Las funciones básicas que se usan para manipular estos archivos son
gnome_desktop_entry_load que devuelve una nueva
estructura GnomeDesktopEntry asignada,
gnome_desktop_entry_launch que toma la estructura
GnomeDesktopEntry como un argumento y lanza el
programa que esta describe y
gnome_desktop_entry_free que libera la
memoria asignada dentro de la estructura.
Un ejemplo de archivo .desktop para su aplicación podría ser algo así:
[Desktop Entry]
Name=Clock
Name[cz]=Hodiny
Comment=Digital Clock
Comment[cz]=Digitalni Hodiny
Exec=digital-clock
Icon=clock.png
Terminal=0
Type=Application
Habrá observado que hay traducciones al checo para los campos Name y
Comment. Para otros programas GNOME observe su archivo .desktop, está
normalmente en algún lugar bajo
<prefijo>/share/apps/, el cual contiene la
jerarquía del sistema de menús. Para que el sistema encuentre su icono
debería estar situado dentro del directorio
<prefijo>/share/pixmaps. Observe que
'prefijo' se refiere a la ruta donde GNOME fue instalado.
Utilidades y ArchivosArchivos
Hay una forma estándar de encontrar archivos que pertenezcan a la
instalación de GNOME, usted no debería realmente usar su propio código
para encontrarlos ya que dispone de funciones para obtener los nombres
de los archivos para los iconos, sonido u otros datos. Estas funciones
son sólo para encontrar archivos que fueron instalados con las
bibliotecas GNOME. No hay en este momento funciones para tratar con
los datos instalados por su aplicación. Dichas funciones son:
Funciones para encontrar archivos
PrototipoDescripciónchar *gnome_libdir_file (const char *nombre_arch)Obtiene la ruta completa de un archivo en el directorio de la
biblioteca o NULL si el archivo no existechar *gnome_unconditional_libdir_file (const char
*nombre_arch)Obtiene la ruta completa de un archivo en el
directorio de la bibliotecachar *gnome_datadir_file (const char *nombre_arch)Obtiene la ruta completa de un archivo en el directorio de
datos o NULL si el archivo no existechar *gnome_unconditional_datadir_file (const char
*nombre_arch)Obtiene la ruta completa de un archivo en el
directorio de datoschar *gnome_sound_file (const char *nombre_arch)Obtiene la ruta completa de un archivo en el directorio de
sonido o NULL si el archivo no existechar *gnome_unconditional_sound_file (const char
*nombre_arch)Obtiene la ruta completa de un archivo en el
directorio de sonidochar *gnome_pixmap_file (const char *nombre_arch)Obtiene la ruta completa de un archivo en el directorio de
'pixmaps' o NULL si el archivo no existechar *gnome_unconditional_pixmap_file (const char
*nombre_arch)Obtiene la ruta completa de un archivo en
el directorio 'pixmaps'char *gnome_config_file (const char *nombre_arch)Obtiene la ruta completa de un archivo en el directorio de
configuración o NULL si el archivo no existechar *gnome_unconditional_config_file (const char
*filename)Obtiene el path completo de un archivo en el
directorio de configuración
Estas funciones devuelven una cadena cuya memoria se asignó con
g_malloc así que debería usar
g_free sobre la cadena cuando termine. Las
funciones gnome_unconditional_* no comprobarán si
el archivo actualmente existe y siempre devolverán un nombre de
archivo. Las funciones normales efectuarán comprobaciones y devolverán
NULL si el archivo no existe. Así que no debería
usar estas funciones cuando esté guardando. Este ejemplo obtendrá un
pixmap del directorio estándar de pixmaps, por ejemplo necesitamos
obtener el icono "gnome-help.png":
gchar *nombre;
...
nombre = gnome_pixmap_file("gnome-help.png");
if(!nombre) {
g_warning("gnome-help.png ¡no existe!");
} else {
/*aquí usamos el archivo*/
...
g_free(nombre);
}
También son de interés las funciones (actualmente macros)
gnome_util_home_file y
gnome_util_user_home.
gnome_util_home_file toma un argumento (cadena) y
devuelve una nueva cadena precedida por el directorio de inicio del usuario
($HOME) seguido de '.gnome'. Así por ejemplo si le pasa, digamos
archivillo, devolvería
/home/jirka/.gnome/archivillo. Parecida es
gnome_util_user_home, toma un argumento y
devuelve el archivo con solo el directorio de inicio añadido. Así que si se
le pasa .oculto, devolvería
/home/jirka/.oculto.Utilidades
Hay también numerosas funciones de GLib para hacer su vida mas fácil
manejando archivos: g_file_exists, la cual toma
un nombre de archivo y devuelve TRUE si existe o
FALSE si no existe; o
g_concat_dir_and_file, que toma un nombre de
archivo y un nombre de directorio, teniendo en cuenta el
'/' (esto es útil cuando se trabaja con cadenas
donde no se quiere chequear el '/', sólo se quiere añadir un
directorio a algún archivo u otro directorio). Observe que tendrá que
usar g_free sobre la cadena que devuelven. Para
más funciones de utilidad, mire dentro de
libgnome/gnome-util.h, está muy bien comentada.
Tipos MIME
Algunas veces es útil saber el tipo MIME de un archivo. Se puede hacer
esto usando la función
gnome_mime_type_or_default, que toma dos
argumentos: el nombre del archivo y una cadena con el tipo MIME por
defecto que será devuelta si no se puede averiguar el tipo MIME del
archivo. Se intenta descubrir el tipo de un archivo mirando en el
nombre del mismo. La cadena que devuelve la función es un puntero a
una base de datos interna y no debería liberarla ya que podría
desembocar más tarde en una violación de segmento. También puede usar
gnome_mime_type, que devolverá NULL si no puede
averiguar el tipo MIME.
Es también posible trabajar con listas URI, como aquellas usadas en
las acciones de "arrastrar y soltar". Normalmente, de una lista URI
usted querrá extraer los nombres de los archivos que recibió. Para eso
use la función gnome_uri_list_extract_filenames,
la cual toma la lista URI como un argumento de tipo cadena, y devuelve
una GList* de cadenas asignadas
dinámicamente. Una vez haya terminado con los archivos, deberá liberar
las cadenas y la lista. Puede usar la función
gnome_uri_list_free_strings que hace esto para
usted.
En el siguiente ejemplo se implementa un manejador de "arrastrar y
soltar" que acepta archivos y devuelve la información sobre el tipo
MIME de los mismos, después usted podría escribir código que hiciera
operaciones basadas en el tipo MIME de los archivos.
/* este es el manejador para la señal 'dragdatareceive', asumiendo que
nuestro widget solo acepta el tipo MIME de datos "text/uri-list".
Para una explicación más detallada de como implementar las operaciones
de arrastrar y soltar, consulte la documentación de GTK+ */
static void
dnd_drop_internal (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *selection_data,
guint info,
guint time)
{
GList *files, *li;
/*aquí extraemos los nombres de los archivos desde la lista
URI que recibimos */
files = gnome_uri_list_extract_filenames(selection_data->data);
/*Pasamos por un bucle a los archivos y obtenemos sus tipos MIME*/
for(li = files; li!=NULL ; li = g_list_next(li)) {
char *mimetype;
char *filename = li->data;
/*suponemos el tipo MIME del archivo*/
mimetype = gnome_mime_type(filename);
/*si no lo podemos averiguar, simplemente saltamos
al siguiente nombre de archivo*/
if(!mimetype) continue;
/*A continuación va el código que realmente puede
hacer algo basado en los tipos MIME del archivo que
recibimos*/
...
}
/*Libera la lista de archivos que obtuvimos*/
gnome_uri_list_free_strings (files);
}
Obsérvese lo fácil que es descubrir qué archivos obtuvo y de qué tipo
eran. Ahora sólo tiene que añadir algún código en vez de los tres
puntos ('...') para hacer algo útil con esa información.
Meta-Datos
Algunas veces es útil almacenar alguna información junto con el nombre
de archivo, esto se puede hacer fácilmente con
gnome-metadata: un conjunto de funciones para
gestionar estos datos. Ya que Unix no soporta de forma nativa
meta-datos, usted debe ayudarle. Por ejemplo si su aplicación copia,
renombra o borra archivos, use las siguientes funciones para mantener
la consistencia en los meta-datos:
Funciones de Meta-Datos
PrototipoDescripciónint gnome_metadata_rename (const char *desde,
const char *hacia)Notifica a la base de datos de los meta-datos que un archivo ha
sido renombradoint gnome_metadata_copy (const char *desde,
const char *hacia)Notifica a la base de datos de los meta-datos que un archivo ha
sido copiadoint gnome_metadata_delete (const char *archivo)Notifica a la base de datos de los metadatos que un archivo ha
sido borradoint gnome_metadata_set (const char *archivo, const char
*nombre, int tam, const char *datos)Establece los datos con la clave 'nombre', asociados con el
archivo 'archivo'. El dato que se incluye es el apuntado por 'datos' y
su tamaño esta representado en bytes por 'tam'. En caso de éxito se
devuelve GNOME_METADATA_OK.int gnome_metadata_get (const char *archivo,
const char *nombre, int *tam, char **buffer)Obtiene datos con la clave 'nombre', asociados con el archivo
'archivo'. Los datos son almacenados en 'buffer' y en 'tam' el tamaño
de dicho buffer. En caso de éxito se devuelve
GNOME_METADATA_OK.char **gnome_metadata_list (const char *archivo)Obtiene una lista de claves para las cuales hay datos asociados
en 'archivo'. La lista será asignada dinámicamente y será terminada
con una cadena NULL. Debe liberarla con g_strfreev
Valores de Retorno de los Meta-Datos
NombreDescripciónGNOME_METADATA_OKNo hay error (Actualmente 0)GNOME_METADATA_IO_ERRORError de Entrada/Salida u otro error de comunicaciones o
almacenamiento de bajo nivel.GNOME_METADATA_NOT_FOUND
Información no encontrada.
Estas funciones actualmente no hacen operaciones sobre los archivos,
sólo cambian los meta-datos de forma consistente. Así que si su
aplicación hace alguna de esas operaciones, debe notificar a la base
de datos de los meta-datos los cambios. No debe confiar en que los
datos van a ser almacenados. Sólo los datos no críticos deberían ser
almacenados en los meta-datos, ya que si su aplicación no notifica a
la base de datos los cambios con las funciones mencionadas
anteriormente, se perderán los datos que haya asociado al
archivo. Esas funciones devolverán 0 o
GNOME_METADATA_OK si no hubo error, o un código
de error en caso contrario (descritos anteriormente).
Si quiere usar los meta-datos para asociar información a los archivos,
usted debe usar las funciones gnome_metadata_set,
gnome_metadata_remove y
gnome_metadata_get. De nuevo estas funciones
devuelven un entero, el cual es un
GNOME_METADATA_OK en caso de que no hubiera
error, o uno de los códigos de error que se usaban en las funciones
anteriores en caso contrario.
Las funciones operan con una clave que es una cadena para la cual
almacenan una porción de datos. Los datos son representados por un
entero para el tamaño y un puntero a una
cadena. gnome_metadata_set toma como primer
argumento el nombre del archivo, el nombre o la clave del dato como
segundo argumento, el tamaño como el tercero y un puntero al dato
actual como cuarto argumento. Esta función sólo asocia el dato para
cada archivo y clave. gnome_metadata_remove
borrara un elemento particular sobre un archivo, así que toma un
archivo y después el nombre de la clave como segundo
argumento. gnome_metadata_get toma el nombre del
archivo como primer argumento y el nombre de la clave como segundo,
después devuelve el tamaño del dato mediante un puntero a entero que
se pasa con el tercer argumento y el actual dato a través de un
puntero a puntero que se pasa como cuarto argumento. El dato devuelto
es asignado dinámicamente y debería ser liberado después de su uso. A
continuación se muestra un pequeño ejemplo (en la vida real debería
comprobar también el valor devuelto por la función para los errores).
int tam;
char *data;
...
/*Establece algunos datos ficticios asociados a un archivo*/
gnome_metadata_set("/algun/nombre/archivo","bogus",5,"BLAH");
...
/*Recupera de nuevo los datos*/
gnome_metadata_get("/algun/nombre/archivo","bogus",&tam,&data);
Usando el Marco de Desarrollo GnomeApp
Aunque el marco de desarrollo de aplicaciones GnomeApp pertenece a la
biblioteca libgnomeui, realmente merece ser tratado de forma
independiente. GnomeApp es la pieza que transforma un programa en una
verdadera aplicación GNOME. Hace la programación simple y elegante y
dota a las aplicaciones de muchos rasgos distintivos, haciéndolas
consistentes con el entorno y configurables por el
usuario. Programando ayudado sólo por GTK+, usted tendría que
implementar multitud de aspectos en sus aplicaciones, reinventando la
rueda en cada momento. Por contra, si emplea GnomeApp éste se hará
cargo de la configuración de la IU (Interfaz de Usuario) por usted,
permitiendo aún que el usuario pueda personalizarla y garantizando la
consistencia entre diferentes aplicaciones.
Introducción a GnomeAppGnomeApp es el widget básico detrás de cada
aplicación en GNOME. Será su ventana principal y contendrá el
documento sobre el que estaremos trabajando, los menús, barras de
herramientas y barras de estado de la aplicación. Recordará así mismo
la posición de las barras, permitiendo al usuario recuperarlas tal y
como las dejó la última vez que utilizó el programa.Creando una Ventana GnomeApp
Crear un nuevo widget GnomeApp es tan sencillo
como llamar a gnome_app_new con el nombre de la
aplicación, el cual es por lo general el nombre del ejecutable o
cualquier otro que sea único relacionado con su programa, y el título
de la ventana principal como argumentos. Luego usted deberá crear
contenidos e ir añadiéndolos al widget GnomeApp
mediante el método gnome_app_set_contents.
Añadir barras de menús, barras de herramientas y barras de estado no
resulta más complicado: simplemente utilice
gnome_app_set_toolbar,
gnome_app_set_menus o
gnome_app_set_statusbar.
gnome_app_set_toolbar
es adecuado para aplicaciones sencillas con tan solo una única barra
de herramientas, si necesita mayor complejidad, use
gnome_app_add_toolbar, que le permite añadir
tantas barras de herramientas como precise.Creación de Barras de Menús y de HerramientasCreación Automática de Menús y Barras de Herramientas
En muchas de sus aplicaciones, usted no creará sus menús directamente
sino que usará funciones de
libgnomeui/gnome-app-helper.h que se ocupen de
todo el proceso por usted. Todo lo que necesita es rellenar un par de
estructuras con la información pertinente, llamar a
gnome_app_create_menus o
gnome_app_create_toolbar pasando dichas
estructuras y ¡voila!, su aplicación tendrá ya menús y barras de
herramientas. Algunas veces deseará pasar un puntero con la dirección
de cierta información adicional a todas las retrollamadas que trabajen
con esas estructuras, en tales ocasiones deberá usar
gnome_app_create_toolbar_with_data y
gnome_app_create_menus_with_data, y proveer un
parámetro extra (El puntero).Definición de la Estructura GnomeUIInfo
En este capítulo se describe la estructura que usted necesita rellenar
para crear los menús de su aplicación (Realmente rellenará un vector de
dichas estructuras). Se incluyen además los enumerados que precisará
para completar la estructura.
/* Estos valores identifican la fuente pixmap que será agregado al
elemento del menú */
typedef enum {
GNOME_APP_PIXMAP_NONE, /* Sin pixmap */
GNOME_APP_PIXMAP_STOCK, /* Usa un pixmap de GNOME
(GnomeStock) */
GNOME_APP_PIXMAP_DATA, /* Usa un pixmap xpm insertado en el
código (Un vector) */
GNOME_APP_PIXMAP_FILENAME /* Usa el pixmap contenido en el
archivo especificado */
} GnomeUIPixmapType;
/* Esta es la estructura que define un elemento dentro de una barra de menús
* o en una barra de herramientas. La idea es crear un vector de estas
* estructuras con la información necesaria para crear los menús.
* La forma más conveniente de trabajar con esta estructura es usar las macros
* GNOMEUIINFO_* definidas más abajo.*/
typedef struct {
GnomeUIInfoType type; /* Tipo de elemento
gchar *label; /* Cadena usada en la etiqueta */
gchar *hint; /* Para los elementos pertenecientes a una
barra de herramientas se trata de la
sugerencia. Para los elementos de un menú,
el mensaje de la barra de estado */
gpointer moreinfo; /* Para un elemento, un elemento conmutable,
o elemento de opción, esto es un puntero a
la función que será llamada cuando el
elemento sea activado. Para un subárbol,
es un puntero a otro vector de estructuras
GnomeUIInfo. Para un elemento de opción
principal, un puntero a un vector de
estructuras GnomeUIInfo que representa el
grupo de elementos de opción asociados.
Para un elemento de ayuda, especifica el
nodo que debe cargar (i.e. El
identificador de la aplicación), NULL
indica el nombre del programa principal.
Si se trata de los datos del constructor,
apunta a la estructura GnomeUIBuilderData
para los elementos consecutivos [N.T:
haz que me entiendan, por favor :-)] */
gpointer user_data; /* Puntero a los datos que paseremos a las
retrollamadas */
gpointer unused_data; /* Reservado para una futura extensión,
debería ser NULL por ahora */
GnomeUIPixmapType pixmap_type; /* Tipo de pixmap para
el elemento */
gpointer pixmap_info; /* Puntero a la información del
pixmap:
Para GNOME_APP_PIXMAP_STOCK, un
puntero al nombre del icono.
Para GNOME_APP_PIXMAP_DATA, un
puntero al xpm (Al vector).
Para GNOME_APP_PIXMAP_FILENAME, un
puntero al nombre del archivo
(Una cadena) */
guint accelerator_key; /* Atajo del teclado, o 0 si no hay */
GdkModifierType ac_mods; /* Máscara de las teclas modificadas por
el atajo */
GtkWidget *widget; /* Rellenado por gnome_app_create*,
podrá ajustarlo una vez haya sido
creado */
} GnomeUIInfo;
No se preocupe si le cuesta recordar todos los elementos o no
comprende lo que significan. Si usted no sabe lo que representa un
miembro de la estructura, sencillamente dejeló en NULL o 0. También
puede recurrir a copiar el menú de otra aplicación que haga lo que
usted desea y modificarlo un poco para ajustarlo a su propio
desarrollo, esto es a menudo mejor y más rápido que perder el tiempo
hurgando en la estructura.Macros Útiles
La mayoría de las veces, crear entradas en un menú será muy simple
gracias a ciertas macros. Por ejemplo, para finalizar un menú,
usaríamos la macro GNOMEUIINFO_END o para
insertar un separador GNOMEUIINFO_SEPARATOR. El
resto de los elementos pueden ser también creados mediante macros,
aunque deberemos proporcionar cierta información a las mismas. Por ejemplo,
si desea crear un elemento con un xpm insertado en código, usted puede
usar la macro GNOMEUIINFO_ITEM(etiqueta, sugerencia,
retrollamada, xpm_data), donde etiqueta es el texto que
aparecerá en el menú, sugerencia es la ayuda que recibirá el usuario
cuando se coloque sobre el elemento (NULL indica
ninguna sugerencia), retrollamada es la función que es invocada cuando
el usuario presiona el elemento, y xpm_data es un puntero al vector xpm
que usted desea usar como icono. Si no quiere que aparezca el icono,
use GNOMEUIINFO_ITEM_NONE(etiqueta, sugerencia,
retrollamada). Si lo que busca es añadir un elemento con un
icono estándar de GNOME (De estos hablaremos más adelante) deberá usar
GNOMEUIINFO_ITEM_STOCK(etiqueta, sugerencia, retrollamada,
stock_id) donde stock_id es el identificador del icono que
quiera usar. Luego, para crear su barra de menús principal o para
insertar submenús dentro ella, puede usar
GNOMEUIINFO_SUBTREE(etiqueta, arbol) y
GNOMEUIINFO_SUBTREE_STOCK(etiqueta, arbol,
stock_id), donde 'arbol' es el vector de estructuras
GnomeUIInfo que usted quiere emplear como
submenú. Existen otras pocas macros que también manejan esta
estructura, pero por lo general las que se usan más a menudo son
éstas. Como ha visto, no precisa comprender la compleja estructura
GnomeUIInfo y sus miembros para crear efectivos
menús.Macros para crear Elementos y Menús Genéricos
Todas las aplicaciones contienen un par de menús genéricos, así que
para mantener una cierta consistencia, se aportan una serie de macros
que rellenaran estos menús todo por usted, sólo tendrá que escribir
las retrollamas necesarias y adaptarlos a un poco a su aplicación. Las
ventajas de usar macros son consistencia entre aplicaciones,
posibilidad de configuración por parte del usuario e
internacionalización de los programas. Elementos de Menú
La mayoría de estas macros tienen la forma:
GNOMEUIINFO_MENU_<nombre>_ITEM (retrollamada,
datos). Hay una excepción, la macro que crea el elemento
"Nuevo xxx" (N.T: nuevo archivo de un procesador de textos, p.ej). La
guía de estilo de GNOME especifica que la palabra "Nuevo" debe estar
dentro de la etiqueta del elemento. No se menciona ni el nombre
completo del elemento ni su sugerencia particular, a diferencia del
resto de los elementos genéricos. Para permitir que el programador
pueda ajustar estos campos, se opta por utilizar una macro con una
sintaxis particular para el elemento "Nuevo xxx":
GNOMEUIINFO_MENU_NEW_ITEM(etiqueta, sugerencia,
retrollamada, datos). La "etiqueta" debería comenzar con
"Nuevo ". Téngase en cuenta que si usted tiene más elementos "Nuevo",
necesita usar la macro que crea un subárbol para elementos de este
tipo, este procedimiento será explicado más tarde.
El Menú Archivo
MacroDescripciónGNOMEUIINFO_MENU_NEW_ITEM(etiqueta, suger, cb, datos)Elemento "Nuevo" (Necesita una etiqueta y una sugerencia)
GNOMEUIINFO_MENU_OPEN_ITEM(cb, datos)Elemento "Abrir" GNOMEUIINFO_MENU_SAVE_ITEM(cb, datos)Elemento "Guardar"GNOMEUIINFO_MENU_SAVE_AS_ITEM(cb, datos)Elemento "Guardar como"GNOMEUIINFO_MENU_REVERT_ITEM(cb, datos)Elemento "Revertir"GNOMEUIINFO_MENU_PRINT_ITEM(cb, datos)Elemento "Imprimir"GNOMEUIINFO_MENU_PRINT_SETUP_ITEM(cb, datos)Elemento "Ajustes para impresión"GNOMEUIINFO_MENU_CLOSE_ITEM(cb, datos)Elemento "Cerrar"GNOMEUIINFO_MENU_EXIT_ITEM(cb, datos)Elemento "Salir"
El Menú Editar
MacroDescripciónGNOMEUIINFO_MENU_CUT_ITEM(cb, datos)Elemento "Cortar"GNOMEUIINFO_MENU_COPY_ITEM(cb, datos)Elemento "Copiar"GNOMEUIINFO_MENU_PASTE_ITEM(cb, datos)Elemento "Pegar"GNOMEUIINFO_MENU_SELECT_ALL_ITEM(cb, datos)Elemento "Marcar Todo"GNOMEUIINFO_MENU_CLEAR_ITEM(cb, datos)Elemento "Limpiar" GNOMEUIINFO_MENU_UNDO_ITEM(cb, datos)Elemento "Deshacer"GNOMEUIINFO_MENU_REDO_ITEM(cb, datos)Elemento "Rehacer"GNOMEUIINFO_MENU_FIND_ITEM(cb, datos)Elemento "Buscar"GNOMEUIINFO_MENU_FIND_AGAIN_ITEM(cb, datos)Elemento "Buscar Otra vez"GNOMEUIINFO_MENU_REPLACE_ITEM(cb, datos)Elemento "Reemplazar"GNOMEUIINFO_MENU_PROPERTIES_ITEM(cb, datos)Elemento "Propiedades", las propiedades del objeto manipulado
El Menú Opciones
MacroDescripciónGNOMEUIINFO_MENU_PREFERENCES_ITEM(cb, datos)Elemento "Preferencias", ajustar preferencias de la aplicación
El Menú Ventana
MacroDescripciónGNOMEUIINFO_MENU_NEW_WINDOW_ITEM(cb, datos)Elemento "Nueva ventana"GNOMEUIINFO_MENU_CLOSE_WINDOW_ITEM(cb, datos)Elemento "Cerrar ventana"
El Menú Ayuda
MacroDescripciónGNOMEUIINFO_MENU_ABOUT_ITEM(cb, datos)Elemento "Acerca de"
Menús, árboles y subárboles
Ya hemos mencionado anteriormente el subárbol "Nuevo". Para usarlo,
debería utilizar la macro GNOMEUIINFO_MENU_NEW_SUBTREE
(arbol), donde arbol es otro vector de estructuras
GnomeUIInfo con diferentes elementos "Nuevo".
Existen también menús de "primer nivel" (N.T: toplevel) genéricos,
entendiéndose como menús de "primer nivel" aquellos que se encuentran
directamente sobe la barra de menús. El mecanismo para trabajar con
ellos es el mismo: debe pasar el vector de estructuras GnomeUIInfo a la
macro correspondiente.
Macros para crear Menús de Primer Nivel.
MacroDescripciónGNOMEUIINFO_MENU_FILE_TREE(arbol)Menú "Archivo"GNOMEUIINFO_MENU_EDIT_TREE(arbol)Menú "Editar"GNOMEUIINFO_MENU_VIEW_TREE(arbol)Menú "Presentación"GNOMEUIINFO_MENU_SETTINGS_TREE(arbol)Menú "Opciones"GNOMEUIINFO_MENU_FILES_TREE(arbol)Menú "Archivos"GNOMEUIINFO_MENU_WINDOWS_TREE(arbol)Menú "Ventanas"GNOMEUIINFO_MENU_HELP_TREE(arbol)Menú "Ayuda"GNOMEUIINFO_MENU_GAME_TREE(arbol)Menú "Juego"
A veces, usted deseará trabajar con la ruta (N.T: path) de un elemento
de cierto menú, por ejemplo para añadir elementos al menú
"Documentos". Para esto, necesitará las macros de la forma
GNOME_MENU_<nombre>_STRING y
GNOME_MENU_<nombre>_PATH. Dichas macros se
expandirán a la cadena apropiada. La macro que termina en
_STRING se expandirá al nombre del menú, y la
macro que finaliza en _PATH al nombre del menú
seguido de un "/". <nombre> puede ser uno de los siguientes
identificadores: FILE, EDIT, VIEW, SETTINGS, NEW, FILES o WINDOWS.
Menú de Ayuda
Su aplicación debería contener un menú de ayuda, que podría ser
definido de esta manera:
GNOMEUIINFO_HELP("mi_aplicación"),
GNOMEUIINFO_MENU_ABOUT_ITEM(retrollamada, datos),
GNOMEUIINFO_END
La macro GNOMEUIINFO_HELP toma el nombre de su aplicación e
inspecciona los archivos de ayuda de GNOME buscando el apropiado.
FIXME[ORIGINAL]: necesitamos añadir alguna sección acerca de los
archivos de ayuda y algunos ejemplos.
Ejemplo
Una aplicación muy sencilla que hace uso de todo lo estudiado hasta
ahora:
/*
* Un sencillo no integrado en el árbol GNOME, sin usar i18n
* uiinfo.c
*/
/* La cabecera GNOME más básica */
#include <gnome.h>
/* Una retrollamada para los botones */
static void
a_callback(GtkWidget *button, gpointer data)
{
/* Imprime una cadena cada vez que es llamada */
g_print("Dentro de la Retrollamada\n");
}
GnomeUIInfo file_menu[] = {
GNOMEUIINFO_MENU_EXIT_ITEM(gtk_main_quit,NULL),
GNOMEUIINFO_END
};
GnomeUIInfo some_menu[] = {
GNOMEUIINFO_ITEM_NONE("_Menuitem","Un elemento de menú",
a_callback),
GNOMEUIINFO_SEPARATOR,
GNOMEUIINFO_ITEM_NONE("M_enuitem2","Otro elemento de menú",
a_callback),
GNOMEUIINFO_END
};
GnomeUIInfo menubar[] = {
GNOMEUIINFO_MENU_FILE_TREE(file_menu),
GNOMEUIINFO_SUBTREE("_Un menú",some_menu),
GNOMEUIINFO_END
};
GnomeUIInfo toolbar[] = {
GNOMEUIINFO_ITEM_STOCK("Salir","Salir del programa",
gtk_main_quit,
GNOME_STOCK_PIXMAP_EXIT),
GNOMEUIINFO_END
};
int
main(int argc, char *argv[])
{
GtkWidget *app;
GtkWidget *button;
GtkWidget *hbox;
GtkWidget *label;
/* Inicializa GNOME, muy similar a gtk_init */
gnome_init ("menu-basic-example", "0.1", argc, argv);
/* Crea un widget GnomeApp, el cual crea una ventana
para su aplicación */
app = gnome_app_new ("menu-basic-example",
"Aplicación básica GNOME");
/* Engancha "delete_event", el evento que es emitido
cuando el usuario cierra la ventana, a gtk_main_quit,
que es la función que fuerza a salir del bucle
gtk_main y finaliza la aplicación */
gtk_signal_connect (GTK_OBJECT (app), "delete_event",
GTK_SIGNAL_FUNC (gtk_main_quit),
NULL);
/* Crea una etiqueta como contenido */
label = gtk_label_new("BLAH BLAH BLAH BLAH BLAH");
/* Añade la etiqueta a la ventana */
gnome_app_set_contents (GNOME_APP (app), label);
/* Crea los menús para la aplicación */
gnome_app_create_menus (GNOME_APP (app), menubar);
/* Crea la barra de herramientas de la aplicación */
gnome_app_create_toolbar (GNOME_APP (app), toolbar);
/* Muestra todos los contenidos de app y a él mismo */
gtk_widget_show_all(app);
/* Entra en el bucle principal */
gtk_main ();
return 0;
}
¡Voila!, una aplicación con un menú y una barra de herramientas. Como
puede ver, añadir nuevos elementos en los menús se reduce a añadir más
definiciones al vector de estructuras GnomeUIInfo
correspondiente.Atajos del Teclado
Probablemente haya notado los guiones bajos en las etiquetas de los
elementos del menú, estos especifican los atajos del teclado asociados
a ese menú concreto. La forma en la que funcionan los atajos es muy
similar a como lo hacen en otros entornos gráficos del mercado: podrá
seleccionar un elemento determinado con sólo
<tecla> si se encuentra dentro del menú o
alt-<tecla> en caso contrario.GnomeAppBar, la Barra de Estado
Toda aplicación debería incluir una barra de estado en la parte baja
de la ventana principal. Esta barra debería mostrar mensajes y
advertencias acerca del funcionamiento actual de la aplicación. Usted
podrá manejarla a su antojo usando las funciones de mensajes de
GnomeApp (descritas en la sección Dialogando con el Usuario). La
barra de estado también puede aportar ayuda acerca de los elementos
del menú o una barra de progreso. Estas son las funciones más
importantes que trabajan con GnomeAppBar.
Métodos Importantes de GnomeAppBar
PrototipoDescripción
GtkWidget * gnome_appbar_new (gboolean hay_progreso, gboolean
hay_estado, GnomePreferencesType interactividad)
Crea un nuevo widget GnomeAppBar. opcionalmente con una barra de
progreso si 'hay_progreso' es TRUE, y una barra de estado si
'hay_estado' es TRUE. La 'interactividad' nos dice si la barra ha de
ser interactiva, lo cual puede ser nunca, siempre o depender de las
preferencias del usuario, GNOME_PREFERENCES_USER, que es posiblemente
la mejor opción. Piense que la implementación de la barra de estado
interactiva aún no esta finalizada
void gnome_appbar_set_status (GnomeAppBar * appbar, const gchar * estado)
Fija el texto de la barra de estado, sin alterar appbar
void gnome_appbar_set_default (GnomeAppBar * appbar, const gchar *
texto_por_defecto)
Fija el texto por defecto de la barra de estado, el texto aparecerá
cuando no haya necesidad de mostrar nada
void gnome_appbar_push (GnomeAppBar * appbar, const gchar * estado)
Apila un mensaje
void gnome_appbar_pop (GnomeAppBar * appbar)
Saca un mensaje de la pila de mensajes de estado
void gnome_appbar_clear_stack (GnomeAppBar * appbar)
Vacia la pila de mensajes de estado
void gnome_appbar_refresh (GnomeAppBar * appbar)
Refresca la barra de estado y fija el actual valor de la pila al valor
por defecto. Útil para limpiar mensajes provenientes del método
gnome_appbar_set_status
void gnome_appbar_set_progress (GnomeAppBar * appbar, gfloat porcentaje)
Ajusta el porcentaje de la barra de progreso
GtkProgress * gnome_appbar_get_progress (GnomeAppBar * appbar)
Obtiene el widget GtkProgress, que es la barra de progreso, para poder
manipularlo directamente
Par añadir una barra de aplicación a una ventana GnomeApp, use el
método gnome_app_set_statusbar. Para poder
visualizar las sugerencias del menú en la barra, debe llamar a
gnome_app_install_menu_hints con un puntero a su
definición GnomeUIInfo de la barra de menús principal. Por ejemplo,
podríamos añadir dicha funcionalidad con el siguiente código:
GtkWidget *w;
w = gnome_appbar_new(FALSE, TRUE, GNOME_PREFERENCES_USER);
gnome_app_set_statusbar(GNOME_APP(app), w);
gnome_app_install_menu_hints(GNOME_APP(app), menubar);
Suponiendo que 'app' es su ventana
de aplicación GnomeApp y 'menubar' es un puntero GnomeUIInfo a su
definición de la barra de menús principal de la aplicación.
La forma en la que trabaja la barra de estado es amontonando los
mensajes en una pila: el mensaje más reciente es el que se muestra en
pantalla. Esto es útil ya que en muchas ocasiones le interesará
mantener un mensaje concreto en la barra la mayor parte del tiempo e
ir mostrando pequeños mensajes de vez en cuando. El nuevo mensaje será
apilado sobre el principal y una vez mostrado cederá paso al mensaje
original, que volverá así a estar en la cima de la pila y a aparecer
en la barra.
Debe tener en cuenta que la barra de estado puede hacerse cargo de
muchos detalles automáticamente, sin precisar nuestra supervisión
directa. Por ejemplo, de mostrar mensajes informativos en la barra se
ocupa gnome_app_flash descrito más tarde en la
sección Dialogando con el Usuario
.
Dialogando con el Usuario
En ocasiones, usted deseará comunicarse con el usuario, puede que
necesite mostrar un error o un mensaje indicando el estado de su
operación. Existen opciones accesibles al usuario que marcan dónde y
cómo se recibe esta información. Si quiere que su código se comporte
de forma consistente con esta característica, deberá usar las
siguientes funciones cuando precise crear un nuevo cuadro de diálogo.
Funciones de Mensajes de GnomeApp
PrototipoDescripción
GtkWidget *
gnome_app_message (GnomeApp * app, const gchar * mensaje)
Muestra un mensaje y solicita confirmación al usuario. Si se trata de
un diálogo, devolverá un puntero a dicho diálogo que puede ser obviado
sin problemas a menos que desee hacer algo con él posteriormente.
void
gnome_app_flash (GnomeApp * app, const gchar * flash)
Muestra un mensaje en la barra de estado durante un par de
segundos. Evidentemente, su uso debería restringirse a mensajes no
críticos o no necesarios para el correcto uso de la aplicación.
GtkWidget *
gnome_app_error (GnomeApp * app, const gchar * error)
Similar a gnome_app_message, pero muestra un error al usuario en vez
de un mensaje.
GtkWidget *
gnome_app_warning (GnomeApp * app, const gchar * warning)
También similar a gnome_app_message, esta vez aparece una advertencia.
El siguiente ejemplo muestra como usar estas funciones.
FILE *fp;
fp = fopen(archivo,"r");
if(!fp) {
char *err = g_strdup_printf(_("No pude abrir: %s"),
archivo);
gnome_app_error(GNOME_APP(app),err);
g_free(err);
...
}
Puede que lo que necesite su aplicación es solicitar datos del
usuario. Las siguientes funciones cubren este supuesto y reciben como
argumento un puntero a una función, del tipo GnomeReplyCallback o
GnomeStringCallback. La función (el contenido del puntero) deber tener
como parámetros un entero o una cadena de caracteres y un puntero de
datos. No cuente con que la retrollamada sea invocada siempre,
pudiera ser que la línea de estado estuviese ocupada en cuyo caso se
obviará la retrollamada y se devolverá NULL. Si la función se ejecuta
devolverá el widget de diálogo. Otro aspecto destacable es que se
trata de funciones no bloqueantes, lo que quiere decir que no se debe
actuar una vez se han llamado como si ya hubieran acabado su ejecución.
Funciones de Petición de Datos de GnomeApp
PrototipoDescripción
GtkWidget *
gnome_app_question (GnomeApp * app, const gchar * pregunta, GnomeReplyCallback retrollamada, gpointer datos)
Una pregunta cuya contestación es sí o no. La retrollamada de
respuesta recibe 0 ó 1 respectivamente.
GtkWidget *
gnome_app_question_modal (GnomeApp * app, const gchar * pregunta, GnomeReplyCallback retrollamada, gpointer datos)
Similar a la anterior, aunque permite al usuario interactuar con el
resto de la aplicación a pesar de que no haya respondido o no haya
cerrado el diálogo.
GtkWidget *
gnome_app_ok_cancel (GnomeApp * app, const gchar * mensaje, GnomeReplyCallback retrollamada, gpointer datos)
Una pregunta del tipo Sí o Cancelar.
GtkWidget *
gnome_app_ok_cancel_modal (GnomeApp * app, const gchar * mensaje, GnomeReplyCallback retrollamada, gpointer datos)
La versión modal de la función gnome_app_ok_cancel.
GtkWidget *
gnome_app_request_string (GnomeApp * app, const gchar * inductor, GnomeStringCallback retrollamada, gpointer datos)
Pregunta al usuario con un inductor de órdenes (N.T: prompt) igual a
'inductor'. La retrollamada recibirá NULL si el usuario cancela el
diálogo. La memoria que ocupa la cadena que recibe la retrollamada
deberá ser liberada por el programador.
GtkWidget *
gnome_app_request_password (GnomeApp * app, const gchar * inductor, GnomeStringCallback retrollamada, gpointer datos)
Pide al usuario una contraseña, mostrando 'inductor'. Idéntica a
gnome_app_request_string, con la salvedad de que el texto no se
muestra en pantalla.
Por ejemplo, solicitaremos al usuario confirmación para eliminar un objeto.
static void
really_delete_handler(int respuesta, gpointer datos)
{
GtkWidget *un_widget;
un_widget = GTK_WIDGET(datos);
if(respuesta == 0) {
... /* "Sí" ha sido seleccionado */
}
}
...
/* Mostrar una pregunta del tipo Sí/No, pasaremos some_widget como el
argumento 'datos' al manejador really_delete_handler */
gnome_app_question(GNOME_APP(app),_("¿Desea borrar el objeto?"),
really_delete_handler,
un_widget);
Usando la Biblioteca libgnomeui
Además de GnomeApp, la biblioteca libgnomeui contiene un gran número
de otros objetos y widgets, incluyendo diálogos estándar, manejo de
sesiones, MDI y otros útiles widgets. También contiene el widget
GnomeCanvas que merece un trato aparte. Esta es la biblioteca que hace
más fácil la vida de los programadores. Es importante que use estos
widgets mejor que simplemente los de GTK+, ya que estos widgets le
darán la consistencia y las características que espera el usuario de
aplicaciones GNOME.
Iconos Predefinidos
Muchas veces deseará usar botones y opciones estándares (como pueden
ser Abrir o Guardar
como...), y también deseará proporcionar iconos en las
opciones del menú, con los botones de la barra de herramientas o los
botones de las ventanas de diálogo; para facilitar la navegación puede
usar algunos de los iconos definidos de gnome-libs
. A estos iconos se les llama iconos predefinidos
o iconos por defecto. Ya ha visto un ejemplo de cómo usar
iconos predefinidos en menús y barras de herramientas (usando la
definición apropiada en libgnomeui/
gnome-stock.h). También hay botones predefinidos pudiendo
usar un widget de botón basado en una descripción predefinida.
Aquí hay una lista de los iconos predefinidos normales
de GNOME, estos tienen un tamaño normalizado
para su uso en barras de herramientas y en otros lugares
donde necesite un icono con tamaño normalizado. Están dados
como definiciones de cadenas de caracteres constantes y su
significado debería ser obvio.
#define GNOME_STOCK_PIXMAP_NEW "New"
#define GNOME_STOCK_PIXMAP_OPEN "Open"
#define GNOME_STOCK_PIXMAP_CLOSE "Close"
#define GNOME_STOCK_PIXMAP_REVERT "Revert"
#define GNOME_STOCK_PIXMAP_SAVE "Save"
#define GNOME_STOCK_PIXMAP_SAVE_AS "Save As"
#define GNOME_STOCK_PIXMAP_CUT "Cut"
#define GNOME_STOCK_PIXMAP_COPY "Copy"
#define GNOME_STOCK_PIXMAP_PASTE "Paste"
#define GNOME_STOCK_PIXMAP_PROPERTIES "Properties"
#define GNOME_STOCK_PIXMAP_PREFERENCES "Preferences"
#define GNOME_STOCK_PIXMAP_HELP "Help"
#define GNOME_STOCK_PIXMAP_SCORES "Scores"
#define GNOME_STOCK_PIXMAP_PRINT "Print"
#define GNOME_STOCK_PIXMAP_SEARCH "Search"
#define GNOME_STOCK_PIXMAP_SRCHRPL "Search/Replace"
#define GNOME_STOCK_PIXMAP_BACK "Back"
#define GNOME_STOCK_PIXMAP_FORWARD "Forward"
#define GNOME_STOCK_PIXMAP_FIRST "First"
#define GNOME_STOCK_PIXMAP_LAST "Last"
#define GNOME_STOCK_PIXMAP_HOME "Home"
#define GNOME_STOCK_PIXMAP_STOP "Stop"
#define GNOME_STOCK_PIXMAP_REFRESH "Refresh"
#define GNOME_STOCK_PIXMAP_UNDO "Undo"
#define GNOME_STOCK_PIXMAP_REDO "Redo"
#define GNOME_STOCK_PIXMAP_TIMER "Timer"
#define GNOME_STOCK_PIXMAP_TIMER_STOP "Timer Stopped"
#define GNOME_STOCK_PIXMAP_MAIL "Mail"
#define GNOME_STOCK_PIXMAP_MAIL_RCV "Receive Mail"
#define GNOME_STOCK_PIXMAP_MAIL_SND "Send Mail"
#define GNOME_STOCK_PIXMAP_MAIL_RPL "Reply to Mail"
#define GNOME_STOCK_PIXMAP_MAIL_FWD "Forward Mail"
#define GNOME_STOCK_PIXMAP_MAIL_NEW "New Mail"
#define GNOME_STOCK_PIXMAP_TRASH "Trash"
#define GNOME_STOCK_PIXMAP_TRASH_FULL "Trash Full"
#define GNOME_STOCK_PIXMAP_UNDELETE "Undelete"
#define GNOME_STOCK_PIXMAP_SPELLCHECK "Spellchecker"
#define GNOME_STOCK_PIXMAP_MIC "Microphone"
#define GNOME_STOCK_PIXMAP_LINE_IN "Line In"
#define GNOME_STOCK_PIXMAP_CDROM "Cdrom"
#define GNOME_STOCK_PIXMAP_VOLUME "Volume"
#define GNOME_STOCK_PIXMAP_BOOK_RED "Book Red"
#define GNOME_STOCK_PIXMAP_BOOK_GREEN "Book Green"
#define GNOME_STOCK_PIXMAP_BOOK_BLUE "Book Blue"
#define GNOME_STOCK_PIXMAP_BOOK_YELLOW "Book Yellow"
#define GNOME_STOCK_PIXMAP_BOOK_OPEN "Book Open"
#define GNOME_STOCK_PIXMAP_ABOUT "About"
#define GNOME_STOCK_PIXMAP_QUIT "Quit"
#define GNOME_STOCK_PIXMAP_MULTIPLE "Multiple"
#define GNOME_STOCK_PIXMAP_NOT "Not"
#define GNOME_STOCK_PIXMAP_CONVERT "Convert"
#define GNOME_STOCK_PIXMAP_JUMP_TO "Jump To"
#define GNOME_STOCK_PIXMAP_UP "Up"
#define GNOME_STOCK_PIXMAP_DOWN "Down"
#define GNOME_STOCK_PIXMAP_TOP "Top"
#define GNOME_STOCK_PIXMAP_BOTTOM "Bottom"
#define GNOME_STOCK_PIXMAP_ATTACH "Attach"
#define GNOME_STOCK_PIXMAP_INDEX "Index"
#define GNOME_STOCK_PIXMAP_FONT "Font"
#define GNOME_STOCK_PIXMAP_EXEC "Exec"
#define GNOME_STOCK_PIXMAP_ALIGN_LEFT "Left"
#define GNOME_STOCK_PIXMAP_ALIGN_RIGHT "Right"
#define GNOME_STOCK_PIXMAP_ALIGN_CENTER "Center"
#define GNOME_STOCK_PIXMAP_ALIGN_JUSTIFY "Justify"
#define GNOME_STOCK_PIXMAP_TEXT_BOLD "Bold"
#define GNOME_STOCK_PIXMAP_TEXT_ITALIC "Italic"
#define GNOME_STOCK_PIXMAP_TEXT_UNDERLINE "Underline"
#define GNOME_STOCK_PIXMAP_TEXT_STRIKEOUT "Strikeout"
#define GNOME_STOCK_PIXMAP_EXIT GNOME_STOCK_PIXMAP_QUIT
Si quiere usarlo fuera de GnomeUIInfo,
necesita obtener el widget con el pixmap. Lo que se hace
es llamar a la función gnome_stock_pixmap_widget
con su ventana principal como primer argumento
(por eso que puede copiar su estilo) y el nombre del icono
(uno de los definidos antes) como segundo argumento. Esto
devuelve un nuevo widget que puede usar como un pixmap. Para los menús puede usar la variedad _MENU_
de los pixmaps predefinidos. Estos son más pequeños
y son lo que debe usar para los elementos del menú por
defecto en su definición de GnomeUIInfo.
#define GNOME_STOCK_MENU_BLANK "Menu_"
#define GNOME_STOCK_MENU_NEW "Menu_New"
#define GNOME_STOCK_MENU_SAVE "Menu_Save"
#define GNOME_STOCK_MENU_SAVE_AS "Menu_Save As"
#define GNOME_STOCK_MENU_REVERT "Menu_Revert"
#define GNOME_STOCK_MENU_OPEN "Menu_Open"
#define GNOME_STOCK_MENU_CLOSE "Menu_Close"
#define GNOME_STOCK_MENU_QUIT "Menu_Quit"
#define GNOME_STOCK_MENU_CUT "Menu_Cut"
#define GNOME_STOCK_MENU_COPY "Menu_Copy"
#define GNOME_STOCK_MENU_PASTE "Menu_Paste"
#define GNOME_STOCK_MENU_PROP "Menu_Properties"
#define GNOME_STOCK_MENU_PREF "Menu_Preferences"
#define GNOME_STOCK_MENU_ABOUT "Menu_About"
#define GNOME_STOCK_MENU_SCORES "Menu_Scores"
#define GNOME_STOCK_MENU_UNDO "Menu_Undo"
#define GNOME_STOCK_MENU_REDO "Menu_Redo"
#define GNOME_STOCK_MENU_PRINT "Menu_Print"
#define GNOME_STOCK_MENU_SEARCH "Menu_Search"
#define GNOME_STOCK_MENU_SRCHRPL "Menu_Search/Replace"
#define GNOME_STOCK_MENU_BACK "Menu_Back"
#define GNOME_STOCK_MENU_FORWARD "Menu_Forward"
#define GNOME_STOCK_MENU_FIRST "Menu_First"
#define GNOME_STOCK_MENU_LAST "Menu_Last"
#define GNOME_STOCK_MENU_HOME "Menu_Home"
#define GNOME_STOCK_MENU_STOP "Menu_Stop"
#define GNOME_STOCK_MENU_REFRESH "Menu_Refresh"
#define GNOME_STOCK_MENU_MAIL "Menu_Mail"
#define GNOME_STOCK_MENU_MAIL_RCV "Menu_Receive Mail"
#define GNOME_STOCK_MENU_MAIL_SND "Menu_Send Mail"
#define GNOME_STOCK_MENU_MAIL_RPL "Menu_Reply to Mail"
#define GNOME_STOCK_MENU_MAIL_FWD "Menu_Forward Mail"
#define GNOME_STOCK_MENU_MAIL_NEW "Menu_New Mail"
#define GNOME_STOCK_MENU_TRASH "Menu_Trash"
#define GNOME_STOCK_MENU_TRASH_FULL "Menu_Trash Full"
#define GNOME_STOCK_MENU_UNDELETE "Menu_Undelete"
#define GNOME_STOCK_MENU_TIMER "Menu_Timer"
#define GNOME_STOCK_MENU_TIMER_STOP "Menu_Timer Stopped"
#define GNOME_STOCK_MENU_SPELLCHECK "Menu_Spellchecker"
#define GNOME_STOCK_MENU_MIC "Menu_Microphone"
#define GNOME_STOCK_MENU_LINE_IN "Menu_Line In"
#define GNOME_STOCK_MENU_CDROM "Menu_Cdrom"
#define GNOME_STOCK_MENU_VOLUME "Menu_Volume"
#define GNOME_STOCK_MENU_BOOK_RED "Menu_Book Red"
#define GNOME_STOCK_MENU_BOOK_GREEN "Menu_Book Green"
#define GNOME_STOCK_MENU_BOOK_BLUE "Menu_Book Blue"
#define GNOME_STOCK_MENU_BOOK_YELLOW "Menu_Book Yellow"
#define GNOME_STOCK_MENU_BOOK_OPEN "Menu_Book Open"
#define GNOME_STOCK_MENU_CONVERT "Menu_Convert"
#define GNOME_STOCK_MENU_JUMP_TO "Menu_Jump To"
#define GNOME_STOCK_MENU_UP "Menu_Up"
#define GNOME_STOCK_MENU_DOWN "Menu_Down"
#define GNOME_STOCK_MENU_TOP "Menu_Top"
#define GNOME_STOCK_MENU_BOTTOM "Menu_Bottom"
#define GNOME_STOCK_MENU_ATTACH "Menu_Attach"
#define GNOME_STOCK_MENU_INDEX "Menu_Index"
#define GNOME_STOCK_MENU_FONT "Menu_Font"
#define GNOME_STOCK_MENU_EXEC "Menu_Exec"
#define GNOME_STOCK_MENU_ALIGN_LEFT "Menu_Left"
#define GNOME_STOCK_MENU_ALIGN_RIGHT "Menu_Right"
#define GNOME_STOCK_MENU_ALIGN_CENTER "Menu_Center"
#define GNOME_STOCK_MENU_ALIGN_JUSTIFY "Menu_Justify"
#define GNOME_STOCK_MENU_TEXT_BOLD "Menu_Bold"
#define GNOME_STOCK_MENU_TEXT_ITALIC "Menu_Italic"
#define GNOME_STOCK_MENU_TEXT_UNDERLINE "Menu_Underline"
#define GNOME_STOCK_MENU_TEXT_STRIKEOUT "Menu_Strikeout"
#define GNOME_STOCK_MENU_EXIT GNOME_STOCK_MENU_QUIT
Si está construyendo el menú usted mismo y quiere utilizar
un elemento de menú con icono y etiqueta predefinida,
puede usar la función
gnome_stock_menu_item. Toma el tipo de icono
predefinido (uno de los definidos arriba) como primer argumento,
el texto del menú como segundo argumento, y devuelve un
widget recién creado de elemento de menú.
Hay botones predefinidos. Estos están para el uso en ventanas
de diálogo (ver más adelante).
#define GNOME_STOCK_BUTTON_OK "Button_Ok"
#define GNOME_STOCK_BUTTON_CANCEL "Button_Cancel"
#define GNOME_STOCK_BUTTON_YES "Button_Yes"
#define GNOME_STOCK_BUTTON_NO "Button_No"
#define GNOME_STOCK_BUTTON_CLOSE "Button_Close"
#define GNOME_STOCK_BUTTON_APPLY "Button_Apply"
#define GNOME_STOCK_BUTTON_HELP "Button_Help"
#define GNOME_STOCK_BUTTON_NEXT "Button_Next"
#define GNOME_STOCK_BUTTON_PREV "Button_Prev"
#define GNOME_STOCK_BUTTON_UP "Button_Up"
#define GNOME_STOCK_BUTTON_DOWN "Button_Down"
#define GNOME_STOCK_BUTTON_FONT "Button_Font"
Para tomar un widget de botón con el texto e icono de predefinido,
puede usar la función gnome_stock_button con el
tipo de botón (uno de los definidos arriba) como argumento. Ahora
bien, algunas veces querrá hacer una mezcla de botones predefinidos y
ordinarios, para conseguirlo llame a la función
gnome_stock_or_ordinary_button con el tipo de
botón predefinido y un texto para la etiqueta del botón. La función
revisa si es una de las cadenas anteriores, y si no está crea un
widget de botón ordinario con el texto como etiqueta. Aquí hay un
ejemplo, crea tres botones empaquetándolos en una caja (no incluimos
el código para hacer la caja), dos de los botones están predefinidos y
el otro es normal:
GtkWidget *w;
GtkWidget *caja;
int i;
char *botones[]={
GNOME_STOCK_BUTTON_OK,
GNOME_STOCK_BUTTON_CANCEL,
"Foo",
NULL
};
...
/* bucle a través de todas las cadenas en el vector de botones*/
for(i = 0; botones[i] != NULL; i++) {
/* creamos el botón, predefinido u ordinario */
w = gnome_stock_or_ordinary_button(botones[i]);
/* Lo mostramos y lo empaquetamos */
gtk_widget_show(w);
gtk_box_pack_start(GTK_BOX(caja),w,FALSE,FALSE,0);
/* Deberíamos unir las señales y más cosas aquí */
...
}
DiálogosDiálogos Genéricos
Si necesita crear su propio diálogo personalizado,
gnome-dialog
es el camino para hacerlo. Puede soportar tanto diálogos
modales como no modales. De todas formas, es
definitivamente mucho más cómodo para los usuarios de
su programa si usa diálogos no modales, si es posible,
porque los diálogos no modales tienden a tener problemas
asociados, y algunas veces pueden causar errores extraños.
Por ejemplo, si un diálogo no modal se asocia con una
ventana, sería bueno conectar la señal destroy
de la ventana para que
también destruya la ventana del diálogo, de otra manera
se podría producir un error al actuar sobre un documento
o ventana que ya no existe. Sin embargo,
los diálogos modales (los que son más fáciles de
programar) normalmente son bastante engorrosos de
usar, por eso evítelos si puede.
Para hacer un nuevo widget GnomeDialog,
use la función gnome_dialog_new.
Se pasa el título del diálogo como primer argumento, y
después múltiples argumentos, con los títulos de los
botones, terminando con un NULL. Los títulos de los
botones también pueden ser las definiciones
GNOME_STOCK_BUTTON_*, si es que
desea botones predefinidos en su diálogo. Ahora
necesita dotar de un contenido al diálogo, el diálogo
se crea con una caja vertical (GtkVBox)
para que usted lo use simplemente con
GNOME_DIALOG(dialog)->vbox.
Con esto puede introducir el contenido.
Debería también establecer la ventana principal de la aplicación (su
GnomeApp) como el padre del diálogo. Esto permite al gestor de
ventanas manejar la ventana más apropiadamente que una simple ventana
genérica. Se completa con la siguiente llamada:
gnome_dialog_set_parent(GNOME_DIALOG(dialog), GTK_WINDOW(app));
En este punto tiene que decidir si desea hacer un diálogo modal
o no modal. En el caso de que quiera un diálogo modal, todo lo que
necesita hacer es llamar a la función
gnome_dialog_run_and_close y ella lo creará,
esperará a que un usuario presione un botón o cierre el diálogo, y
entonces cerrará el diálogo. En el caso de que no quiera que el
diálogo se cierre cuando algún botón sea pulsado, use la función
gnome_dialog_run y después de obtener un
resultado, haga lo que necesite para ese botón en
particular. Entonces, si desea continuar con el diálogo, vuelva a
gnome_dialog_run, y si desea cerrarlo, use
gnome_dialog_close. Aquí hay un ejemplo.
GtkWidget *dlg;
GtkWidget *etiqueta;
int i;
...
/*creamos un nuevo diálogo, NO olvide el NULL al final,
¡es muy importante!*/
dlg = gnome_dialog_new("Un diálogo",
GNOME_STOCK_BUTTON_OK,
GNOME_STOCK_BUTTON_APPLY,
GNOME_STOCK_BUTTON_CLOSE,
NULL);
/* asumimos que app es un puntero a nuestra ventana GnomeApp */
gnome_dialog_set_parent(GNOME_DIALOG(dlg), GTK_WINDOW(app));
...
/*añadimos algún contenido al diálogo aquí*/
etiqueta = gtk_label_new("Algún contenido aleatorio");
gtk_box_pack_start(GTK_BOX(GNOME_DIALOG(dlg)->vbox),etiqueta,
FALSE,FALSE,0);
...
/*hacemos un bucle infinito*/
for(;;) {
i = gnome_dialog_run(GNOME_DIALOG(dlg));
if(i == 0 || i == 2) {
/* el usuario presionó OK o close, por eso
saldremos del bucle y cerraremos el diálogo*/
gnome_dialog_close(GNOME_DIALOG(dlg));
break;
} else if(i < 0) {
/* el usuario cerró el diálogo desde el gestor
de ventanas*/
break;
} else if(i == 1) {
/*el usuario presionó aplicar, no queremos que
se cierre*/
...
}
}
Por defecto el diálogo se destruye cuando es cerrado,
por eso no se tiene que preocupar por su destrucción.
Puede cambiar esta conducta si lo desea.
Si va a hacer una ventana de diálogo no modal, las cosas se vuelven
más complicadas. Cree un diálogo como antes, pero tiene que conectar
la señal clicked del widget
GnomeDialog. Esta señal tiene como su segundo
argumento el número del botón que fue presionado. Después de esto
debería usar la función gnome_dialog_set_close
para decir a GnomeDialog que queremos cerrar el
diálogo cuando el usuario presionó cualquier botón, si quiere este
comportamiento. De otro modo deberá llamar a
gnome_dialog_close en la función que atiende a la
señal clicked, en los botones en los que se debe
cerrar el diálogo. Después de esto sólo hay que mostrar el diálogo con
gtk_widget_show. A continuación mostramos un
ejemplo:
/*la función de tratamiento de clicked*/
static void
dialog_clicked(GnomeDialog *dlg, int boton, gpointer data)
{
switch(boton) {
case 1:
/*el usuario presionó aplicar*/
...
return;
case 0:
/*el usuario presionó OK*/
...
/*seguir hacia abajo para cerrar*/
case 2:
/*el usuario presionó cerrar*/
gnome_dialog_close(dlg);
break;
}
}
/*en algún lugar del archivo fuente*/
...
GtkWidget *dlg;
...
/*crear un nuevo diálogo, NO olvide NULL al final, ¡es
es muy importante!*/
dlg = gnome_dialog_new("Un diálogo",
GNOME_STOCK_BUTTON_OK,
GNOME_STOCK_BUTTON_APPLY,
GNOME_STOCK_BUTTON_CLOSE,
NULL);
/* asumimos que app es un puntero a nuestra ventana GnomeApp */
gnome_dialog_set_parent(GNOME_DIALOG(dlg), GTK_WINDOW(app));
...
/*añadimos algún contenido al diálogo aquí*/
...
/*asociamos la función de tratamiento de clicked*/
gtk_signal_connect(GTK_OBJECT(dlg),"clicked",
GTK_SIGNAL_FUNC(dialog_clicked),
NULL);
/*mostramos el diálogo, note que esto no es un diálogo
modal, por eso el programa no se bloquea aquí, sigue*/
gtk_widget_show(dlg);
Esto implementa el mismo diálogo que el ejemplo modal anterior, sólo que
no modal. Asegúrese de que tiene alguna forma de destruir el diálogo en
caso de que no sea necesario, por ejemplo, si un diálogo va a modificar algún
objeto, debería ser destruido cuando ese objeto es destruido.
Diálogos de mensajeGnomeMessageBox es un objeto derivado de
GnomeDialog. Se maneja exactamente de la
misma manera, la única diferencia es que automáticamente
se introduce una etiqueta simple y un icono del tipo del
mensaje que contenga el diálogo. Los tipos de diálogos de
mensaje son los que siguen:
#define GNOME_MESSAGE_BOX_INFO "info"
#define GNOME_MESSAGE_BOX_WARNING "warning"
#define GNOME_MESSAGE_BOX_ERROR "error"
#define GNOME_MESSAGE_BOX_QUESTION "question"
#define GNOME_MESSAGE_BOX_GENERIC "generic"
Para crear un diálogo de mensaje, use la función
gnome_message_box_new con el texto del mensaje
como primer argumento, el segundo es el tipo de mensaje (uno de los
definidos antes), y después cualquier número de botones terminado por
un NULL exactamente como en el caso de
GnomeDialog. Una vez creado se usa como
GnomeDialog.
Diálogos de propiedades
Si tiene que configurar alguna propiedad en su aplicación, tendría que
usar el diálogo GnomePropertyBox para realizar
esta configuración y así hacer las aplicaciones más
consistentes. Nuevamente este objeto se deriva de
GnomeDialog, por eso su uso es similar. La diferencia es que
GnomePropertyBox define algunas nuevas señales,
estas son apply (aplicar) y
help (ayuda). Ambas pasan el número de página
como segundo argumento, lo que es útil para mostrar la ayuda apropiada
en caso de que se solicite. No es así para apply,
ya que este sistema
se creó para tener un botón de aplicar por cada página y todavía no
está acabado, por lo que debería ignorar cualquier señal
apply con un número de página distinto de -1, que es el
número de aplicación global a todas las páginas.
Puede conseguir un
apply por cada página introduciendo su propio código, pero no es
seguro que este código se llegue a completar. Debería ser más seguro
aplicar los cambios globalmente, ya que es lo que está implementado
en gnome-libs 1.0.
Para usar diálogos de propiedades, llame a
gnome_property_box_new, esto creará un diálogo nuevo con un
cuaderno de notas y los siguientes cuatro
botones:OK, que llamará a la función que se
encarga de atender la señal 'apply' pasándole todas las páginas una a
una y terminando en la página -1, momento en el que cerrará el
diálogo.Aplicar, que efectua la misma operación que
el anterior con la salvedad de que no cierra el diálogo.
Cerrar, sólo cerrará el diálogo, y
Ayuda que llamará a la función de tratamiento de
ayuda si usted ligó alguna.Después de crear el diálogo debe conectar la señal
apply al manejador correspondiente que usted
implemente, y recomendamos que conecte también la señal
destroy a la ventana apropiada para destruir los
datos asociados con el diálogo de propiedades cuando dicha ventana se
cierre.Ahora ya puede crear las diferentes páginas de su diálogo de
propiedades, añadiéndolas con
gnome_property_box_append_page, este método toma su página
como segundo argumento y una etiqueta como tercero (normalmente será
simplemente un GtkLabel). Tiene también que
conectar las diferentes señales a los widgets de sus páginas para que
al editar alguno de ellos, el diálogo de propiedades aparezca como
cambiado (de otra forma los botones Aplicar y OK no serán sensibles).
Esto se hace llamando a gnome_property_box_changed
cada vez que el usuario cambia algo en los widgets. Por
ejemplo en el widget de entrada (y derivados) se conecta la señal
changed. Siga este ejemplo:
/*rutina de tratamiento de aplicar*/
static void
property_apply(GnomePropertyBox *caja, int pag_num, gpointer data)
{
/*ignora los números de página distintos de -1*/
if(pag_num!=-1)
return;
/*hacer la rutina de aplicar aquí*/
...
}
...
/*en algún lugar del archivo fuente*/
GtkWidget *pcaja;
GtkWidget *widget;
...
pcaja = gnome_property_box_new();
gtk_signal_connect(GTK_OBJECT(pcaja),"apply",
GTK_SIGNAL_FUNC(property_apply),NULL);
...
/*se crea una página para la caja de propiedades y se añade
al contenedor llamado widget*/
gnome_property_box_append_page(GNOME_PROPERTY_BOX(pcaja),
widget, gtk_label_new("UnaPágina"));
/*ahora se añaden otras páginas de una manera similar*/
...
/*mostramos la caja de diálogo*/
gtk_widget_show_all(pcaja);
Diálogo de Selección de Archivos
GNOME no tiene su propio diálogo de selección de archivos, aunque está
planeado crearlo en un futuro, por ahora tiene que usar el típico
diálogo de GTK+.
El uso del diálogo de selección de archivos es muy simple. Cree el
diálogo con gtk_file_selection_new, pasando el
título del diálogo como argumento. Después de esto conecte la señal
clicked a los botones OK y
Cancelar. Por ejemplo para un diálogo de "abrir
archivo", puede probar que el archivo es del tipo correcto cuando el
usuario presiona OK y si todo va bien cierre el
diálogo (normalmente con gtk_widget_destroy), o
bien para el diálogo de guardar archivo, puede preguntar si el archivo
existe. Normalmente es más seguro y sencillo hacer el diálogo de
selección de archivos no modal. Asi se asegura de que destruirá el
diálogo de selección de archivo cuando el objeto o ventana vaya a
trabajar con él. Aquí está la función que invoca al diálogo de
"guardar como" para Achtung, un programa de
presentaciones en el que estamos trabajando.
void
presentation_save_as (AchtungPresentation *p)
{
GtkFileSelection *fsel;
g_return_if_fail (p != NULL);
g_return_if_fail (p->doc != NULL);
fsel = (GtkFileSelection *)
gtk_file_selection_new (_("Guardar presentación como..."));
if (p->real_file && p->filename)
gtk_file_selection_set_filename (fsel, p->filename);
gtk_object_set_data(GTK_OBJECT(fsel),"p",p);
/* Conectamos las señales con OK y Cancelar */
gtk_signal_connect (GTK_OBJECT (fsel->ok_button), "clicked",
GTK_SIGNAL_FUNC (save_ok), fsel);
gtk_signal_connect_object (GTK_OBJECT (fsel->cancel_button),
"clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT(fsel));
gtk_window_position (GTK_WINDOW (fsel), GTK_WIN_POS_MOUSE);
/*si la presentación muere también lo harán los diálogos*/
gtk_signal_connect_object_while_alive(GTK_OBJECT (p), "destroy",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT(fsel));
gtk_widget_show (GTK_WIDGET (fsel));
}
Actualmente, esta función es un método "Guardar como" de la clase
AchtungPresentation, utilizando lenguaje de
orientación a objetos. AchtungPresentation es un
GTKObject que usamos para guardar todos los datos de la presentación
(este es un ejemplo bonito de cómo usar GtkObject para cosas no
relacionadas directamente con los widgets o la programación de
GUI's). Primero se revisan los argumentos de la función con
g_return_if_fail, esto se hace sólo por motivos
de depuración. Después creamos un nuevo
GtkFileSelection con el título "Guardar
presentación como...". Ignore la macro _()
alrededor de las cadenas por el momento, se usa para la
internacionalización. Después de todo esto se verifica si la
presentación ya tiene un nombre de archivo asociado, y si es así, se
pone este nombre en el diálogo de selección de archivo. Entonces
conectamos el botón OK con la función
save_ok definida en algún lugar del archivo y
pasamos el diálogo como argumento. Entonces usamos
connect_object para unir el botón
Cancel con la destrucción del diálogo de
selección. El método connect_object es similar a
connect pero cuando llama a la función pasará el
objeto del campo de datos como primer argumento de la función. Por eso
al conectarlo con gtk_widget_destroy destruirá el
objeto pasado en el campo de datos, que es el diálogo de selección de
archivos. Entonces situamos el diálogo cerca del ratón. En un futuro,
cuando este diálogo se derive de GnomeDialog, no
necesitará hacerlo, porque se hará de acuerdo con el resto de los
diálogos de GNOME. Todavía usamos otro método conectado a una
señal... Esta vez
gtk_signal_connect_object_while_alive, el cual es
similar a connect_object, pero tiene un preciosa
peculiaridad. La señal se desconectará cuando el objeto pasado en el
campo de datos muera. Necesitamos que ocurra porque el diálogo de
selección de archivos con toda probabilidad será destruido antes de
que la presentación misma sea destruida, podría intentar destruir un
diálogo de selección de archivos que en realidad no existe y con
probabilidad causará una violación de segmento, colgándose. Este
camino es seguro y si el diálogo de selección de archivo está cuando
la presentación es destruida, es destruida con él.
Diálogo Acerca de...
Probablemente quiera tener una opción "Acerca de..." en el menú
"Ayuda" de su aplicación, y debería mostrarse en un diálogo
estándar. Hay un diálogo en GNOME para este tipo de propósitos, la
clase GnomeAbout. Se crea con gnome_about_new,
que tiene el siguiente prototipo
GtkWidget* gnome_about_new(const gchar *title, /* Nombre de la aplicación. */
const gchar *version, /* Versión. */
const gchar *copyright, /* Copyright(una línea.) */
const gchar **authors, /* Lista con los autores
terminada con NULL. */
const gchar *comments, /* Otros comentarios. */
const gchar *logo /* Un archivo pixmap
con el logo. */
);
Después de crear la caja de diálogo, debería establecer la ventana de
la aplicación como su padre con
gnome_dialog_set_parent. Y entonces puede
mostrar el diálogo. Lo siguiente implementa un diálogo "acerca de",
asumimos que VERSION ha sido definida con #define para ser una cadena
con el número de la versión. También pasamos como logotipo un NULL,
indicando que no tenemos una imagen que mostrar.
GtkWidget* dlg;
char *authors[] = {
"George Lebl",
NULL;
};
dlg = gnome_about_new("Alguna aplicación", /* Nombre de la aplicación. */
VERSION, /* Versión. */
"(c) 1999 George Lebl", /* Copyright (una línea.) */
authors, /* Lista con los autores, terminado con NULL*/
"bla bla bla", /* Otros comentarios. */
NULL /* Un archivo pixmap con el logo. */
);
gnome_dialog_set_parent(GNOME_DIALOG(dlg), GTK_WINDOW(app));
gtk_widget_show(dlg);
Entradas
Algunas veces, especialmente en los diálogos de propiedades, querrá
añadir campos para introducir texto, archivos, pixmaps, iconos o
reales de doble precisión (N.T: double). Esto es lo que hace los
widgets gnome-*entry.
GnomeEntry
Es una entrada para texto regular, pero incluye historia de los
valores introducidos anteriormente. Note que este widget no está
derivado de GtkEntry, sino que contiene a un
objeto de esta clase. Todo esto significa que no puede usar los
métodos de GtkEntry sobre este objeto
directamente, tiene que obtener un puntero al objeto
GtkEntry que hay dentro de
GnomeEntry. Cuando llama a
gnome_entry_new, se le pasa una cadena
history_id, es un identificador único para
identificar a esta entrada o a este tipo de entradas en su
aplicación. Todas las entradas que comparten
history_id tendrán una historia común. Después de
que cree un GnomeEntry tendrá que usar la función
gnome_entry_gtk_entry para obtener el puntero al
objeto GtkEntry y asociarle alguna señal o
manipular el texto. Aquí hay un ejemplo:
GtkWidget *gnomeentry;
GtkWidget *gtkentry;
...
gnomeentry = gnome_entry_new("texto1");
/* tomamos GtkEntry para enlazarlo con la señal "changed" y conocer
cuándo el usuario ha cambiado la entrada */
gtkentry = gnome_entry_gtk_entry(GNOME_ENTRY(gnomeentry));
gtk_signal_connect(GTK_OBJECT(gtkentry),"changed",
GTK_SIGNAL_FUNC(entry_changed), NULL);
GnomeFileEntryGnomeEntry es la base para
GnomeFileEntry. De nuevo no está derivado, sino
que una instancia de GnomeEntry, está contenida en
GnomeFileEntry. Este tipo de jerarquía está
presente en todos los widget de entrada de
GNOME. GnomeFileEntry añade un botón de búsqueda
en la parte derecha de la entrada, acepta también archivos que
provengan del gestor de archivos por medio del mecanismo de "arrastrar
y soltar", por ejemplo. Su uso es sumamente similar a
GnomeEntry. Se crea la entrada con
gnome_file_entry_new. El primer argumento es el
history_id del GnomeEntry, y
el segundo argumento es el título de la caja de diálogo del
buscador. Para conseguir el GtkEntry, de nuevo
use el método gtk_entry, llamado
gnome_file_entry_gtk_entry. Para obtener
finalmente el nombre del archivo, puede leer el texto de la misma
forma que GtkEntry, o podría usar otros métodos,
gnome_file_entry_get_full_path , el cual toma una
bandera file_must_exist como segundo
argumento. Si éste está activado (TRUE), la función devuelve NULL
cuando no existe el archivo. Si es FALSE o el archivo existe, la
función devuelve el camino entero hasta el archivo.
GnomePixmapEntry
Esto es una entrada para introducir imágenes cualquier tipo. Vuelve a
incluir (no se deriva de) GnomeFileEntry, por eso
puede hacer todo lo que GnomeFileEntry puede
hacer (incluso aceptar los archivos arrastrados desde el gestor de
archivos). De todas formas esta entrada añade un cuadro de
previsualización para la imagen que se encuentra en la entrada.
También su diálogo de selección de archivo incluye un cuadro de
previsualización en el lado derecho del listado de archivos.Su uso es muy similar a las entradas anteriores. Se llama a
gnome_pixmap_entry_new con los mismos argumentos
que GnomeFileEntry, con una bandera añadida,
do_preview. Esta bandera especifica si la caja de
previsualización es visible o no. Pero tenga cuidado, esto no ahorra
memoria, sólo ahorra espacio. Use de nuevo
gnome_pixmap_entry_gtk_entry para obtener el widget
GtkEntry. Para leer el nombre del archivo de la
imagen, si pudo ser cargada como una imagen en el previsualizado
(usando Imlib), puede usar
gnome_pixmap_entry_get_filename, que devuelve
NULL si el archivo de la imagen no existe o no pudo ser cargada, y el
nombre completo del archivo en el caso contrario.
GnomeIconEntry
La entrada de iconos es muy similar a
GnomePixmapEntry, pero está pensado para imágenes con el
tamaño estándar de los iconos de 48x48. También al margen de la caja
de previsualización hay un botón con la imagen escalada a 48x48. Si
presiona el botón, obtendrá una lista de imágenes del mismo directorio
en el que está el icono actual. Para crear una entrada de icono use
gnome_icon_entry_new con
history_id y
browse_dialog_title como argumentos tipo
cadena. Una vez que tiene un icono existente con una una imagen real,
use gnome_icon_entry_get_filename que funciona
como gnome_pixmap_entry_get_filename. También
puede conseguir GtkEntry usando
gnome_icon_entry_gtk_entry. Ejemplo:
GtkWidget *iconentry;
GtkWidget *gtkentry;
char *algunarchivo;
...
iconentry = gnome_icon_entry_new("icono","Explorar...");
/* queremos entablecer "somefile" como icono por defecto */
gnome_icon_entry_set_icon(GNOME_ICON_ENTRY(iconentry), algunarchivo);
/* tomamos el GtkEntry para enlazar con la señal "changed" */
gtkentry = gnome_icon_entry_gtk_entry(GNOME_ICON_ENTRY(iconentry));
gtk_signal_connect(GTK_OBJECT(gtkentry),"changed",
GTK_SIGNAL_FUNC(entry_changed), NULL);
...
/* aquí queremos tomar el icono seleccionado */
char *icono;
icono = gnome_icon_entry_get_filename(GNOME_ICON_ENTRY(iconentry));
...
/* nos aseguramos de liberar el icono después de usarlo */
g_free(icon);
GnomeNumberEntryGnomeNumberEntry es un widget para la
introducción de números de doble precisión con una calculadora. La
mayor parte del tiempo para la entrada de números querrá usar el
widget GtkSpinButton , de todas formas para
aplicaciones como el cálculo de hipotecas, o programas de finanzas,
donde las calculadoras son necesarias, querrá usar este tipo de
entrada. Básicamente es un widget GnomeEntry con
un botón a la derecha que llama a un diálogo con una calculadora. El
usuario puede usar la calculadora y presionar OK, entonces el número
de la entrada es actualizado con lo que tenga la calculadora. Para
crear uno de estos widget, use
gnome_number_entry_new, pasando como primer argumento el
history_id y el título del diálogo de la
calculadora como segundo argumento. Para obtener el widget
GtkEntry sólo use
gnome_number_entry_gtk_entry. Para leer el número
como un valor tipo double (real de doble
precisión), use el método
gnome_number_entry_get_number.
Utilizando Imágenes
Cuando necesite utilizar imágenes en sus aplicaciones, lo más probable
es que quiera emplear GnomePixmap. Este widget
permite usar imágenes de una manera más fácil y sin tener que aprender
Imlib, la biblioteca de imágenes usada por este widget.
Hay varias funciones new (constructores) para
GnomePixmap, dependiendo del origen del mapa de
pixels. El más usado probablemente sea
gnome_pixmap_new_from_file el cual toma un
archivo (que es una imagen que puede cargar Imlib) y crea un widget.
Hay también gnome_pixmap_new_from_file_at_size ,
idéntica a la anterior, con la salvedad de que puede fijar el tamaño
al que será escalada la imagen. Si ya tiene cargada la imagen con
Imlib (en el caso de que quiera hacer primero otras cosas con el mapa
de pixels), puede utilizar
gnome_pixmap_new_from_imlib y
gnome_pixmap_new_from_imlib_at_size, la cual toma
GdkImlibImage como el primer argumento. Si ya
tiene el widget y quiere cambiar la imagen de dentro, puede usar
gnome_pixmap_load_* que tiene casi la misma
sintaxis que las funciones "new" anteriores, a excepción de que recibe
un GnomePixmap como primer argumento, y por
supuesto cambia en su nomenclatura el _new_from_ por
_load_from_.
Un ejemplo de su uso:
GtkWidget *pix;
...
/* Carga archivo1.png y lo escala a 48x48 */
pix = gnome_pixmap_new_from_file_at_size("archivo1.png",48,48);
/* Ahora podemos empaquetar pix en otro widget */
...
/*Ahora cambiaremos el pixmap que contiene pix por el que se encuentra
en archivo2.png. No lo escalaremos */
gnome_pixmap_load_file(GNOME_PIXMAP(pix),"archivo2.png");
Manejo de Sesiones
Sus aplicaciones pueden recordar el estado en que el usuario la
abandonó la última vez y reestablecerlo tal cual cuando éste las
reinicie en otro momento, incluso pueden permitir que el usuario
almacene distintos de estos estados (A partir de aquí sesiones). Por
ejemplo el usuario podría tener una sesión normal, pero a veces entrar
en una sesión especial donde tenga las aplicaciones configuradas de
diferente forma. gnome-libs abstrae los aspectos
desagradables de esto. Usted, como programador, no debería preocuparse
de las interioridades del control de sesiones a menos que sus
aplicaciones tengan algún estado especialmente complicado de
guardar. Para almacenar una sesión sólo necesitas el siguiente código
(la mayor parte pertenece al programa de ejemplo gnome-hello-4-SM):
/* El manejador save_yourself, al implementarlo hemos ignorado
tranquilamente la mayoría de sus complicados parámetros, salvará
nuestra sesión y devolverá TRUE */
static int
save_yourself(GnomeClient *client, int phase,
GnomeSaveStyle save_style, int shutdown,
GnomeInteractStyle interact_style, int fast,
gpointer client_data)
{
/* Obtenemos el identificador que acompañará al nombre de
nuestra configuración */
char *prefix= gnome_client_get_config_prefix (client);
/* Construimos una orden que nos servirá para borrar una
sesión salvada anteriormente */
char *argv[]= { "rm", "-r", NULL };
/* Salvamos el actual estado */
gnome_config_push_prefix (prefix);
gnome_config_set_int("Seccion/Clave",un_valor);
...
gnome_config_pop_prefix ();
gnome_config_sync();
/* Aquí comienza el verdadero uso del controlador de
sesiones. Vamos a reiniciar/borrar la sesión que salvamos:
para ellos usaremos la orden que montamos anteriormente */
argv[2] = gnome_config_get_real_path (prefix);
gnome_client_set_discard_command (client, 3, argv);
/* Ahora ajustaremos las órdenes "clone" y "restart" de esta
aplicación. Note que hemos usado el mismo valor para ambas
("rm -f ..."). El controlador de sesiones automáticamente
completará el resto de las opciones requeridas para fijar el
identificador de sesión al comienzo. El parámetro
"client_data" fue fijado al arrancar la aplicación, en el
momento en que "save_yourself" fue conectado. */
argv[0]= (gchar*) client_data;
gnome_client_set_clone_command (client, 1, argv);
gnome_client_set_restart_command (client, 1, argv);
return TRUE;
}
static void
die (GnomeClient *client, gpointer client_data)
{
/* Una salida civilizada de la aplicación. No salvaremos
ningún estado aquí, porque el controlador de sesión debería
haber enviado un mensaje a save_yourself antes. */
gtk_exit (0);
}
...
GnomeClient *client;
...
/* Aquí iría todo lo que haga su función main DESPUÉS de la llamada a
gnome_init */
/* Obtenemos el cliente "maestro", éste se conectó al controlador de
sesiones durante la llamada 'gnome_init'. Toda las comunicaciones con
el controlador de sesión, serán realizadas desde este cliente maestro */
client = gnome_master_client ();
/* Enganchamos nuestros manejadores a las señales correspondientes del
cliente maestro. */
gtk_signal_connect (GTK_OBJECT (client), "save_yourself",
GTK_SIGNAL_FUNC (save_yourself),
(gpointer) argv[0]);
gtk_signal_connect (GTK_OBJECT (client), "die",
GTK_SIGNAL_FUNC (die), NULL);
/* Comprobamos si estamos conectados al controlador de sesiones */
if (GNOME_CLIENT_CONNECTED (client)) {
/* Estamos conectados: obtendremos ahora el prefijo bajo el
que salvamos nuestra sesión la última vez y cargaremos
nuestros datos */
gnome_config_push_prefix
(gnome_client_get_config_prefix (client));
some_value = gnome_config_get_int("Section/Key=0");
gnome_config_pop_prefix ();
} else {
/* No estamos conectados al controlador de
sesiones. Continuamos la ejecución de la forma habitual */
...
}
Este es un ejemplo de control de sesiones muy sencillo el cual puede
ser suficiente para la mayoría de los programas, para más información
puede consultar la documentación del desarrollador de
GNOME.Interfaz para Varios DocumentosLa Ventana Principal MDI
Si su aplicación maneja documentos, lo más probable es que quiera que
maneje varios documentos a la vez. GNOME proporciona un modelo MDI que
es particularizable por el usuario y sencillo de usar. Se puede usar
tres modelos del documento en pantalla. Uno al estilo de un cuaderno
de notas es el más útil: los documentos pueden ser almacenados en
cuadernos de notas, y si se desea pueden ser separados en diferentes
ventanas. Otro es el estilo de "nivel superior" donde cada documento
se separa en una ventana de nivel superior. O para terminar un estilo
modal donde solo hay una ventana y los documentos pueden seleccionarse
a través de un menú. (Nótese que el código que aquí se muestra
pertenece a gnome-hello-7-mdi ejemplo de
aplicación en gnome-libs, ligeramente
modificado. Este ejemplo no es tan largo en gnome-libs porque aquí se
reproducen sólo las partes del ejemplo más importantes)
Para usar las características de MDI. Tiene que sustituir la llamada
gnome_app_new por
gnome_mdi_new con los mismos argumentos de
gnome_app_new. Para añadir menús y barras de
herramientas, puede utilizar
gnome_mdi_set_menubar_template y
gnome_mdi_set_toolbar_template con la GnomeUIInfo
como argumento. Para MDI, estos no son los actuales menús, añadiremos
nuestras propias entradas a los menús de cada hijo. Después de esto se
puede establecer dónde tendrán lugar las inclusiones del menú. Puedes
llamar a gnome_mdi_set_child_menu_path para el
nombre principal del menú del que colgarán después los propios hijos
del menú introducido. Esto es en la mayoría de los casos el menú
"Archivo" o "Documentos" . Entonces puede especificar la ruta (nombre
del menú) para acceder al menú en el que quiera insertar la lista de
los hijos, puede hacer esto haciendo la llamada
gnome_mdi_set_child_list_path con el nombre del
menú y añadir un '/' al final para especificar que quiere introducir
esas entradas en el menú. Por ejemplo:
GtkWidget *mdi;
...
mdi = gnome_mdi_new("gnome-hello-7-mdi", "GNOME MDI Hello");
...
/* main_menu y toolbar_info son las descripciones del menú y de la
barra de herramientas, respectivamente */
gnome_mdi_set_menubar_template(mdi, main_menu);
gnome_mdi_set_toolbar_template(mdi, toolbar_info);
/* Insertamos algunos elementos en mdi (Sí desea más información, vea
las funciones de inserción de menus de gnome-app-helper) */
gnome_mdi_set_child_menu_path(GNOME_MDI(mdi), "Archivo");
gnome_mdi_set_child_list_path(GNOME_MDI(mdi), "Hijo/");
En su estructura GnomeUIInfo
tendrá definido un menú llamado "Archivo" y y otro llamado "Hijo". El
menú "Hijo" es solamente un menú vacío, ya que no le hemos introducido
ninguna entrada.
Ahora usted debe abrir la ventana principal con
gnome_mdi_open_toplevel. Esto abrirá una ventana
de nivel superior sin ningún hijo.Si desea manejar sesiones que impliquen MDI, debe definir una
función que cree un hijo (Un widget GnomeMDIChild) a partir de una
cadena de caracteres (por ejemplo, la ruta completa de un archivo
podría servir para que un procesador de textos recuperase la
sesión). Después debe llamar a
gnome_mdi_restore_state: este método toma como
argumentos una ruta de configuración y un puntero a la función que
usted implementó antes.
Por ejemplo, usando el manejador de sesión mostrado anteriormente, se
podría utilizar:
gnome_config_push_prefix (gnome_client_get_config_prefix (client));
restart_ok = gnome_mdi_restore_state(GNOME_MDI(mdi), "MDI Session",
my_child_new_from_config);
gnome_config_pop_prefix ();
La variable restart_ok es booleana, le informa si se han cargado todos
los datos correctamente.
También puede unir la señal destroy del objeto
MDI para hacer gtk_main_quit cuando el MDI sea
destruido.El Hijo MDI
Para aplicaciones complejas, todos los hijos podrían ser derivados de
la clase virtual GnomeMDIChild. Para
aplicaciones sencillas no necesitará derivar nuevas clases, puede
utilizar GnomeMDIGenericChild, y utilizar el
hecho de que puede almacenar datos arbitrarios en un
GtkObject para guardar sus propios datos en el
objeto.
Para utilizar un objeto 'hijo' genérico
GnomeMDIChild, debe crearlo con
gnome_mdi_generic_child_new, esta función precisa
de un nombre que será asignado a la nueva instancia. Además necesita
configurarlo antes de poder utilizarlo.Primero debe añadir una función que cree nuevas vistas del
objeto. Una vista es sólo una nueva ventana que muestra el contenido
del mismo archivo o los mismos datos que mostraba la ventana
original. Esta función aceptará como parámetros un GnomeMDIChild (El
objeto del que vamos a crear una nueva vista) y un puntero de datos
genérico, y devolverá una instancia de
GnomeMDIGenericChild que representa la nueva
vista. Para añadir esta función haga una llamada a
gnome_mdi_generic_child_set_view_creator,
pasándole un puntero a la función y el puntero de datos genérico que
se le pasarará cuando sea llamada.El siguiente paso es ajustar los menús del objeto con
gnome_mdi_child_set_menu_template, que recibe un
vector de punteros GnomeUIInfo con las
definiciones del menú que poseera el objeto.Después debe llamar a
gnome_mdi_generic_child_set_config_func para
establecer una función que devuelva una cadena de caracteres (Cuya
memoría debe ser asignada dinámicamente) que será guardada en el
archivo de configuración. Esta cadena de caracteres se utilizará para
cargar el objeto mediante una llamada a
gnome_mdi_restore_state la siguiente vez que el
usuario utilice la aplicación. La cadena debería ser evidentemente un
nombre de archivo de un documento, o alguna cadena de caracteres desde
la cual pueda recrear completamente la ventana/el documento.Por último necesita establecer una función que permita ajustar
la etiqueta del MDI. Esto lo hará llamando a
gnome_mdi_generic_child_set_label_func con un
puntero a dicha función. Está función debe tomar como argumentos un
GnomeMDIGenericChild (El objeto), un puntero a la
antigua etiqueta, el cual será nulo si todavía no hubierá ninguna, y
un puntero de datos genérico. La etiqueta puede ser cualquier widget,
por ejemplo en gnome-hello-7-mdi se utiliza una
caja horizontal dentro de la cual se añaden un mapa de pixels y una
etiqueta GTK+. Esta función o bien puede crear una etiqueta nueva y
destruir la antigua, o bien establecerá la etiqueta si la etiqueta no
existía.Después de esto usted ya puede añadir el 'hijo' al MDI, si por
ejemplo está cargando un nuevo archivo, utilice
gnome_mdi_add_child y
gnome_mdi_add_view, para añadir un nuevo hijo y
una nueva vista al MDI. Si está creando un nuevo hijo desde la función
gnome_mdi_restore_state, ésta debería devolver el
hijo, el MDI lo incluirá y añadirá las vistas adecuadas.
Probablemente también querrá almacenar sus datos en el widget 'hijo'
en este momento.
Esto es un pequeño ejemplo de la creación de un nuevo hijo.
GnomeMDI *mdi;
...
GnomeMDIGenericChild *child;
...
/* Crea un nuevo hijo con el nombre 'nombre'*/
if((child = gnome_mdi_generic_child_new("nombre") != NULL) {
/* Creamos una vista */
gnome_mdi_generic_child_set_view_creator(child,
my_child_create_view, NULL);
/* Ajustamos una plantilla de menú para el menú de child */
gnome_mdi_child_set_menu_template(GNOME_MDI_CHILD(child),
main_child_menu);
/* Ajustamos una función que permitirá trabajar con la cadena
de configuración */
gnome_mdi_generic_child_set_config_func(child,
my_child_get_config_string, NULL);
/* Ajustamos function que cambia o crea una etiqueta */
gnome_mdi_generic_child_set_label_func(child, my_child_set_label,
NULL);
/* Añadimos el hijo al MDI */
gnome_mdi_add_child(mdi, GNOME_MDI_CHILD(child));
/* Y añadimos una nueva vista del hijo */
gnome_mdi_add_view(mdi, GNOME_MDI_CHILD(child));
}
Widgets VariadosEnlaces Web con GnomeHRef
A veces querrá poner un botón en sus aplicaciones que arranque
un navegador para el usuario o apuntar un navegador ya arrancado a
algún sitio. Todo lo que necesita es llamar a gnome_href_new con una
URL como el primer argumento y la etiqueta como el segundo. Por
ejemplo:
GtkWidget *widget;
...
widget = gnome_href_new("http://www.gnome.org", "La página de GNOME");
Seleccionando Iconos con GnomeIconSelection
Normalmente querrá añadir iconos utilizando el widget GnomeIconEntry, pero a veces puede
querer poner el icono listándolo en la ventana directamente dentro de
su aplicación. El widget que se mostrará a continuación solamente es
útil si tiene algún directorio desde el cual coger los iconos. Cree el
widget con gnome_icon_selection_new. Entonces
cuando quiera añadir un directorio de iconos, utilice el método
gnome_icon_selection_add_directory pasándole como
argumento el directorio que desea añadir. Puede añadir varios
directorios. Una vez termine este paso, llame a
gnome_icon_selection_show_icons: esto cargará y
mostrará los iconos. Para seleccionar un icono específico, utilice
gnome_icon_selection_select_icon con el nombre
del archivo del icono elegido. El nombre del archivo debería ser sólo
el nombre base del icono y no la ruta entera. Una vez haya
seleccionado el icono, puede obtener su ruta con el método
gnome_icon_selection_get_icon el cual coge un
argumento booleano 'full_path' y devuelve un puntero a una cadena de
caracteres con el nombre del archivo o NULL si no había nada elegido.
Si el argumento 'full_path' es TRUE, el valor devuelto será la ruta
completa del icono. Señalar que el valor devuelto apunta a memoria
interna por lo tanto no tiene que liberarla. Ejemplo:
GtkWidget *widget;
char *some_icon_directory;
char *some_other_icon_directory;
...
/* Creamos el widget */
widget = gnome_icon_selection_new();
gnome_icon_selection_add_directory(GNOME_ICON_SELECTION(widget),
some_icon_directory);
gnome_icon_selection_add_directory(GNOME_ICON_SELECTION(widget),
some_other_icon_directory);
gnome_icon_selection_show_icons(GNOME_ICON_SELECTION(widget));
...
/* Queremos obtener la selección (La ruta completa) */
char *filename;
...
filename = gnome_icon_selection_get_icon(GNOME_ICON_SELECTION(widget), TRUE);
El Widget GnomeCanvas
Aunque el widget GnomeCanvas está dentro de la
biblioteca libnomeui, se le dedica un capítulo separado. El lienzo
(N.T: canvas) es un widget para el dibujo de gráficos de muy alto
nivel y de alto rendimiento, que resulta muy sencillo de
utilizar. Incluye dos modos para trabajar con gráficos: Xlib y con
suaviazado. El modo Xlib es más rápido (especialmente sobre redes) y
con el modo con suavizado se obtienen unos resultados visuales
mejores.
Creando un Widget Lienzo
Para crear un widget lienzo, usted debe llamar a
gnome_canvas_new. Necesita asegurarse de que el
lienzo se crea con un X visual (N.T: descripción del formato de los
datos en la pantalla de un servidor X, incluyendo número de bits
usados por cada color, la forma en que los bits son traducidos en
valores RGB para su representación y la forma en que los mismos son
almacenados en memoria) y un mapa de colores adecuado. Por ejemplo, si
desea dibujar imágenes Imlib dentro del lienzo, debería hacer esto:
GtkWidget *canvas;
...
gtk_widget_push_visual(gdk_imlib_get_visual());
gtk_widget_push_colormap(gdk_imlib_get_colormap());
canvas = gnome_canvas_new();
gtk_widget_pop_visual();
gtk_widget_pop_colormap();
Después de esto debería llamar a
gnome_canvas_set_pixels_per_unit para establecer
la escala del lienzo. Puede entonces hacer
gtk_widget_set_usize para establecer el tamaño
del widget, y gnome_canvas_set_scroll_region
para indicar la región en la cual se puede dibujar, está dado en (x1,
y1, x2, y2). Básicamente son los límites externos de su dibujo. Una
vez el lienzo está creado puede hacer:
GnomeCanvas *canvas;
...
/*ya está creado el lienzo, ahora ajustamos algunos parámetros*/
gnome_canvas_set_pixels_per_unit(canvas,10);
gnome_canvas_set_scroll_region(canvas,0.0,0.0,50.0,50.0);
Grupos y Elementos
En el lienzo hay elementos, que son los objetos que están actualmente
sobre el lienzo, y grupos, los cuales son asociaciones de
elementos. Un grupo está derivado de la clase base
GnomeCanvasItem Esto es útil para aplicar
funciones a todos los elementos dentro del grupo: acciones como mover
o esconder un grupo entero. Hay también un grupo por defecto, el grupo
raíz. Puede obtener este grupo llamando a
gnome_canvas_root.
Creando Elementos
Crear elementos de un lienzo no es diferente de crear otros
objetos.
Se usa el mecanismo de argumentos del modelo de objetos estándar de
GTK+. Básicamente se llama a gnome_canvas_item,
con el grupo ráiz del lienzo padre como el primer argumento, el tipo
de objeto como segundo argumento, y el resto de argumentos dados en
pares (argumento, valor), terminado con un NULL. Esto se ilustra
mejor con un ejemplo:
GnomeCanvas *canvas;
GnomeCanvasItem *item;
...
item = gnome_canvas_item_new(gnome_canvas_root(canvas),
GNOME_TYPE_CANVAS_RECT,
"x1", 1.0,
"y1", 1.0,
"x2", 23.0,
"y2", 20.0,
"fill_color", "black",
NULL);
Note que es extremadamente importante que el valor sea del tipo
exacto, el compilador no lo convertirá implícitamente por usted. Si
está haciendo algún cálculo y no está seguro de que obtiene el tipo
correcto, haga una conversión explicita. Según creemos, todos los
números (O la gran mayoría) que se emplean en el lienzo son reales de
doble precisión.
Para encontrar los argumentos que cada elemento acepta, consulte la
documentación de GNOME o mire en los archivos de cabecera
libgnomeui/gnome-canvas*.h. Estos contienen una
tabla como la que se muestra a continuación justo al principio del
archivo (la cual ha sido tomado de
libgnomeui/gnome-canvas-rect-ellipse.h).
Por ejemplo, aquí están los argumentos para el rectángulo
(GNOME_TYPE_CANVAS_RECT) y la elipse (GNOME_TYPE_CANVAS_ELLIPSE):
Argumentos para los Elementos Rectángulo y Elipse
NombreTipoRead/Write (lectura/escritura)Descripciónx1real de doble precisiónRW
Coordenada más a la izquiera del rectángulo o elipse
y1real de doble precisiónRW
Coordenada más arriba del rectángulo o elipse
x2real de doble precisiónRW
Coordenada más a la derecha del rectángulo o elipse
y2real de doble precisiónRW
Coordenada más abajo del rectángulo o elipse
fill_colorcadenaW
Especificación de color X para rellenar con un color, o un puntero a
NULL para ninguno (transparente)
fill_color_gdkGdkColor*RW
GdkColor ubicado para rellenar
outline_colorcadenaW
Especificación de color X para perfilar con un color, o un puntero a
NULL para ninguno (transparente)
outline_color_gdkGdkColor*RW
GdkColor ubicado para bordear
fill_stippleGdkBitmap*RW
Modelo punteado para rellenar
outline_stippleGdkBitmap*RW
Modelo punteado para bordear
width_pixelsentero sin signoRW
Anchura del perfil en pixels. El perfil no será escalado cuando
se cambie el factor de escala del lienzo.
width_unitsreal de doble precisiónRW
Anchura del perfil en unidades de lienzo. El perfil será escalado
cuando se cambie el factor de escala del lienzo.
Ahora supongamos que queremos cambiar algunas de estas
propiedades. Esto se hace con una llamada a
gnome_canvas_item_set. El primer argumento de
esta función es un puntero a un objeto elemento del lienzo. Los
argumentos siguientes son los mismos pares de argumentos vistos antes
cuando creábamos un nuevo objeto lienzo. Por ejemplo, si queremos que
el rectágulo que creamos antes cambie su color a rojo, haremos esto:
GnomeCanvas *canvas;
GnomeCanvasItem *item;
...
gnome_canvas_item_set(item, "fill_color", "red", NULL);
Hay métodos para operar sobre los elementos. Por ejemplo el método
gnome_canvas_item_move tomará x e y como segundo
y tercer argumento y moverá el elemento de forma relativa a su
posición en x e y. Otros métodos son
gnome_canvas_item_hide y
gnome_canvas_item_show, los cuales esconden y
muestran el elemento, respectivamente. Para controlar el orden de los
elementos en z, puede usar los métodos
gnome_canvas_item_raise_to_top y
gnome_canvas_item_lower_to_bottom para subir o
bajar el elmento hasta lo más alto o lo más al del fondo del orden en
z de su grupo padre. Para tener un control más fino sobre z se puede
usar gnome_canvas_item_raise y
gnome_canvas_item_lower, estos métodos toman un
argumento entero más que vale 1 o más, y especifica el número de
niveles que el elemento tiene que moverse en z.
Lienzo con Suavizado
Para crear un lienzo que use suavizado para representar sus elementos,
en vez de gnome_canvas_new, debe usar
gnome_canvas_new_aa. Siga el siguiente ejemplo para crear
un nuevo lienzo con suavizado:
GtkWidget *canvas;
...
gtk_widget_push_visual (gdk_rgb_get_visual ());
gtk_widget_push_colormap (gdk_rgb_get_cmap ());
canvas = gnome_canvas_new_aa ();
gtk_widget_pop_colormap ();
gtk_widget_pop_visual ();
Después de esto puede usar el lienzo exactamente de la
misma forma que un lienzo normal.
Los elementos del lienzo con suavizado pueden, generalmente, hacer más
cosas que los lienzos normales. Esto se debe a las limitaciones de
Xlib como librería gráfica. Por ejemplo puede hacer cualquier tipo de
transformación afín en cualquiera de sus miembros, cuando en un lienzo
Xlib sólo se podrían hacer transformaciones afines en ciertos objetos.
Arrastrar y Soltar
A pesar de que la técnica de "arrastrar y soltar" (N.T: drag and drop)
está implementada en GTK+, hemos pensado que sería mejor cubrir esto
antes que algunas partes de GNOME sean discutidas.Aceptando la Acción de Soltar
Ya ha visto un manipulador de la acción de soltar antes, cuando fueron
discutidos los tipos MIME. Básicamente, para aceptar un objeto
arrastrado, tiene que decidir qué tipo MIME del dato quiere
recibir. Ha visto uno para "text/uri-list". Básicamente su manipulador
sólo recibirá datos de aquellos tipos MIME que ha especificado, por
eso sólo necesita saber cómo decodificar esos tipos concretos.
Para especificar el tipo MIME que desea recibir, cree un vector de
estructuras GtkTargetEntry, donde el primer
elemento es una cadena con la descripción del tipo MIME, el segundo es
una bandera y el tercero es un entero con la información que usted
desee añadir. Puede dejar la bandera a 0. El campo de información
puede ser usado si está aceptando varias entradas distintas, como este
entero será pasado a la función que se encarga de aceptar la acción de
soltar, usted lo podrá usar como discriminador entre los los distintos
tipos de datos (mediante la sentencia "switch", por ejemplo). Si sólo
tiene un tipo, déjelo en 0.
Después de esto necesita preparar el widget para que pueda ser
arrastrado. Esto se hace llamando a la función
gtk_drag_dest_set. El primer argumento es el
widget que quiere preparar, el segundo es una bandera para establecer
que tipos de comportamientos por defecto se usaran al arrastrar, puede
dejar esto como GTK_DEST_DEFAULT_ALL. El
argumento siguiente es el número de elementos en el vector. El último
argumento es el tipo de acción que acepta. Los tipos pueden ser de
cualquiera de los siguientes: GDK_ACTION_DEFAULT,
GDK_ACTION_COPY,
GDK_ACTION_MOVE,
GDK_ACTION_LINK,
GDK_ACTION_PRIVATE y
GDK_ACTION_ASK. Los más útiles son
GDK_ACTION_COPY y
GDK_ACTION_MOVE. Si usted está, por ejemplo,
pasando cadenas u otros datos, debería usar sólo
GDK_ACTION_COPY.
Luego necesita establecer un manipulador para recibir el objeto. Este
manipulador debería tener el siguiente prototipo:
void
target_drag_data_received (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *data,
guint info,
guint time);
El dato está en la estructura GtkSelectionData,
dentro del campo data. Eso es todo lo que
necesita para hacer un arrastrar y soltar normal. Aquí tiene un
ejemplo:
static void
target_drag_data_received (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *data,
guint info,
guint time)
{
g_print("Obtuve: %s\n",data->data);
}
...
static GtkTargetEntry target_table[] = {
{ "text/plain", 0, 0 }
}
...
gtk_drag_dest_set (widget,
GTK_DEST_DEFAULT_ALL,
target_table, 1,
GDK_ACTION_COPY);
gtk_signal_connect (GTK_OBJECT (widget), "drag_data_received",
GTK_SIGNAL_FUNC (target_drag_data_received),
NULL);
Para más información sobre arrastrar y soltar, debería leer la
documentación de GTK+ en www.gtk.org.
Permitiendo la Acción de Arrastrar
Ahora vamos a ver la otra cara de DND (N.T acrónimo de "Drag aNd
Drop", arrastrar y soltar). Prepare el vector
GtkTargetEntry, de la misma forma que antes. Entonces,
reemplace el argumento bandera por una máscara con
los botones del ratón que se usarán para arrastrar. Puede ser
GDK_BUTTON1_MASK | GDK_BUTTON3_MASK para el
primer y tercer botón del ratón. Luego necesita unir la señal
drag_data_get que mandará el dato, y
drag_data_delete si la acción es
GDK_ACTION_MOVE, para borrar el dato después de que el
movimiento se complete con éxito. Aquí hay un ejemplo simple que
funcionará con el código anterior:
static void
source_drag_data_get (GtkWidget *widget,
GdkDragContext *context,
GtkSelectionData *selection_data,
guint info,
guint time,
gpointer data)
{
char string[] = "Alguna Cadena!";
gtk_selection_data_set (selection_data,
selection_data->target,
8, string, sizeof(string));
}
...
static GtkTargetEntry target_table[] = {
{ "text/plain", 0, 0 }
};
...
gtk_drag_source_set (widget,
GDK_BUTTON1_MASK|GDK_BUTTON3_MASK,
target_table, 1,
GDK_ACTION_COPY);
gtk_signal_connect (GTK_OBJECT (widget), "drag_data_get",
GTK_SIGNAL_FUNC (source_drag_data_get),
NULL);
La función gtk_selection_data_set copia el dato dentro
de la selección de datos, que es usado para la transferencia.Construyendo Aplicaciones GNOMEUsando un Sencillo Makefile
Usar un sencillo Makefile es la forma mas rápida de compilar una
pequeña aplicación GNOME. Si requiere un entorno de construcción mas
sofisticado, debería usar una configuración con autoconf/automake, de
la cual hablaremos brevemente mas tarde.
El Guión gnome-config
La linea de órdenes que necesita el compilador C para construir una
aplicación GNOME puedes ser bastante larga y podría ser muy pesado
escribirla a mano. Por ello las bibliotecas GNOME instalan un guión
(N.T: script) para simplificar todo esto. Se llama
gnome-config y toma dos opciones,
––cflags y
––libs. La opción
––cflags dará al compilador las opciones necesarias para
el paso de compilación, y ––libs le dará las opciones
precisas que necesita pasar al enlazador. También necesita aportar
otro conjunto de argumentos a este guión de configuración de GNOME. Es
necesario saber qué bibliotecas desea usar. Para nuestros propósitos
esta es gnome y
gnomeui. Así, por ejemplo para obtener los
opciones del compilador para algún programa que use las bibliotecas
estándar gnome y gnomeui, debería llamar a "gnome-config
––cflags gnome gnomeui".
Un Ejemplo Sencillo de Makefile
Ahora para construir un Makefile sencillo, puede usar variables
CFLAGS y LDFLAGS y las
reglas implícitas que como mínimo soporta el make de GNU (otras probablemente lo
harán bien, pero no estoy familiarizado con otros 'makes'). Así por
ejemplo digamos que tiene una aplicación que tiene un main.c, main.h,
extra.c y extra.h y el ejecutable es llamado gnome-foo. Ahora
construyamos un sencillo Makefile para esta aplicación.
CFLAGS=-g -Wall `gnome-config ––cflags gnome gnomeui`
LDFLAGS=`gnome-config ––libs gnome gnomeui`
all: gnome-foo
gnome-foo: main.o extra.o
main.o: main.c main.h extra.h
extra.o: extra.c extra.h
clean:
rm -f core *.o gnome-foo
Este es un ejemplo de Makefile extremadamente sencillo, pero debería
servir para empezar.
Usando automake/autoconf
Usar automake/autoconf esta realmente fuera del ámbito de este
documento, pero debería leer los manuales en linea en
http://www.gnu.org/manual/manual.html, o leer las paginas info
si las tiene instaladas con su navegador de ayuda GNOME (gnome-help-browser).
Hay ahora un ejemplo de aplicación la cual puede ayudar a iniciarle
con autoconf/automake, la configuración de internacionalización, y
otros aspectos de la construcción, también puede servir como un buen
ejemplo de "hola mundo". Lo puede obtener en cualquier mirror ftp de
GNOME (vaya a
http://www.gnome.org/ftpmirrors.shtml para una lista de
mirrors) en el directorio sources/GnomeHello/.
ConclusiónObteniendo Más Ayuda
Una de las mejores maneras de obtener ayuda con la programación en
GNOME es probablemente leer primero la documentación disponible en
www.gnome.org, o el sitio
web del desarrollador en developer.gnome.org. También
se puede subscribir a gnome-devel-list@gnome.org,
para suscribirse, mande un mensaje con subscribe
en el asunto a
gnome-devel-list-request@gnome.org. Para reducir el trafico en
la lista debería primero consultar la documentación antes de preguntar
algo. También mire en
www.gnome.org/mailing-lists/ para obtener una lista de todas
las listas de correo, incluyendo la lista GTK+.
A pesar de todo nosotros consideramos más útiles los archivos de
cabecera de las bibliotecas como fuente de información. Esto es sobre
todo porque todavía no se encuentran todas las bibliotecas
documentadas, sin embargo en los archivos de cabecera encontrará
siempre todas las definiciones acordes con la versión actual, aunque
el manual bien podría no estarlo. La mayoría de los nombres de
funciones GTK+ y GNOME son muy descriptivos y es fácil imaginarse qué
es lo que hacen. Nosotros usamos sólo los archivos de cabecera. A
menudo es mucho más fácil mirar el prototipo de la función y figurarse
qué hace, que consultar un manual de referencia. Esto le obliga a
saber qué archivo de cabecera está buscando, lo cual no es tan difícil
como parece dado que el nombre de los archivos de cabecera se
corresponde con el nombre de los objetos o módulos que
representan. Por ejemplo, el archivo de cabecera para
gnome-config es
libgnome/gnome-config.h. El archivo de cabecera
para GnomeCanvas es
libgnomeui/gnome-canvas.h.Futuros Desarrollos de las Bibliotecas GNOME
Este tutorial cubre la programación con la versión 1.0 de las
bibliotecas GNOME, pero por supuesto hay vida despues de la 1.0. Hay
muchas cosas para las bibliotecas que todavía están sólo sobre el
papel. Pero no se preocupe, en el futuro se intentará mantener toda la
compatibilidad que sea humanamente posible con la versión 1.0.
Aquí tiene una pequeña lista de cosas que posiblemente estarán en
posteriores versiones o en las que actualmente se está trabajando.
Más diálogos comunes,
tales como los diálogos de selección de archivos (Actualmente GNOME
utiliza para esto el diálogo de selección de archivo estadar de
GTK+).Más integración con CORBA
en todos los aspectos del escritorio. Se incluirá mucho más soporte
para CORBA en las bibliotecas que conforman el núcleo de
GNOME.Mejoras en el widget
GnomeCanvas, incluyendo mejoras en la manipulación del canal alfa e
impresión directamente desde el
widget.Reimplementación completa
del sistema de configuración. ¡Y mucho mas!
... ¡Características que todavía ni siquiera hemos
imaginado!