## Reglas Generales para el uso de Componentes de Texto

JTextComponent es la base para los componentes de texto swing, y proporciona estas características personalizables para todos sus descendientes:

- Un modelo separado, conocido como document, para manejar el contenido del componente.
- Una vista separada, que se encarga de mostrar el componente en la pantalla. Este tutorial no explica las vistas.
- Un controlador separado, conocido como un editor kit, que puede leer y escribir texto e implementa capacidades de edición con comandos action.
- Mapas de teclas personalizados.
- Soporte para repetir/deshacer infinito.
- Cursores conectables y oyentes de cambios de cursor.

Esta sección utiliza la aplicación mostrada abajo para explorar cada una de estas capacidades. La aplicación demo contiene un JTextPane-- una de las subclases de JTextComponent que soporta texto formateado, iconos y componentes embebidos -- para ilustrar las capacidades, heredadas por todas las subclases de JTextComponent. Para más información específica sobre JTextPane pueder ver la página Cómo usar Editor Panes y Text Panes.
El componente de texto superior es el JTextPane personalizado. El componente de texto inferior es un JTextArea, que sirve como un diario que reporta todos los cambios realizados en el contenido del text pane. La línea de estado de la parte inferior de la ventana informa sobre la localización de la selección o de la posición del cursor, dependiendo de si hay o no texto seleccionado.

> Nota: Es fichero fuente de esta aplicación es TextComponentDemo.java. También nevesitarás LimitedStyledDocument.java.

A través de esta aplicación de ejemplo, aprenderás cómo usar la capacidades de los componentes de texto y cómo personalizarlos. Esta sección cubre los siguientes texto, que puesen ser aplicados a todas las subclases de JTextComponent: :

- Sobre Documentos
- Personalizar un Documento
- Escuchar los cambios de un Documento
- Sobre los Kits de Edición
- Asociar Acciones con menús y botones
- Sobre los Mapas de teclado
- Associar Acciones con pulsaciones de teclas
- Implementar deshacer/repetir
- Escuchar los cambios de cursor y de selección

#### Sobre los Documentos

> Al igual que muchos otros componentes Swing, un componente de texto separa su contenido de su vista. El contenido de un componente de este es manejado por su documento, el cual contiene el texto, soporte para edición, y notifica a los oyente los cambios en el texto. Un documento es un ejemplar de una clase que implementa el interface Document o su subinterface StyledDocument.

#### Personalizar un Documento

> La aplicación de ejemplo mostrada anteriormente tiene un documento persoanlziado LimitedStyledDocument, que limita el número de caracteres que puede contener. LimitedStyledDocument es una subclase de DefaultStyledDocument, el documento por defecto para JTextPane.

> Aquí está el código del programa de ejemplo que crea un LimitedStyledDocument y lo designa como el documento para le text pane

>     ...donde se declaren las variables miembro...
>     JTextPane textPane;
>     static final int MAX_CHARACTERS = 300;
>         ...en el constructor del frame...
>         //Crea el documento para el área de texto
>         LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS);
>         ...
>         //Crea el text pane y lo configura
>         textPane = new JTextPane();
>         textPane.setDocument(lpd);
>         ...

Para limitar los caracteres permitidos en el docuemnto, LimitedStyledDocument sobreescribe el método insertString de su superclase, que es llamado cada vez que se inserta texto en el documento.

    public void insertString(int offs, String str, AttributeSet a)
    	           			throws BadLocationException {
        if ((getLength() + str.length()) <= maxCharacters)
            super.insertString(offs, str, a);
        else
            Toolkit.getDefaultToolkit().beep();
    }

Además de insertString, los documentos personalizados también sobreescriben el método remove, que es llamado cada vez que se elimina texto de un documento.

Un uso común para un documento personalizado es para crear un campo de texto validado. (un campo cuyo valor es chequeado cada vez que es editado). Para ver dos ejemplos de campos de texto validados pueder ir a Crear un Campo de Texto Validado.

Para más información puedes ver las tablas del API: Clases e Interfaces que Representan Documentos y Métodos útiles para trabajar con Documento.

#### Escuchar los Cambios de un Documento

> Un documento notifica sus cambios a los oyentes interesados. Se utiliza un oyente de Document para reaccionar cuando se inserta o se elimina texto de un documento, o cuando cambia el estilo de alguna parte del texto.

> El programa TextComponentDemo usa un oyente de Document para actualizar el diario de cambios siempre que ocurra un cambio en el text pane. Esta línea de código registra un ejemplar de MyDocumentListener como oyente del LimitedStyledDocument usado en el ejemplo:

>     LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS);
>     lpd.addDocumentListener(new MyDocumentListener());

> Aquí está la implementación de MyDocumentListener:

>     protected class MyDocumentListener implements DocumentListener {
>         public void insertUpdate(DocumentEvent e) {
>             update(e);
>         }
>         public void removeUpdate(DocumentEvent e) {
>             update(e);
>         }
>         public void changedUpdate(DocumentEvent e) {
>             //Display the type of edit that occurred
>             changeLog.append(e.getType().toString() +
>                              ": from " + e.getOffset() +
>                              " to " + (e.getOffset() + e.getLength() - 1) +
>                              newline);
>             changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1);
>         }
>         private void update(DocumentEvent e) {
>             //Display the type of edit that occurred and
>             //the resulting text length
>             changeLog.append(e.getType().toString() +
>                              ": text length = " +
>                              e.getDocument().getLength() + newline);
>             changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1);
>         }
>     }

> El oyente de nuestro ejemplo mueatr el timpo de cambio que ha ocurrido y, si está afectada por el cambio, la longitud del texto. Para información general sobre los oyente de Docuement y eventos de Document, puedes ver Cómo escribir un Oyente de Document.

> Recuerda que el documento para este text pane limita el número de caracteres permitidos en el decumento. Si intentas añadir texto hasta exceder el límite máximo, el documento bloquea el cambio y no se llamará al método insertUpdate del oyente. Los oyentes de Document sólo son notificados si el cambio ha ocurrido realmente.

> Algunas veces, podrías estar tentado de cambiar el texto del documento desde dentro de un oyente de Document. Por ejemplo, si tienes un campo de texto sólo debería contener enteros y el usuario introduce algún otro tipo de datos, podrías querer cambiar el texto a 0. Sin embargo, nunca se debe modificar el contenido de un componente de texto desde dentro de un oyente de Document. De hecho, si intentas hacerlo, tu programa se quedará bloqueado. En su lugar proporciona un documento personalizado y sobreescribe los métodos insert y remove. Crear un Campo de texto validado te enseña como hacerlo.

#### Sobre los Kits de Edición

> Todos los componentes de Texto Swing soportan comandos de edición estándard como cortar, copiar, pegar y la inserción de caracteres. Cada comando de edición está representada e implementado por un objeto action. Esto hace sencillo el asociar un comando con un componente GUI, como un ítem de menú o un botón, y construir un GUI alrededor de un componente de texto.

> Un componente de texto usa un objeto EditorKit para crear y manejar estas acciones. Además de manejar un conjunto de acciones para un componente de texto, un kit de editor también sabe leer y escribir documentos en un formato particular.

> El paquete text de Swing proporciona estos tres kits de editor:

> DefaultEditorKit
>   Lee y escribe texto sin estilo. Proporciona un conjunto básico de comandos de edición. Los demás kits de editor descienden de este.
> StyledEditorKit
>   Lee y escribe texto con estilo y proporciona un conjunto de acciones mínimo para texto con estulo. Esta clase es una subclase de DefaultEditorKit y es el kit de editor usado por defecto por JTextPane.
> HTMLEditorKit
>   Lee, escribe y edita HTML. Esta es una subclase de StyledEditorKit.
> La mayoría de los programas no necesitan escribir código que interactúe directamente con los kits de editor porque JTextComponent proporciona el API necesario para invocar directamente a las capacidades del kit. Por ejemplo, JTextComponent proporicona métodos read y write, que llaman a los métodos read y write del kit de editor. JTextComponent también proporcionar un método, getActions, que devuelve todas las acciones soportadas por un componente. Este método obtiene una lista de acciones desde el kit de editor del componente. Sin embargo, las clases del kit de editor proporciona útiles clases internas y variables de clases que son muy útiles para crear un GUI alrededor de un componente de texto. Asociar Acciones con Ítems de Menú muestra como asociar una acción con un ítem de menú y Asociar Acciones con Pulsaciones de Teclas muestra como asociar una acción con una pulsación de teclas determinadas. Ambas secciones hacen uso de clases manejables o de variables definidas en los kits de editor estándars de Swing.

#### Asociar Acciones con Ítems de Menú

> Como se mencionó anteriormente, podemos llamar al método getActions sobre cualquier componente para obtener un array que contenga todas las acciones soportadas por dicho componente. Es conveniente cargar el array de acciones en un Hashtable para que nuestro programa pueda recuperar una acción por su nombre. Aquí está el código de TextComponentDemo que obtiene las acciones del text pane y las carga dentro de un Hashtable:

>     private void createActionTable(JTextComponent textComponent) {
>         actions = new Hashtable();
>         Action[] actionsArray = textComponent.getActions();
>         for (int i = 0; i < actionsArray.length; i++) {
>             Action a = actionsArray[i];
>             actions.put(a.getValue(Action.NAME), a);
>         }
>     }

> Y aquí hay un método de conveniencia para recuperar una acción por su nombre desde el hashtable:

>     private Action getActionByName(String name) {
>         return (Action)(actions.get(name));
>     }

> Puedes utilizar ambos métodos de forma casi literal en tus programas. Sólo tienes que cambiar actions por el nombre de tu hashtable.

> Ahora, vemaos cómo se crea el ítem de meú Cut y como se asocia a la acción de eliminar texto de un componente de texto:

>     protected JMenu createEditMenu() {
>         JMenu menu = new JMenu("Edit");
>         ...
>         menu.add(getActionByName(DefaultEditorKit.cutAction));
>         ...

> Este código obtiene la acción por su nombre usando el método descrito anteriormente y añade la acción al menú. Esto es todo lo que necesitas hacer. El menú y la acción tienen en cuenta todo lo demás. Observaras que el nombre de la acción viene de DefaultEditorKit. Este kit proporciona acciones para la edición básica de texto y es la superclase para todos los kits de editor proporcionados por Swing. Por eso sus capacidades están disponibles para todos los componentes de texto a menos que se hayan sobreescrito por una personalización.

> Por razones de rendimiento y eficiencia, los componentes de texto comparten acciones. El objeto Action devuelto por getActionByName(DefaultEditorKit.cutAction) es compartido por el JTextArea (no editable) de la parte inferior de la ventana. Esto tiene dos importantes ramificaciones:

> - Generalmente hablando, no se deben modificar los objetos Action obtenidos de los kits de editor. Si lo hacemos, los cambios afectarán a todos los componentes de texto de nuestro programa.
> - Los objetos Action pueden operar con otros componentes de texto del programa, quizás más de los esperados. En este ejemplo, incluso aunque no sea editable, el JTextArea comparte las acciones con el JTextPane. Si no queremos compartir, deberemos ejemplarizar el objeto Action nosotros mismos. DefaultEditorKit define varias sublcases de Action muy útiles.
> Configurar el menú Style es similar. Aquí está el código que crea y pone el ítem de menú Bold en él:

>     protected JMenu createStyleMenu() {
>         JMenu menu = new JMenu("Style");

>         Action action = new StyledEditorKit.BoldAction();
>         action.putValue(Action.NAME, "Bold");
>         menu.add(action);
>         ...

> StyledEditorKit proporciona sublcases de Action para implementar comandos de edición para texto con estilo. Habrás notado que en lugar de obtener la accón del kit de editor, este código crea un ejemplar de la clase BoldAction. Asñi, esta acción no será compartida por ningún otro componente de texto, y cambiando su nombre no afectará a ningún otro componente de texto.

> Además de asociar una acción con componente GUI, también podemos asociar una acción con una pulsación de teclas Asociar Acciones con Pulsaciones de Teclas muestra como hacerlo.

> Puedes ver la tabla de API relacionada con los Comandos de Edición de Texto.

#### Sobre los Mapas de Teclado

> Cada componente de texto tiene uno o más keymaps-- cada uno de los cuales es un ejemplar de la clase Keymap. Un keymap contiene una colección de parejas nombre-valor donde el nombre es un KeyStroke (pulsación de tecla) y el valor es una Action. Cada parej enlaza el keystroke con la acción por lo tanto cada vez que el usuario pulsa la tecla, la acción ocurrirá.

> Por defecto, un componente de texto tiene un keymap llamado JTextComponent.DEFAULT_KEYMAP. Este keymap contiene enlaces básicos estándars. Por ejemplo, las teclas de flechas están mapeadas para mover el cursor, etc. Se puede modificar o ampliar el kjeymap por defecto de las siguientes formas:

> - Añadiendo un keymao personalizado al componente de texto con del método addKeymap de JTextComponent.
> - Añadiendo enlaces de teclas al keymap por defecto con el método addActionForKeyStroke de Keymap. El Keymap por defecto es compartido entre todos los componentes de texto, utilizalo con precaución.
> - Eliminado enlaces de teclas del keymap por defecto con el método removeKeyStrokeBinding de Keymap. El Keymap por defecto es compartido entre todos los componentes de texto, utilizalo con precaución.
> Cuando se resuelve una pulsación a su acción, el componente de texto chequea el keymap en el orden en que fueron añadidos al componente de texto. Así, el enlace para una pulsación específica en un keymap que hayamos añadido a un componente de texto sobreescribe cualquier enlace para la misma pulsación en el keymap por defecto.

#### Asociar Acciones con Pulsaciones de Teclas

> El text pane de TextComponentDemo añade cuatro enlaces de teclas al keymap por defecto.
> - CTRL-B para mover el cursor un caracter hacia atrás
> - CTRL-F para mover el cursor un caracter hacia adelante
> - CTRL-P para mover el cursor una línea hacia arriba
> - CTRL-N para mover el cursor una línea hacia abajo.
> El siguiente código añade el enlace de tecla CTRL-B al keymap por defecto. El código para añadir las otras tres es similar.

>     //Obtiene el mapa por defecto actual
>     Keymap keymap = textPane.addKeymap("MyEmacsBindings",
>     				   textPane.getKeymap());

>     //Ctrl-b para ir hacia atrás un caracter.
>     Action action = getActionByName(StyledEditorKit.backwardAction);
>     KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK);
>     keymap.addActionForKeyStroke(key, action);

> Este código primero añade el keymap al árbol de componentes. El método addKeymap crea el keymap por nosotros con el nombre y padre proprocionados en la llamada al método. En este ejemplo, el padre es el keymap por defecto del text pane. Luego, el código obtiene la acción de ir hacia atrás del kit de editor y obtiene un objeto KeyStroke que representa la secuencia de teclas CTRL-B. Finalmente, el código añade la pareja acción y puslación al keymap, y por lo tanto enlaza la tecla con la acción.

> Puedes ver el API relacionado en la tabla Enlazar Pulsaciones a Acciones.

#### Implementar Deshacer y Repetir

> > Nota: La implementación de deshacer/repetir en TextComponentDemo fue copiada directamente del NotePad que viene con Swing. La mayoría de los programadores también podrán copiar esta implementación sin modificaciones.

> Implementar Deshacer/repetir tiene dos partes:

> - Recordar las ediciones "reversibles" que han ocurrido.
> - Implementar los comandos deshacer y repetir y proporcionar un interface de usuari para ellos.

> Parte 1: Recordar Ediciones "Reversibles"
> Para soportar deshacer/repetir, un componente de texto debe recordar cada edición que ha ocurrido sobre él, el orden en que ocurren las ediciones en relación a otras, y que hacer para deshacerlas. El programa de ejemplo usa un manejar de deshacer, un ejemplar de la clase UndoManager del paquere undo de Swing, para menjar una lista de ediciones reversibles. El undomanager se crea donde se declaran las variables miembros:

>     protected UndoManager undo = new UndoManager();

> Ahora veamos como el programa encuentra las ediciones reversibles y las añade al undomanager.

> Un documento noticia a los oyentes interesados si en su contenido ha ocurrido una edición reversible. Un paso importante en la implementación de deshacer/repetir es registrar un oynete de 'undoable edit' en el documento del componente de texto. Este código añade un ejemplar de MyUndoableEditListener al documento del text pane:

>     lpd.addUndoableEditListener(new MyUndoableEditListener());

> El oyente usado en nuestro ejemplo añade la edición a la lista del undomanager:

>     protected class MyUndoableEditListener implements UndoableEditListener {
>         public void undoableEditHappened(UndoableEditEvent e) {
>             //Recuerda la edición y actualiza los menús
>             undo.addEdit(e.getEdit());
>             undoAction.update();
>             redoAction.update();
>         }
>     }

> Observa que este método actualizad so objetos: undoAction y redoAction. Estos dosn dos objetos actión añadidos a los ítems de menú Undo (Deshacer) y Redo (Repetir) menu items, respectivamente. El siguiente paso es ver como se crean los dos ítems de menú y la implementación de las dos acciones.

> Para información general sobte oyentes y eventos de 'undoable edit' puedes ver: Cómo escribir un oyente de Undoable Edit.

> Parte 2: Implementar los Comandos Deshacer/Repetir
> El primer paso de esta parte de implementación es crear las acciones y ponerlas en el menú Edit.

>     JMenu menu = new JMenu("Edit");

>     //Deshacer y repetir son acciones de nuestra propia creacción
>     undoAction = new UndoAction();
>     menu.add(undoAction);

>     redoAction = new RedoAction();
>     menu.add(redoAction);
>     ...

> Las acciones deshacer y repetir son implementadas por subclases personalizadas de AbstractAction: UndoAction y RedoAction respectivamente. Estas clases son clases internas de la clases primaria del ejemplo.

> Cuando el usuario llama al comando Undo, el método actionPerformed de UndoAction mostrado aquí, obtiene la llamada:

>     public void actionPerformed(ActionEvent e) {
>         try {
>             undo.undo();
>         } catch (CannotUndoException ex) {
>             System.out.println("Unable to undo: " + ex);
>             ex.printStackTrace();
>         }
>         update();
>         redoAction.update();
>     }

> Este método llama al método undo del undomanager y actualiza los ítems de menú para reflejar el nuevo estado de deshacer/repetir.

> De forma similar, cuando el usuario llama al comando Redo, el método actionPerformed de RedoAction obtiene la llamada:

>     public void actionPerformed(ActionEvent e) {
>         try {
>             undo.redo();
>         } catch (CannotRedoException ex) {
>             System.out.println("Unable to redo: " + ex);
>             ex.printStackTrace();
>         }
>         update();
>         undoAction.update();
>     }

> Este método es similar excepto en que llama al método redo de undomanager.

> La mayoría del código de las clases UndoAction y RedoAction está dedicada a habilitar o deshabilitar las acciones de forma apropiada al estado actual, y cmabiar los nombres de los ítems de menú para reflejar la edición a deshacer o repetir.

#### Escuchar los cambios de cursor o de selección

> El programa TextComponentDemo usa un oyente de caret para mostrar la posición actual del cursor o, si hay texto selecciónado la extensión de la selección.

> El oyente de caret de este ejemplo es también una etiqueta. Aquí está el código que crea la etiqueta, la añade a la ventana, y la hace oyente de caret del text pane:

>     //Crea el área de estado
>     JPanel statusPane = new JPanel(new GridLayout(1, 1));
>     CaretListenerLabel caretListenerLabel = new CaretListenerLabel(
>     						"Caret Status");
>     statusPane.add(caretListenerLabel);
>     ...
>     textPane.addCaretListener(caretListenerLabel);

> Un oyente de caret debe implemenar un método, caretUpdate, que es llamado cada vez que el cursor se mueva o cambie la selección. Aquí está la implementación que CaretListenerLabel hace de caretUpdate:

>     public void caretUpdate(CaretEvent e) {
>         //Obtiene la posición en el texto
>         int dot = e.getDot();
>         int mark = e.getMark();
>         if (dot == mark) {  // no hay selección
>             try {
>                 Rectangle caretCoords = textPane.modelToView(dot);
>                 //Convierte las coordenadas
>                 setText("caret: text position: " + dot +
>                         ", view location = [" +
>                         caretCoords.x + ", " + caretCoords.y + "]" +
>                         newline);
>             } catch (BadLocationException ble) {
>                 setText("caret: text position: " + dot + newline);
>             }
>          } else if (dot < mark) {
>             setText("selection from: " + dot + " to " + mark + newline);
>          } else {
>             setText("selection from: " + mark + " to " + dot + newline);
>          }
>     }

> Cómo puedes ver, este oyenbte actualiza su etiqueta de texto para reflejar el estado actual del cursor o la selección. El oyente obtiene la información mostrada desde el objeto caret event. Para información general sobre los oyentes y eventos de cursor puedes ver Cómo escribir un oyente de Caret.

> Al igual que los oyenentes de document, un oyente de caret es pasivo. Reacciona a los cambios del cursor o de la selección, pero no cambia el cursor ni la selección. En vez de modidicar el cursor o la seleccíon desde un oyente de caret, deberemos usar un caret personalizado. Para crear un caret peresonalizado, debemos escribir una clase que implemente el interface Caret, luego proporcionar un ejemplar de nuestra clase como argumento a setCaret sobre un componente de texto.

Ozito