Volver al índice ; Próximo: Motif ; Previo: Usuario de X

X Toolkit

Introducción

Es difícil construir el interfaz gráfico de una aplicación programando a nivel de Xlib. X Toolkit intenta facilitar esta labor pero, siguiendo la filosofía de incluir mecanismos pero no políticas, no define un conjunto fijo de componentes de diálogo sino un mecanismo general para manejar objetos de diálogo (widgets) construidos por la aplicación o que forman parte de una biblioteca predefinida como Motif.

X Toolkit presenta un modelo de programación dirigido por los eventos (por el usuario) frente al modelo dirigido por la aplicación de Xlib.

Widgets

Las widgets están organizadas en clases siguiendo un modelo orientado a objetos. Cada clase hereda todas las características de la superclase de la que deriva definiendo únicamente sus características específicas. Cuando un programa crea una widget (XtCreateWidget) de una clase está creando una instancia de esa clase.

Cada clase tiene asociados un conjunto de recursos (algunos propios otros heredados) cuyos valores deben especificarse cuando se crea una instancia. Esta especificación puede estar en el programa usando XtSetValues (vector de argumentos) o XtVaSetValues (lista de argumentos), o puede haber sido definida por el usuario usando el mecanismo general de especificación de recursos.

Un tipo de recurso que incluye la mayoría de las clases de widgets son las listas de callback. La aplicación puede asociar una función propia con una lista de callback usando XtAddCallback. Cuando se cumplen determinadas condiciones en la widget se invocará a la función especificada por la aplicación.

En Xt existen algunas clases predefinidas (metaclases) que generalmente no se instancian:

Todas las clases no son normalmente instanciadas sino que sirven como superclase para el resto de clases, ya sean programadas por la aplicación o, más típicamente, contenidas en una librería predefinida como Motif.

A continuación veremos el modo de operación de las widgets y, como consecuencia de éste, la típica estructura de la aplicación que usa Xt:

  1. Iniciar Xt usando, por ejemplo, XtAppInitialize que creará la widget TopLevelShell raíz del árbol de widgets de la aplicación.
  2. Crear toda la jerarquía de la widgets (XtCreateWidget) y añadirla al conjunto de widgets gestionadas por la widget padre (XtManageChild, o XtCreateManagedWidget si desea hacerlo en un sólo paso). En la creación, cada widget obtendrá los valores de sus recursos ya sea por que se los proporciona la aplicación u obteniéndolos de la base de datos de recursos.
  3. Asociar funciones de la aplicación con los callback de las widgets creadas.
  4. Crear y proyectar todas las ventanas asociadas a las widgets usando XtRealizeWidget sobre la widget que se creó al iniciar Xt.
  5. Llamar a XtAppMainLoop, el bucle de tratamiento de eventos, que hace que Xt tome control de la aplicación que a partir de ese momento tendrá un comportamiento pasivo. El programa estará dirigido por los eventos existiendo dos posibilidades:

Callbacks

Eventos vs. Callbacks

Generalmente, las aplicaciones que usan Xt no necesitan tratar con eventos sino con listas de callbacks. Sin embargo, Xt proporciona facilidades para manejar directamente eventos permitiendo que la aplicación pueda asociar una función a la ocurrencia de uno o varios eventos. Para ello se usa XtAddEventHandler en la que se especifica la función que se invocará cuando suceda en la ventana asociada a la widget alguno de los eventos que se seleccionan en una máscara. Cuando el programa invoque XtAppMainLoop la ocurrencia de alguno de estos eventos dará lugar a la ejecución de la función asociada que recibirá como parámetros, entre otros, la widget donde se produjo el evento y el tipo de evento.

Normalmente las aplicaciones no manejan directamente eventos sino listas de callbacks que proporcionan una abstracción de más alto nivel. La activación de un callback está vinculada a la ocurrencia de uno o varios eventos, pero esta asociación puede ser modificada permitiendo que el código de la aplicación sea independiente de los eventos que maneja (ver traducciones). Cada clase de widget define varios recursos de tipo callback. Cuando un programa crea una instancia de esa clase puede asociar funciones a dichos recursos usando XtAddCallback. Durante el bucle de tratamiento de eventos (XtAppMainLoop), la activación del callback causará que se ejecute la función.

Por ejemplo, la clase PushButton de Motif incluye, entre otros, el recurso XmNactivateCallback. Este callback indica la activación del botón de diálogo que, por defecto, está asociado a la ocurrencia del evento que indica que el botón 1 del ratón se ha soltado en la ventana asociada a la widget. Sin embargo, esta asociación puede ser modificada.

Acciones

Aunque hasta ahora hemos hablado de asociaciones entre eventos y callbacks, realmente existe una asociación entre eventos y acciones mediante la tabla de traducciones.

Una acción es una función interna de la widget que se invocará cuando se produzcan los eventos especificados. Normalmente, las acciones, además de realizar los cambios pertinentes sobre la widget, acaban activando algún callback. Por ejemplo, la clase PushButton de Motif incluye la acción Activate que después de modificar el dibujo del botón en pantalla, activa XmNactivateCallback. En algunos casos, sin embargo, la acción no activa ningún callback. Por ejemplo, la clase Text de Motif incluye una acción forward-character que sólo avanza el cursor un carácter sin invocar ningún callback.

Adicionalmente, un programa, con el objetivo de aumentar la funcionalidad de una widget, puede definir nuevas acciones asociadas a funciones del programa que, como ocurre con las acciones predefinidas, estarán asociadas a eventos de forma configurable.

Traducciones

Una traducción asocia una secuencia de eventos con una acción. Cada widget tiene un recurso heredado de la clase Core denominado XmNtranslations que se corresponde con la tabla de traducciones de esa widget. La tabla de traducciones tiene un valor por defecto que, como cualquier otro recurso, puede ser modificado por la aplicación o por el usuario.

El siguiente ejemplo tomado del fichero de recursos del netscape muestra un fragmento de la definición de una tabla de traducciones que modifica el comportamiento por defecto de las widgets de la clase XmTextField que se usen en este programa.

*XmTextField.translations:              #override                       \n\
        ~Meta ~Alt Ctrl<Key>a:          beginning-of-line()             \n\
        ~Meta ~Alt Ctrl<Key>b:          backward-character()            \n\
        ~Meta ~Alt Ctrl<Key>d:          delete-next-character()         \n\
        ~Meta ~Alt Ctrl<Key>e:          end-of-line()                   \n\
        ~Meta ~Alt Ctrl<Key>f:          forward-character()             \n\
        ~Meta ~Alt Ctrl<Key>g:          process-cancel()                \n\
        ~Meta ~Alt Ctrl<Key>h:          delete-previous-character()     \n\
        ~Meta ~Alt Ctrl<Key>k:          delete-to-end-of-line()         \n\
        ~Meta ~Alt Ctrl<Key>u:          beginning-of-line()             \
                                        delete-to-end-of-line()         \n\
        ~Meta ~Alt Ctrl<Key>w:          cut-clipboard()                 \n\
        Meta ~Ctrl<Key>b:               backward-word()                 \n\
         Alt ~Ctrl<Key>b:               backward-word()                 \n\
        Meta ~Ctrl<Key>d:               delete-next-word()              \n\
         Alt ~Ctrl<Key>d:               delete-next-word()              \n\
        Meta ~Ctrl<Key>f:               forward-word()                  \n\
         Alt ~Ctrl<Key>f:               forward-word()                  \n\
        Meta ~Ctrl<Key>w:               copy-clipboard()                \n\
         Alt ~Ctrl<Key>w:               copy-clipboard()                \n\
	....................................................................
La tabla comienza con una directiva (#override) que indica cómo se mezclará esta tabla con la que actualmente está activa. En este caso indica que cuando una traducción entre en conflicto con una ya existente, prevalecerá la nueva. A continuación existe una línea por cada traducción. Cada línea asocia una secuencia de eventos (por ejemplo, <Key>w indica el evento de la tecla correspondiente a la w pulsada) asociados a una acción (por ejemplo cut-clipboard()). Cada línea puede comenzar con una lista de modificadores que tendrán que estar activados (o desactivados si tienen el carácter ~ delante) para que se cumpla la regla (por ejemplo, ~Meta ~Alt Ctrl indica que deben estar desactivados los modificadores meta y alt pero activado control).

Aceleradores

Un acelerador es una traducción que permite asociar eventos que se producen en una widget con acciones de otra widget. Es un mecanismo de carácter general pero su nombre proviene de su uso más común: establecer que una secuencia de teclado dispare una acción que normalmente se activa mediante el ratón.

Cada widget tiene un recurso XtNaccelerators heredado de la clase Core que contiene la tabla de definición de aceleradores. El formato y la forma de especificar esta tabla es idéntico a la de traducciones. Una vez definido este recurso en una widget, se dede llamar a XtInstallAccelerators para especificar otra widget en la que la ocurrencia de los eventos establecidos activará las correspondientes acciones de la primera widget.

Por ejemplo, supongamos una widget contenedora que incluye un botón (clase PushButton en Motif) y queremos que éste se active, además de con el ratón, cuando se pulse la tecla de retorno de carro tanto si el puntero está en la ventana del botón como si está en la ventana contenedora. En este caso, se debería definir un acelerador asociado a la widget del botón e "instalarlo" en la widget contenedora.

Manejo de otras entradas

Debido a la estructura de un programa que usa Xt, que después de llamar a XtAppMainLoop se convierte en pasivo, es difícil manejar otros eventos no relacionados con X. Para facilitar esta labor, Xt permite que algunos eventos externos se puedan tratar de una forma similar a los propios de X, esto es, dejando que el programa pueda asociar funciones con la ocurrencia de dichos eventos.

Recursos

La principal diferencia entre la gestión de los recursos en Xt respecto a Xlib es que en este nivel superior no se trata de una convención del programador sino que es una labor de Xt. Los principales aspectos de la gestión de recursos en Xt son los siguientes:

  1. XtInitialize carga y mezcla todas las definiciones de recursos del usuario en la base de datos de recursos.
  2. Cada widget tiene un nombre, especificado en su creación, y una clase. De esta forma, el nombre y la clase de un recurso quedan fijados por los nombres y clases de la jerarquía de widgets correspondiente. Cuando se crea una widget, Xt consulta "automáticamente" la base de datos de recursos para buscar los valores de los recursos de la widget que no hayan sido especificados por la aplicación.
  3. Para acceder a los recursos de la aplicación que no corresponden con recursos de las widgets se usa XtGetApplicationResources.

    Volver al índice ; Próximo: Motif ; Previo: Usuario de X