/* Jaxe - Editeur XML en Java Copyright (C) 2003 Observatoire de Paris 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. 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 . 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. */ package jaxe; import java.awt.Component; import java.awt.Container; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.io.StringReader; import java.util.ArrayList; import java.util.Stack; import javax.swing.AbstractAction; import javax.swing.JFrame; import javax.swing.JPopupMenu; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultHighlighter; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.Position; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.TabSet; import javax.swing.text.TabStop; import javax.swing.text.TextAction; import javax.swing.undo.CannotUndoException; import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoableEdit; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import jaxe.elements.JEStyle; import jaxe.elements.JESwing; import jaxe.elements.JETexte; import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.ProcessingInstruction; import org.xml.sax.InputSource; /** * Zone de texte éditable correspondant à un document XML. Peut être utilisée * indépendamment de JaxeFrame et JaxeMenuBar. */ public class JaxeTextPane extends JTextPane { static int cmdMenu; //undo helpers private JaxeUndoManager undo = new JaxeUndoManager(); private boolean ignorerEdition = false; private boolean editionSpeciale = false; private CompoundEdit editSpecial; private int niveauEditionSpeciale = 0; private Stack ignorerEditionStack = new Stack(); // de Boolean private static Object pressePapier = null; private static String ppTexte = null; private static JTPClipOwner clipOwner = new JTPClipOwner(); static String texteRecherche = null; private ArrayList ecouteursArbre = new ArrayList(); private ArrayList ecouteursAnnulation = new ArrayList(); private DialogueRechercher dlg = null; private JaxeDocument doc; public JFrame jframe; public JaxeTextPane(JaxeDocument doc, JFrame jframe) { super(); setEditorKit(doc.createEditorKit()); setStyledDocument(doc); this.doc = doc; this.jframe = jframe; doc.setTextPane(this); // setFont Serif to workaround a bug in the Java 1.4.2 JVM on MacOS X 10.3 // where Lucida Grande (the default font) does not have italic glyphs setFont(new Font(JaxeDocument.kPoliceParDefaut, Font.PLAIN, JaxeDocument.kTailleParDefaut)); cmdMenu = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); Keymap kmap = getKeymap(); KeyStroke cmdx = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, cmdMenu); kmap.removeKeyStrokeBinding(cmdx); kmap.addActionForKeyStroke(cmdx, new ActionCouper()); KeyStroke cmdc = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_C, cmdMenu); kmap.removeKeyStrokeBinding(cmdc); kmap.addActionForKeyStroke(cmdc, new ActionCopier()); KeyStroke cmdv = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_V, cmdMenu); kmap.removeKeyStrokeBinding(cmdv); kmap.addActionForKeyStroke(cmdv, new ActionColler()); KeyStroke cmdsp = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, cmdMenu); kmap.addActionForKeyStroke(cmdsp, new ActionMenuContextuel()); doc.addUndoableEditListener(new MyUndoableEditListener()); addCaretListener(new MyCaretListener()); setTabs(4); setHighlighter(new JaxeHighlighter()); } class JaxeHighlighter extends DefaultHighlighter { public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException { Object o = super.addHighlight(p0, p1, p); selectZone(p0, p1, true, false); return (o); } public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException { Highlighter.Highlight highlight = (Highlighter.Highlight) tag; int v0 = highlight.getStartOffset(); int v1 = highlight.getEndOffset(); super.changeHighlight(tag, p0, p1); selectZone(v0, v1, false, false); selectZone(p0, p1, true, false); return; } public void removeHighlight(Object tag) { super.removeHighlight(tag); Highlighter.Highlight highlight = (Highlighter.Highlight) tag; selectZone(highlight.getStartOffset(), highlight.getEndOffset(), false, false); return; } } public JaxeUndoManager getUndo() { return (undo); } public void undo() { try { undo.undo(); } catch (CannotUndoException ex) { System.out.println(JaxeResourceBundle.getRB().getString( "annulation.ImpossibleAnnuler") + ": " + ex); ex.printStackTrace(); } miseAJourAnnulation(); } public boolean getEditionSpeciale() { return (editionSpeciale); } public boolean getIgnorerEdition() { return (ignorerEdition); } // inspiré de DefaultEditorKit.CutAction, mais EN FRANCAIS protected class ActionCouper extends TextAction { public ActionCouper() { super(JaxeResourceBundle.getRB().getString("menus.Couper")); } public void actionPerformed(ActionEvent e) { JTextComponent target = getTextComponent(e); if (target instanceof JaxeTextPane) ((JaxeTextPane) target).couper(); } } protected class ActionCopier extends TextAction { public ActionCopier() { super(JaxeResourceBundle.getRB().getString("menus.Copier")); } public void actionPerformed(ActionEvent e) { JTextComponent target = getTextComponent(e); if (target instanceof JaxeTextPane) ((JaxeTextPane) target).copier(); } } protected class ActionColler extends TextAction { public ActionColler() { super(JaxeResourceBundle.getRB().getString("menus.Coller")); } public void actionPerformed(ActionEvent e) { JTextComponent target = getTextComponent(e); if (target instanceof JaxeTextPane) ((JaxeTextPane) target).coller(); } } protected class ActionMenuContextuel extends TextAction { public ActionMenuContextuel() { super("menuContextuel"); } public void actionPerformed(ActionEvent e) { JTextComponent target = getTextComponent(e); if (target instanceof JaxeTextPane) ((JaxeTextPane) target).menuContextuel(target .getCaretPosition(), null); } } public void processMouseEvent(MouseEvent e) { if (e.isPopupTrigger() && this.isEditable()) { showPopup(e); } else { super.processMouseEvent(e); } } private void showPopup(MouseEvent e) { if (e.isPopupTrigger()) menuContextuel(-1, e.getPoint()); } // pos != -1 || pt != null private void menuContextuel(int pos, Point pt) { if (pos == -1 && pt == null) return; if (pos == -1 && doc.rootJE != null) pos = viewToModel(pt); if (pt == null) { try { Rectangle r = modelToView(pos); pt = r.getLocation(); } catch (BadLocationException ex) { ex.printStackTrace(); return; } } JPopupMenu popup = new JPopupMenu(); ArrayList autorisees = null; Config conf; JaxeElement je; if (doc.rootJE == null) { je = null; conf = doc.cfg; autorisees = conf.listeRacines(); } else { je = doc.elementA(pos); if (je == null) return; if (je instanceof JETexte) je = je.getParent(); if (pos == je.debut.getOffset() && !(je instanceof JESwing)) je = je.getParent(); if (je == null || !je.getEditionAutorisee()) return; int start = getSelectionStart(); int end = getSelectionEnd(); if (start == end || pos < start || pos > end) { setCaretPosition(pos); moveCaretPosition(pos); } if (doc.cfg == null) { conf = null; autorisees = new ArrayList(); } else { conf = doc.cfg.getElementConf((Element) je.noeud); Element parentdef = conf.getBaliseDef(je.noeud.getNodeName()); autorisees = conf.listeSousbalises(parentdef); } } Position ppos; try { ppos = doc.createPosition(pos); } catch (BadLocationException ble) { System.err.println("BadLocationException: " + ble.getMessage()); ppos = null; } for (int i = 0; i < autorisees.size(); i++) { String nombalise = (String) autorisees.get(i); Element balisedef = conf.getBaliseDef(nombalise); if (balisedef != null) { boolean cache = "true".equals(balisedef.getAttribute("cache")); if (!("style".equals(doc.cfg.typeBalise(balisedef))) && (!cache)) { if (je == null || conf.insertionPossible(je, ppos, balisedef)) popup.add(new ActionInsertionBalise(doc, balisedef)); } } } if (autorisees.size() > 0) { // Seperator between elements and // Copy'n'Paste popup.addSeparator(); } if (getSelectionEnd() != getSelectionStart()) { // Copy allowed ? popup.add(new ActionCouper()); popup.add(new ActionCopier()); } popup.add(new ActionColler()); if (je != null && conf != null) { popup.addSeparator(); popup.add(new ActionAide(conf.getElementDef((Element) je.noeud))); } popup.show(this, pt.x, pt.y); } class ActionAide extends AbstractAction { Element balisedef; ActionAide(Element balisedef) { super(JaxeResourceBundle.getRB().getString("aide.element") + " " + doc.cfg.nomBalise(balisedef)); this.balisedef = balisedef; } public void actionPerformed(ActionEvent e) { DialogueAideElement dlg = new DialogueAideElement(balisedef, doc.cfg.getDefConf(balisedef), (JFrame) getTopLevelAncestor()); dlg.show(); } } public void selectZone(int debut, int fin, boolean select, boolean modsel) { ArrayList tel = doc.rootJE.elementsDans(debut, fin - 1); if (select) { // on change la sélection pour ne pas inclure des moitié d'éléments // (sauf pour le texte) int debut2; int fin2; int ndebut = debut; int nfin = fin; do { debut2 = ndebut; fin2 = nfin; JaxeElement firstel = doc.rootJE.elementA(debut2); if (firstel instanceof JETexte || firstel instanceof JEStyle) firstel = firstel.getParent(); while (firstel.debut.getOffset() == debut2 && firstel.getParent() instanceof JESwing) firstel = firstel.getParent(); if (firstel.fin.getOffset() < nfin - 1 && !tel.contains(firstel) || firstel.noeud instanceof ProcessingInstruction) { ndebut = firstel.fin.getOffset() + 1; } if (firstel.fin.getOffset() == nfin - 1 && !tel.contains(firstel)) { nfin = firstel.fin.getOffset(); } if (firstel.debut.getOffset() == ndebut && !tel.contains(firstel) && !(firstel instanceof JESwing)) ndebut++; JaxeElement lastel = doc.rootJE.elementA(fin2); if (lastel != null && lastel.fin.getOffset() == fin2 && lastel.getParent() instanceof JESwing) lastel = lastel.getParent(); if (lastel != null && lastel.debut.getOffset() == fin2) lastel = lastel.getParent(); if (lastel instanceof JETexte || lastel instanceof JEStyle) lastel = lastel.getParent(); if (doc.rootJE.elementA(nfin - 1).noeud instanceof ProcessingInstruction) { lastel = doc.rootJE.elementA(nfin - 1); nfin = lastel.debut.getOffset(); } else if (lastel == null) nfin = fin2 - 1; else if (lastel.debut.getOffset() == ndebut && !tel.contains(lastel) && !(lastel instanceof JESwing)) ndebut++; else if (lastel.debut.getOffset() > ndebut && !tel.contains(lastel)) { nfin = lastel.debut.getOffset(); } if (nfin < ndebut) nfin = ndebut; } while (ndebut != debut2 || nfin != fin2); if (modsel && (ndebut != debut || nfin != fin)) { if (nfin == ndebut) nfin = ndebut = debut; setCaretPosition(ndebut); moveCaretPosition(nfin); } if (ndebut != debut || nfin != fin) tel = doc.rootJE.elementsDans(ndebut, nfin - 1); } for (int i = 0; i < tel.size(); i++) { JaxeElement je = (JaxeElement) tel.get(i); je.selection(select); } } /** * Positionne le document à la ligne indiquée (la première ligne a le numéro * 1) */ public void allerLigne(int ligne) { if (ligne > 0) ligne--; else ligne = 0; int pos = doc.getDefaultRootElement().getElement(ligne) .getStartOffset(); // bidouille pour afficher la position en haut de la fenêtre try { scrollRectToVisible(modelToView(doc.getLength())); scrollRectToVisible(modelToView(pos)); } catch (BadLocationException ex) { } } public void debutIgnorerEdition() { ignorerEdition = true; } public void finIgnorerEdition() { ignorerEdition = false; } class EditSpecial extends CompoundEdit { String titre; public EditSpecial(String titre) { this.titre = titre; } public String getPresentationName() { return (titre); } public String getUndoPresentationName() { return (JaxeResourceBundle.getRB().getString("menus.Annuler") + " " + titre); } public String getRedoPresentationName() { return (JaxeResourceBundle.getRB().getString("menus.Retablir") + " " + titre); } } /** * Edition spéciale: combinaison d'un ensemble de JaxeUndoableEdit. */ public void debutEditionSpeciale(String titre, boolean ignorerEdition) { if (niveauEditionSpeciale < 0) System.err.println("Erreur: niveauEditionSpeciale < 0 !"); if (niveauEditionSpeciale == 0) { editSpecial = new EditSpecial(titre); editionSpeciale = true; this.ignorerEdition = ignorerEdition; } else { ignorerEditionStack.push(new Boolean(ignorerEdition)); this.ignorerEdition = ignorerEdition; } niveauEditionSpeciale += 1; } public void finEditionSpeciale() { niveauEditionSpeciale -= 1; if (niveauEditionSpeciale < 0) System.err.println("Erreur: niveauEditionSpeciale < 0 !"); if (niveauEditionSpeciale == 0) { editSpecial.end(); undo.addEdit(editSpecial); miseAJourAnnulation(); editionSpeciale = false; ignorerEdition = false; editSpecial = null; } else { this.ignorerEdition = ((Boolean) ignorerEditionStack.pop()) .booleanValue(); } } public void addEdit(UndoableEdit edit) { if (editionSpeciale) { editSpecial.addEdit(edit); } else { getUndo().addEdit(edit); miseAJourAnnulation(); } } //This one listens for edits that can be undone. protected class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { //Remember the edit and update the menus. if (!ignorerEdition) { undo.addEdit(e.getEdit()); miseAJourAnnulation(); } } } public void couper() { int debut = getSelectionStart(); int fin = getSelectionEnd(); couper(debut, fin); } /** * Cuts something out of the document * @param debut Startposition * @param fin Endposition */ public void couper(int debut, int fin) { JaxeElement firstel = doc.rootJE.elementA(debut); JaxeElement lastel = doc.rootJE.elementA(fin - 1); if (firstel == lastel && firstel instanceof JETexte) { pressePapier = null; ppTexte = null; cut(); } else { Object pp = doc.copier(debut, fin); if (pp != null) { String s = doc.pp2string(pp); Clipboard clip = getToolkit().getSystemClipboard(); StringSelection contents = new StringSelection(s); clip.setContents(contents, clipOwner); // va appeler lostOwnership pressePapier = pp; ppTexte = s; try { doc.remove(debut, fin - debut); } catch (BadLocationException ex) { System.err.println("BadLocationException: " + ex.getMessage()); } } else getToolkit().beep(); } } public void copier() { int debut = getSelectionStart(); int fin = getSelectionEnd(); JaxeElement firstel = doc.rootJE.elementA(debut); JaxeElement lastel = doc.rootJE.elementA(fin - 1); if (firstel == lastel && (firstel instanceof JETexte)) { pressePapier = null; ppTexte = null; copy(); } else { Object pp = doc.removeProcessingInstructions(doc.copier(debut, fin)); if (pp != null) { String s = doc.pp2string(pp); Clipboard clip = getToolkit().getSystemClipboard(); StringSelection contents = new StringSelection(s); clip.setContents(contents, clipOwner); // va appeler lostOwnership pressePapier = pp; ppTexte = s; } else getToolkit().beep(); } } public boolean coller() { // test si contenu presse-papier = ppTexte boolean accept = false; Toolkit tk = Toolkit.getDefaultToolkit(); Clipboard clip = tk.getSystemClipboard(); Transferable trans = clip.getContents(this); if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) { Object frag = pressePapier; String spp; try { spp = (String) trans.getTransferData(DataFlavor.stringFlavor); } catch (Exception ex) { spp = null; } if (spp == null || !spp.equals(ppTexte)) frag = null; if (frag == null && spp != null) frag = analyseString(spp); if (frag != null) { try { accept = doc.coller(frag, doc.createPosition(getCaretPosition())); } catch (BadLocationException ex) { System.err.println("BadLocationException: " + ex.getMessage()); } } else { doc.coller(this); accept = true; } } else { doc.coller(this); accept = true; } return accept; } /** * Efface la référence au fragment XML du presse-papier pour pouvoir lire * autre chose ne venant pas de Jaxe. Est appelé par JTPClipOwner.lostOwnership(). */ public static void effacerPressePapier() { pressePapier = null; } private DocumentFragment analyseString(String g) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentFragment df = doc.DOMdoc.createDocumentFragment(); try { DocumentBuilder builder = factory.newDocumentBuilder(); String balise1doc, balise2doc; Element racine = doc.DOMdoc.getDocumentElement(); if (racine == null) { balise1doc = ""; balise2doc = ""; } else { balise1doc = "<" + racine.getNodeName(); // on reprend tous les attributs pour pouvoir obtenir les espaces de noms // en fonctions des préfixes NamedNodeMap attrs = racine.getAttributes(); for (int i=0; i"; } String sxmldoc = "" + balise1doc + g + balise2doc; org.w3c.dom.Document parseddoc = builder.parse(new InputSource(new StringReader(sxmldoc))); copieEnfants(doc.DOMdoc, parseddoc.getDocumentElement(), df); if (hasOnlyTextnodes(df)) df = null; } catch (Exception ex) { //System.err.println(ex.getClass().getName() + ": " + ex.getMessage()); df = null; } return df; } private boolean hasOnlyTextnodes(Node n) { if (n.hasChildNodes()) { Node child = n.getFirstChild(); while (child != null) { if (child.getNodeType() != Node.TEXT_NODE) return false; child = child.getNextSibling(); } } return true; } private void copieEnfants(org.w3c.dom.Document targetdoc, Node source, Node target) { Node child = source.getFirstChild(); while (child != null) { target.appendChild(targetdoc.importNode(child, true)); child = child.getNextSibling(); } } public void toutSelectionner() { setCaretPosition(0); moveCaretPosition(doc.getLength()); } public void rechercher() { if (dlg == null) dlg = new DialogueRechercher(doc, this); dlg.show(); } public void rechercher(String s) { texteRecherche = s; int len = texteRecherche.length(); int ind = -1; String text; // recherche bourrin try { for (int i = 0; i < doc.getLength() - len; i++) { text = doc.getText(i, len); if (text.equals(texteRecherche)) { ind = i; break; } } } catch (BadLocationException ex) { System.err.println("BadLocationException: " + ex.getMessage()); return; } if (ind != -1) { setCaretPosition(ind); moveCaretPosition(ind + len); } else getToolkit().beep(); } public void suivant() { if (dlg != null) { texteRecherche = dlg.getTexteRecherche(); if (dlg.RechXpath) dlg.suivantXpath(getSelectionStart()); else dlg.suivant(getSelectionStart()); } } public void ajouterEcouteurArbre(EcouteurMAJ ec) { ecouteursArbre.add(ec); } public void retirerEcouteurArbre(EcouteurMAJ ec) { ecouteursArbre.remove(ec); } public void miseAJourArbre() { for (int i = 0; i < ecouteursArbre.size(); i++) ((EcouteurMAJ) ecouteursArbre.get(i)).miseAJour(); } public void ajouterEcouteurAnnulation(EcouteurMAJ ec) { ecouteursAnnulation.add(ec); } public void retirerEcouteurAnnulation(EcouteurMAJ ec) { ecouteursAnnulation.remove(ec); } public void miseAJourAnnulation() { for (int i = 0; i < ecouteursAnnulation.size(); i++) ((EcouteurMAJ) ecouteursAnnulation.get(i)).miseAJour(); } //This listens for and reports caret movements. protected class MyCaretListener implements CaretListener { int vdot = 0; int vmark = 0; public void caretUpdate(CaretEvent e) { int dot = e.getDot(); int mark = e.getMark(); if (dot == mark) { // no selection if (vmark - vdot > 0) // on déselectionne selectZone(vdot, vmark, false, true); } else { //la sélection des images du texte n'est pas gérée par // Swing ! if (dot > mark) { dot += mark; // faut pas gâcher les variables mark = dot - mark; dot = dot - mark; } if (vdot != dot || vmark != mark) selectZone(vdot, vmark, false, true); selectZone(dot, mark, true, true); } vdot = dot; vmark = mark; } } public void setTabs(int charactersPerTab) { FontMetrics fm = getFontMetrics(getFont()); int charWidth = fm.charWidth('w'); int tabWidth = charWidth * charactersPerTab; TabStop[] tabs = new TabStop[10]; for (int j = 0; j < tabs.length; j++) { int tab = j + 1; tabs[j] = new TabStop(tab * tabWidth); } TabSet tabSet = new TabSet(tabs); SimpleAttributeSet attributes = new SimpleAttributeSet(); StyleConstants.setTabSet(attributes, tabSet); int length = doc.getLength(); debutIgnorerEdition(); doc.setParagraphAttributes(0, length, attributes, false); finIgnorerEdition(); } // evil kludge for Java bug 4839979 public void add(Component comp, Object constraints) { if (System.getProperty("java.version").startsWith("1.4.2") && comp.getClass().getName().indexOf( "ComponentView$Invalidator") != -1) { if (((Container) comp).getComponentCount() > 0) { // add a dummy component to the Invalidator Component child = ((Container) comp).getComponent(0); ((Container) comp).add(new Component() { }); super.add(comp, constraints); } } else super.add(comp, constraints); } }