Source code: jaxe/JaxeTextPane.java
1 /*
2 Jaxe - Editeur XML en Java
3
4 Copyright (C) 2003 Observatoire de Paris
5
6 Ce programme est un logiciel libre ; vous pouvez le redistribuer et/ou le modifier conformément aux dispositions de la Licence Publique Générale GNU, telle que publiée par la Free Software Foundation ; version 2 de la licence, ou encore (à votre choix) toute version ultérieure.
7
8 Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou D'ADAPTATION A UN OBJET PARTICULIER. Pour plus de détail, voir la Licence Publique Générale GNU .
9
10 Vous devez avoir reçu un exemplaire de la Licence Publique Générale GNU en même temps que ce programme ; si ce n'est pas le cas, écrivez à la Free Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, Etats-Unis.
11 */
12
13 package jaxe;
14
15 import jaxe.elements.JETexte;
16 import jaxe.elements.JEStyle;
17 import jaxe.elements.JESwing;
18
19 import java.awt.*;
20 import java.awt.datatransfer.*;
21 import java.awt.event.*;
22 import java.util.*;
23 import javax.swing.*;
24 import javax.swing.event.*;
25 // attention à ne pas confondre org.w3c.dom.Document avec javax.swing.text.Document
26 import javax.swing.text.*;
27 import javax.swing.undo.*;
28
29 import org.w3c.dom.Element;
30 import org.w3c.dom.Node;
31
32
33 /**
34 * Zone de texte éditable correspondant à un document XML.
35 * Peut être utilisée indépendamment de JaxeFrame et JaxeMenuBar.
36 */
37 public class JaxeTextPane extends JTextPane implements ClipboardOwner {
38
39 static int cmdMenu;
40
41 //undo helpers
42 private UndoManager undo = new UndoManager();
43 private boolean ignorerEdition = false;
44 private boolean editionSpeciale = false;
45 private CompoundEdit editSpecial;
46 private int niveauEditionSpeciale = 0;
47 private Stack ignorerEditionStack = new Stack(); // de Boolean
48
49 private static Object pressePapier = null;
50 private static String ppTexte = null;
51
52 private String texteRecherche = null;
53
54 private ArrayList ecouteursArbre = new ArrayList();
55 private ArrayList ecouteursAnnulation = new ArrayList();
56
57 private JaxeDocument doc;
58
59 public JFrame jframe;
60
61
62 public JaxeTextPane(JaxeDocument doc, JFrame jframe) {
63 super();
64 setEditorKit(doc.createEditorKit());
65 setStyledDocument(doc);
66 this.doc = doc;
67 this.jframe = jframe;
68 doc.setTextPane(this);
69 cmdMenu = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
70
71 Keymap kmap = getKeymap();
72 KeyStroke cmdx = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, cmdMenu);
73 kmap.removeKeyStrokeBinding(cmdx);
74 kmap.addActionForKeyStroke(cmdx, new ActionCouper());
75 KeyStroke cmdc = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_C, cmdMenu);
76 kmap.removeKeyStrokeBinding(cmdc);
77 kmap.addActionForKeyStroke(cmdc, new ActionCopier());
78 KeyStroke cmdv = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_V, cmdMenu);
79 kmap.removeKeyStrokeBinding(cmdv);
80 kmap.addActionForKeyStroke(cmdv, new ActionColler());
81
82 doc.addUndoableEditListener(new MyUndoableEditListener());
83 addCaretListener(new MyCaretListener());
84
85 setTabs(4);
86 }
87
88 public UndoManager getUndo() {
89 return(undo);
90 }
91
92 public void undo() {
93 try {
94 undo.undo();
95 } catch (CannotUndoException ex) {
96 System.out.println(JaxeResourceBundle.getRB().getString("annulation.ImpossibleAnnuler") +
97 ": " + ex);
98 ex.printStackTrace();
99 }
100 miseAJourAnnulation();
101 }
102
103 public boolean getEditionSpeciale() {
104 return(editionSpeciale);
105 }
106
107 public boolean getIgnorerEdition() {
108 return(ignorerEdition);
109 }
110
111 // inspiré de DefaultEditorKit.CutAction, mais EN FRANCAIS
112 protected class ActionCouper extends TextAction {
113 public ActionCouper() {
114 super(JaxeResourceBundle.getRB().getString("menus.Couper"));
115 }
116
117 public void actionPerformed(ActionEvent e) {
118 JTextComponent target = getTextComponent(e);
119 if (target instanceof JaxeTextPane)
120 ((JaxeTextPane)target).couper();
121 }
122 }
123
124 protected class ActionCopier extends TextAction {
125 public ActionCopier() {
126 super(JaxeResourceBundle.getRB().getString("menus.Copier"));
127 }
128
129 public void actionPerformed(ActionEvent e) {
130 JTextComponent target = getTextComponent(e);
131 if (target instanceof JaxeTextPane)
132 ((JaxeTextPane)target).copier();
133 }
134 }
135
136 protected class ActionColler extends TextAction {
137 public ActionColler() {
138 super(JaxeResourceBundle.getRB().getString("menus.Coller"));
139 }
140
141 public void actionPerformed(ActionEvent e) {
142 JTextComponent target = getTextComponent(e);
143 if (target instanceof JaxeTextPane)
144 ((JaxeTextPane)target).coller();
145 }
146 }
147
148 public void processMouseEvent(MouseEvent e){
149 if(e.isPopupTrigger() && this.isEditable()){
150 showPopup(e);
151 }else{
152 super.processMouseEvent(e);
153 }
154 }
155
156 private void showPopup(MouseEvent e) {
157 if (e.isPopupTrigger()) {
158 JPopupMenu popup = new JPopupMenu();
159 int pos = viewToModel(e.getPoint());
160 JaxeElement je = doc.elementA(pos);
161 if (je == null)
162 return;
163
164 if (je instanceof JETexte)
165 je = je.getParent();
166 if (pos == je.debut.getOffset() && !(je instanceof JESwing))
167 je = je.getParent();
168
169 if (!je.getEditionAutorisee())
170 return;
171
172 setCaretPosition(pos);
173 moveCaretPosition(pos);
174
175 Element parentdef = doc.cfg.getBaliseDef(je.noeud.getNodeName());
176 ArrayList autorisees = doc.cfg.listeSousbalises(parentdef);
177 for (int i=0; i<autorisees.size(); i++) {
178 String nombalise = (String)autorisees.get(i);
179 Element balisedef = doc.cfg.getBaliseDef(nombalise);
180 if (balisedef == null)
181 ;//System.err.println("Erreur: Impossible de trouver la définition de " + nombalise);
182 else if (!"style".equals(doc.cfg.typeBalise(balisedef)))
183 popup.add(new ActionInsertionBalise(doc, balisedef));
184 }
185
186 if (autorisees.size() > 0) { // Seperator between elements and Copy'n'Paste
187 popup.addSeparator();
188 }
189
190 if (getSelectionEnd() != getSelectionStart()) { // Copy allowed ?
191 popup.add(new ActionCouper());
192 popup.add(new ActionCopier());
193 }
194
195 popup.add(new ActionColler());
196
197 popup.show(e.getComponent(), e.getX(), e.getY());
198 }
199 }
200
201
202 public void selectZone(int debut, int fin, boolean select) {
203 ArrayList tel = doc.rootJE.elementsDans(debut, fin-1);
204 if (select) {
205 // on change la sélection pour ne pas inclure des moitié d'éléments (sauf pour le texte)
206 int debut2;
207 int fin2;
208 int ndebut = debut;
209 int nfin = fin;
210 do {
211 debut2 = ndebut;
212 fin2 = nfin;
213 JaxeElement firstel = doc.rootJE.elementA(debut2);
214 if (firstel instanceof JETexte)
215 firstel = firstel.getParent();
216 if (firstel.fin.getOffset() < nfin-1 && !tel.contains(firstel))
217 ndebut = firstel.fin.getOffset() + 1;
218 if (firstel.fin.getOffset() == nfin-1 && !tel.contains(firstel)) {
219 if (!(firstel instanceof JEStyle))
220 nfin = firstel.fin.getOffset();
221 }
222 if (firstel.debut.getOffset() == ndebut && !tel.contains(firstel) &&
223 !(firstel instanceof JEStyle) && !(firstel instanceof JESwing))
224 ndebut++;
225 JaxeElement lastel = doc.rootJE.elementA(fin2);
226 if (lastel != null && lastel.debut.getOffset() == fin2)
227 lastel = lastel.getParent();
228 if (lastel instanceof JETexte)
229 lastel = lastel.getParent();
230 if (lastel == null)
231 nfin = fin2 - 1;
232 else if (lastel.debut.getOffset() == ndebut && !tel.contains(lastel) &&
233 !(lastel instanceof JEStyle) && !(lastel instanceof JESwing))
234 ndebut++;
235 else if (lastel.debut.getOffset() > ndebut && !tel.contains(lastel))
236 nfin = lastel.debut.getOffset();
237 if (nfin < ndebut)
238 nfin = ndebut;
239 } while (ndebut != debut2 || nfin != fin2);
240 if (ndebut != debut || nfin != fin) {
241 if (nfin == ndebut)
242 nfin = ndebut = debut;
243 setCaretPosition(ndebut);
244 moveCaretPosition(nfin);
245 }
246 if (ndebut != debut || nfin != fin)
247 tel = doc.rootJE.elementsDans(ndebut, nfin-1);
248 }
249 for (int i=0; i< tel.size(); i++) {
250 JaxeElement je = (JaxeElement)tel.get(i);
251 je.selection(select);
252 }
253 }
254
255 /**
256 * Positionne le document à la ligne indiquée (la première ligne a le numéro 1)
257 */
258 public void allerLigne(int ligne) {
259 if (ligne > 0)
260 ligne--;
261 else
262 ligne = 0;
263 int pos = doc.getDefaultRootElement().getElement(ligne).getStartOffset();
264 // bidouille pour afficher la position en haut de la fenêtre
265 try {
266 scrollRectToVisible(modelToView(doc.getLength()));
267 scrollRectToVisible(modelToView(pos));
268 } catch (BadLocationException ex) {
269 }
270 }
271
272 public void debutIgnorerEdition() {
273 ignorerEdition = true;
274 }
275
276 public void finIgnorerEdition() {
277 ignorerEdition = false;
278 }
279
280 class EditSpecial extends CompoundEdit {
281 String titre;
282 public EditSpecial(String titre) {
283 this.titre = titre;
284 }
285 public String getPresentationName() {
286 return(titre);
287 }
288 public String getUndoPresentationName() {
289 return(JaxeResourceBundle.getRB().getString("menus.Annuler") + " " + titre);
290 }
291 public String getRedoPresentationName() {
292 return(JaxeResourceBundle.getRB().getString("menus.Retablir") + " " + titre);
293 }
294 }
295
296 /**
297 * Edition spéciale: combinaison d'un ensemble de JaxeUndoableEdit.
298 */
299 public void debutEditionSpeciale(String titre, boolean ignorerEdition) {
300 if (niveauEditionSpeciale < 0)
301 System.err.println("Erreur: niveauEditionSpeciale < 0 !");
302 if (niveauEditionSpeciale == 0) {
303 editSpecial = new EditSpecial(titre);
304 editionSpeciale = true;
305 this.ignorerEdition = ignorerEdition;
306 } else {
307 ignorerEditionStack.push(new Boolean(ignorerEdition));
308 this.ignorerEdition = ignorerEdition;
309 }
310 niveauEditionSpeciale += 1;
311 }
312
313 public void finEditionSpeciale() {
314 niveauEditionSpeciale -= 1;
315 if (niveauEditionSpeciale < 0)
316 System.err.println("Erreur: niveauEditionSpeciale < 0 !");
317 if (niveauEditionSpeciale == 0) {
318 editSpecial.end();
319 undo.addEdit(editSpecial);
320 miseAJourAnnulation();
321 editionSpeciale = false;
322 ignorerEdition = false;
323 editSpecial = null;
324 } else {
325 this.ignorerEdition = ((Boolean)ignorerEditionStack.pop()).booleanValue();
326 }
327 }
328
329 public void addEdit(UndoableEdit edit) {
330 if (editionSpeciale) {
331 editSpecial.addEdit(edit);
332 } else {
333 getUndo().addEdit(edit);
334 miseAJourAnnulation();
335 }
336 }
337
338 //This one listens for edits that can be undone.
339 protected class MyUndoableEditListener
340 implements UndoableEditListener {
341 public void undoableEditHappened(UndoableEditEvent e) {
342 //Remember the edit and update the menus.
343 if (!ignorerEdition) {
344 undo.addEdit(e.getEdit());
345 miseAJourAnnulation();
346 }
347 }
348 }
349
350 public void couper() {
351 int debut = getSelectionStart();
352 int fin = getSelectionEnd();
353 JaxeElement firstel = doc.rootJE.elementA(debut);
354 JaxeElement lastel = doc.rootJE.elementA(fin - 1);
355 if (firstel == lastel && firstel instanceof JETexte) {
356 cut();
357 pressePapier = null;
358 ppTexte = null;
359 } else {
360 Object pp = doc.copier(debut, fin);
361 if (pp != null) {
362 String s = doc.pp2string(pp);
363 Clipboard clip = getToolkit().getSystemClipboard();
364 StringSelection contents = new StringSelection(s);
365 clip.setContents(contents, this); // va appeler lostOwnership
366 pressePapier = pp;
367 ppTexte = s;
368 try {
369 doc.remove(debut, fin-debut);
370 } catch (BadLocationException ex) {
371 System.err.println("BadLocationException: " + ex.getMessage());
372 }
373 } else
374 getToolkit().beep();
375 }
376 }
377
378 public void copier() {
379 int debut = getSelectionStart();
380 int fin = getSelectionEnd();
381 JaxeElement firstel = doc.rootJE.elementA(debut);
382 JaxeElement lastel = doc.rootJE.elementA(fin - 1);
383 if (firstel == lastel && (firstel instanceof JETexte || firstel instanceof JEStyle)) {
384 copy();
385 pressePapier = null;
386 ppTexte = null;
387 } else {
388 Object pp = doc.copier(debut, fin);
389 if (pp != null) {
390 String s = doc.pp2string(pp);
391 Clipboard clip = getToolkit().getSystemClipboard();
392 StringSelection contents = new StringSelection(s);
393 clip.setContents(contents, this); // va appeler lostOwnership
394 pressePapier = pp;
395 ppTexte = s;
396 } else
397 getToolkit().beep();
398 }
399 }
400
401 public void coller() {
402 if (pressePapier != null) {
403 // test si contenu presse-papier = ppTexte
404 Toolkit tk = Toolkit.getDefaultToolkit();
405 Clipboard clip = tk.getSystemClipboard();
406 Transferable trans = clip.getContents(this);
407 if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
408 String spp;
409 try {
410 spp = (String)trans.getTransferData(DataFlavor.stringFlavor);
411 } catch (Exception ex) {
412 spp = null;
413 }
414 if (spp != null && spp.equals(ppTexte)) {
415 try {
416 doc.coller(pressePapier, doc.createPosition(getCaretPosition()));
417 } catch (BadLocationException ex) {
418 System.err.println("BadLocationException: " + ex.getMessage());
419 }
420 } else
421 doc.coller(this);
422 } else
423 doc.coller(this);
424 } else
425 doc.coller(this);
426 }
427
428 public void toutSelectionner() {
429 setCaretPosition(0);
430 moveCaretPosition(doc.getLength());
431 }
432
433 public void rechercher() {
434 DialogueRechercher dlg = new DialogueRechercher(this);
435 dlg.show();
436 texteRecherche = dlg.getTexteRecherche();
437 }
438
439 public void rechercher(String s) {
440 texteRecherche = s;
441 int len = texteRecherche.length();
442 int ind = -1;
443 String text;
444 // recherche bourrin
445 try {
446 for (int i=0; i<doc.getLength()-len; i++) {
447 text = doc.getText(i, len);
448 if (text.equals(texteRecherche)) {
449 ind = i;
450 break;
451 }
452 }
453 } catch (BadLocationException ex) {
454 System.err.println("BadLocationException: " + ex.getMessage());
455 return;
456 }
457 if (ind != -1) {
458 setCaretPosition(ind);
459 moveCaretPosition(ind+len);
460 } else
461 getToolkit().beep();
462 }
463
464 public void suivant() {
465 if (texteRecherche == null || texteRecherche.length() == 0)
466 return;
467 int len = texteRecherche.length();
468 int ind = -1;
469 int i0 = getCaretPosition() + 1;
470 String text;
471 // recherche bourrin
472 try {
473 for (int i=i0; i<doc.getLength()-len; i++) {
474 text = doc.getText(i, len);
475 if (text.equals(texteRecherche)) {
476 ind = i;
477 break;
478 }
479 }
480 } catch (BadLocationException ex) {
481 System.err.println("BadLocationException: " + ex.getMessage());
482 return;
483 }
484 if (ind != -1) {
485 setCaretPosition(ind);
486 moveCaretPosition(ind+len);
487 } else
488 getToolkit().beep();
489 }
490
491 public void lostOwnership(Clipboard clipboard, Transferable contents) { // ne marche pas :(
492 pressePapier = null;
493 }
494
495 public void ajouterEcouteurArbre(EcouteurMAJ ec) {
496 ecouteursArbre.add(ec);
497 }
498
499 public void retirerEcouteurArbre(EcouteurMAJ ec) {
500 ecouteursArbre.remove(ec);
501 }
502
503 public void miseAJourArbre() {
504 for (int i=0; i<ecouteursArbre.size(); i++)
505 ((EcouteurMAJ)ecouteursArbre.get(i)).miseAJour();
506 }
507
508 public void ajouterEcouteurAnnulation(EcouteurMAJ ec) {
509 ecouteursAnnulation.add(ec);
510 }
511
512 public void retirerEcouteurAnnulation(EcouteurMAJ ec) {
513 ecouteursAnnulation.remove(ec);
514 }
515
516 public void miseAJourAnnulation() {
517 for (int i=0; i<ecouteursAnnulation.size(); i++)
518 ((EcouteurMAJ)ecouteursAnnulation.get(i)).miseAJour();
519 }
520
521 //This listens for and reports caret movements.
522 protected class MyCaretListener implements CaretListener {
523
524 int vdot = 0;
525 int vmark = 0;
526
527 public void caretUpdate(CaretEvent e) {
528 int dot = e.getDot();
529 int mark = e.getMark();
530 if (dot == mark) { // no selection
531 if (vmark - vdot > 0) // on déselectionne
532 selectZone(vdot, vmark, false);
533 } else { //la sélection des images du texte n'est pas gérée par Swing !
534 if (dot > mark) {
535 dot += mark; // faut pas gâcher les variables
536 mark = dot - mark;
537 dot = dot - mark;
538 }
539 if (vdot != dot || vmark != mark)
540 selectZone(vdot, vmark, false);
541 selectZone(dot, mark, true);
542 }
543 vdot = dot;
544 vmark = mark;
545 }
546 }
547
548 public void setTabs(int charactersPerTab) {
549 FontMetrics fm = getFontMetrics(getFont());
550 int charWidth = fm.charWidth('w');
551 int tabWidth = charWidth * charactersPerTab;
552
553 TabStop[] tabs = new TabStop[10];
554
555 for (int j = 0; j < tabs.length; j++) {
556 int tab = j + 1;
557 tabs[j] = new TabStop( tab * tabWidth );
558 }
559
560 TabSet tabSet = new TabSet(tabs);
561 SimpleAttributeSet attributes = new SimpleAttributeSet();
562 StyleConstants.setTabSet(attributes, tabSet);
563 int length = doc.getLength();
564 doc.setParagraphAttributes(0, length, attributes, false);
565 }
566 }