Texte & Document
Rémi Forax

Plan

Composants de gestion du texte

Les documents

Les elements

La sélection

L'editor kit

Undo/Redo

La gestion du Texte

Dans le paquetage javax.swing.text, JTextComponent est le composant de base

JTextArea, JTextField,JPasswordField, JFormattedTextField ont un seul style pour tous le texte

JEditorPane, JTextPane possèdent de multiples styles et rendus différents

JTextComponent

JTextField

JTextArea

JEditorPane

JTextPane

JFormattedTextField

JPasswordField

Exemple

Les composants de gestion du texte en action :

JTextField

JPasswordField

JFormattedField

JTextArea

JTextPane

JEditorPane

Document

L'interface Document correspond au modèle sous-jacent au JTextComponent et à ses sous-classes

Elle permet d'extraire, modifier le contenu du texte

Elle possède une vue structurée du texte

Elle permet la gestions des évènements
(modification du document + undo/redo)

=> Interface trop verbeuse

Document / changer le texte

Accéder, modifier, ajouter des caractères :

int	getLength()
Renvoie la taille du document

void remove(int offs, int len)
supprime une portion du document

void insertString(int offset, String str, AttributeSet a)
insert une chaîne de caractère dans le document.
AttributeSet correspond au style associé

String getText(int offset, int length)
permet d'extraire du texte

void getText(int offset, int length, Segment txt)
permet d'extraire du texte qui sera stockée dans le segment

Segment

String mutable pour éviter la création d'objet intermédaire

S'utilise comme si elle était non mutable,
à l'extérieur de getText(int offset, int length, Segment txt)
et comme mutable à l'intérieur.

Segment

partial return

getText()

getText()

getText()

Document / Position

Permet d'obtenir un objet correspondant à une position dans un texte

En règle générale, l'offset de la position est mise à jour si il y a des modifications avant celle-ci.

Position getStartPosition()
retourne le début du document

Position getEndPosition()
retourne la fin du document

Position createPosition(int offs)
crée une position particulière

Document / Element

Les Elements représente sous forme d'une forêt d'arbre, les structures du document

La première structure sert pour calculer les objets utiliser pour l'affichage du document

Element getDefaultRootElement()
Renvoie l'arbre des éléments auquel sera associé un arbre de vue (View)

Element[] getRootElements()
Renvoie les racines de chaque structure du document

L'interface Element

Un élément est défini par :

Le document auquel il se rattache (getDocument)

Un nom (pour le debug ??) (getName)

Deux offset start (getStartOffset) et end (getEndOffset)

Un ensemble d'attribut (getAttributes)

Il est représenté sous forme d'arbre et donc :

Il possède un père (getParentElement)

Il sait s'il est une feuille (isLeaf)

Il connaits ses fils (getElementCount/getElement(int index))

Il est capable pour un offset entre start et end de trouver à quel fils cela correspond getElementIndex(int offset) en indiquant son index.

Exemple

On visualise sous forme d'un arbre (JTree) l'arborescence des élément d'un document correspondant à un document HTML.

Exemple

static TreeModel createElementTreeModel(Document document) {

final Element root=document.getDefaultRootElement();

return new TreeModel() {

public Object getRoot() {

return root;

}

public boolean isLeaf(Object node) {

return getChildCount(node)==0;

}

public int getChildCount(Object parent) {

return ((Element)parent).getElementCount();

}

public Object getChild(Object parent, int index) {

return ((Element)parent).getElement(index);

}

public int getIndexOfChild(Object parent, Object child) {

return ((Element)parent).getElementIndex(

((Element)child).getStartOffset()); // astuce pas sûre efficace

}

public void valueForPathChanged(TreePath path, Object newValue) {

}

public void addTreeModelListener(TreeModelListener l) {

}

public void removeTreeModelListener(TreeModelListener l) {

}

};

Document/évènement

Listeners liés au Document :

add/removeDocumentListener(DocumentListener)
permet d'enregistrer/résilier un écouteur des modifications effectuées sur le texte

add/removeUndoableEditListener()
permet d'enregistrer/résilier un écouteur sur les actions effectués lors d'un undo/redo.

DocumentListener :

void changedUpdate(DocumentEvent e)
envoyé lors que les attributs du texte change

void insertUpdate(DocumentEvent e)
indique l'insertion de caractère dans le document

void removeUpdate(DocumentEvent e)
indique la supression de caractère dans le document

DocumentEvent

Événement lancé suite à une modification, une suppression ou un ajout dans le document

DocumentEvent.EventType getType()
type de l'évènement parmis CHANGE, INSERT, REMOVE

Document getDocument()
la source de l'évènement

int	getOffset()
l'offset du début du changement

int	getLength()
la longeur du changement

DocumentEvent.ElementChange getChange(Element elem)
Indique ce qui a changé en terme d'élément

Exemple

On souhaite enregistrer les modifications sur le texte

final ChangesTableModel model=new ChangesTableModel();

area.getDocument().addDocumentListener(new DocumentListener() {

public void changedUpdate(DocumentEvent e) {

addChanges(e);

}

public void removeUpdate(DocumentEvent e) {

addChanges(e);

}

public void insertUpdate(DocumentEvent e) {

addChanges(e);

}

private void addChanges(DocumentEvent event) {

String text;

EventType type = event.getType();

if (type!=EventType.REMOVE) {

try {

text = document.getText(event.getOffset(),event.getLength());

} catch (BadLocationException e) {

throw new AssertionError(e);

}

} else

text=null;

model.add(new Change(type,text));

}

});

Texte et Sélection

Il n'y a pas de SelectionModel sur JTextComponent, pour savoir si la sélection à changer on doit utiliser le CaretListener (add/removeCaretListener)

CaretListener

void caretUpdate(CaretEvent e)
appelé lorsque la position du caret change

CaretEvent(Object source)

int	getDot()
position du caret

int	getMark()
position de la fin de la sélection

Caret

Interface définissant l'endroit où l'utilisateur va inséré ou supprimer du texte

La méthode getCaret() permet d'obtenir le Caret d'un JTextComponent

Le Caret possède les méthodes suivantes :

La position du caret (get/setDot)

La position de fin de sélection (getMark)

La visibilité du caret (is/setVisible)

La visibilité de la sélection (is/setSelectionVisible)

La féquence de clignotement (get/setBlinkRate)

Sur un JTextComponent

Début de la sélection: get/setSelectionStart()

Fin de la sélection get/setSelectionEnd()

Exemple

Lorsque l'on clique dans l'arbre, on veux visualiser le texte correspondant dans le JEditorPane

public static void main(String[] args) throws IOException {

final JEditorPane editor=new JEditorPane();

editor.setPage("http://www.google.fr");

final Document document=editor.getDocument();

final JTree tree=new JTree(createElementTreeModel(document));

tree.getSelectionModel().addTreeSelectionListener(

new TreeSelectionListener() {

public void valueChanged(TreeSelectionEvent e) {

TreePath path=e.getPath();

if (path==null)

return;

Element element=(Element)path.getLastPathComponent();

editor.setSelectionStart(element.getStartOffset());

editor.setSelectionEnd(element.getEndOffset());

editor.getCaret().setSelectionVisible(true);

}

});

}

EditorKit

Un EditorKit est un objet associé à un composant :

install/desinstall(JEditorPane)

Permet de récupérer :

Ensemble des actions du composants
Action[] getActions()

Créé un document : Document createDefaultDocument()

Créé un caret : Caret createCaret()

Un type MIME : String getContentType()

Renvoie une view factory
ViewFactory getViewFactory()

EditorKit

Entrée/sortie :

void write(OutputStream out, Document doc, int pos, int len)

void write(Writer out, Document doc, int pos, int len)

read(InputStream in, Document doc, int pos)

read(Reader in, Document doc, int pos)

Exemple

Il est possible de récupérer l'ensemble des actions d'un composant de texte

public static void main(String[] args) throws IOException {

final JTextPane pane=new JTextPane();

pane.setText("Ceci est un\ntexte de demo\n");

EditorKit editorKit=pane.getEditorKit();

final Action[] actions=editorKit.getActions();

ViewFactory

Factory qui associe une vue (view) à un Element

View create(Element elem)

Une vue ressemble fortement un composant graphique, en plus léger

Cela permet de spécifier une vue pour une portion particulière du texte

Hiérarchie des EditorKit

Il existe différents EditorKit
en fonction des fonctionnalités
proposées

DefaultEditorKit, texte sans style

StyledEditorKit, on peut
assigner un style de caractère ou
de paragraphe

HTMLEditorKit, reconnaît le HTML 3.2

RTFEditorKit, reconnaît un
sous-ensemble basique du RTF

EditorKit

DefaultEditorKit

StyledEditorKit

HTMLEditorKit

RTFEditorKit

Document/EditorKit

Chaque EditorKit possède un Document spécifique

Document

AbstractDocument

StyledDocument

DefaultStyledDocument

HTMLDocument

EditorKit

DefaultEditorKit

StyledEditorKit

HTMLEditorKit

RTFEditorKit

PlainDocument

StyledDocument

Permet de prendre en compte des Styles

Ajouter un nouveau style
Style addStyle(String nm, Style parent)

Obtenir un style
Style getStyle(String nm)

Supprimer un style
void removeStyle(String nm)

StyledDocument

Changer les attributs :

void setParagraphAttributes(int offset, int length, AttributeSet s, boolean replace)

void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace)

Lire les attributs

Color getForeground/Background(AttributeSet attr)

Font getFont(AttributeSet attr)

AttributeSet et Style

AtributeSet est un table de hachage nom -> propriété

boolean	containsAttribute(Object name, Object value)

boolean	containsAttributes(AttributeSet attributes)

Enumeration<?>	getAttributeNames()

boolean	isDefined(Object attrName)

Object	getAttribute(Object key)

int getAttributeCount()

boolean	isEqual(AttributeSet attr)

AttributeSet	copyAttributes()

AttributeSet	getResolveParent()

AttributeSet et Style

Style est un AttributeSet nommée et possédant des listeners

Nom du style

String	getName()

Permet d'être averti des changements de propriété d'un style

void add/removeChangeListener(ChangeListener l)

private static void fillTextPane(JTextPane pane) {

StyledDocument doc = pane.getStyledDocument();

// Initialize styles

Style defaultStyle = StyleContext.getDefaultStyleContext().

getStyle(StyleContext.DEFAULT_STYLE);

Style regular = doc.addStyle("regular", defaultStyle);

StyleConstants.setFontFamily(regular, "SansSerif");

Style italic = doc.addStyle("italic", regular);

StyleConstants.setItalic(italic, true);

Style bold = doc.addStyle("bold", regular);

StyleConstants.setBold(bold, true);

Style small = doc.addStyle("small", regular);

StyleConstants.setFontSize(small, 10);

Style large = doc.addStyle("large", regular);

StyleConstants.setFontSize(large, 16);

Undo/Redo

UndoableEdit

Interface des actions qui marche avec le undo/redo

boolean canUndo()/void undo()

boolean canRedo()/ void redo()

undo/redo impossible: void die()

boolean isSignificant()

boolean addEdit(UndoableEdit anEdit)

boolean replaceEdit(UndoableEdit anEdit)

String	getPresentationName()

String	getRedoPresentationName()

String	getUndoPresentationName()

AbstractUndoableEdit

Implantation de l'interface UndoableEdit

On doit redéfinir undo()/redo() et faire un appel à super.undo()/super.redo()

Il faut aussi redéfinir getPresentationName()

getRedoPresentationName()/ resp.	getUndoPresentationName() correspond à getPrésentationName() préfixé par « undo »/resp « redo »

CompoundEdit

Hérite de AbstractUndoableEdit

Permet de grouper plusieurs edits à l'intérieur d'un seul et même edit (bref c'est un composite)

Cela permet d'annuler ou de remettre plusieurs actions en une fois

Gestion des évènements

Lorsque l'on effectue une action déclanchant un undo possible, alors il faut prévenir un UndoableEditListener

UndoableEditListener

void undoableEditHappened(UndoableEditEvent e)

UndoableEditEvent :

UndoableEditEvent(Object source, UndoableEdit edit)

UndoableEdit getEdit()

UndoableEditSupport

Implantation par défaut du mécanisme de listenener UndoableEditListener

UndoableEditSupport(Object source)

add/removeUndoableEditListener(UndoableEditListener l)

UndoableEditListener[] getUndoableEditListeners()

Bulk update /

void	beginUpdate()

void	endUpdate()

void postEdit(UndoableEdit e) signale l'edit

UndoManager

C'est un CompoundEdit qui stocke une liste (un Vector) des derniers edits

Deux façons de l'utiliser :

En appelant boolean	addEdit(UndoableEdit anEdit)

En tant que UndoableEditListener

L'undo manager ne possède pas de mécanisme de listener indiquant que l'on vient d'effectuer un undo ou un redo.

Exemple

Application qui peut créer des shapes
en cliquant sur la zone graphique
et qui possède un menu undo/redo

Le ShapeModel

Modèle contenant les shapes

class ShapeModel {

public List<? extends Shape> getShapes() {

return unmodifiableShapes;

}

public UndoableEdit addShape(final String name,final Shape shape) {

...

}

protected void fireModelChanged() {

for(ChangeListener l:listeners)

l.stateChanged(event);

}

public void addChangeListener(ChangeListener listener) {

listeners.add(listener);

}

public void removeChangeListener(ChangeListener listener) {

listeners.remove(listener);

}

final ArrayList<Shape> shapes=new ArrayList<Shape>();

private final List<Shape> unmodifiableShapes=

Collections.unmodifiableList(shapes);

private final ArrayList<ChangeListener> listeners=

new ArrayList<ChangeListener>();

private final ChangeEvent event=new ChangeEvent(this);

}

La vue : ShapeCanvas

Vue affichant des shapes

class ShapeCanvas extends JComponent {

public ShapeCanvas(ShapeModel model) {

this.model=model;

model.addChangeListener(new ChangeListener() {

public void stateChanged(ChangeEvent e) {

paintImmediately(0,0,getWidth(),getHeight());

}

});

}

@Override

protected void paintComponent(Graphics g) {

g.clearRect(0,0,getWidth(),getHeight());

Graphics2D g2d=(Graphics2D)g;

for(Shape shape:model.getShapes())

g2d.draw(shape);

}

@Override

public Dimension getPreferredSize() {

return new Dimension(400,300);

}

}

Modèle avec undo/redo

Création d'un edit lors de l'ajout d'un shape

class ShapeModel {

public UndoableEdit addShape(final String name,final Shape shape) {

shapes.add(shape);

fireModelChanged();

AbstractUndoableEdit edit=new AbstractUndoableEdit() {

@Override

public void undo() throws CannotUndoException {

super.undo();

shapes.remove(shape);

fireModelChanged();

}

@Override

public void redo() throws CannotRedoException {

super.redo();

shapes.add(shape);

fireModelChanged();

}

@Override

public String getPresentationName() {

return name;

}

};

postEdit(edit);

return edit;

}

...

Modèle avec undo/redo

UndoableEditSupport permet la gestion des évènements

class ShapeModel {

public UndoableEdit addShape(final String name,final Shape shape) {

shapes.add(shape);

fireModelChanged();

AbstractUndoableEdit edit=new AbstractUndoableEdit() {

...

};

postEdit(edit);

return edit;

}

...

protected void postEdit(UndoableEdit edit) {

undoSupport.postEdit(edit);

}

public void addUndoableEditListener(UndoableEditListener listener) {

undoSupport.addUndoableEditListener(listener);

}

public void removeUndoableEditListener(UndoableEditListener listener) {

undoSupport.removeUndoableEditListener(listener);

}

private final UndoableEditSupport undoSupport=

new UndoableEditSupport(this);

}

UndoManager

Ajout de l'undo manager en tant que listener

class ShapeCanvas extends JComponent {

public ShapeCanvas(ShapeModel model) {

this.model=model;

model.addChangeListener(new ChangeListener() {

public void stateChanged(ChangeEvent e) {

...

}

});

model.addUndoableEditListener(manager);

}

public Action getUndoAction() {

...

}

public Action getRedoAction() {

...

}

private final ShapeModel model;

final UndoManager manager=new UndoManager();

}

Action Undo

Action undo dépend du undo manager

class ShapeCanvas extends JComponent {

public Action getUndoAction() {

final AbstractEditAction undo=new AbstractEditAction("undo") {

public void actionPerformed(java.awt.event.ActionEvent e) {

manager.undo();

}

@Override

protected void updateState() {

setEnabled(manager.canUndo());

putValue(Action.NAME,manager.getUndoPresentationName());

}

};

model.addUndoableEditListener(undo);

model.addChangeListener(undo);

undo.setEnabled(false);

return undo;

}

...

final UndoManager manager=new UndoManager();

}

Action Undo / suite

La classe AbstractEditAction

abstract class AbstractEditAction extends AbstractAction

implements UndoableEditListener, ChangeListener {

public AbstractEditAction(String name) {

super(name);

}

public void undoableEditHappened(UndoableEditEvent e) {

updateState();

}

public void stateChanged(ChangeEvent e) {

updateState();

}

protected abstract void updateState();

}

Action Redo

L'action corrrespondant à un redo

class ShapeCanvas extends JComponent {

public Action getRedoAction() {

final AbstractEditAction redo=new AbstractEditAction("undo") {

public void actionPerformed(java.awt.event.ActionEvent e) {

manager.redo();

}

@Override

protected void updateState() {

setEnabled(manager.canRedo());

putValue(Action.NAME,manager.getRedoPresentationName());

}

};

model.addUndoableEditListener(redo);

model.addChangeListener(redo);

redo.setEnabled(false);

return redo;

}

...

final UndoManager manager=new UndoManager();

}

Le main

Le main

public static void main(String[] args) {

final ShapeModel model=new ShapeModel();

ShapeCanvas canvas=new ShapeCanvas(model);

canvas.addMouseListener(new MouseAdapter() {

@Override

public void mouseClicked(MouseEvent e) {

if (random.nextBoolean())

model.add("rectangle",new Rectangle(e.getX(),e.getY(),50,50));

else

model.add("ellipse",new Ellipse2D.Float(e.getX(),e.getY(),50,50));

}

private final Random random=new Random();

});

JMenu edit=new JMenu("Edit");

edit.add(canvas.getUndoAction());

edit.add(canvas.getRedoAction());

JMenuBar bar=new JMenuBar();

bar.add(edit);

JFrame frame=new JFrame("Undo/Redo");

frame.setJMenuBar(bar);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.add(canvas);

frame.pack();

frame.setVisible(true);

}

Exemple

A tous moment, il est possible de faire un undo/redo

Forax Rémi

2004-11-19T16:43:19