Curso de programaci├│n Java VII (el ├║ltimo de la serie)- Abraham Otero
martes, julio 5, 2011 at 4:56PM
javaHispano

Curso de programación Java VII

 

Artículo publicado originalmente en la revista Sólo Programadores

 

Descargas

 

Curso de programación Java VII

 

Hace muchos años que dejó de ser aceptable que el usuario final de una aplicación emplease una consola y el teclado para interaccionar con ella. Ahora, las expectativas de los usuarios han crecido y esperan bonitas interfaces de usuario con botones, menús, barras de tareas, etcétera. El objetivo de este capítulo es enseñar a diseñar interfaces gráficas de usuario empleando para ello la librería Swing. 

 

Swing es la librería gráfica que en Java 1.2 sustituyó a la vieja AWT. La nueva librería cuenta con más componentes y proporciona una mayor cantidad de opciones sobre ellos (como distintas apariencias, control sobre el focus, mayor control sobre su aspecto, mayor facilidad para pintar al hacer el buffering transparente al usuario....) que su antecesor. Además, se diferencia radicalmente de ésta en su implementación. En AWT cuando añadíamos, por ejemplo, un botón a nuestra interfaz la máquina virtual le pedía al sistema operativo la creación de un botón en un determinado sitio con unas determinadas propiedades; en Swing ya no se pide al sistema operativo nada: se dibuja el botón sobre la ventana en la que lo queríamos. Con esto se eliminaron muchos problemas que existían antes con los códigos de las interfaces gráficas: debido a que dependían del sistema operativo para obtener sus componentes gráficos, era necesario testar los programas en distintos sistemas operativos, pudiendo tener distintos bugs en cada uno de ellos.

 

Esto, evidentemente, iba en contra de la filosofía de Java, supuestamente un lenguaje que no dependía de la plataforma. Con Swing se mejoró bastante este aspecto: lo único que se pide al sistema operativo es una ventana; una vez que tenemos la ventana dibujamos botones, listas, scroll-bars... y todo lo que necesitemos sobre ella. Evidentemente esta aproximación gana mucho en lo que a independencia de la plataforma se refiere. Además, el hecho de que el botón no sea un botón del sistema operativo sino un botón pintado por Java nos da un mayor control sobre su apariencia.

 

1 Creando nuestra primera ventana

 

JFrame es el contenedor que emplearemos para situar en él todos los demás componentes que necesitemos para construir de una interfaz gráfica. Este contenedor es una ventana típica de un sistema operativo. La ventana tendrá un borde, una barra de título y, en Windows, tendrá los tres típicos iconos en su esquina superior derecha para minimizar, maximizar y cerrar la ventana.

 

En la figura 1 se muestra la jerarquía de herencia de este componente desde Object que, como ya hemos explicado, es el padre de todas las clases de Java. Los métodos de este componente estarán repartidos a lo largo de todos sus ascendientes, cosa que hemos de tener en cuenta cuando consultemos el javadoc de esta clase. Así, por ejemplo, resulta intuitivo que debiera haber un método para cambiar el color de fondo del frame, pero él no tiene ningún método para ello; lo tiene su tatarabuelo, la clase Component. 

 

FIGURA 1: Jerarquía de herencia de JFrame

 

En el listado 1 vemos el código necesario para crear una ventana. La forma más habitual de hacerlo es creando una clase propia que herede de JFrame. En el constructor de esta clase empleamos dos métodos de JFrame para indicar cuál será el título de la ventana y su tamaño. En una clase auxiliar, la que tiene el método main, creamos una instancia de esta clase y la hacemos visible (por defecto los JFrames son invisibles).

 
//LISTADO 1:
import javax.swing.*;
class MiFrame extends JFrame {
    public MiFrame(){
        setTitle("Hola!!!");
        setSize(300,200);
    }
}
public class Ejemplo1{
    public static void main (String[] args){
        JFrame frame = new MiFrame();
        frame.setVisible(true);
    }
}
 

 

El código del listado 1 tiene un problema: cuando cerremos la ventana la máquina virtual no se detendrá. La única forma de terminarla será pulsando control-C si hemos ejecutado el programa desde una consola, o con Control-Alt-Supr y eliminando su tarea correspondiente si lo ejecutamos desde Windows. En Netbeans, podemos usar el botón rojo de "Stop" de la lengüeta que se muestra en la parte inferior del entorno de desarrollo al ejecutar una aplicación, lengüeta que se corresponde con la salida de consola de la aplicación.

 

¿Por qué no se detiene la aplicación una vez que se cierra la ventana? Porque no hemos escrito el código necesario para ello. Para que se detenga la aplicación debemos escribir código que escuche los eventos de ventana, y que ante el evento de intentar cerrar la ventana reaccione terminando la máquina virtual. A continuación, antes de seguir presentando más componentes de la librería Swing, veamos qué es un evento y cómo gestionarlo.

 

2 El modelo de delegación de eventos

 

El sistema de gestión de eventos de Java 1.2 es el mismo que el de Java 1.1 y por lo tanto el mismo que para la librería AWT. Aunque los desarrolladores de Java considerasen que para mejorar la plataforma se necesitaba dejar a un lado la librería AWT e introducir Swing no sintieron lo mismo del sistema de gestión de eventos. Este sistema es bastante elegante y sencillo, sobre todo si se compara con el sistema de gestión de eventos de Java 1.0, mucho más engorroso de usar y menos elegante.

 

¿Qué es un evento? Todos los sistemas operativos están constantemente atendiendo a los eventos generados por los usuarios. Estos eventos pueden ser pulsar una tecla, mover el ratón, hacer clic con el ratón, pulsar el ratón sobre un botón o menú (Java distingue entre simplemente pulsar el ratón en un sitio cualquiera o hacerlo, por ejemplo, en un botón). El sistema operativo notifica a las aplicaciones que están ocurriendo estos eventos, y ellas deciden si han de responder o no de algún modo a este evento.

 

El modelo de gestión de eventos de Java se conoce como el modelo de delegación de eventos. El evento se produce en un determinado componente, por ejemplo un scroll. Donde se produce el evento se denomina “fuente del evento”. A continuación el evento se transmite a un "manejador de eventos” (event listener) que está asociado al componente en el que se produjo el evento. El objeto que escucha los eventos es el que se encargará de responder a ellos. Esta separación de código entre generación del evento y actuación respecto a él facilita la labor del programador y da una mayor claridad a los programas.

 

Lo que la fuente de eventos le pasa al objeto encargado de escuchar los eventos es, como no, otro objeto cuyo tipo es Event. Este objeto contiene toda la información necesaria para la correcta gestión del evento por parte del objeto que escucha los eventos.

 

El objeto que escucha los eventos ha de implementar para ello una interface. El nombre de esta interface es siempre el nombre del evento más “Listener”: para que un objeto escuche eventos de ratón ha de implementar la interface MouseListener, para que escuche eventos de teclado KeyListener, etcétera.

 

Para hacer que un objeto escuche los eventos de alguna fuente de eventos se emplea el método dd[NombreEvento]Listener, así si tuviésemos un JFrame llamado frame y quisiésemos que el objeto llamado manejador escuchase los eventos de ratón de frame lo haríamos del siguiente modo:

 

frame.addMouseListener(manejador);

 

manejador ha de pertenecer a una clase que implemente la interface MouseListener, que tiene un total de 7 métodos que debemos sobrescribir. Al implementar la interfaz tenemos que sobreescribir todos los métodos que se definen en ella, incluso aunque no los usemos,  sino la clase que se encargaría de escuchar los eventos sería abstracta y no podríamos crear ningún objeto de ella. Para resolver este problema, para cada interface que tenga más de un método existe una clase llamada [NombreEvento]Adapter (MouseAdapter, por ejemplo), que implementa todos los métodos de la interface sin hacer nada en ellos. Nosotros lo único que tendremos que hacer es que nuestra clase que escuche eventos extienda esta clase y sobrescriba los métodos que nos interesen.

 

La figura 2 muestra cómo funciona el modelo de delegación de eventos. En este caso a la fuente del evento, un botón, le indicamos quién será su manejador de eventos, manejador que ha de extender la clase Adapter correspondiente o implementar la interfaz Listener (interfaz ActionLitener en este caso). Cuando el usuario genere el evento deseado (en este caso pulse el botón), el objeto fuente empaqueta información a cerca del evento generado en un objeto de tipo Event (ActionEvent en este caso) e invoca el método correspondiente del manejador (actionPerformed(actionEvent)) pasándole como información el objeto de tipo Event generado. Es responsabilidad del manejador, y no de la fuente, responder al evento, por ello se dice que la fuente delega la gestión del evento en el manejador.

 

 

FIGURA 2: Esquema del modelo de gestión de eventos de Swing

 

En el listado 2 podemos ver una nueva versión del programa del listado 1 donde en el constructor del JFrame añadimos un manejador de eventos de ventana. El lector puede consultar los métodos de la interfaz WindowListener, así como qué evento causa la invocación de cada método, en el javadoc de dicha interfaz. Se ha creado una clase adicional que extiende a la clase WindowAdapter y sobreescribe el método que es invocado cuando se cierra la ventana: public void windowClosing(WindowEvent e). Dentro de ese método se imprime un texto por consola, simplemente para que podamos comprobar que efectivamente se ha ejecutado dicho método al cerrar la ventana, y se termina la máquina virtual con la sentencia System.exit(0). Ahora, al cerrar esta ventana se detendrá la máquina virtual, que es el comportamiento que deseábamos en este caso.

 
//LISTADO 2: Un JFrame que al cerrarse detiene la máquina virtual
class Frame extends JFrame {
    public Frame(){
        setTitle("Hola!!!");
        setSize(300,200);
        addWindowListener (new manejador());
    }
}
 class manejador extends WindowAdapter{
            public void windowClosing(WindowEvent e){
                System.out.println("Saliendo");
                System.exit(0);
            }
        }
public class Ejemplo2{...}
 

 

3 Contenedores y Layout Managers

 

No se considera una buena práctica de programación añadir componentes directamente sobre un contenedor de “pesado” (frames y applets por lo que a nosotros respecta). Lo correcto es añadir al contenedor pesado uno o varios contenedores ligeros (habitualmente paneles) y añadir sobre éstos los componentes que necesitemos. Un contenedor es cualquier clase que derive de java.awt.Container, clase que contiene la funcionalidad genérica de ser un contenedor, es decir, la funcionalidad de poder contener a otros componentes gráficos. Un contenedor "pesado" es un contenedor que se pide directamente al sistema operativo, como es el caso de un JFrame. Un contenedor "ligero" es un contenedor que, al igual que sucede con prácticamente todos los componentes de la librería Swing, se dibuja sobre un contenedor pesado del sistema operativo. Un ejemplo es JPanel.

 

Para añadir un JPanel a nuestro frame  primero obtenemos uno de los objetos que forman el JFrame: el “panel contenedor” (content pane). Un JFrame no es como un folio, es decir, no tiene una sola capa. Más bien, es como una pequeña pila de folios: tiene varias capas (cinco), cada una de ellas con distinta funcionalidad. Para un curso básico de programación gráfica como éste, sólo nos interesa una de esas capas: la del panel contenedor. A esta capa es a la que debemos añadir cualquier componente que queramos que se vea en la ventana.

 

Para obtener el panel contenedor se emplea el método getContentPane. El objeto que devuelve será de tipo Container:

 

Container contentpane = frame.getContentPane();

 

Sobre este contenedor deberemos añadir toda nuestra interface gráfica. Pero ¿Cómo definimos la posición de los distintos componentes en pantalla?. Éste es precisamente el propósito de los Layout Maneger: con ellos se especifican unas posiciones determinadas en un contenedor donde será posible añadir nuestros componentes, así como el comportamiento de dichos componentes cuando la ventana cambie de tamaño.

 

Dado que cualquier contenedor de swing es también un componente, es posible anidar contenedores, e indicar un Layout Maneger diferente para cada uno de ellos. Esto proporciona una gran flexibilidad a la hora de definir la posición de los componentes en pantalla.

 

En Swing existen una gran cantidad de Layout Manegers. Aquí presentaremos el funcionamiento de sólo tres de ellos, para que el lector haga una idea de qué es lo que proporcionan y cómo se pueden emplear para construir interfaces gráficas de usuario. En el video que acompaña a este artículo se muestra el funcionamiento de un cuarto Layout Maneger, más orientado a ser empleado por entornos de desarrollo que por los programadores directamente.

 

 

3.1 FlowLayout

 

Es el layout que tienen los JPanel por defecto. Los objetos se van colocando en filas en el mismo orden en que se añadieron al contenedor. Tanto el alto como el ancho de los componentes serán respetados por el layout. Cuando se llena una fila se pasa a la siguiente. Tiene tres posibles constructores:

 

 

 

 

Una imagen vale más que 1000 palabras, y un programa funcionando vale más que 1000 descripciones de cómo funciona un programa. Recomiendo al lector que ejecute el código FlowLayoutEjemplo.java que puede encontrar en el CD de este número de la revista y que pruebe a cambiarle el tamaño a la ventana que aparecerá, así como que juegue con las distintas opciones del constructor de FlowLayout, para comprender su funcionamiento.

 

 

3.2 GridLayout

 

Como su propio nombre indica, crea un grid y va añadiendo los componentes a él de izquierda a derecha y de arriba a abajo. Todas las cuadrículas serán del mismo tamaño y crecerán o se harán más pequeñas hasta ocupar toda el área del contenedor. Los constructores más comunes son:

 

 

 

 

Nuevamente, recomiendo al lector que para comprender el funcionamiento de este layaout ejecute el código GridLayoutEjemplo.java del CD.

 

3.3 BorderLayout

 

Como se muestra en la figura 3, este layout tiene cinco zonas predeterminadas: son norte, sur, este, oeste y centro. Las zonas norte y sur al cambiar el tamaño del contenedor se estirarán hacia los lados para llegar a ocupar toda el área disponible, pero sin variar su tamaño en la dirección vertical. Las zonas este y oeste presentan el comportamiento contrario: variarán su tamaño en la dirección vertical pero sin nunca variarlo en la dirección horizontal. 

 

 

FIGURA 3: Distintas posiciones definidas en un BorderLayout

 

En cuanto a la zona central, crecerá o disminuirá en todas las direcciones para rellenar todo el espacio vertical y horizontal que queda entre las zonas norte, sur, este y oeste. Este layout posee dos contructores:

 

 

 

 

A la hora de añadir más paneles o componentes a este layout hay una pequeña diferencia respecto a los otros dos: en los otros al añadir los componentes se iban situando en un determinado orden, aquí especificamos en el método add la región donde queremos añadir el componente:

 

contenedor.add(componente, BorderLayout.NORTH);

 

Con esta llamada al método add añadiremos el componente en la región norte. Cambiando de NORTH por SOUTH, EAST, WEST, CENTER lo añadiremos en la región correspondiente. El código BorderLayoutEjemplo.java del CD ilustra el funcionamiento de este layaout.

 

 

4 Creando nuestra primera aplicación

 

Ha llegado el momento de introducir un componente que no sea un contenedor. Empecemos por un botón. Para crear un botón podemos emplear su constructor por defecto:

 

JButton boton = new JButton();

 

O también podemos indicar el texto que queremos que se muestre en él:

 

JButton boton = new JButton(“texto va aquí”);

 

Cada vez que se haga un clic sobre el botón se genera un evento del tipo ActionEvent. Para poder escuchar dichos eventos necesitaremos una clase que implemete la interface ActionListener, interface que tiene un sólo método: actionPerformed (ActionEvent). En nuestro caso, será el propio JFrame el que implemente esta interfaz. Para indicar que va a ser el frame el que escucha los eventos del botón necesitamos el siguiente código:

 

boton.addActionListener(this);

 

Nuestra aplicación, cuyo código se muestra en el listado 3, tendrá un total de cuatro botones. Al pulsar cada uno de ellos, el color de fondo de un panel cambiará al color que indica la etiqueta de cada botón. El problema que se nos plantea ahora es el siguiente: cada vez que un botón sea pulsando se generará un ActionEvent y se invocará al método actionPerformed, pero ¿Cómo sabremos qué botón fue accionado para saber qué color ha de tener el fondo del panel? Esto lo lograremos gracias a que en el objeto evento (ActionEvent) hay información sobre quién produjo dicho evento: invocando al método getSource() de ActionEvent obtenemos una referencia al componente que generó dicho evento. 

 

Para controlar el tamaño de cada uno de los botones que estarán en las posiciones norte, sur, este y oeste de un BorderLayout, se invoca a un método del botón, setPreferredSize(Dimension). Este método fija un par de constantes del objeto botón a los valores indicados por el objeto dimensión, que no es más que un objeto que contiene dos enteros. Estos valores son el tamaño que preferentemente ha de tener el botón. BorderLayout respetará la altura de los botones si los añadimos en las posiciones sur o norte, o su ancho si los añadimos en las posiciones este u oeste. Si no hubiésemos establecido un tamaño preferido el botón sería muy fino, con lo cual quedaría poco estético y además no se daría leído su etiqueta.

 
//LISTADO 3: Una pequeña aplicación que cambia el color de un panel 
...
class Frame extends JFrame implements ActionListener{
    private JPanel panel = new JPanel();
    private JButton azul,rosa,amarillo,verde;
 
    public Frame(){
        setTitle("Hola!!!");
        setSize(500,400);
        addWindowListener (new manejador());
        Container contentpane = getContentPane();
        panel.setLayout(new BorderLayout());
        azul = new JButton("Azul");
        azul.addActionListener(this);
        Dimension d = new Dimension(100,40);
        azul.setPreferredSize(d);
 
        verde = new JButton("Verde");
        verde.addActionListener(this);
        verde.setPreferredSize(d);
 
        amarillo = new JButton("Amarillo");
        amarillo.addActionListener(this);
        amarillo.setPreferredSize(d);
 
        rosa = new JButton("Rosa");
        rosa.addActionListener(this);
        rosa.setPreferredSize(d);
        panel.add(azul,BorderLayout.SOUTH);
        panel.add(verde,BorderLayout.NORTH);
        panel.add(amarillo,BorderLayout.EAST);
        panel.add(rosa,BorderLayout.WEST);
 
        contentpane.add(panel);
        panel.setBackground(Color.red);
 
 
    }
    public void actionPerformed (ActionEvent e){
        Object source = e.getSource();
        if (source ==azul)
            panel.setBackground(Color.blue);
        if (source ==verde)
            panel.setBackground(Color.green);
        if (source ==amarillo)
            panel.setBackground(Color.yellow);
        if (source ==rosa)
            panel.setBackground(Color.pink);
 
    }
}
...

 

 

5 Revisión de algunos componentes Swing

 

En este apartado haremos una rápida revisión de varios de los componentes de la librería Swing más empleados. Consultando su javadoc, pueden encontrase más detalles sobre qué eventos generan, y cómo configurar su apariencia. La gestión de estos eventos es análoga al ejemplo presentado en el listado 3, y siempre sigue el modelo de delegación de eventos.

 

 

 

 

Esta lista está muy lejos de ser una lista exhaustiva de todos los componentes de la librería Swing. La mejor forma de hacerse una idea de lo que es posible hacer con Swing es ejecutando la demo SwingSet2 que viene con todos los jdk. En Windows, puedes encontrar esa demo en el directorio "C:\Archivos de programa\Java\jdk1.6.0\demo\jfc\SwingSet2"; con hacer doble clic sobre el archivo "SwingSet2.jar" que encontrarás en esa localización se lanzará la demo que se puede ver en la figura 4. En ella se demuestran todos los componentes de swing, y se muestra el código fuente necesario para conseguir cada uno de los efectos.

 

FIGURA 4: Demo SwingSet2; esta demo que se distribuye con el JDK demuestra todos los componentes de la librería Swing.

 

Otra recomendación que le doy al lector para trabajar con Swing es que emplee el sentido común. Por ejemplo ¿Habrá algún método dentro de JList se me permita cambiar el tipo de fuente que emplea para mostrar los elementos de la lista? Parece que tiene bastante sentido que exista esta posibilidad es ¿no? Y efectivamente, existe. Si la intuición dice que un determinado método debería existir en un componente, lo más probable es que exista. Y, habitualmente, con echar un vistazo al javadoc y leer los nombres de los métodos es suficiente para localizarlo.

 

6 Creando un Applet

 

Un applet es un programa Java que puede ser incluido en una página web y que puede ejecutarse dentro de un navegador. Los applets funcionan siempre y cuando el equipo que los ejecuta tenga instalada una máquina virtual Java. 

 

Para ejecutar una aplicación Java dentro de una página web sin que el visitante de la página web sufra alguna violación de su intimidad o se arriesgue a sufrir daños en su ordenador hay ciertas limitaciones que poseen los applets respecto a las aplicaciones normales; estas son fundamentalmente:

 

 

 

 

Si un applet está firmado es posible, siempre bajo la autorización del usuario, darle a este applet más privilegios y romper las restricciones que hemos indicado sobre estas líneas. En el caso de los applets no firmados, debemos jugar con las reglas expuestas arriba.

 

Es muy simple convertir cualquier aplicación Swing que respete la lista de restricciones aquí recogida en un applet. Para ello simplemente tenemos que:

 

 

 

 

Como mostramos en el listado 5, con sólo seguir estos simples pasos podemos transformar el código del listado 3 en un applet. En la figura 5 podemos ver nuestro applet ejecutándose en FireFox.

 

FIGURA 5: Nuestro applet ejecutándose en FireFox

 
//LISTADO 5: Nuestra aplicación transformada en un applet
... 
public class Applet extends JApplet implements ActionListener{
    private JPanel panel = new JPanel();
    private JButton azul,rosa,amarillo,verde;
    public void init(){
        Container contentpane = getContentPane();
        panel.setLayout(new BorderLayout());
 
        azul = new JButton("Azul");
        azul.addActionListener(this);
        Dimension d = new Dimension(100,40);
        azul.setPreferredSize(d);
        //...
      //Ídem para los botones verde, amarillo y rosa
        panel.add(azul,BorderLayout.SOUTH);
        panel.add(verde,BorderLayout.NORTH);
        panel.add(amarillo,BorderLayout.EAST);
        panel.add(rosa,BorderLayout.WEST);
 
        contentpane.add(panel);
        panel.setBackground(Color.red);
 
    }
    public void actionPerformed (ActionEvent e){
        Object source = e.getSource();
        if (source ==azul)
            panel.setBackground(Color.blue);
        if (source ==verde)
            panel.setBackground(Color.green);
        if (source ==amarillo)
            panel.setBackground(Color.yellow);
        if (source ==rosa)
            panel.setBackground(Color.pink);
 
    }
 
}

 

 

7 Diseñadores gráficos de interfaces Swing

 

Cualquier entorno de desarrollo Java actual que se precie incluye un diseñador gráfico de aplicaciones Swing. Estos diseñadores son herramientas en las cuales es posible construir una aplicación Swing simplemente seleccionando componentes de una paleta y arrastrándolos a una ventana en la cual vamos construyendo nuestra interfaz de usuario. Los diseñadores también nos permiten generar de modo automático gran parte del código necesario para la gestión de un evento. Lo más habitual cuando desarrollamos una aplicación de escritorio es apoyarnos en una de estas herramientas, y no escribir todo el código a mano cómo hemos hecho en este artículo. No obstante, para aprender programación gráfica lo más recomendable es hacer lo que hemos hecho en este artículo: escribir el código a mano. Si comenzamos a emplear directamente los diseñadores gráficos, no comprenderemos el código que generan y, cuando este código no se ajuste a nuestras necesidades, nos será imposible modificarlo. También nos será imposible retocar ese código sin la ayuda del diseñador.

 

 En el video del CD de la revista puedes ver cómo usar el diseñador de interfaces gráficas de Netbeans. Empleando ese diseñador, volveremos a recrear el ejemplo del listado 3 de este artículo, y después construiremos una pequeña calculadora (ver figura 6) que permita realizar sumas, restas, multiplicaciones y divisiones sobre números reales.

 

FIGURA 6: Calculadora construida con el diseñador de interfaces gráficas de usuario de Netbeans

 

8 Conclusiones

 

Con esto, damos por terminado este curso de programación sobre el lenguaje Java. A lo largo de estos siete capítulos, hemos presentado los fundamentos de la sintaxis del lenguaje, introducido la orientación a objetos y presentado las librerías más importantes: el framework de colecciones, la librería de entrada y salida, y Swing. Con la base que hemos dado hasta este momento el lector puede decidir cuál es el camino que más le interesa seguir dentro de Java: la programación de terminales móviles, la programación de aplicaciones de web o profundizar más en el desarrollo de aplicaciones de escritorio. 

 

Espero que esta serie de artículos os haya servido para dar nuestros primeros pasos en Java. También espero que sean sólo los primeros y que continuéis profundizando en el lenguaje de programación más usado en el mundo empresarial en la actualidad.

 

Cápitulos anteriores del curso:

 

 

Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.