/* 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.BorderLayout; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FileDialog; import java.awt.GridLayout; import java.awt.Point; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Properties; import java.util.ResourceBundle; import javax.swing.Action; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import jaxe.elements.JESwing; import org.w3c.dom.Element; import org.w3c.dom.Node; import textbender.recombinant.common.InstanceList; import textbender.recombinant.common.awt.ComponentBoundsPreference; import textbender.recombinant.common.awt.WindowX; import textbender.recombinant.common.hold.Spool1; import textbender.recombinant.common.util.prefs.PreferencesX; /** * Fenêtre de Jaxe */ public class JaxeFrame extends JFrame implements ComponentListener { private static ResourceBundle rb = JaxeResourceBundle.getRB(); private static final ArrayList iList = new ArrayList( /*initial capacity*/2 ); private JaxeTextPane textPane; private JScrollPane paneScrollPane; private JTabbedPane sidepane ; private ArbreXML arbrexml ; private AllowedElementsPanel allowed; private AttributePanel attpane; private JSplitPane split; private CaretListenerLabel caretListenerLabel; private boolean afficherSide = true; private boolean afficherArbre = true; private boolean afficherAllowed = true; private boolean afficherAttributs = true; private JaxeMenuBar menuBar; private String nomFichierCfg; private HTMLFrame htmlFrame = null; private ValidationFrame validationFrame = null; private SourceFrame sourceFrame = null; private JMenuBar barreBalises; public JaxeDocument doc; // private File fichierAOuvrir; private URI fichierAOuvrir; private File configAOuvrir; public JaxeFrame() { super("Jaxe"); assert java.awt.EventQueue.isDispatchThread(); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { fermer(false); } /*public void windowActivated(WindowEvent e) { if (System.getProperty("os.name").startsWith("Mac")) if (textPane != null) textPane.requestFocus(); // gnaaaargh ah ah ! je l'ai eu mon focus ! 8¬> // le requestFocus provoque des activate de fenêtres en boucle sur Windows // et maintenant sur Mac quand on fait Nouveau->Ouvrir }*/ }); Properties prefs = Preferences.getPref(); if (prefs != null) { String prefArbre = prefs.getProperty("fenetreArbre"); if (prefArbre != null) afficherArbre = "true".equals(prefArbre); String prefInsertion = prefs.getProperty("fenetreInsertion"); if (prefInsertion != null) afficherAllowed = "true".equals(prefInsertion); String prefAttributs = prefs.getProperty("fenetreAttributs"); if (prefAttributs != null) afficherAttributs = "true".equals(prefAttributs); afficherSide = afficherArbre || afficherAllowed || afficherAttributs; } menuBar = new JaxeMenuBar(this); setJMenuBar(menuBar); final Spool1 spool = new Spool1(); new WindowX.SpoolTie(JaxeFrame.this,spool).addListeners(); // unwind when frame disposed // no need to unregister, registry does not outlive listeners final int iIndex = InstanceList.register(JaxeFrame.this, iList, spool); // ...and so unregister Dimension ecran = getToolkit().getScreenSize(); int largeur = (ecran.width * 2) / 3; if (largeur < 750) largeur = ecran.width - 20; int hauteur = (ecran.height * 3) / 4; if (hauteur < 550) hauteur = ecran.height - 50; int posx, posy; if (Jaxe.allFrames.size() < 1) { posx = 10; posy = 40; } else { JaxeFrame lastj = (JaxeFrame)Jaxe.allFrames.get(Jaxe.allFrames.size()-1); Point jp = lastj.getLocationOnScreen(); posx = jp.x + 20; if (posx + largeur > ecran.width && ecran.width - posx > 300) largeur = ecran.width - posx; posy = jp.y + 20; } // setSize(new Dimension(largeur, hauteur)); // // la dimension pourrait être modifiée si pack était appelé dans initNew // setLocation(posx, posy); final java.util.prefs.Preferences preferences = PreferencesX.userNodeForClass(JaxeFrame.class).node(Integer.toString(iIndex)); ComponentBoundsPreference boundsP = new ComponentBoundsPreference(JaxeFrame.this, preferences); boundsP.size().restore(/*default*/largeur,hauteur, Common.i().slowRunBuffer()); boundsP.location().restore(/*default*/posx,posy, Common.i().slowRunBuffer()); } public JaxeTextPane getTextPane() { return(textPane); } public JaxeMenuBar getJaxeMenuBar() { return(menuBar); } public SourceFrame getSourceFrame() { return(sourceFrame); } public void setSourceFrame(SourceFrame sourceFrame) { this.sourceFrame = sourceFrame; } public void setAffichageSide(boolean visible) { if (afficherSide != visible) { if (!afficherSide) getContentPane().remove(paneScrollPane); else getContentPane().remove(split); afficherSide = visible; modifierSide(); validate(); textPane.getCaret().setVisible(true); } } public boolean getAffichageSide() { return(afficherSide); } public void setAffichageArbre(boolean visible) { if (afficherArbre != visible) { if (!visible) textPane.retirerEcouteurArbre(arbrexml); if (!afficherSide) getContentPane().remove(paneScrollPane); else getContentPane().remove(split); afficherArbre = visible; afficherSide = (afficherArbre || afficherAllowed || afficherAttributs); modifierSide(); validate(); textPane.getCaret().setVisible(true); } } public boolean getAffichageArbre() { return(afficherArbre); } public void setAffichageAllowed(boolean visible) { if (afficherAllowed != visible) { if (!visible) textPane.retirerEcouteurArbre(allowed); if (!afficherSide) getContentPane().remove(paneScrollPane); else getContentPane().remove(split); afficherAllowed = visible; afficherSide = (afficherArbre || afficherAllowed || afficherAttributs); modifierSide(); validate(); textPane.getCaret().setVisible(true); } } public boolean getAffichageAllowed() { return(afficherAllowed); } public void setAffichageAttributs(boolean visible) { if (afficherAttributs != visible) { if (!visible) textPane.retirerEcouteurArbre(attpane); if (!afficherSide) getContentPane().remove(paneScrollPane); else getContentPane().remove(split); afficherAttributs = visible; afficherSide = (afficherArbre || afficherAllowed || afficherAttributs); modifierSide(); validate(); textPane.getCaret().setVisible(true); } } public boolean getAffichageAttributs() { return(afficherAttributs); } public void modifierSide() { if (afficherSide) { sidepane = new JTabbedPane(); if (afficherAllowed) { allowed = new AllowedElementsPanel((JaxeDocument)textPane.getDocument()); textPane.addCaretListener(allowed); textPane.ajouterEcouteurArbre(allowed); sidepane.addTab(rb.getString("tabs.insertion"), allowed); } else allowed = null; if (afficherArbre) { arbrexml = new ArbreXML(doc) ; textPane.ajouterEcouteurArbre(arbrexml); sidepane.addTab(rb.getString("tabs.arbre"), arbrexml); } else arbrexml = null; if (afficherAttributs) { attpane = new AttributePanel((JaxeDocument)textPane.getDocument()); textPane.addCaretListener(attpane); textPane.ajouterEcouteurArbre(attpane); sidepane.addTab(rb.getString("tabs.attributs"), attpane); } else attpane = null; split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); split.setLeftComponent(sidepane); split.setRightComponent(paneScrollPane); split.setDividerLocation(250); //split.addPropertyChangeListener(new EcouteurPropriete()); getContentPane().add(split, BorderLayout.CENTER); } else { sidepane = null; arbrexml = null; allowed = null; attpane = null; getContentPane().add(paneScrollPane, BorderLayout.CENTER); } } // initialisation de l'objet Jaxe, appelé par le constructeur et doOpenConf et doNew public void initNew(String nomFichierCfg) { this.nomFichierCfg = nomFichierCfg; if (nomFichierCfg == null) { JOptionPane.showMessageDialog(this, rb.getString("erreur.ConfigIntrouvable"), rb.getString("erreur.Fatale"), JOptionPane.ERROR_MESSAGE); System.exit(1); } getContentPane().removeAll() ; try { doc = new JaxeDocument(nomFichierCfg); } catch(Exception e) { Common.i().showThrowable(e); System.exit(1); } affichageDoc(); doc.nouveau(); caretListenerLabel.setText(""); textPane.addCaretListener(caretListenerLabel); //if (doc.rootJE == null) majMenus(textPane.getCaretPosition()); // CaretListenerLabel.caretUpdate n'est pas appelé s'il n'y a pas de racine // ce n'est pas non plus appelé si windowActivated est désactivé setTitle(rb.getString("document.Nouveau")); //pack(); if (afficherArbre) arbrexml.newdoc(doc) ; setVisible(true); // lance une Exception avec bug Java 4353673 if (System.getProperty("java.version").startsWith("1.3")) // encore une bidouille pour éviter un bug de Java: // la deuxième balise division ne s'affiche pas si on ne fait pas ça ici textPane.revalidate(); if (htmlFrame != null) { htmlFrame.setVisible(false); htmlFrame.dispose(); htmlFrame = null; } if (validationFrame != null) { validationFrame.setVisible(false); validationFrame.dispose(); validationFrame = null; } if (sourceFrame != null) { sourceFrame.setVisible(false); sourceFrame.dispose(); sourceFrame = null; } } public void affichageDoc() { // zone de texte textPane = new JaxeTextPane(doc, this); doc.textPane = textPane; //textPane.setStyledDocument(doc); paneScrollPane = new JScrollPane(textPane); paneScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); paneScrollPane.setPreferredSize(new Dimension(500, 400)); paneScrollPane.setMinimumSize(new Dimension(100, 50)); // barre des menus du document if (doc.cfg == null) barreBalises = null; else barreBalises = doc.cfg.makeMenus(doc); //Create the status area. JPanel statusPane = new JPanel(new GridLayout(1, 1)); caretListenerLabel = new CaretListenerLabel(rb.getString("status.Chargement"), doc); statusPane.add(caretListenerLabel); if (barreBalises != null) getContentPane().add(barreBalises, BorderLayout.NORTH); getContentPane().add(statusPane, BorderLayout.SOUTH); modifierSide(); textPane.ajouterEcouteurAnnulation(menuBar); removeComponentListener(this); addComponentListener(this); validate(); } public void ouvrir(File f) { ouvrirAvecConf(f, null); } public void ouvrirAvecConf(File f, File fconf) { ouvrirAvecConf(f.toURI(), null); } public void ouvrir(String fileName) { ouvrirAvecConf(fileName, null); } public void ouvrirAvecConf(String fileName, File fconf) { try { URI uri = new URI(fileName); if (uri.getScheme() != null ) { ouvrirAvecConf(uri, fconf); return; } } catch(URISyntaxException x) {} // fall through ouvrirAvecConf(new File(fileName), fconf); } public void ouvrir(URI f) { ouvrirAvecConf(f, null); } public void ouvrirAvecConf(URI f, File fconf) { fichierAOuvrir = f; configAOuvrir = fconf; // on ne peut pas éviter le bug 4353673 si on utilise un Thread if (System.getProperty("java.version").startsWith("1.3")) ouvrirPlusTard(); else // (new ThreadOuverture()).start(); //// dangerous. If the intent was a slight, non-blocking delay, try one of these: Common.i().slowRunBuffer().schedule( new Runnable() { // EventQueue.invokeLater( new Runnable() { public final void run() { ouvrirPlusTard(); } // a little later }); } // class ThreadOuverture extends Thread { // public void run() { // ouvrirPlusTard(); // } // } // public void ouvrirPlusTard() { if (validationFrame != null) { validationFrame.setVisible(false); validationFrame.dispose(); validationFrame = null; } if (sourceFrame != null) { sourceFrame.setVisible(false); sourceFrame.dispose(); sourceFrame = null; } setVisible(true); DialogueAttente attente; if (System.getProperty("java.version").startsWith("1.3")) attente = null; else { attente = new DialogueAttente(this, rb.getString("status.Chargement"), 1, 100); attente.show(); } doc = new JaxeDocument(); getContentPane().removeAll() ; affichageDoc(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); getContentPane().setVisible(false); // to speed up loading with Java 1.4 // (otherwise Container.validate calls Component.updateCursorImmediately) if (attente != null) attente.setProgress(5); URL u ; try { u = fichierAOuvrir.toURL(); } catch (MalformedURLException ex) { System.err.println("MalformedURLException: " + ex.getMessage()); if (attente != null) attente.dispose(); setCursor(null); return; } String cheminConfig; if (configAOuvrir == null) cheminConfig = null; else cheminConfig = configAOuvrir.getAbsolutePath(); if (!doc.lire(u, cheminConfig)) { if (attente != null) attente.dispose(); setCursor(null); return; } if (attente != null) attente.setProgress(60); nomFichierCfg = doc.nomFichierCfg; setTitle(u.getFile()); caretListenerLabel.setText(""); textPane.addCaretListener(caretListenerLabel); // il faut refaire les menus parce-qu'on a lu le type de document (UEL, IUFM, DEA) if (barreBalises != null) getContentPane().remove(barreBalises); if (doc.cfg != null) { barreBalises = doc.cfg.makeMenus(doc); getContentPane().add(barreBalises, BorderLayout.NORTH); } else barreBalises = null; if (attente != null) attente.setProgress(90); if (arbrexml != null) arbrexml.newdoc(doc) ; if (afficherArbre) sidepane.setSelectedComponent(arbrexml); getContentPane().validate(); getContentPane().setVisible(true); textPane.setCaretPosition(0); if (doc.fsave != null && doc.cfg != null) { if (htmlFrame == null) { // on n'affiche le HTML que s'il existe déjà File htmlFile = HTMLFrame.fichierHTML(doc.fsave); if (htmlFile.exists()) htmlFrame = new HTMLFrame(doc); } else { htmlFrame.newdoc(doc); htmlFrame.setVisible(true); } } if (attente != null) attente.dispose(); setCursor(null); toFront(); } public boolean fermer(boolean quit) { if (doc != null && doc.modif) { int r = JOptionPane.showConfirmDialog(this, rb.getString("fermeture.EnregistrerAvant"), rb.getString("fermeture.Fermeture"), JOptionPane.YES_NO_CANCEL_OPTION); if (r == JOptionPane.YES_OPTION) enregistrer(); else if (r == JOptionPane.CANCEL_OPTION) return(false); } try { setVisible(false); } catch (Exception ex) { ex.printStackTrace(); // l'erreur causée par le bug 4353673 est ignorée } if (htmlFrame != null) { htmlFrame.setVisible(false); htmlFrame.dispose(); htmlFrame = null; } if (validationFrame != null) { validationFrame.setVisible(false); validationFrame.dispose(); validationFrame = null; } if (sourceFrame != null) { sourceFrame.setVisible(false); sourceFrame.dispose(); sourceFrame = null; } Jaxe.allFrames.remove(this); // aide pour le garbage collector try { getContentPane().removeAll(); validate(); } catch (Exception ex) { // still trying to workaround Java bug 4353673 or bug 4839979 System.err.println("fermer: " + ex.getClass().getName() + ": " + ex.getMessage()); } textPane = null; paneScrollPane = null; sidepane = null; arbrexml = null; allowed = null; attpane = null; split = null; caretListenerLabel = null; JaxeMenuBar menuBar = null; nomFichierCfg = null; barreBalises = null; doc = null; dispose(); if (!quit && Jaxe.allFrames.size() == 0) Jaxe.dialogueDepart(); return(true); } public void enregistrer() { if (doc.fsave == null) enregistrerSous(); else { try { doc.ecrire(doc.fsave); } catch (IOException ex) { JOptionPane.showMessageDialog(this, ex.getLocalizedMessage(), "IOException", JOptionPane.ERROR_MESSAGE); return; } setTitle(doc.fsave.getName()); } } public void enregistrerSous() { FileDialog fd = new FileDialog(this, null, FileDialog.SAVE); fd.show(); String sf = fd.getFile(); if (sf != null) { File f = new File(fd.getDirectory(), sf); if (f.getName().indexOf('.') == -1) { f = new File(f.getPath() + ".xml"); if (f.exists()) { if (JOptionPane.showConfirmDialog(this, rb.getString("enregistrement.remplacer"), "", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) { fd = null; enregistrerSous(); return; } } } try { doc.ecrire(f); } catch (IOException ex) { JOptionPane.showMessageDialog(this, ex.getLocalizedMessage(), "IOException", JOptionPane.ERROR_MESSAGE); return; } setTitle(f.getName()); } } //Listener pour garder la cohérence entre le redimensionnement de la fenêtre de l'arbre à la main et le cocheMenu /* class EcouteurPropriete implements PropertyChangeListener { public void propertyChange (PropertyChangeEvent pce) { if (pce.getPropertyName().equals("dividerLocation")) { // mise à jour de la position des composants dans le texte if (doc != null) doc.styleChanged(); if (((Integer)pce.getNewValue()).compareTo(new Integer(50)) <= 0) { //menuBar.setSideMenu(false) ; setAffichageSide(false); } else if (((Integer)pce.getOldValue()).compareTo(new Integer(50)) <= 0) { //menuBar.setSideMenu(true) ; setAffichageSide(true); } } } } */ public void componentHidden(ComponentEvent e) { //System.out.println("hidden"); } public void componentMoved(ComponentEvent e) { //System.out.println("moved"); } public void componentResized(ComponentEvent e) { // pour corriger un bug sur Windoze //System.out.println("resized"); // modif pour garder une taille raisonnable pour la fenêtre de l'arbre if (sidepane != null) if (sidepane.getSize().width > 0 && sidepane.getSize().width < 100) split.setDividerLocation(250) ; // mise à jour de la position des composants dans le texte if (doc != null) doc.styleChanged(); } public void componentShown(ComponentEvent e) { //System.out.println("shown"); if (textPane != null) { SwingUtilities.invokeLater(new Runnable() { public void run() { textPane.requestFocus(); textPane.getCaret().setVisible(true); textPane.invalidate(); } }); } } //This listens for and reports caret movements. protected class CaretListenerLabel extends JLabel implements CaretListener { public CaretListenerLabel (String label, JaxeDocument doc) { super(label); } public void caretUpdate(CaretEvent e) { int dot = e.getDot(); int mark = e.getMark(); if (dot == mark) { // no selection setText(dot + ": " + doc.getPathAsString(dot)); } majMenus(dot); } } /** * Mise à jour des menus (grisé / non grisé) avec la liste des balises autorisées */ public void majMenus(int pos) { if (doc.cfg == null || barreBalises == null) return; JaxeElement parent = null; if (doc.rootJE != null) parent = doc.rootJE.elementA(pos); if (parent != null && parent.debut.getOffset() == pos && !(parent instanceof JESwing)) parent = parent.getParent() ; if (parent != null && parent.noeud.getNodeType() == Node.TEXT_NODE) parent = parent.getParent(); ArrayList autorisees = null; Config parentconf = null; if (parent == null) { parentconf = doc.cfg; autorisees = doc.cfg.listeRacines(); } else { Element parentdef = doc.cfg.getElementDef((Element)parent.noeud); if (parentdef == null) return; parentconf = doc.cfg.getDefConf(parentdef); autorisees = parentconf.listeSousbalises(parentdef); } for (int i=0; i