Personal tools
You are here: Home Development Documents gvSIG desktop 1.0 / 1.1 gvSIG Andami
Document Actions

Andami

by Victor Acevedo last modified 2012-09-08 00:35

Documentación del framework Andami.



Introducción

Descripción general

Un framework es una estructura de soporte, que permite desarrollar una aplicación sobre él. En general, define la arquitectura básica de la aplicación y provee de servicios que agilizarán y simplificarán el desarrollo del proyecto.

Andami es el framework sobre el que se construye gvSIG. Está diseñado para ser extensible mediante plugins (módulos que añaden nueva funcionalidad a la aplicación). Además de proveer el mecanismo de plugins, Andami da soporte a gvSIG en la composición básica de la interfaz de usuario y en la gestión de ventanas y eventos. Proporciona una simplificación importante en la gestión de estos aspectos, de forma que resulta muy sencillo crear un plugin funcional de gvSIG, incluso para un recién llegado.

Dentro del código fuente de gvSIG, el proyecto Andami se localiza en el directorio "_fwAndami". Una vez compilado, se genera un fichero andami.jar que se suele situar en la raíz de la estructura de directorios de gvSIG.

Relación con el resto de gvSIG

Andami contiene la parte de gvSIG que inicia la ejecución, realiza la carga de plugins e inicializa todos los subsistemas de la aplicación. Principalmente, hay dos tipos de entidades que se relacionan con Andami, de forma directa o indirecta: plugins y resto de librerías. Un plugin es una librería (más algunos ficheros de configuración y datos) con una estructura especial que Andami reconoce y carga al arrancar, y que se conecta con la interfaz de usuario en unos puntos definidos por el plugin. El resto de librerías son aquellas que también usamos en gvSIG pero que no adoptan forma de plugins.

Diagrama de bloques de la relación de Andami con el resto de gvSIG.

Diagrama de bloques de la relación de Andami con el resto de gvSIG.

La mayor parte de la funcionalidad de gvSIG la proporcionan los plugins, que a su vez se apoyan en librerías. El propio gvSIG es un plugin de Andami, aportándole el concepto de documento (vista, tabla, mapa), la capacidad de cargar y salvar proyectos, el gestor de proyectos, y muchas otras funcionalidades. En las explicaciones, conviene por tanto distinguir cuándo nos referimos al plugin gvSIG (llamado com.iver.cit.gvsig), y cuándo a la aplicación gvSIG (que incluye Andami + plugin gvSIG + resto de plugins + librerías).

Las librerías que están en el directorio "bin/lib" de una instalación de gvSIG, se incluyen en el CLASSPATH inicial y por tanto están disponibles para toda la aplicación. Las librerías que están en el directorio de librerías de un plugin sólo están disponibles para el plugin que las contiene, y para los plugins que declaran una dependencia sobre aquél. Normalmente, todos los plugins definen una dependencia sobre el plugin gvSIG (com.iver.cit.gvsig), y por tanto las librerías de gvSIG están disponibles para todos ellos.

Servicios que proporciona

Andami proporciona una variedad de servicios a los plugins. Posteriormente, los explicaremos en profundidad, de momento vamos a enumerarlos brevemente para tener una idea de la funcionalidad aportada:

  • Extensibilidad mediante plugins. Carga de plugins, carga dinámica de clases y recursos de los plugins, etc.
  • Creación de la interfaz inicial de usuario desde ficheros XML (menús, barras de herramientas y barra de estado).
  • Gestión de ventanas: creación, cerrado, cambio de propiedades, etc.
  • Persistencia de datos: permite a los plugins almacenar datos en disco y recuperarlos posteriormente de forma muy sencilla.
  • Traducciones: proporciona servicios de traducción de la interfaz de usuario.
  • Ejecución de tareas en segundo plano: facilita la creación y gestión de tareas en segundo plano.
  • Servicio de registro: aporta un registro (log) de sistema en el que anotar errores o eventos significativos (útil para detección y depuración de errores).
  • Acceso al portapapeles.
  • Creación de ficheros temporales.
  • Acceso a recursos de los plugins

Vista general

Vamos a mostrar una perspectiva general de los bloques funcionales de Andami, explicando la funcionalidad aportada por cada uno.

Diagrama de bloques funcionales de Andami.

Diagrama de bloques funcionales de Andami

Gestión de ventanas

Entre las funciones aportadas por Andami se encuentra la gestión de ventanas. Esta gestión es bastante particular, porque el programador sólo necesita crear un panel con el contenido de la ventana, y Andami creará la ventana real. También se encarga de registrar la ventana en el menú de ventanas, y de enviar a primer plano la ventana correspondiente cuando seleccionamos una entrada de ese menú. Asimismo, debemos dirigirnos a Andami cuando queramos cambiar las propiedades de una ventana (redimensionar, maximizar, etc), para obtener la ventana activa, la lista de ventanas, etc.

Podemos encontrar más información en el capítulo Las ventanas de Andami .

Gestión de la interfaz gráfica de usuario

Otra funcionalidad aportada por Andami es la creación de la interfaz de usuario y la gestión de los eventos producidas en la misma. En concreto, al arrancar Andami creará la ventana principal y los menús, las barras de herramientas y la barra de estado (con sus controles correspondientes). Además, ofrecerá los mecanismos adecuados para modificar estos elementos durante la ejecución del programa.

La gestión de la interfaz está muy relacionada con el siguiente bloque funcional, la gestión de plugins y extensiones, porque la creación inicial de la interfaz se realiza en respuesta a lo especificado en los ficheros de configuración de los plugins. En sentido inverso, las acciones que el usuario produzca sobre la interfaz (pulsar un menú o un botón, por ejemplo) también tienen efecto en la gestión de extensiones ya que Andami obtendrá la extensión asociada al botón o menú y le notificará la acción realizada. A continuación la extensión realizará el procesamiento adecuado para dicha acción.

La gestión de ventanas también está relacionada con la gestión de la interfaz porque las herramientas seleccionables están asociadas a la ventana activa, y por tanto cada vez que cambiamos de ventana, cambia también la herramienta seleccionada.

Debido a la íntima relación de la gestión de interfaz gráfica y la gestión de plugins, ambos aspectos se explican en el capítulo Plugins y extensiones.

Gestión de plugins y extensiones

Andami está diseñado para ser extensible mediante plugins y extensiones. Simplificando, las extensiones son clases Java que añaden funcionalidad a nuestra aplicación, y los plugins son agrupaciones de extensiones que permiten a Andami reconocer y cargar las extensiones.

Al arrancar, Andami inspecciona los plugins instalados, realiza la carga e inicialización de las extensiones que contienen y carga los elementos de la interfaz de usuario especificados en los ficheros de configuración del plugin. Cada uno de estos elementos (botones, menús, controles) está asociado a una extensión concreta y la visibilidad de estos elementos vendrá determinada por dicha extensión. Además, cuando el usuario pulse sobre un elemento, se enviará una notificación a la extensión asociada.

En el capítulo Plugins y extensiones se puede encontrar una explicación en profundidad de estos temas.

Servicios a los plugins

El último bloque funcional engloba una diversos servicios que Andami es capaz de prestar a los Plugins. Algunos de estos servicios están ligados a cada plugin y otros funcionan de forma idéntica para todos ellos, como se explica a continuación:

  • Servicio de traducciones: Los plugins poseen unos ficheros de traducciones que son leídos durante el inicio de la aplicación. Estos ficheros contienen claves de traducción, y la traducción asociada a cada clave, para cada idioma. De esta forma, los plugins pueden solicitar la traducción de una clave y Andami devolverá la traducción para el idioma que el usuario haya elegido (a través de la configuración del idioma). Usando este mecanismo se puede presentar al usuario una interfaz totalmente traducida a su idioma preferido.
  • Servicio de registro (log): El servicio de registro permite mantener un histórico de sucesos de una sesión completa de gvSIG. Los sucesos pueden tener distinta categoría: Información, Avisos, Errores o Información para Depuración. Al registrar algún mensaje en el log, automáticamente se registra también el plugin que genera el mensaje.
  • Persistencia de datos: Existe un fichero (plugin-persistence.xml) en el que los plugins pueden almacenar información (ejemplos: un plugin de WMS podría almacenar la lista de servidores recientes; un plugin de nomenclátor podría almacenar las últimas búsquedas realizadas). De esta forma, al volver a arrancar gvSIG, los plugins pueden recuperar la información guardada y presentar (por ejemplo) la lista de servidores usados en la última sesión. Cada plugin sólo tiene acceso (de forma directa) a la información que él mismo produce. Debemos aclarar que este fichero es adecuado para guardar pequeñas cantidades de información de texto, pero no es adecuado para guardar otros tipos de información como miniaturas de la cartografía, un listado de nombres de municipios, etc.
  • Ficheros temporales: Andami ofrece facilidades para crear ficheros temporales, que son eliminados automáticamente al salir de gvSIG (siempre que gvSIG finalice correctamente). Por una parte, si suministramos un nombre y unos datos, Andami creará el fichero temporal con el contenido proporcionado. Por otra parte, podemos solicitar la ruta del directorio de ficheros temporales y crear en él los ficheros que necesitemos. En ambos casos, los ficheros se eliminan automáticamente al salir de gvSIG.
  • Portapapeles: Andami permite obtener texto del portapapeles, o depositar texto en él. Para otros tipos de datos, debe usarse directamente los métodos que Java ofrece para acceder al portapapeles.
  • Tareas en segundo plano: Andami facilita la creación de tareas en segundo plano, permitiendo además que estas tareas sean cancelables (interrumpibles). Si deseamos realizar un procesamiento prolongado, conviene lanzarlo en un hilo separado (proceso en segundo plano), para no bloquear la interfaz gráfica. Un típico ejemplo de tarea a lanzar en segundo plano es la exportación de una capa a un fichero. Se presentará un diálogo al usuario con el progreso de la tarea, y un botón que permita la cancelación del proceso.
  • Accesso a recursos del plugin: Andami posibilita acceder a recursos de los plugins (imágenes, ficheros XML, etc) usando rutas relativas al directorio raíz del plugin, para favorecer la portabilidad del código.

Se puede encontrar más detalles en el capítulo Servicios a los plugins.


Plugins y extensiones

Plugins

Plugins y extensiones

En un gvSIG instalado, existe un subdirectorio llamado gvSIG/extensiones. Dicho subdirectorio contiene a su vez un conjunto de directorios, cada uno de los cuales constituye un plugin. Los plugins no son más que un conjunto de clases Java que añaden nuevas funcionalidades a la aplicación. Si inspeccionamos el mencionado directorio observaremos que existe un plugin llamado com.iver.core, que contiene libCorePlugin (el skin de Andami) y un plugin llamado com.iver.cit.gvsig, que contiene el plugin gvSIG (el plugin principal de la aplicación). Dependiendo de la instalación de gvSIG que hayamos realizado, podremos localizar otros plugins como com.iver.cit.gvsig.cad (funcionalidades de edición de cartografía), org.gvsig.scripting (que añade las capacidades de scripting), com.iver.cit.gvsig.wms (cliente de WMS), etc

Las nuevas funcionalidades añadidas por los plugins, deben conectarse de alguna forma con el resto de la aplicación. Este punto de conexión lo aportan las extensiones, que son clases Java que implementan la interfaz IExtension, y que hacen de puente entre la funcionalidad ya existente y la nueva funcionalidad aportada por el plugin.

Estructura de un plugin

El contenido mínimo que un plugin debe incluir se reduce a un fichero XML llamado config.xml. Este fichero contiene el nombre del plugin y un abanico de detalles relevantes sobre el mismo: el directorio que contendrá las librerías aportadas por el plugin, las dependencias que tiene con otros plugins, el nombre base de los ficheros de traducciones, las extensiones que aporta, y una descripción de los componentes que deben añadirse a la interfaz de usuario (menús, barras de herramientas, etc).

Dependiendo de los elementos definidos en el config.xml, el plugin puede incluir también: uno o varios ficheros JAR en el directorio de librerías, varios ficheros de traducciones, imágenes o cualquier otro tipo de datos (cartografía, datos de conexión a algún servicio, paletas de colores, etc)

Podemos ver la estructura del plugin WFS para hacernos una idea de una distribución típica de ficheros:

gvSIG/
extensiones/
    com.iver.cit.gvsig.wfs2/
    build.number

    config.xml

    text_en.properties

    text_fr.properties

    \.\.

    text.properites

    lib/
        com.iver.cit.gvsig.wfs2.jar
    images/
        backward.png

        down-arrow.png

        fastbackward.png

        fastforward.png

Normalmente, las librerías se depositan en el directorio raíz del plugin, o en un subdirectorio lib/. Las imágenes suelen situarse en un subdirectorio images. Sin embargo, la estructura puede alterarse a nuestro antojo, siempre que en el código usemos la ruta relativa adecuada (en el caso de las imágenes) o definamos correctamente el directorio en el config.xml (en el caso de las librerías).

El fichero config.xml

La filosofía en la que se apoya la extensibilidad de Andami es la siguiente: existen unas clases especiales, llamadas extensiones, que implementan la interfaz IExtension, la cual incluye un método execute() (entre otros). En el fichero config.xml del plugin, se definen una serie de elementos de interfaz de usuario que se van a añadir a la aplicación (herramientas, menús y controles de la barra de estado), y se asocia cada uno de estos elementos con una extensión. En concreto, cuando el usuario pinche en una herramienta o una entrada de menú, Andami ejecutará el método execute de la extensión asociada. De esta forma, la extensión realizará las acciones necesarias para ejecutar la operación solicitada, posiblemente apoyándose en otras clases o librerías del plugin.

Vemos por tanto que la extensión es el punto de acceso a las nuevas funcionalidades aportadas por el plugin, y que los elementos de interfaz de usuario (definidos en el config.xml) junto con la extensión asociada constituyen el nexo entre el resto de la aplicación y el plugin.

Ejemplo de fichero config.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<plugin-config>
  <depends plugin-name="com.iver.cit.gvsig" />
  <libraries library-dir="./lib"/>
  <resourceBundle name="text"/>
  <extensions>
    <extension class-name="com.iver.cit.gvsig.wfs.WFSClientExtension"
      description="Support to access WFS"
      active="true"
      priority="1">
    </extension>
    <extension
      class-name="com.iver.gvsig.centerviewpoint.CenterViewToPointExtension"
      description="Haces zoom a partir de un par de coordenadas"
      active="true">
        <menu text="Vista/Centrar_la_Vista_sobre_un_punto"
          tooltip="Centrar_la_Vista_sobre_un_punto" 
          action-command="CENTERVIEWTOPOINT" 
          icon="images/centerviewtopoint.png" />
        <tool-bar name="com.iver.cit.gvsig.Herramientas">
          <action-tool icon="images/centerviewtopoint.png"
            action-command="CENTERVIEWTOPOINT"
            tooltip="Centrar_la_Vista_sobre_un_punto"
            enable-text="debería de estar activada" last="true"/>
        </tool-bar>
    </extension>
  </extensions>
</plugin-config>

El fichero utiliza una sintaxis XML, que básicamente implica que tiene una estructura marcada por etiquetas que deben abrirse y posteriormente cerrarse en orden inverso al orden de apertura. Se puede encontrar más información sobre XML en el sitio web del W3 Consortium.

Etiquetas permitidas

A continuación, se describe en detalle cada una de las etiquetas permitidas. No obstante, dentro de la distribución del código fuente de gvSIG, existe un fichero llamado plugin-config.xsd, situado dentro de _fwAndami/schemas, donde se describen formalmente todas las etiquetas permitidas y el tipo de valores que aceptan. Este fichero está escrito en lenguaje XSD Schema, un lenguaje de descripción de sintaxis XML. Ese fichero debe constituir la referencia principal a la hora de conocer las etiquetas permitidas.

Jerarquía de etiquetas válidas de plugin-config.xml

Jerarquía de etiquetas válidas de plugin-config.xml

Cabecera

El fichero XML debe ir encabezado por una cabecera como la siguiente:

<?xml version="1.0" encoding="UTF-8"?>

Es importante que el primer carácter < sea el primero del fichero (es decir, que no haya ningún carácter en blanco antes de la cabecera), de lo contrario el fichero se leerá incorrectamente.

  • version: Indica la versión de estándar XML que vamos a usar (en nuestro caso, "1.0")
  • encoding: Indica la codificación de caracteres con la que vamos a salvar el fichero en disco. La codificación que especifiquemos aquí debe coincidir con la codificación que realmente usemos en el momento de salvar el fichero (los editores de texto normalmente permiten especificar la codificación en algún apartado). Algunas codificaciones típicas son "UTF-8", "ISO-8859-15" e "ISO-8859-1"

<plugin-config>

Etiqueta que simplemente marca el inicio y el fin del fichero de configuración; es la raíz del árbol XML.

<resourceBundle>

Indica el nombre base que tendrán los ficheros de traducción. Por ejemplo, si escribimos <resourceBundle name="text"/>, los ficheros de traducciones se llamarán text.properties, text_en.properties, etc.

<depends>

Indica los plugins de los que va a depender nuestro plugins, a nivel de librerías. Esto significa que si deseamos usar algunas clases o librerías que estén en un plugin distinto, debemos declarar una dependencia de ese plugin. Debe haber una entrada <depends> por cada plugin del que dependamos.

Las dependencias determinan también el orden de carga de plugins, de forma que un plugin siempre se carga después de que sus dependencias han sido ya cargadas. Normalmente, todos los plugins dependen del plugin gvSIG (com.iver.cit.gvsig), por lo que este plugin se carga el primero.

<libraries>

Establece el directorio del que se leerán las librerías de nuestro plugin. Normalmente suele usarse <libraries library-dir="."> (para indicar el directorio raíz del plugin), o bien <libraries library-dir="lib"> (para indicar el subdirectorio lib). Dos plugins distintos pueden tener versiones distintas de una misma librería, y ambas funcionarán correctamente sin interferencias, salvo en los siguientes supuestos:

  • Si un plugin A declara una dependencia de plugin B, y ambos poseen la misma librería pero en diferente versión: el plugin B usará su propia versión sin ningún problema; el plugin A usará su propia versión si el plugin B se carga después de A, en caso contrario se usará la versión de B.
  • Si existe un plugin A que posee una librería, y esta librería está también en el CLASSPATH inicial de Andami (declarado al lanzar Andami), el plugin A usará la versión presente en el CLASSPATH inicial.

<label-set>

Indica el comienzo de un grupo de etiquetas (label) para la barra de estado. Cada grupo de etiquetas (label-set) está asociado a una clase, y sólo es visible cuando la ventana visible es instancia de dicha clase. Acepta los siguientes atributos:

  • class-name Nombre de la clase asociada a este label-set. Las etiquetas del label-set sólo serán visibles cuando la ventana activa sea instancia de esta clase.

<label>

Instala una etiqueta (una caja de texto no editable) en la barra de estada. La etiqueta siempre pertenece a un grupo de etiquetas (label-set) y sólo será visible cuando lo sea el label-set. Acepta los siguientes atributos:

  • id Identificador (nombre) de la etiqueta. Se usará para acceder a la etiqueta y escribir sobre ella.
  • size Anchura de la etiqueta (en píxeles).

<extensions>

Marca el inicio de la lista de extensiones.

<extension>

Declara una extensión de Andami. Principalmente, la declaración incluye el nombre de la clase Java que implementa esta extensión, y una lista de elementos de interfaz de usuario que deberán añadirse a la aplicación y estarán asociados con esta extensión. <extension> acepta los siguientes atributos:

  • class-name Nombre de la clase que implementa esta extensión. La clase debe estar localizada en alguna de las librerías incluidas en el plugin.
  • description Descripción de la funcionalidad aportada por la extensión.
  • active Establece si la extensión va a estar activa o no. Si no está activa, es como si no existiese.
  • priority La prioridad de la extensión determina el orden de carga respecto a otras extensiones del mismo plugin. Una extensión con una prioridad menor se cargará antes que otra extensión con una prioridad mayor. Hay que tener en cuenta que las dependencias entre plugin son las que determinan el orden de carga de plugins, por tanto si lo que queremos es cambiar el orden de carga de dos extensiones que están en plugins distintos, debemos modificar las dependencias entre ellos, no la prioridad. Por convención, se usan valores entre 1 y 99999 (aunque realmente se admiten rangos mayores). Para una extensión sin necesidades especiales, podemos omitir el valor prioridad, o darle un valor de 2000.

<tool-bar> Crea una barra de herramientas (grupo de herramientas) con el nombre dado. Dentro esta etiqueta se anidarán los botones que deseamos añadir a esta barra de herramientas. Diferentes extensiones pueden añadir botones a la misma barra de herramientas, basta con que se llamen de la misma forma. <tool-bar> acepta los siguientes atributos:

  • name Nombre de la barra de herramientas. Sirve para agrupar botones de extensiones diferentes(si el nombre de la tool-bar es el mismo, se añadirán a la misma tool-bar. También se mostrará en la lista de barras de herramientas, traducido al idioma correspondiente, por lo que name debe considerarse como una clave de traducción.
  • position Sirve para establecer la posición de la barra de herramientas respecto a otras. Una tool-bar con un valor de position más bajo que la de otra tool-bar indica que la primera tool-bar se situará más a la izquierda que la segunda. Si diferentes extensiones definen diferentes positions para la misma barra de herramientas, sólo se tendrá en cuenta la position definida en la extensión de mayor prioridad (la que tenga un valor de priority más bajo).
  • is-visible Determina si la tool-bar estará inicialmente visible u oculta. Esto puede cambiarse posteriormente por código, o bien por el usuario desde los menús.

<action-tool>

Una action-tool es una herramienta (un botón) que lanza una acción concreta. El mecanismo exacto es el siguiente: el usuario pulsa el botón, Andami recibe el evento de pulsado, consulta la extensión asociada a este botón y lanza el método execute(String actionCommand) de dicha extensión, pasándole como parámetro el comando (action-command) definido para este botón. action-tool acepta los siguientes atributos:

  • action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
  • name Nombre del botón. Útil si posteriormente deseamos obtener el botón para cambiarle el icono, por ejemplo.
  • position Sirve para establecer la posición del botón dentro de la barra de herramientas. Un botón con un valor position más bajo que la de otro botón se situará más a la izquierda que el segundo botón. Si diferentes botones poseen la misma posición o no poseen posición, se ordenarán arbitrariamente. Un botón que posea posición siempre irá a la izquierda de un botón sin posición. No obstante, no se recomienda omitir este atributo.
  • icon Icono que se mostrará dentro del botón. La ruta de la imagen debe ser relativa al directorio raíz del plugin.
  • text Texto (opcional) a mostrar junto al icono.
  • tooltip Texto que se mostrará en el globo de texto (tooltip) que aparece al mantener quieto el ratón sobre el botón durante unos segundos.
  • enable-text Texto que informa al usuario de las condiciones que deben producirse para que este botón esté activo. Cuando el botón está visible pero desactivado, el tooltip muestra este texto en vez del normal, para que el usuario sepa qué debe hacer para usar esta herramienta.
  • last Indica que deseamos dejar un espacio extra a la derecha de este icono. Normalmente se usa en el último icono de una barra de herramientas, para marcar más la separación con la barra de herramientas contigua.

<selectable-tool>

Una selectable-tool es un botón que puede estar pulsado (herramienta seleccionada) o no pulsado (herramienta no seleccionada), y no ejecuta una acción como la action-tool, sino que indica un cambio de estado (por ejemplo, "Zoom Más seleccionado", "Zoom Menos seleccionado", etc). Cuando una selectable-tool está pulsada, las demás están no-pulsadas. Si pulsamos en otra selectable-tool, la anterior deja de estar pulsada. La selectable-tool seleccionada en cada momento va asociada a la ventana activa: si cambiamos de ventana activa, se carga la herramienta seleccionada asociada a la nueva ventana activa. Si volvemos a la ventana anterior, se vuelve a seleccionar la herramienta asociada a la ventana anterior.

Detalle de dos herramientas seleccionables (la izquierda está seleccionada)

Detalle de dos herramientas seleccionables (la izquierda está seleccionada)

La etiqueta selectable-tool admite los mismos atributos que action-tool, casi todos con la misma semántica, salvo action-command que en este caso indica la herramienta seleccionada. Adicionalmente, selectable-tool permite:

  • is-default Determina si esta herramienta estará seleccionada inicialmente.

<menu> Crea una entrada de menú. El menú funcionará a todos los efectos como una action-tool, es decir, al pinchar en el menú se lanzará el método execute() de la extensión asociada, pasándole como parámetro el action-command especificado por el menú. Admite los siguientes atributos:

  • action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
  • text Determina la localización y el texto a mostrar por la entrada del menú. text debe tener la forma "Archivo/Abrir". Cada porción separada por barras (/) indica un contenedor de menú, como Archivo, excepto la última porción que indica el texto a mostrar en la entrada misma. Por ejemplo, "Archivo/Abrir" creará una entrada llamada Abrir dentro del menú Archivo. Otro ejemplo: "Archivo/Plantillas/Abrir Plantillas" creará una entrada Abrir Plantillas dentro del submenú Plantillas, dentro del menú Archivo. Si los menús contenedores no existiesen, se crearían automáticamente. Hay que tener en cuenta que el texto real mostrado en los menús y submenús se traduce al idioma seleccionado por el usuario, por lo que cada porción de texto empleado aquí constituye una clave de traducción.
  • position Sirve para establecer la posición de la entrada de menú dentro del submenú en el que esté situada. Una entrada con un valor position más bajo que la de otra entrada se situará más arriba (más cerca del inicio del menú) que la segunda entrada. Si diferentes entradas poseen la misma posición, o no poseen posición, se ordenarán arbitrariamente. Una entrada que posea posición siempre estará más arriba que una entrada sin posición. No obstante, no se recomienda omitir este atributo.
  • icon Icono que se mostrará dentro del botón. La ruta de la imagen debe ser relativa al directorio raíz del plugin.
  • tooltip Texto que se mostrará en el globo de texto (tooltip) que aparece al mantener quieto el ratón sobre el botón durante unos segundos.
  • enable-text Texto que informa al usuario de las condiciones que deben producirse para que este botón esté activo. Cuando el botón está visible pero desactivado, el tooltip muestra este texto en vez del normal, para que el usuario sepa qué debe hacer para usar esta herramienta.
  • key La key es el carácter que forma la combinación de teclas necesaria para lanzar este menú. La combinación se forma usando el modificador de ese sistema operativo + key. Por ejemplo, en Windows el modificador es la tecla ALT, y la key de la entrada de menú Añadir capa es O, así que pulsando ALT+O nos aparece el diálogo de añadir capa.
  • mnemonic El mnemonic es la tecla que activará esta entrada de menú cuando el menú esté desplegado y tenga el foco. Por ejemplo, cuando el menú Archivo está seleccionado, y pulsamos la tecla A, aparece el diálogo de abrir proyecto porque A es el mnemotécnico de la entrada Abrir proyecto.
  • is_separator Atributo que indica que deseamos añadir un separador en esta posición de la barra de menús. Es un atributo especial que debe añadirse en una entrada de menú aparte, ya que no añade un separador debajo de la entrada actual, sino que transforma la entrada actual en un separador (y por tanto no se mostrará el texto ni el icono, ni se podrá pulsar sobre ella).

<combo-scale> Añade a la barra de estado un control de tipo combo box (control que permite elegir un valor de una lista de valores, o bien escribir el valor deseado), con una etiqueta de texto a la izquierda describiendo su función. Acepta los siguientes atributos:

  • name Nombre del control. Debería ser diferente al nombre de cualquier otro control.
  • label El texto que será mostrado a la izquierda del combo box.
  • elements Una lista de valores numéricos separados por punto y coma. Cada valor constituirá una entrada del combo box.
  • value Valor que estará inicialmente seleccionado. Si se omite, el primer elemento estará seleccionado.
  • action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
Detalle de un combo-scale

Detalle de un combo-scale


Extensiones

La interfaz IExtension

Como ya hemos explicado, las extensiones son clases que sirven de puente entre la funcionalidad básica de gvSIG y las funcionalidades aportadas por nuestro plugin. La extensión recibirá notificación de las acciones realizadas por el usuario, y ejecutará el código apropiado en respuesta a esas acciones (posiblemente usando otras clases de nuestro plugin).

El siguiente diagrama muestra de forma esquemática todas las clases que vamos a explicar en este capítulo y sus relaciones:

Diagrama de las clases implicadas en la creación de extensiones

Diagrama de las clases implicadas en la creación de extensiones

Todas las extensiones implementan la interfaz IExtension, que incluye los siguientes métodos:

  • public void initialize() Una vez se leen y ordenan todas las extensiones presentes en los plugins, se llama a este método para cada una de ellas, de forma secuencial siguiendo el orden definido en la prioridad de las extensiones.
  • public void postInitialize() Una vez se han inicializado todas las extensiones, se llama a este método para cada una de ellas, siguiendo el mismo orden de antes.
  • public void terminate() Al salir de la aplicación, se llama a este método para cada una de las extensiones, siguiendo un orden secuencial inverso al que se siguió en la inicialización (es decir, la última extensión que se inicializó es la primera que se finalizará, y la primera extensión que se inicializó será la última en ser finalizada).
  • public void execute(String actionCommand) En el config.xml se definen elementos de la interfaz de usuario (botones, menús, etc) que están asociados a una extensión. Cuando el usuario pulsa en uno de estos elementos de la interfaz, se busca la extensión asociada y se llama a este método. Los botones, menús, etc pueden tener asociado una cadena que actúa como identificador de comando (action command). Cuando se llama al método execute, se pasa como parámetro el actionCommand asociado al botón que se pulsó. De esta forma, si tenemos varios botones, menús, etc asociados con esta extensión, podemos discriminarlos comprobando el actionCommand que recibimos. Normalmente, será en este método donde escribiremos las principales acciones de nuestra extensión (abrir un asistente, comenzar algún procesamiento, etc).

Los dos siguientes métodos determinan si la extensión (y sus elementos de interfaz de usuario asociados) serán visibles y estarán activos. Estas propiedades se comprueban para todas las extensiones cada vez que se cambia de ventana activa y también justo después de procesar un evento de interfaz de usuario (es decir, después de llamar al método execute de una extensión).

  • public boolean isEnabled() Los elementos de interfaz de usuario (botones, menús, controles de la barra de estado) asociados a esta extensión estarán activados (o desactivados) dependiendo del valor devuelto por este método. Cuando un botón está desactivado, aparece en gris claro y no es posible pulsar sobre él.
Detalle de un menú activo y otro desactivado.

Detalle de un menú activo y otro desactivado.

  • public boolean isVisible() Los elementos de interfaz de usuario (botones, menús, controles de la barra de estado) asociados a esta extensión estarán visibles (o invisibles) dependiendo del valor devuelto por este método. Cuando un botón está invisible, no se mostrará independientemente del valor de isEnabled().

Los dos métodos siguientes sólo se usan con el mecanismo ExclusiveUIExtension, que permite a una extensión tomar el control de la interfaz y activar o desactivar (mostrar u ocultar) otras extensiones. Esto puede usarse para crear extensiones que personalicen gvSIG (por ejemplo, una extensión que transforme gvSIG en un simple visor de cartografía):

  • public boolean isEnabled(IExtension extension) Decide si la extensión pasada como parámetro estará activada o no, ignorando el valor del método isEnabled de la propia extensión.
  • public boolean isVisible(IExtension extension) Decide si la extensión pasada como parámetro estará visible o no, ignorando el valor del método isVisible de la propia extensión.
La clase abstracta Extension

Salvo que tengamos una necesidad muy especial, normalmente no implementaremos directamente la interfaz IExtension, sino que extenderemos la clase abstracta Extension, que a su vez ya implementa IExtension. Esto nos ofrece una cierta tranquilidad de futuro en el funcionamiento de nuestra extensión, ya que si se añadiese algún método a la interfaz IExtension, este método se implementaría en la clase Extension de forma que nuestra extensión seguiría funcionando sin cambios (a no ser que necesitase un comportamiento para el nuevo método distinto del implementado en Extension).

HiddableExtension y ExtensionDecorator

La interfaz HiddableExtension y la clase ExtensionDecorator permiten cambiar la visibilidad de una extensión (y sus controles asociados) en tiempo de ejecución, ignorando el valor que indique la propia extensión en el método isVisible. De esta forma podemos instalar una extensión que oculte un conjunto de otras extensiones, creando un gvSIG personalizado (que sólo mostraría las extensiones deseadas).

La interfaz HiddableExtension sólo contiene dos métodos:

  • public void setVisibility(int state) Permite definir una visibilidad para una extensión, que prevalece sobre la visibilidad definida en el método isVisible() de la propia extensión. Los valores de visibilidad aceptados incluyen: ExtensionDecorator.INACTIVE (el Decorator está desactivado y por tanto se obedecerá a la establecido en el método isVisible() de la propia extensión), ExtensionDecorator.ALWAYS_VISIBLE (la extensión estará siempre visible) y ExtensionDecorator.ALWAYS_INVISIBLE (la extensión estará siempre invisible).
  • public int getVisibility() Consulta la visibilidad que se ha adjudicado a la extensión. Los valores devueltos pueden ser ExtensionDecorator.INACTIVE, ExtensionDecorator.ALWAYS_VISIBLE o ExtensionDecorator.ALWAYS_INVISIBLE.

La clase ExtensionDecorator implementa la interfaz HiddableExtension. Cada extensión cargada por Andami posee un ExtensionDecorator asociado, que podemos obtener para cambiar la visibilidad de esa extensión. Mostramos como ejemplo el código necesario para ocultar la extensión "com.iver.cit.gvsig.StartEditing", que es la que permite poner una capa en edición. De esta forma, estaríamos desactivando las capacidades de edición de gvSIG:

ExtensionDecorator decorator = PluginServices.getDecoratedExtension(com.iver.cit.gvsig.StartEditing.class);
decorator.setVisibility(ExtensionDecorator.ALWAYS_INVISIBLE);

Por supuesto, para llevar a cabo con éxito una personalización de gvSIG, necesitamos conocer las extensiones que deseamos ocultar. Además, para poder acceder correctamente a la clase de la extensión a desactivar (en este ejemplo, com.iver.cit.gvsig.StartEditing.class) necesitamos declarar una dependencia del plugin que contenga la extensión, de lo contrario obtendremos una excepción de tipo ClassNotFoundException al ejecutar ese código. En este caso, com.iver.cit.gvsig.StartEditing.class está contenido en el plugin com.iver.cit.gvsig.cad, por tanto necesitamos añadir al config.xml de nuestro plugin una línea como la siguiente:

<depends plugin-name="com.iver.cit.gvsig.cad" />

Téngase en cuenta que cualquier extensión puede usar este mecanismo en cualquier momento para ocultar o mostrar otra extensión. Para evitar comportamientos impredecibles, ExtensionDecorator sólo debería usarse en extensiones especiales que deseen personalizar gvSIG, y no deberían instalarse dos extensiones de este tipo al mismo tiempo (de lo contrario, es posible que lo que una de ellas vaya ocultando, la otra lo vaya mostrando y será difícil prever el resultado final, salvo que ambas extensiones realicen estas ocultaciones de forma coordinada).

El mecanismo ExclusiveUIExtension

El mecanismo ExclusiveUIExtension permite realizar algo similar a lo que se consigue con ExtensionDecorator, pero en este caso existe una única extensión que decidirá el estado de las demás. Es decir, con ExtensionDecorator cualquier extensión puede modificar el estado de las demás. Con ExclusiveUIExtension, se definirá una única extensión que tendrá la capacidad de mostrar/ocultar y activar/desactivar el resto de extensiones.

En cualquier caso, ambos mecanismos pueden usarse al mismo tiempo y ExclusiveUIExtension respetará lo definido por ExtensionDecorator, de forma que si ExtensionDecorator define una visibilidad ALWAYS_VISIBLE (siempre visible), la extensión estará visible pese a que ExclusiveUIExtension defina la extensión como oculta. Es decir, ExclusiveUIExtension sólo actuará en caso de que el ExtensionDecorator esté inactivo (modo ExtensionDecorator.INACTIVE). En cualquier caso, ExtensionDecorator sólo actúa sobre la visibilidad de la extensión, mientras que el mecanismo ExclusiveUIExtension puede definir también si la extensión está activa (enabled) o no.

Para usar ExclusiveUIExtension necesitamos:

  • Tener una extensión que implemente correctamente los métodos isEnabled(IExtension extension) y isVisible(IExtension extension). Esta extensión actuará como ExclusiveUIExtension, decidiendo el estado (visible/invisible, activa/desactivada) del resto de extensiones.
  • Registrarse como ExclusiveUIExtension en Andami, dentro del método initialize de nuestra extensión, utilizando el método PluginServices.setExclusiveUIExtension().
  • Otra opción equivalente a registrarnos utilizando el método setExclusiveUIExtension consiste en sumistrar un parámetro de arranque a gvSIG. El parámetro es el siguiente: ExclusiveUIExtension=NombreDeExtension, donde NombreDeExtension debe sustituirse por el nombre de la extensión mencionada en el punto anterior.
    • En Linux, esto puede conseguirse bien editando el lanzador gvSIG.sh añadiéndole este parámetro antes del símbolo "$@", o bien pasándole el parámetro a dicho lanzador.
    • En Windows se puede modificar el fichero gvSIG.ini, en la línea que empieza por command, añadiendo el parámetro justo antes del símbolo #ARGS#. También es posible pasar el parámetro al comando gvSIG.exe en el momento de su ejecución.

Las ventanas de Andami

Introducción

Andami facilita la creación y gestión de ventanas, ofreciendo una interfaz de ventana que se abstrae de los diferentes tipos de ventanas de Java Swing. De esta forma, cuando queramos crear una ventana, en vez de pensar en ventanas de Swing, simplemente crearemos un panel que implemente la interfaz IWindow y le pediremos a Andami que cree una ventana con las propiedades que deseemos. En función de las propiedades pedidas y el skin instalado, se creará un tipo de ventana distinto (JInternalFrame, JDialog, etc), pero esto será indiferente para nosotros. Para no confundir la ventana real (creada por Andami) con el contenido (creado por nosotros y que implementa la interfaz IWindow), de ahora en adelante usaremos los términos frame , marco o ventana real para referirnos a la ventana creada por Andami, y ventana para referirnos al contenido (que implementa IWindow).

Cuando hablamos de ventanas de Andami, podemos hacer una primera distinción entre ellas: ventana principal, y resto de ventanas. La ventana principal (MainFrame) es la que contiene las barras de herramientas, la barra de estado y (en algunas plataformas) la barra de menús. Esta ventana no está bajo control del gestor de ventanas, sino que se accede a ella usando un método específico de la clase PluginServices. El resto de ventanas sí están bajo el control del gestor de ventanas (MDIManager), al cual debemos dirigirnos para crearlas, cerrarlas o cambiar sus propiedades.

Diagrama de las principales clases implicadas en la gestión de ventanas

Diagrama de las principales clases implicadas en la gestión de ventanas

  • En el capítulo Gestión de ventanas podemos aprender como crear ventanas y cómo manipularlas haciendo uso del gestor de ventanas (MDIManager).
  • En el capítulo La ventana principal podemos encontrar más información sobre las posibilidades ofrecidas por dicha ventana (MainFrame).

La ventana principal

MainFrame

La interfaz MainFrame representa la ventana principal de gvSIG, la ventana a la que se añaden los menús, la barras de herramientas, etc. La clase que la implementa (MDIFrame) hereda de JFrame, e incluye el JDesktopPane que se usa para contener las ventanas internas (vistas, mapas, etc). Normalmente, Andami crea automáticamente la interfaz de usuario partiendo de los ficheros config.xml de los plugins. No obstante, MainFrame contiene algunos métodos para modificar por código algunas partes de la interfaz.

  • Los métodos addMenu(), changeMenuName() y removeMenu() permiten alterar los menús de gvSIG.
  • El método getJMenuBar() da acceso a la barra de menús, lo cual también permite alterar los menús.
  • El método getStatusBar() permite acceder a la barra de estado.
  • setTitle() cambia el título de la ventana.
  • setStatusBarLabels(Class clase, Label[] labels) y removeStatusBarLabels(Class clase) añaden o eliminan etiquetas de la barra de estado, respectivamente. Las etiquetas se asocian a una clase (objeto tipo Class), y serán visibles cuando la ventana activa sea un objeto de esa clase.
  • getComponentByName(String name) permite obtener un componente a partir de su nombre. De esta forma podemos obtener barras de herramientas, controles de la barra de herramientas o de la barra de estado, y menús.
  • addStatusBarControl() y removeStatusBarControl() nos permiten añadir controles personalizados a la barra de estado. Serán visibles y estarán activos cuando la extensión asociada sea visible y esté activa.
La barra de estado: NewStatusBar

La barra de estado es la banda que se muestra en la parte inferior de la ventana principal, y que contiene etiquetas y algunos controles. Es posible cambiar el texto de las etiquetas existentes, así como añadir otras etiquetas o controles.

  • public void setMessage(String id, String nuevoTexto)

Cambia el texto mostrado en la etiqueta id por nuevoTexto. id se debe corresponder con el atributo id de una etiqueta definida en el config.xml o si se trata de un control, con el nombre del control.

  • setInfoText(String text)

Muestra un mensaje de información en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().

  • setWarningText(String text)

Muestra un mensaje de aviso en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().

  • setErrorText(String text)

Muestra un mensaje de error en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().

Detalle de mensajes de información, aviso y error en la barra de estado

Detalle de mensajes de información, aviso y error en la barra de estado

  • setInfoTextTemporal(String text), setWarningTextTemporal(String text), setErrorTextTemporal(String text) y restaurarTexto()

Muestran un mensaje de información, aviso u error (respectivamente), pero sólo de forma temporal, ya que si a continuación usamos el método restaurarTexto() se vuelve a mostrar el mensaje anterior.

  • setProgress(int progress)

Si progress está entre 0 y 99 (inclusive), muestra una barra de progreso en la parte izquierda y avanza la posición a ese valor. Si progress es mayor o igual que 100, oculta la barra de progreso.

Detalle de la barra de progreso

Detalle de la barra de progreso

Skins de Andami (libCorePlugin)

Mientras la gestión de plugins se incorpora directamente en Andami, la gestión de ventanas se delega en los llamados skins de Andami. Andami ofrece unas interfaces y objetos para la gestión de ventanas: IWindow, MDIManager, WindowInfo, IWindowListener, SingletonWindow... dichas interfaces deben ser implementadas por el skin, quien realmente realizará la gestión de ventanas. De esta forma, se podría llevar a cabo una gestión de ventanas distinta, implementando un skin distinto. Actualmente sólo existe un Skin implementado, llamado CorePlugin. Formalmente, es un plugin más de Andami, pero con la particularidad de que Andami no arrancará si CorePlugin (u otro skin) no está instalado. Los skins deben incluir una extensión especial, que implemente IExtension y MDIManager, y que se define en el config.xml con la etiqueta skin-extension.

Clases principales de CorePlugin y clases relacionadas de Andami.

Clases principales de CorePlugin y clases relacionadas de Andami


Gestión de ventanas

La interfaz IWindow

Cualquier ventana que deseemos crear a través de Andami debe implementar la interfaz IWindow. Conviene aclarar que IWindow no será una ventana de Swing, sino el contenido de la ventana (normalmente una clase que herede de JPanel a la que añadiremos todos los componentes necesarios). La ventana propiamente dicha (frame), será creada por Andami y no tendremos acceso directo a ella.

La interfaz IWindow sólo posee un método, que especifica las propiedades iniciales que deberá tener la ventana creada (posición, tamaño, tipo, título, etc.):

public WindowInfo getWindowInfo()

Este método será usado por Andami en el momento de la creación de la ventana real para determinar las propiedades y el tipo de ventana a crear. El objeto devuelto por este método es de tipo WindowInfo, el cual describe las propiedades de una ventana de Andami (explicado en la sección siguiente). Debemos hacer hincapié en que este método sólo devuelve las propiedades iniciales y que por tanto no debemos usar este método para obtener o modificar las propiedades de la ventana, para esta tarea nos dirigiremos al gestor de ventanas.

La mayoría de las ventanas que creemos no implementarán directamente IWindow, sino una interfaz más específica, SingletonWindow, que asocia un modelo a cada ventana. Este modelo se utiliza para dotar de identidad a las ventanas, de forma que al intentar crear una ventana con un modelo que ya esté asociado a otra ventana, obtendremos una referencia a esta segunda ventana.

Propiedades de ventana: WindowInfo

El objeto WindowInfo almacena las propiedades de una ventana de Andami, de forma que existe un objeto WindowInfo asociado a cada ventana (IWindow) creada por Andami. Puede usarse tanto para consultar las propiedades actuales de la ventana como para modificarlas. Al modificar una propiedad de WindowInfo, este ventana se refleja inmediatamente en la ventana asociada.

Atendiendo el comportamiento que tendrá la ventana respecto a las otras, existen ventanas de tres tipos: normales, modales y modeless.

  • Llamamos ventana modal a aquella que siempre están encima de las otras y además no permite que el foco pase a ninguna de las otras. Cuando creamos una ventana modal, no podremos interactuar con los menús, herramientas, etc., de la ventana principal hasta que cerremos la ventana modal (ni tampoco podremos interactuar con el resto de ventanas). Además, las ventanas modales suspenden el hilo de ejecución principal de la aplicación, de forma que el código de nuestra extensión no continuará ejecutándose hasta que cerremos la ventana modal.
  • Llamamos ventana modeless a aquella que siempre está encima de las otras pero a diferencia de la modal sí permite que el foco pase a otras ventanas, y por tanto permite interactuar con los menús y herramientas de la ventana principal y con el contenido de otras ventanas.
  • Ventanas normales son las que pueden estar encima o debajo de las otras de forma normal, es decir, las que no imponen ninguna restricción especial respecto al foco o a la superposición a otras ventanas.

Debemos tener en cuenta que las ventanas modeless sólo lo serán respecto a las ventanas normales; es decir, una ventana modeless siempre estará encima de las ventanas normales existentes, pero podrá estar encima o debajo de otra ventana modeless, dependiendo de qué ventana seleccionemos.

De forma similar, las ventanas modales sólo lo serán respecto a las ventanas normales o modeless; es decir, siempre estarán encima de las ventanas normales o modeless existentes, pero podrán estar encima o debajo de otras ventanas modales, dependiendo de qué ventana esté seleccionada.

El tipo de ventana que se creará a partir de un objeto WindowInfo depende de las propiedades suministradas al constructor del objeto. Por ejemplo, para crear una ventana modal, el método getWindowInfo() de la ventana deberá contener el siguiente código:

WindowInfo windowInfo = new WindowInfo(WindowInfo.MODALDIALOG);
return windowInfo;

Para crear una ventana modeless:

WindowInfo windowInfo = new WindowInfo(WindowInfo.MODELESSDIALOG);
return windowInfo;

Para crear una ventana normal:

WindowInfo windowInfo = new WindowInfo();
return windowInfo;

Existen otras propiedades que podemos especificar en el constructor, que añadiremos usando el operador de suma binaria |:

  • WindowInfo.RESIZABLE Especifica que se permitirá al usuario redimensionar la ventana. Si no se incluye, la ventana no será redimensionable (tendrá tamaño fijo).
  • WindowInfo.MAXIMIZABLE Especifica que la ventana podrá ser maximizable. Una ventana maximizable posee dos modos: modo maximizado, en el cual se empotra en la ventana principal de Andami, ocupando todo su espacio, y modo no maximizado, en el cual la ventana posee su tamaño normal. Para pasar de un modo a otro, el usuario debe pulsar el botón de Maximizar/Restaurar (en la barra del título). El objeto WindowInfo posee también unos métodos para cambiar este estado a nivel de código. Si no se especifica la propiedad MAXIMIZABLE, la ventana no será MAXIMIZABLE (siempre estará en el estado no maximizado.
  • WindowInfo.ICONIFIABLE Especifica que se permitirá al usuario minimizar la ventana. Cuando la ventana se minimiza, desaparece y en su lugar aparece un botón en la parte inferior izquierda de la ventana principal. Al pulsar en este botón, la ventana vuelve a su estado normal.
  • WindowInfo.PALETTE Algunas ventanas están diseñadas para poder ser empotradas dentro de otras. Estas ventanas tienen dos modos: modo empotrado y modo paleta (flotante). Cuando están en modo paleta, el contenido se muestra en una ventana independiente, de tipo MODELESSDIALOG. Cuando están en modo empotrado, el contenido está integrado en algún área de otra ventana. El localizador gráfico, por ejemplo, es una herramienta que es capaz de mostrarse en una ventana independiente o bien empotrado en la parte inferior izquierda de una vista. Las ventanas tipo paleta deben implementar la interfaz IWindowTransform.
  • Además, existen los dos atributos que ya hemos explicado anteriormente: WindowInfo.MODALDIALOG y WindowInfo.MODELESSDIALOG.

Para crear una ventana modeless redimensionable y maximizable:

WindowInfo windowInfo = new WindowInfo(WindowInfo.MODELESSDIALOG | WindowInfo.RESIZABLE | WindowInfo.MAXIMIZABLE);
return windowInfo;

No es posible utilizar simultáneamente las propiedades MODELESSDIALOG y MODALDIALOG. Además de que no tiene sentido, si lo hacemos obtendremos un error de Andami.

Hasta aquí hemos visto las propiedades que acepta el constructor de la clase WindowInfo, y que determinan el comportamiento de la ventana. Además de esto, existe una serie de métodos que permiten consultar diversas propiedades de la ventana, así como modificarlas:

  • public int getX()

Devuelve la coordenada X (en píxeles) del vértice superior izquierdo de la ventana. El origen de coordenadas (punto 0, 0) se sitúa en la parte superior izquierda de la ventana principal, justo debajo de la barra de herramientas.

  • public void setX(int x)

Establece la coordenada X (en píxeles) del vértice superior izquierdo de la ventana.

  • public int getY()

Devuelve la coordenada Y (en píxeles) del vértice superior izquierdo de la ventana. El origen de coordenadas (punto 0, 0) se sitúa en la parte superior izquierda de la ventana principal, justo debajo de la barra de herramientas.

  • public void setY(int y)

Establece la coordenada Y (en píxeles) del vértice superior izquierdo de la ventana.

  • public int getNormalX()

Cuando la ventana no está maximizada, obtiene la coordenada X (en píxeles) del vértice superior izquierdo. Cuando está maximizada, devuelve la coordenada X que tendría la ventana si no estuviese maximizada.

  • public void setNormalX(int x)

Establece la coordenada X (en píxeles) del vértice superior izquierdo de la ventana cuando la ventana está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurar la ventana al estado normal la coordenada X será la establecida aquí.

  • public int getNormalY()

Cuando la ventana no está maximizada, obtiene la coordenada Y (en píxeles) del vértice superior izquierdo. Cuando está maximizada, devuelve la coordenada Y que tendría la ventana si no estuviese maximizada.

  • public void setNormalY(int y)

Establece la coordenada y (en píxeles) del vértice superior izquierdo de la ventana cuando la ventana está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurar la ventana al estado normal la coordenada X será la establecida aquí.

  • public int getWidth()

Devuelve la anchura (en píxeles) de la ventana.

  • public void setWidth(int width)

Establece la anchura (en píxeles) de la ventana.

  • public int getHeight()

Devuelve la altura (en píxeles) de la ventana.

  • public void setHeight(int height)

Establece la altura (en píxeles) de la ventana.

  • public int getNormalWidth()

Cuando la ventana no está maximizada, obtiene la anchura (en píxeles) de la ventana. Cuando está maximizada, devuelve la anchura que tendría la ventana si no estuviese maximizada.

  • public void setNormalWidth(int width)

Establece la anchura (en píxeles) de la ventana cuando no está minimizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la anchura establecida aquí.

  • public int getNormalHeight()

Cuando la ventana no está maximizada, obtiene la altura (en píxeles) de la ventana. Cuando está maximizada, devuelve la altura que tendría la ventana si no estuviese maximizada.

  • public void setNormalHeight(int height)

Establece la altura (en píxeles) de la ventana cuando no está minimizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la altura establecida aquí.

  • public Rectangle getBounds()

Obtiene la posición y dimensiones (en píxeles) de la ventana.

  • public void setBounds(Rectangle bounds)

Establece la posición y dimensiones (en píxeles) de la ventana.

  • public Rectangle getNormalBounds()

Cuando la ventana no está maximizada, obtiene la posición y dimensiones (en píxeles) de la ventana. Cuando está maximizada, devuelve la posición y dimensiones que tendría la ventana si no estuviese maximizada.

  • public void setBounds(Rectangle bounds)

Establece la posición y dimensiones (en píxeles) de la ventana cuando está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la posición y dimensiones establecidas aquí.

  • public String getSelectedTool()

Obtiene el actionCommand la herramienta seleccionada asociada a la ventana actual (ver la descripción de <selectable-tool> para conocer el mecanismo de herramientas seleccionables).

  • public String getTitle()

Obtiene el título de la ventana.

  • public void setTitle(String title)

Establece el título de la ventana.

  • public int getId()

Obtiene el identificador de ventana.

  • public void setId(int id)

Establece el identificador de ventana.

  • public boolean isClosed()

Devuelve cierto (true) si la ventana está cerrada, falso (false) en caso contrario.

  • public void setClosed(boolean closed)

Establece si la ventana está abierta o cerrada. La ejecución de este método no cambia el estado de la ventana, use PluginServices.getMDIManager().addWindow(window) y PluginServices.getMDIManager().closeWindow(window) para abrir o cerrar una ventana.

  • public boolean isIconfiable()

Devuelve cierto (true) si la ventana es minimizable, falso (false) en caso contrario.

  • public boolean isMaximizable()

Devuelve cierto (true) si la ventana es maximizable, falso (false) en caso contrario.

  • public void setMaximizable(boolean maximizable)

Establece si la ventana es maximizable o no. La ejecución de este método no cambia el estado de la ventana una vez se ha creado, se debe definir si la ventana es maximizable antes de crearla.

  • public boolean isMaximized()

Devuelve cierto (true) si la ventana está maximizada, falso (false) en caso contrario.

  • public void setMaximized(boolean maximized)

Maximiza o restaura la ventana dependiendo del parámetro maximized.

  • public boolean isModal()

Devuelve cierto (true) si la ventana es modal, falso (false) en caso contrario.

  • public boolean isModeless()

Devuelve cierto (true) si la ventana es modeles, falso (false) en caso contrario.

  • public boolean isPalette()

Devuelve cierto (true) si la ventana está actualmente en modo paleta (flotante), falso (false) si está empotrada en la ventana correspondiente.

  • public void toPalette(boolean b)

Establece si la ventana está en modo paleta o no. La ejecución de este método no cambia el estado de la ventana, simplemente actualiza el objeto WindowInfo. Use la interfaz IWindowTransform para pasar una ventana a modo paleta o restaurarla a modo empotrado.

  • public boolean isResizable()

Devuelve cierto (true) si la ventana es redimensionable, falso (false) en caso contrario.

  • public boolean isVisible()

Devuelve cierto (true) si la ventana es visible, falso (false) en caso contrario.

  • public void setPersistence(boolean persist)

El tamaño, la posición y el estado de las ventanas se almacena en los proyectos de gvSIG, esta información se usa al abrir un proyecto para restaurar las ventanas a su estado anterior. Es posible que no deseemos que se almacene el estado de una ventana concreta (por ejemplo, porque inicialmente deba estar siempre cerrada o en una posición exacta). Este método establece si el estado de esta ventana debe guardarse en los proyectos de gvSIG o debe ser ignorada.

  • public boolean checkPersistence()

Devuelve cierto (true) si el estado de esta ventana debe guardarse en los proyectos de gvSIG, falso (false) si debe ser ignorada.

  • public XMLEntity getXMLEntity()

Obtiene un árbol XML que almacena las propiedades de este objeto WindowInfo. Es útil para salvar las propiedades de la ventana.

public static WindowInfo createFromXMLEntity(XMLEntity xml)

Método estático que crea un objeto WindowInfo a partir de las propiedades almacenadas en el árbol XML suministrado.

El gestor de ventanas: MDIManager

La interfaz MDIManager nos permite interactuar con las ventanas. Es el punto al que debemos dirigirnos para crear ventanas, cerrarlas, cambiarles el tamaño, obtener la ventana activa, obtener la lista de todas las ventanas, etc. Durante la creación de ventanas, se encarga de crear las ventanas con unas propiedades homogéneas (teniendo en cuenta, eso sí, las propiedades que hemos solicitado) y registrarlas en el menú Ventana, entre otras cosas. También se encarga de escuchar los cambios producidos en los objetos WindowInfo para reflejar estos cambios en la ventana real asociada.

Como ya se ha comentado, la gestión de ventanas de Andami es bastante particular porque el programador sólo debe crear un panel con el contenido de la ventana, y MDIManager creará la ventana real. Por esta razón, para cambiar las propiedades de una ventana debemos dirigirnos a MDIManager, que es quien conoce la implementación real de la ventana (frame).

Diagrama que muestra las principales clases relacionadas con el gestor de ventanas

Diagrama que muestra las principales clases relacionadas con el gestor de ventanas

MDIManager posee un amplio rango de métodos para interactuar con las ventanas. La mayoría de ellos requieren una referencia al objeto IWindow que deseamos alterar. Otros, por contra, devuelven una referencia a tal objeto.

Métodos para crear ventanas
  • public IWindow addWindow(IWindow panel)

Crea un nuevo frame cuyo contenido será panel. Las propiedades iniciales de la ventana creada (tamaño, tipo, etc), vendrán determinadas por el método getWindowInfo() de panel. Si la ventana es de tipo MODAL, se creará en posición centrada, ignorando la posición especificada en getWindowInfo. Si la ventana es de tipo SingletonWindow y ya existe una ventana con el mismo modelo, no se creará una ventana nueva sino que la ventana ya existente se traerá a primer plano y se le dará el foco. En este caso se devolverá una referencia a dicha ventana ya existente.

  • public IWindow addCentredWindow(IWindow panel)

Método idéntico a addWindow, salvo que crea todas las ventanas en posición centrada, ignorando la posición especificada por el método getWindowInfo() de panel.

Métodos para cerrar ventanas
  • public void closeAllWindows()

Cierra todas las ventanas abiertas.

  • public void closeWindow(IWindow window)

Cierra la ventana window.

  • public boolean closeSingletonWindow(Object model)

Cierra la ventana Singleton que tenga como modelo el objeto model.

  • public boolean closeSingletonWindow(Class viewClass, Object model)

Cierra la ventana Singleton cuya clase y modelo se pasan como parámetros.

Métodos para obtener las ventans existentes
  • public IWindow[] getAllWindows()

Obtiene un vector que contiene todas las ventanas que están abiertas actualmente, incluyendo las minimizadas y maximizadas. La ventana principal no está incluída, ya que no está bajo el control de MDIManager.

  • public IWindow[] getOrderedWindows()

Obtiene un vector que contiene todas las ventanas que están abiertas actualmente, ordenado según la profundidad (posición Z) de las ventanas. Es decir, la primera ventana del vector será la ventana que esté en primer plano (delante de las demás) y la última ventana del vector será la que esté más al fondo (detrás de las demás).

  • public getActiveWindow()

Devuelve la ventana activa en este momento, excluyendo las ventanas modales y las ventanas tipo PALETTE. Si la ventana activa actual es modal o tipo PALETE, devuelve la última ventana activa que no fuese modal ni tipo PALETTE. Esto es así porque se considera que las ventanas modales y tipo PALETTE no están al mismo nivel que las demás, sino que son ventanas auxiliares (normalmente contienen algún tipo de herramienta para manipular el contenido de otra ventana o presentan alguna información puntual).

  • public getFocusWindow()

Devuelve la ventana que posee el foco en este momento, excluyendo las ventanas modales. Si la ventana que posee el foco actualmente es modal, devuelve la última ventana no-modal que tuvo el foco.

Métodos para modificar propiedades de ventanas
  • public WindowInfo getWindowInfo(IWindow window)

Obtiene el objeto WindowInfo asociado a la ventana window. Este objeto contiene información actualizada sobre la posición, tamaño, título, etc. Además de proporcionar información, este objeto puede utilizarse para hacer cambios en las propiedades de la ventana (tamaño, posición, etc). En Propiedades de ventana: WindowInfo se describe en detalle las propiedades que se pueden modificar usando este objeto.

  • public synchronized void centreWindow(IWindow panel)

Centra la ventana panel.

  • public void changeWindowInfo(IWindow window, WindowInfo wi)

Actualiza las propiedades de la ventana window usando los valores especificados en el objeto wi.

  • public void refresh(IWindow win)

Normalmente, las ventanas se redibujan automáticamente cuando corresponde, de acuerdo al layout asignado y a los cambios de tamaño, etc. Sin embargo, si queremos forzar un redibujado, podemos hacerlo usando este método.

Otros métodos de utilidad
  • public void setWaitCursor()

Muestra el icono de espera como cursor del ratón, y bloquea todos los eventos que se produzcan en la ventana principal hasta que se reciba una llamada a restoreCursor().

  • public void restoreCursor()

Restaura el icono de ratón que hubiese antes de comenzar la operación, y reactiva el manejo de eventos de la ventana principal.

  • public void setBackgroundImage(ImageIcon image, String typeDesktop)

Establece la imagen proporcionada como fondo de la ventana principal. El argumento typeDesktop debe tomar uno de los siguientes valores: Theme.CENTERED, Theme.EXPAND, Theme.MOSAIC (para indicar que la imagen debe situarse bien centrada, bien debe expandirse para ocupar todo el tamaño de la ventana, o bien debe repetirse formando un mosaico hasta llenar todo el espacio).

La interfaz SingletonWindow

Existe un tipo de especial de ventanas de Andami: las ventanas Singleton, ventanas que tienen asociado un modelo. El modelo puede ser cualquier objeto Java que elijamos, y nos sirve para dotar de identidad a la ventana. De esta forma, existirá una única ventana asociada a un modelo, y podremos hacer referencia a la ventana a través del modelo. Si intentamos añadir una ventana Singleton cuyo modelo ya está asociado a otra ventana existente, en vez de crearse una nueva ventana se mandará la ventana existente a primer plano, y obtendremos una referencia a dicha ventana.

La interfaz Singleton cuenta simplemente de un método:

public Object getWindowModel()

Devuelve el modelo de esta ventana Singleton. El modelo debería ser constante en el tiempo para una ventana dada, salvo que realmente queramos cambiar la identidad de la ventana.

La interfaz SingletonWindow extiende la interfaz IWindow, por lo que cualquier ventana que implemente SingletonWindow implementará también IWindow.

La interfaz IWindowListener

La interfaz IWindowListener permite a las ventanas que la implementen registrarse como listener (oyente) de los eventos que suceden sobre sí mismas.

public void windowActivated()

Este método se ejecutará cada vez que se active la ventana.

public void windowClosed()

Este método se ejecutará cuando la ventana se esté cerrando.

Para recibir estos eventos, basta con que las ventanas implementen esta interfaz, Andami lo detectará y les enviará los eventos correspondientes sin necesidad de seguir ningún paso adicional.

La interfaz IWindowTransform

Las ventanas tipo PALETTE (ventanas que pueden empotrarse dentro de otras o bien estar en modo flotante o paleta) deben implementar esta interfaz, para pasar correctamente de modo paleta a modo empotrado.

Esta interfaz posee tres métodos, que la ventana debe implementar correctamente de acuerdo a su contexto (ya que cada herramienta adoptará una forma particular en modo empotrado y en modo flotante):

public void toPalette()

Pide a la ventana que se transforme en modo flotante.

public void restore()

Pide a la ventana que se transforme en modo empotrado.

isPalette()

Devuelve cierto si la ventana está en modo paleta (flotante), falso si está en modo empotrado.


Servicios a los plugins

La clase PluginServices

Andami proporciona una variedad de servicios a los plugins, casi todos a través de la clase PluginServices. Estos servicios cubren varias tareas que se realizan con frecuencia en cualquier parte de la aplicación, y por tanto conviene ofrecer una forma sencilla de realizarlas.

Cada plugin tiene un objeto PluginServices asociado, que podemos obtener con la llamada:

PluginServices.getPluginSerivices(pluginName)

o bien

PluginServices.getPluginServices(object),

donde object debe corresponderse con algún objeto perteneciente al plugin. Este objeto PluginServices permite acceder a servicios específicos del plugin, como obtención de recursos (imágenes, etc), el nombre del plugin, persistencia de datos, etc. Existen otros servicios no asociados específicamente a cada plugin que son accesibles a través de métodos estáticos de esta misma clase.

Traducciones

Los plugins poseen unos ficheros de traducciones que son leídos durante el inicio de la aplicación. Estos ficheros contienen claves de traducción, y la traducción asociada a cada clave, para cada idioma. De esta forma, los plugins pueden solicitar la traducción de una clave y Andami devolverá la traducción para el idioma que el usuario haya elegido (a través de la configuración del idioma). Usando este mecanismo se puede presentar al usuario una interfaz totalmente traducida a su idioma preferido.

Los ficheros de traducción deben estar situados en el directorio raíz del plugin, y se deben llamar de acuerdo a lo especificado en la etiqueta resourceBundle del config.xml. Los sufijos de idioma de cada nombre de fichero corresponden con el código de dos caracteres ISO 639-1 (en para inglés, fr para francés, etc). El fichero sin sufijo corresponde al idioma Castellano. Siguiendo estas convenciones, las traducciones del plugin son cargadas automáticamente durante la carga de plugins.

Para acceder a estas traducciones desde el plugin, usaremos un código como el siguiente:

PluginServices ps = PluginServices.getPluginServices(this);
ps.getText("clave_de_traduccion");

o bien, a través de un método estático:

PluginServices.getText(this,"clave_de_traduccion");

Hay que tener en cuenta que todos los plugins tienen un espacio de claves de traducción único. Esto significa que si un plugin A posee una traducción como la siguiente en el fichero text.properties:

error_conexion="Error de conexión"

y un plugin B posee una traducción como:

error_conexion="Error conectando a la base de datos"

entonces habrá un conflicto de claves. Si suponemos que el plugin A se carga antes (y por tanto sus traducciones también se cargan antes), cuando el plugin B intente acceder a la traducción:

PluginServices ps = PluginServices.getPluginServices(this);
ps.getText(error_conexion);

obtendría la traducción errónea: "Error de conexión". Por tanto, debemos intentar utilizar claves que describan con precisión el mensaje, para evitar este de error. Si el plugin no va a ser integrado en la distribución principal de gvSIG, podemos usar un prefijo de clave para curarnos en salud:

org.gvsig.MipluginCasero.error_conexion="Error conectando a la base de datos"

Registro (log)

El servicio de registro permite mantener un histórico de sucesos de una sesión completa de gvSIG. Los sucesos pueden tener distinta categoría: Información, Avisos, Errores o Información para Depuración. Al registrar algún mensaje en el log, automáticamente se registra también el plugin que genera el mensaje.

Es importante registrar los errores en el log, a fin de poder depurarlos posteriormente. Es frecuente que los usuarios encuentren errores que somos incapaces de reproducir en nuestra máquina, y la única herramienta con la que podemos corregir estos errores es mediante la información registrada en el log. El fichero de log se almacena en el directorio gvSIG dentro del directorio del usuario. En Windows, típicamente está en:

C:\Archivos de programa\Documents and Settings\%USER%\gvSIG\gvSIG.log

y en Linux, está en:

/home/$USER/gvSIG/gvSIG.log

Utilizar el log desde un plugin es muy sencillo:

Logger logger = PluginServices.getLogger();
logger.info("Mensaje a mostrar en el log");

Como ya hemos dicho, existen 4 niveles de severidad:

  • depuración (DEBUG), indica que esta información sólo es interesante para depurar (corregir) el programa. En esta severidad podemos mostrar (por ejemplo) los típicos mensajes que verifican que estamos entrando dentro de un if.
  • información (INFO), indica que el mensaje es meramente informativo (no contiene ningún error).
  • aviso (WARNING), indica que la información mostrada tiene la intención de avisar de algún hecho especial. Debemos mostrar en esta severidad los hechos inusuales o inesperados que puedan ser indicios de error (pero no errores claros).
  • error (ERROR), indica que se ha producido un error en la aplicación. Debemos mostrar información relevante para poder detectar el origen del error y permitir subsanarlo (ya sea al usuario o al programador).

Accederemos a estos cuatro niveles usando los correspondientes métodos: debug(), info(), warning() y error().

Si se ha producido una excepción y deseamos incluir en el log la salida del método printStackTrace, podemos hacerlo de forma muy sencilla:

catch (Exception excepcion) {
  logger.error("Mensaje de error", excepcion);
}

Persistencia de datos (plugin-persistence.xml)

Existe un fichero (plugin-persistence.xml) en el que los plugins pueden almacenar información (ejemplos: un plugin de WMS podría almacenar la lista de servidores recientes; un plugin de nomenclátor podría almacenar las últimas búsquedas realizadas). De esta forma, al volver a arrancar gvSIG, los plugins pueden recuperar la información guardada y presentar (por ejemplo) la lista de servidores usados en la última sesión. Cada plugin sólo tiene acceso (de forma directa) a la información que él mismo produce. Debemos aclarar que este fichero es adecuado para guardar pequeñas cantidades de información de texto, pero no es adecuado para guardar otros tipos de información como miniaturas de la cartografía, un listado de nombres de municipios, etc.

El fichero se lee automáticamente durante el inicio de Andami, y se guarda automáticamente al salir de la aplicación. El fichero está en formato XML, que almacena la información con una estructura de árbol. Para poder leer o escribir información en esta estructura, haremos uso del objeto XMLEntity, que representa una subrama del árbol (una subrama puede contener desde un único nodo hasta el árbol completo).

Para obtener la subrama asociada a nuestro plugin, utilizaremos:

XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();

Si suponemos que previamente habíamos almacenado una propiedad llamada "last.server", podemos recuperarla usando:

try {
  XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
  String lastServer = entity.getStringProperty("last.server");
}
catch (NotExistInXMLEntity exception) {
  // mostrar el error y tomar alguna acción en respuesta
}

Podemos ver el objeto XMLEntity como una especie de tabla Hash que asocia claves con valores. En el ejemplo anterior, "last.server" es una clave.

Para almacenar una nueva clave:

XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
entity.putProperty("last.server", "http://www.gvsig.org");

Es posible almacenar diversos tipos de objetos en el XMLEntity:

XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
entity.putProperty("last.server", "http://www.gvsig.org");
entity.putProperty("maxConnections", 5);
entity.putProperty("timeout", 3.258);
String[] servers = {"http://www.gvsig.org","www.gvsig.gva.es","www.cit.gva.es"}
entity.putProperty("preferred.servers", servers);
int[] allowedValues = {1,2,5,10};
entity.putProperty("allowed.values", allowedValues);

Y para recuperar estos objetos:

try {
  XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
  String lastServer = entity.getStringProperty("last.server");
  int maxConnections =  entity.getProperty("maxConnections");
  float timeout = entity.getFloatProperty("timeout");
  String[] servers = entity.getStringArrayProperty("preferred.servers");
  int[] allowedValues = entity.getIntArrayProperty("allowed.values");
}
catch (NotExistInXMLEntity exception) {
  // mostrar el error y tomar alguna acción en respuesta
}

Además, podemos anidar los objetos XMLEntity:

XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
XMLEntity hijo = new XMLEntity();
hijo.setName("ColorData");
hijo.putProperty("red", "#ff0000");
hijo.putProperty("green", "#00ff00");
hijo.putProperty("blue", "#0000ff");
hijo.putProperty("black", "#000000");
hijo.putProperty("white", "#ffffff");
entity.addChild(hijo);

Y para recuperar el XMLEntity anidado:

XMLEntity child=null, tmpchild; 
for (int i=entity.getChildrenCount()-1; i>=0; i--) {
  tmpchild = entity.getChild(i);
  if (child.getName().equals("ColorData")) {
    child = tmpchild;
    break;
  }
}

if (child!=null) {
  try {
    String rojo = child.getStringProperty("red");
    String verde = child.getStringProperty("green");
    String azul = child.getStringProperty("blue");
    String negro = child.getStringProperty("black");
    String blanco = child.getStringProperty("white");
  }
  catch (NotExistInXMLEntity exception) {
    // mostrar el error y tomar alguna acción en respuesta
  }
}

Tareas en segundo plano

Andami facilita la creación de tareas en segundo plano, permitiendo además que estas tareas sean cancelables (interrumpibles). Si deseamos realizar un procesamiento prolongado, conviene lanzarlo en un hilo separado (proceso en segundo plano), para no bloquear la interfaz gráfica. Un típico ejemplo de tarea a lanzar en segundo plano sería la exportación de una capa a un fichero. Se presentará un diálogo al usuario con el progreso de la tarea, y un botón que permita la cancelación del proceso.

Para crear la tarea usaremos el método:

  • public static void cancelableBackgroundExecution IMonitorableTask task)

Para crear cómodamente una tarea en segundo plano, existe la clase abstracta AbstractMonitorableTask que implementa IMonitorableTask y que podemos extender anónimamente para crear nuestra tarea:

PluginServices.cancelableBackgroundExecution(new AbstractMonitorableTask() {
  public void run() {
    for (int i=0; i<100001; i++) {
      System.out.println("hilo mundo "+i);
    }
  }
});

Simplemente con esto, la parte que está dentro del método run() se ejecutará en un nuevo hilo.

Si queremos mostrar un diálogo de progreso y dar la posibilidad de cancelar la operación, podemos añadir algunas líneas:

PluginServices.cancelableBackgroundExecution(new AbstractMonitorableTask() {
  public void run() {
    setNote(PluginServices.getText(this, "Exportar_fichero"));
    setInitialStep(0);
    setFinalStep(100000);
    for (int i=0; i<100001; i++) {
      if (!isCanceled()){
        System.out.println("hilo mundo"+i);
        setCurrentStep(i);
      }
      else {
        return;
      }
    }
  }
});

El diálogo desaparecerá automáticamente al llegar al final step (en este caso 100.000), y no desaparecerá hasta que llegue al final step aunque el nuevo hilo ya haya finalizado.

Si tenemos alguna necesidad especial que AbstractMonitorableTask no cubre, existen varias opciones:

  • Extender AbstractMonitorableTask de forma no anónima, reescribiendo los métodos que necesitemos y el constructor.
  • Extender DefaultCancellableMonitorable en una nueva clase que a la vez implemente IMonitorableTask, reescribiendo los métodos que necesitemos y el constructor.
  • Crearnos una clase totalmente nueva que simplemente implemente IMonitorableTask e incorpore toda la funcionalidad que necesitemos.

El siguiente diagrama muestra toda la jerarquía de clases implicada en la creación de tareas en segundo plano, para que veamos con más claridad el significado de extender de una u otra clase:

Al extender AbstractMonitorableTask de forma anónima y pedirle que muestre el diálogo de progreso, nos muestra un diálogo de progreso indeterminado, es decir, la barra no avanza desde el inicio hasta el fin, si no que oscila a izquierda y derecha continuamente hasta que acaba la tarea. Ahora vamos a mostrar un ejemplo en el que extenderemos AbstractMonitorableTask de forma no anónima a fin de mostrar una barra de progreso determinada:

public class BackgroundTask extends AbstractMonitorableTask {
  public BackgroundTask() {
    setDeterminatedProcess(true);
    setNote(PluginServices.getText(this, "Exportar_fichero"));
    setStatusMessage(PluginServices.getText(this, "Exportando..."));
    setInitialStep(0);
    setFinalStep(100000);
  }   
  public void run() {
    for (int i=0; i<100001; i++) {
      if (!isCanceled()){
        System.out.println("hilo hilero"+i);
        setCurrentStep(i);

      }
      else {
        return;
      }
    }
  }
}

y haremos uso de ella de la siguiente forma:

PluginServices.cancelableBackgroundExecution(new BackgroundTask());

Ficheros temporales

Andami ofrece facilidades para crear ficheros temporales, que son eliminados automáticamente al salir de gvSIG (siempre que gvSIG finalice correctamente). Por una parte, si suministramos un nombre y unos datos, Andami creará el fichero temporal con el contenido proporcionado. Por otra parte, podemos solicitar la ruta del directorio de ficheros temporales y crear en él los ficheros que necesitemos. En ambos casos, los ficheros se eliminan automáticamente al salir de gvSIG. Para acceder a este servicio no usamos PluginServices sino una clase llamada Utilities.

public static void createTemp(String fileName, String data)throws IOException

Crea un fichero llamado fileName, escribe en él el contenido de data y finalmente cierra el fichero. Al salir de gvSIG, el fichero se cerrará automáticamente.

public static String createTempDirectory()

Crea el directorio para ficheros temporales, si no existe, y devuelve la ruta al mismo. Todos los ficheros que creemos en este directorio se borrarán automáticamente al salir de gvSIG.

Accesso a recursos del plugin

Es frecuente que los plugins incluyan diversos recursos (normalmente imágenes, tal vez algún fichero XML, cartografía en algún plugin especial...). A la hora de acceder a ellos, no debemos usar rutas absolutas ni tampoco rutas relativas que incluyan el nombre del plugin, ya que eso es poco portable (un cambio en el nombre del plugin provocaría que el código dejase de funcionar). Lo correcto es acceder a ellos usando una ruta relativa al directorio raíz del plugin.

Cada plugin tiene asociado un PluginClassLoader que es capaz de cargar recursos usando rutas relativas al directorio raíz del plugin. Para acceder a dicho ClassLoader, tenemos varias opciones:

PluginServices ps = PluginServices.getPluginServices(this);
PluginClassLoader loader = ps.getClassLoader();

o bien

ClassLoader loader = this.getClass().getClassLoader();

Una vez tengamos el ClassLoader, usaremos el método getResource() para obtener una URL con la ruta absoluta del recurso. Por ejemplo, supongamos que queremos cargar una imagen llamada close.png que está en el subdirectorio images de nuestro plugin. Es decir, la imagen está en gvSIG/extensiones/org.gvsig.miplugin/images/close.png y por tanto la ruta relativa al directorio raíz del plugin sería images/close.png. Para acceder a ella usaríamos:

URL imageURL = loader.getResource("images/close.png");
if (imageURL!=null)
  ImageIcon icon = new ImageIcon(imageURL);

Acceso al portapapeles

Andami ofrece métodos para obtener texto del portapapeles, o depositar texto en él:

public static void putInClipboard(String data)

Deposita en el portapapeles la cadena de texto contenida en data.

public static String getFromClipboard()

Obtiene el contenido de texto del portapapeles si existe alguno, o null en caso contrario.

Estos métodos están pensados para agilizar el acceso cuando requiramos manipulaciones simples del portapapeles, y consituyen simples atajos para los métodos estándar de Java . Si necesitamos manipular otros tipos de datos (o hacer uso de características avanzadas como la selección de sistema), debemos echar un vistazo a las clases ClipBoard, Transferable y DataFlavour de Java y a los métodos Toolkit.getDefaultToolkit().getSystemClipboard() y Toolkit.getDefaultToolkit().getSystemSelection().


Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: