/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.text; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.io.*; import java.util.*; import javax.swing.JEditorPane; import javax.swing.text.*; import javax.swing.event.ChangeListener; import org.openide.ErrorManager; import org.openide.awt.UndoRedo; import org.openide.actions.*; import org.openide.cookies.EditorCookie; import org.openide.cookies.OpenCookie; import org.openide.cookies.CloseCookie; import org.openide.cookies.SaveCookie; import org.openide.cookies.PrintCookie; import org.openide.filesystems.*; import org.openide.loaders.*; import org.openide.nodes.NodeAdapter; import org.openide.windows.*; import org.openide.util.Task; import org.openide.util.WeakListener; import org.openide.util.actions.SystemAction; import org.openide.nodes.NodeListener; import org.openide.nodes.Node; /** Support for associating an editor and a Swing {@link Document} to a data object. * Can be assigned as a cookie to any editable data object. * Then this data object will be capable of being opened in an editor, and there will be ways of retrieving and acting upon the Swing document which is editing it. * * @author Jaroslav Tulach * @deprecated Use DataEditorSupport instead */ public class EditorSupport extends OpenSupport implements EditorCookie.Observable, OpenCookie, CloseCookie, PrintCookie { /** Common name for editor mode. * @deprecated Use {@link CloneableEditorSupport#EDITOR_MODE} instead. */ public static final String EDITOR_MODE = CloneableEditorSupport.EDITOR_MODE; /** @deprecated no longer used */ protected String modifiedAppendix = " *"; // NOI18N /** The flag saying if we should listen to the document modifications */ private boolean listenToModifs = true; /** delegating support */ Del del; /** Support an existing loader entry. The file is taken from the * entry and is updated if the entry is moved or renamed. * @param entry entry to create instance from */ public EditorSupport(MultiDataObject.Entry entry) { super(entry, new DelEnv(entry.getDataObject())); del = new Del ( entry.getDataObject (), (DelEnv)env, allEditors ); } /** Message to display when an object is being opened. * @return the message or null if nothing should be displayed */ protected String messageOpening () { return del.superMessageOpening (); } /** Message to display when an object has been opened. * @return the message or null if nothing should be displayed */ protected String messageOpened () { return del.superMessageOpened (); } /** Constructs message that should be displayed when the data object * is modified and is being closed. * * @return text to show to the user */ protected String messageSave () { return del.superMessageSave (); } /** Constructs message that should be used to name the editor component. * * @return name of the editor */ protected String messageName () { return del.superMessageName (); } /** Text to use as tooltip for component. * * @return text to show to the user */ protected String messageToolTip () { return del.superMessageToolTip (); } /** Updates titles of all editors. */ protected void updateTitles () { del.superUpdateTitles (); } /* A method to create a new component. Overridden in subclasses. * @return the {@link Editor} for this support */ protected CloneableTopComponent createCloneableTopComponent () { // initializes the document if not initialized prepareDocument (); DataObject obj = findDataObject (); Editor editor = new Editor (obj); return editor; } /** Create an undo/redo manager. * This manager is then attached to the document, and listens to * all changes made in it. * <P> * The default implementation simply uses <code>UndoRedo.Manager</code>. * * @return the undo/redo manager */ protected UndoRedo.Manager createUndoRedoManager () { return new UndoRedo.Manager (); } /** Passes the actual opening to internal delegate support. * Overrides superclass method. */ public void open() { del.open(); } // editor cookie ....................................................................... /** Closes all opened editors (if the user agrees) and * flushes content of the document to the file. * * @return <code>false</code> if the operation is cancelled */ public boolean close () { return del.close (); } /** Closes the editor, asks if necessary. * @param ask true if we should ask the user * @return true if succesfully closed */ protected boolean close (boolean ask) { return del.superClose (ask); } /** Load the document into memory. This is done * in different thread. A task for the thread is returned * so anyone may test whether the loading has been finished * or is still in process. * * @return task for control over loading */ public synchronized Task prepareDocument () { return del.prepareDocument (); } /** Get the document associated with this cookie. * It is an instance of Swing's {@link StyledDocument} but it should * also understand the NetBeans {@link NbDocument#GUARDED} to * prevent certain lines from being edited by the user. * <P> * If the document is not loaded the method blocks until * it is. * * @return the styled document for this cookie that * understands the guarded attribute * @exception IOException if the document could not be loaded */ public StyledDocument openDocument () throws IOException { return del.openDocument (); } /** Get the document. This method may be called before the document initialization * (<code>prepareTask</code>) * has been completed, in such a case the document must not be modified. * @return document or <code>null</code> if it is not yet loaded */ public StyledDocument getDocument () { return del.getDocument (); } /** Test whether the document is in memory, or whether loading is still in progress. * @return <code>true</code> if document is loaded */ public boolean isDocumentLoaded() { return del.isDocumentLoaded (); } /** Save the document in this thread. * Create 'orig' document for the case that the save would fail. * @exception IOException on I/O error */ public void saveDocument () throws IOException { del.superSaveDocument (); } /** * Actually write file data to an output stream from an editor kit's document. * Called during a file save by {@link #saveDocument}. * <p>The default implementation just calls {@link EditorKit#write(OutputStream, Document, int, int) EditorKit.write(...)}. * Subclasses could override this to provide support for persistent guard blocks, for example. * @param doc the document to write from * @param kit the associated editor kit * @param stream the open stream to write to * @throws IOException if there was a problem writing the file * @throws BadLocationException should not normally be thrown * @see #loadFromStreamToKit */ protected void saveFromKitToStream (StyledDocument doc, EditorKit kit, OutputStream stream) throws IOException, BadLocationException { del.superSaveFromKitToStream (doc, kit, stream); } /** Test whether the document is modified. * @return <code>true</code> if the document is in memory and is modified; * otherwise <code>false</code> */ public boolean isModified () { return del.isModified (); } /** Finds data object the entry belongs to. * @return data object or null */ protected MultiDataObject findDataObject () { return entry.getDataObject (); } /** Create a position reference for the given offset. * The position moves as the document is modified and * reacts to closing and opening of the document. * * @param offset the offset to create position at * @param bias the Position.Bias for new creating position. * @return position reference for that offset */ public final PositionRef createPositionRef (int offset, Position.Bias bias) { return del.createPositionRef (offset, bias); } /** Get the line set for all paragraphs in the document. * @return positions of all paragraphs on last save */ public Line.Set getLineSet () { return del.getLineSet (); } // other public methods ................................................................ /** * Set the MIME type for the document. * @param s the new MIME type */ public void setMIMEType (String s) { del.setMIMEType (s); } /** @deprecated has no effect */ public void setActions (SystemAction[] actions) { } /** Creates editor kit for this source. * @return editor kit */ protected EditorKit createEditorKit () { return del.superCreateEditorKit (); } /** Utility method which enables or disables listening to modifications * on asociated document. * <P> * Could be useful if we have to modify document, but do not want the * Save and Save All actions to be enabled/disabled automatically. * Initially modifications are listened to. * @param listenToModifs whether to listen to modifications */ public void setModificationListening (final boolean listenToModifs) { this.listenToModifs = listenToModifs; } /** Adds a listener for status changes. An event is fired * when the document is moved or removed from memory. * @param l new listener */ public void addChangeListener (ChangeListener l) { del.addChangeListener (l); } /** Removes a listener for status changes. * @param l listener to remove */ public void removeChangeListener (ChangeListener l) { del.removeChangeListener (l); } public final void addPropertyChangeListener(java.beans.PropertyChangeListener l) { del.addPropertyChangeListener (l); } public final void removePropertyChangeListener(java.beans.PropertyChangeListener l) { del.removePropertyChangeListener (l); } /** The implementation of @see org.openide.cookies.PrintCookie#print() method. */ public void print() { del.print (); } /** * Actually read file data into an editor kit's document from an input stream. * Called during a file load by {@link #prepareDocument}. * <p>The default implementation just calls {@link EditorKit#read(InputStream, Document, int) EditorKit.read(...)}. * Subclasses could override this to provide support for persistent guard blocks, for example. * @param doc the document to read into * @param stream the open stream to read from * @param kit the associated editor kit * @throws IOException if there was a problem reading the file * @throws BadLocationException should not normally be thrown * @see #saveFromKitToStream */ protected void loadFromStreamToKit (StyledDocument doc, InputStream stream, EditorKit kit) throws IOException, BadLocationException { del.superLoadFromStreamToKit (doc, stream, kit); } /** Reload the document in response to external modification. * @see #reloadDocumentTask */ protected void reloadDocument() { reloadDocumentTask ().waitFinished (); } /** Starts reloading of document. Could not be named reloadDocument, * because of backward compatibility. * * @return task one can listen on when reloading the document */ protected Task reloadDocumentTask () { return del.superReloadDocument (); } /** Forcibly create one editor component. Then set the caret * to the given position. * @param pos where to place the caret * @return always non-<code>null</code> editor */ protected Editor openAt(PositionRef pos) { return (Editor)del.openAt (pos, -1); } /** Should test whether all data is saved, and if not, prompt the user * to save. * * @return <code>true</code> if everything can be closed */ protected boolean canClose () { return del.superCanClose (); } /* List of all JEditorPane's opened by this editor support. * The first item in the array should represent the component * that is currently selected or has been selected lastly. * * @return array of panes or null if no pane is opened. * In no case empty array is returned. */ public JEditorPane[] getOpenedPanes () { return del.getOpenedPanes (); } /** Notification method called when the document become unmodified. * Called after save or after reload of document. */ protected void notifyUnmodified () { EditorSupport.this.modifySaveCookie (false); del.superNotifyUnmodified (); } /** Overrides the super method to add a save cookie if the * document has been marked modified. * * @return true if the environment accepted being marked as modified * or false if it refused it and the document should still be unmodified */ protected boolean notifyModified () { if (del.superNotifyModified ()) { EditorSupport.this.modifySaveCookie (true); return true; } else { return false; } } /** Called when the document is closed and released from memory. */ protected void notifyClosed() { del.superNotifyClosed (); } /** Utility method to extract EditorSupport from Del instance. * @param ces cloneable editor support * @return EditorSupport * @exception ClassCastException if the variables do not match */ static EditorSupport extract (CloneableEditorSupport ces) { EditorSupport.Del del = (Del)ces; return del.es (); } /** Modifies the save cookie, if necessary * @param add true if we should add the cookie */ final void modifySaveCookie (boolean add) { if (listenToModifs) { if (add) { ((EntryEnv)env).addSaveCookie (); } else { ((EntryEnv)env).removeSaveCookie (); } } } /** Cloneable top component to hold the editor kit. */ public static class Editor extends CloneableEditor { /** data object to work with */ protected DataObject obj; static final long serialVersionUID =-185739563792410059L; /** For externalization of subclasses only */ public Editor () { super(); } /** Constructor * @param obj data object we belong to. The appropriate editor support is * acquired as the DataObject's EditorSupport.class cookie. */ public Editor (DataObject obj) { this(obj, (EditorSupport)obj.getCookie(EditorSupport.class)); } /** Constructor * @param obj data object we belong to. * @param support editor support to use. */ public Editor (DataObject obj, EditorSupport support) { super (support.del); this.obj = obj; } /* Deserialize this top component. * @param in the stream to deserialize from */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); Object ces = cloneableEditorSupport (); if (ces instanceof Del) { obj = ((Del)ces).getDataObjectHack (); } } } // end of Editor inner class /** Special implementation of CloneableEditorSupport that is used * by all methods of EditorSupport to delegate on it. */ private final class Del extends DataEditorSupport implements EditorCookie.Observable { /** Listener on node changes. */ private NodeListener nodeL; /** Constrcutor. Takes environemnt and ref object to replace * the default one */ public Del ( DataObject obj, CloneableEditorSupport.Env env, CloneableTopComponent.Ref ref ) { super (obj, env); this.allEditors = ref; } /** Getter */ public final EditorSupport es () { return EditorSupport.this; } protected void notifyUnmodified () { EditorSupport.this.notifyUnmodified (); } protected boolean notifyModified () { return EditorSupport.this.notifyModified (); } protected void notifyClosed() { EditorSupport.this.notifyClosed (); } final void superNotifyUnmodified () { super.notifyUnmodified (); } final boolean superNotifyModified () { return super.notifyModified (); } final void superNotifyClosed() { // #28256(#27645) Unregisters lisntening on node // when all components closed. nodeL = null; super.notifyClosed (); } protected CloneableEditor createCloneableEditor() { CloneableTopComponent ctc = createCloneableTopComponent(); if(ctc instanceof Editor) { return (CloneableEditor)ctc; } else { return new Editor(getDataObject()); } } // // Messages // protected String messageToolTip() { return EditorSupport.this.messageToolTip (); } protected String messageName() { return EditorSupport.this.messageName (); } protected String messageOpening() { return EditorSupport.this.messageOpening (); } protected String messageOpened() { return EditorSupport.this.messageOpened (); } protected String messageSave() { return EditorSupport.this.messageSave (); } protected void updateTitles () { EditorSupport.this.updateTitles (); } final String superMessageToolTip() { return super.messageToolTip (); } final String superMessageName() { return super.messageName (); } final String superMessageOpening() { return super.messageOpening (); } final String superMessageOpened() { return super.messageOpened (); } final String superMessageSave() { return super.messageSave (); } final void superUpdateTitles () { super.updateTitles (); } // // close // protected boolean close(boolean ask) { return EditorSupport.this.close (ask); } protected boolean superClose(boolean ask) { return super.close (ask); } /** Overrides superclass method. Delegates the creation * of component to enclosing class and adds initializing of * editor component. */ protected CloneableTopComponent createCloneableTopComponent() { CloneableTopComponent ctc = EditorSupport.this.createCloneableTopComponent (); if(ctc instanceof CloneableEditor) { initializeCloneableEditor((CloneableEditor)ctc); } return ctc; } /** Overrides superclass method. Initializes editor component. */ protected void initializeCloneableEditor (CloneableEditor editor) { DataObject obj = getDataObject(); if(obj.isValid()) { org.openide.nodes.Node ourNode = obj.getNodeDelegate(); editor.setActivatedNodes(new org.openide.nodes.Node[] {ourNode}); editor.setIcon(ourNode.getIcon (java.beans.BeanInfo.ICON_COLOR_16x16)); NodeListener nl = new DataNodeListener(editor); ourNode.addNodeListener(WeakListener.node(nl, ourNode)); nodeL = nl; } } // // Stream manipulation // final void superLoadFromStreamToKit( StyledDocument doc,InputStream stream,EditorKit kit ) throws IOException, BadLocationException { super.loadFromStreamToKit (doc, stream, kit); } protected void loadFromStreamToKit( StyledDocument doc,InputStream stream,EditorKit kit ) throws IOException, BadLocationException { EditorSupport.this.loadFromStreamToKit (doc, stream, kit); } protected void superSaveFromKitToStream( StyledDocument doc,EditorKit kit,OutputStream stream ) throws IOException, BadLocationException { super.saveFromKitToStream (doc, kit, stream); } protected void saveFromKitToStream( StyledDocument doc,EditorKit kit,OutputStream stream ) throws IOException, BadLocationException { EditorSupport.this.saveFromKitToStream (doc, kit, stream); } protected Task reloadDocument () { return EditorSupport.this.reloadDocumentTask (); } final Task superReloadDocument () { return super.reloadDocument (); } public void saveDocument() throws IOException { EditorSupport.this.saveDocument(); } final void superSaveDocument() throws IOException { super.saveDocument(); } // // Undo manager // protected UndoRedo.Manager createUndoRedoManager() { return EditorSupport.this.createUndoRedoManager (); } // // Editor kit // EditorKit superCreateEditorKit() { return super.createEditorKit (); } protected EditorKit createEditorKit() { return EditorSupport.this.createEditorKit (); } /* protected StyledDocument createStyledDocument(EditorKit kit) { return EditorSupport.this.createStyledDocument (kit); } */ // // canClose // final boolean superCanClose() { return super.canClose (); } protected boolean canClose() { return EditorSupport.this.canClose (); } /** Class which supports listening on node delegate. */ private final class DataNodeListener extends NodeAdapter { /** Asociated editor */ private final CloneableEditor editor; DataNodeListener (CloneableEditor editor) { this.editor = editor; } public void propertyChange (java.beans.PropertyChangeEvent ev) { if (Node.PROP_DISPLAY_NAME.equals(ev.getPropertyName())) { updateTitles(); } if (Node.PROP_ICON.equals(ev.getPropertyName())) { DataObject obj = getDataObject(); if (obj.isValid()) { editor.setIcon(obj.getNodeDelegate().getIcon ( java.beans.BeanInfo.ICON_COLOR_16x16)); } } } } // End of DataNodeListener class. } /** Implementation of the default Environment for EditorSupport */ private static class EntryEnv extends DataEditorSupport.Env implements SaveCookie { /** generated Serialized Version UID */ static final long serialVersionUID = 354528097109874355L; /** Constructor. * @param obj this support should be associated with */ public EntryEnv (MultiDataObject obj) { super (obj); } /** Getter for file associated with this environment. * @return the file input/output operation should be performed on */ protected FileObject getFile () { return getDataObject ().getPrimaryFile (); } /** Locks the file. * @return the lock on the file getFile () * @exception IOException if the file cannot be locked */ protected FileLock takeLock () throws IOException { return ((MultiDataObject)getDataObject ()).getPrimaryEntry ().takeLock (); } /** Gives notification that the DataObject was changed. * @param ev PropertyChangeEvent */ public void propertyChange(PropertyChangeEvent ev) { if (DataObject.PROP_PRIMARY_FILE.equals(ev.getPropertyName())) { changeFile (); } if (DataObject.PROP_NAME.equals(ev.getPropertyName())) { EditorSupport es = (EditorSupport)getDataObject ().getCookie ( EditorSupport.class ); if (es != null) { es.updateTitles (); } } super.propertyChange (ev); } /** Invoke the save operation. * @throws IOException if the object could not be saved */ public void save() throws java.io.IOException { // Do not use findCloneableOpenSupport; it will not work if the // DataObject has both an EditorSupport and an OpenSupport attached // at once. EditorSupport es = (EditorSupport)getDataObject ().getCookie (EditorSupport.class); if (es == null) throw new IOException ("no EditorSupport found on this data object"); // NOI18N else es.saveDocument (); } /* void clearSaveCookie() { DataObject dataObj = findDataObject(); // remove save cookie (if save was succesfull) dataObj.setModified(false); releaseFileLock(); } */ /** Adds save cookie to the DO. */ final void addSaveCookie() { DataObject dataObj = getDataObject (); // add Save cookie to the data object if (dataObj instanceof MultiDataObject) { if (dataObj.getCookie(SaveCookie.class) == null) { getCookieSet((MultiDataObject)dataObj).add(this); } } } /** Removes save cookie from the DO. */ final void removeSaveCookie() { DataObject dataObj = getDataObject (); // add Save cookie to the data object if (dataObj instanceof MultiDataObject) { if (dataObj.getCookie(SaveCookie.class) == this) { getCookieSet((MultiDataObject)dataObj).remove(this); } } } // UGLY private static java.lang.reflect.Method getCookieSetMethod = null; private static final org.openide.nodes.CookieSet getCookieSet (MultiDataObject obj) { try { if (getCookieSetMethod == null) { getCookieSetMethod = MultiDataObject.class.getDeclaredMethod ("getCookieSet", new Class[] { }); // NOI18N getCookieSetMethod.setAccessible (true); } return (org.openide.nodes.CookieSet) getCookieSetMethod.invoke (obj, new Object[] { }); } catch (Exception e) { ErrorManager.getDefault().notify(e); return new org.openide.nodes.CookieSet (); } } /** Method that allows environment to find its * cloneable open support. * @return the support or null if the environemnt is not in valid * state and the CloneableOpenSupport cannot be found for associated * data object */ public CloneableOpenSupport findCloneableOpenSupport() { CloneableOpenSupport s = super.findCloneableOpenSupport (); if (s != null) { return s; } EditorSupport es = (EditorSupport)getDataObject ().getCookie (EditorSupport.class); if (es != null) { return es.del; } else { return null; } } } // end of EntryEnv /** Environment for delegating object. */ private static final class DelEnv extends EntryEnv { /** generated Serialized Version UID */ static final long serialVersionUID = 174320972368471234L; public DelEnv (MultiDataObject obj) { super (obj); } /** Finds delegating environment for this editor object. */ public CloneableOpenSupport findCloneableOpenSupport() { // Svata: is this really needed ? EditorSupport does not implement CloneableOpenSupport anyway. CloneableOpenSupport o = super.findCloneableOpenSupport (); if (o instanceof EditorSupport) { EditorSupport es = (EditorSupport)o; return es.del; } return o; } } }