All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rsyntaxtextarea.TextEditorPane Maven / Gradle / Ivy

Go to download

RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.

There is a newer version: 3.5.1
Show newest version
/*
 * 11/25/2008
 *
 * TextEditorPane.java - A syntax highlighting text area that has knowledge of
 * the file it is editing on disk.
 *
 * This library is distributed under a modified BSD license.  See the included
 * LICENSE file for details.
 */
package org.fife.ui.rsyntaxtextarea;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;

import org.fife.io.UnicodeReader;
import org.fife.io.UnicodeWriter;
import org.fife.ui.rtextarea.RTextAreaEditorKit;

/**
 * An extension of {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}
 * that adds information about the file being edited, such as:
 *
 * 
    *
  • Its name and location. *
  • Is it dirty? *
  • Is it read-only? *
  • The last time it was loaded or saved to disk (local files only). *
  • The file's encoding on disk. *
  • Easy access to the line separator. *
* * Loading and saving is also built into the editor.

* * When saving UTF-8 files, whether or not a BOM is written is controlled by * the {@link UnicodeWriter} class. * Use {@link UnicodeWriter#setWriteUtf8BOM(boolean)} to toggle writing BOMs * for UTF-8 files.

* * Both local and remote files (e.g. ftp) are supported. See the * {@link FileLocation} class for more information. * * @author Robert Futrell * @version 1.0 * @see FileLocation */ public class TextEditorPane extends RSyntaxTextArea implements DocumentListener { private static final long serialVersionUID = 1L; /** * Property change event fired when the file path this text area references * is updated. * * @see #load(FileLocation, String) * @see #saveAs(FileLocation) */ public static final String FULL_PATH_PROPERTY = "TextEditorPane.fileFullPath"; /** * Property change event fired when the text area's dirty flag changes. * * @see #setDirty(boolean) */ public static final String DIRTY_PROPERTY = "TextEditorPane.dirty"; /** * Property change event fired when the text area should be treated as * read-only, and previously it should not, or vice-versa. * * @see #setReadOnly(boolean) */ public static final String READ_ONLY_PROPERTY = "TextEditorPane.readOnly"; /** * Property change event fired when the text area's encoding changes. * * @see #setEncoding(String) */ public static final String ENCODING_PROPERTY = "TextEditorPane.encoding"; /** * The location of the file being edited. */ private FileLocation loc; /** * The charset to use when reading or writing this file. */ private String charSet; /** * Whether the file should be treated as read-only. */ private boolean readOnly; /** * Whether the file is dirty. */ private boolean dirty; /** * The last time this file was modified on disk, for local files. * For remote files, this value should always be * {@link #LAST_MODIFIED_UNKNOWN}. */ private long lastSaveOrLoadTime; /** * The value returned by {@link #getLastSaveOrLoadTime()} for remote files. */ public static final long LAST_MODIFIED_UNKNOWN = 0; /** * The default name given to files if none is specified in a constructor. */ private static final String DEFAULT_FILE_NAME = "Untitled.txt"; /** * Constructor. The file will be given a default name. */ public TextEditorPane() { this(INSERT_MODE); } /** * Constructor. The file will be given a default name. * * @param textMode Either INSERT_MODE or * OVERWRITE_MODE. */ public TextEditorPane(int textMode) { this(textMode, false); } /** * Creates a new TextEditorPane. The file will be given * a default name. * * @param textMode Either INSERT_MODE or * OVERWRITE_MODE. * @param wordWrapEnabled Whether or not to use word wrap in this pane. */ public TextEditorPane(int textMode, boolean wordWrapEnabled) { super(textMode); setLineWrap(wordWrapEnabled); try { init(null, null); } catch (IOException ioe) { // Never happens ioe.printStackTrace(); } } /** * Creates a new TextEditorPane. * * @param textMode Either INSERT_MODE or * OVERWRITE_MODE. * @param wordWrapEnabled Whether or not to use word wrap in this pane. * @param loc The location of the text file being edited. If this value * is null, a file named "Untitled.txt" in the current * directory is used. * @throws IOException If an IO error occurs reading the file at * loc. This of course won't happen if * loc is null. */ public TextEditorPane(int textMode, boolean wordWrapEnabled, FileLocation loc) throws IOException { this(textMode, wordWrapEnabled, loc, null); } /** * Creates a new TextEditorPane. * * @param textMode Either INSERT_MODE or * OVERWRITE_MODE. * @param wordWrapEnabled Whether or not to use word wrap in this pane. * @param loc The location of the text file being edited. If this value * is null, a file named "Untitled.txt" in the current * directory is used. This file is displayed as empty even if it * actually exists. * @param defaultEnc The default encoding to use when opening the file, * if the file is not Unicode. If this value is null, * a system default value is used. * @throws IOException If an IO error occurs reading the file at * loc. This of course won't happen if * loc is null. */ public TextEditorPane(int textMode, boolean wordWrapEnabled, FileLocation loc, String defaultEnc) throws IOException { super(textMode); setLineWrap(wordWrapEnabled); init(loc, defaultEnc); } /** * Callback for when styles in the current document change. * This method is never called. * * @param e The document event. */ @Override public void changedUpdate(DocumentEvent e) { } /** * Returns the default encoding for this operating system. * * @return The default encoding. */ private static String getDefaultEncoding() { // NOTE: The "file.encoding" system property is not guaranteed to be // set by the spec, so we cannot rely on it. return Charset.defaultCharset().name(); } /** * Returns the encoding to use when reading or writing this file. * * @return The encoding. * @see #setEncoding(String) */ public String getEncoding() { return charSet; } /** * Returns the full path to this document. * * @return The full path to the document. */ public String getFileFullPath() { return loc==null ? null : loc.getFileFullPath(); } /** * Returns the file name of this document. * * @return The file name. */ public String getFileName() { return loc==null ? null : loc.getFileName(); } /** * Returns the timestamp for when this file was last loaded or saved * by this editor pane. If the file has been modified on disk by * another process after it was loaded into this editor pane, this method * will not return the actual file's last modified time.

* * For remote files, this method will always return * {@link #LAST_MODIFIED_UNKNOWN}. * * @return The timestamp when this file was last loaded or saved by this * editor pane, if it is a local file, or * {@link #LAST_MODIFIED_UNKNOWN} if it is a remote file. * @see #isModifiedOutsideEditor() */ public long getLastSaveOrLoadTime() { return lastSaveOrLoadTime; } /** * Returns the line separator used when writing this file (e.g. * "\n", "\r\n", or "\r").

* * Note that this value is an Object and not a * String as that is the way the {@link Document} interface * defines its property values. If you always use * {@link #setLineSeparator(String)} to modify this value, then the value * returned from this method will always be a String. * * @return The line separator. If this value is null, then * the system default line separator is used (usually the value * of System.getProperty("line.separator")). * @see #setLineSeparator(String) * @see #setLineSeparator(String, boolean) */ public Object getLineSeparator() { return getDocument().getProperty( RTextAreaEditorKit.EndOfLineStringProperty); } /** * Initializes this editor with the specified file location. * * @param loc The file location. If this is null, a default * location is used and an empty file is displayed. * @param defaultEnc The default encoding to use when opening the file, * if the file is not Unicode. If this value is null, * a system default value is used. * @throws IOException If an IO error occurs reading from loc. * If loc is null, this cannot happen. */ private void init(FileLocation loc, String defaultEnc) throws IOException { if (loc==null) { // Don't call load() just in case Untitled.txt actually exists, // just to ensure there is no chance of an IOException being thrown // in the default case. this.loc = FileLocation.create(DEFAULT_FILE_NAME); charSet = defaultEnc==null ? getDefaultEncoding() : defaultEnc; // Ensure that line separator always has a value, even if the file // does not exist (or is the "default" file). This makes life // easier for host applications that want to display this value. setLineSeparator(System.getProperty("line.separator")); } else { load(loc, defaultEnc); // Sets this.loc } if (this.loc.isLocalAndExists()) { File file = new File(this.loc.getFileFullPath()); lastSaveOrLoadTime = file.lastModified(); setReadOnly(!file.canWrite()); } else { lastSaveOrLoadTime = LAST_MODIFIED_UNKNOWN; setReadOnly(false); } setDirty(false); } /** * Callback for when text is inserted into the document. * * @param e Information on the insertion. */ @Override public void insertUpdate(DocumentEvent e) { if (!dirty) { setDirty(true); } } /** * Returns whether or not the text in this editor has unsaved changes. * * @return Whether or not the text has unsaved changes. * @see #setDirty(boolean) */ public boolean isDirty() { return dirty; } /** * Returns whether this file is a local file. * * @return Whether this is a local file. */ public boolean isLocal() { return loc.isLocal(); } /** * Returns whether this is a local file that already exists. * * @return Whether this is a local file that already exists. */ public boolean isLocalAndExists() { return loc.isLocalAndExists(); } /** * Returns whether the text file has been modified outside of this editor * since the last load or save operation. Note that if this is a remote * file, this method will always return false.

* * This method may be used by applications to implement a reloading * feature, where the user is prompted to reload a file if it has been * modified since their last open or save. * * @return Whether the text file has been modified outside of this * editor. * @see #getLastSaveOrLoadTime() */ public boolean isModifiedOutsideEditor() { return loc.getActualLastModified()>getLastSaveOrLoadTime(); } /** * Returns whether or not the text area should be treated as read-only. * * @return Whether or not the text area should be treated as read-only. * @see #setReadOnly(boolean) */ public boolean isReadOnly() { return readOnly; } /** * Loads the specified file in this editor. This method fires a property * change event of type {@link #FULL_PATH_PROPERTY}. * * @param loc The location of the file to load. This cannot be * null. * @param defaultEnc The encoding to use when loading/saving the file. * This encoding will only be used if the file is not Unicode. * If this value is null, the system default encoding * is used. * @throws IOException If an IO error occurs. * @see #save() * @see #saveAs(FileLocation) */ public void load(FileLocation loc, String defaultEnc) throws IOException { // For new local files, just go with it. if (loc.isLocal() && !loc.isLocalAndExists()) { this.charSet = defaultEnc!=null ? defaultEnc : getDefaultEncoding(); this.loc = loc; setText(null); discardAllEdits(); setDirty(false); return; } // Old local files and remote files, load 'em up. UnicodeReader will // check for BOMs and handle them correctly in all cases, then pass // rest of stream down to InputStreamReader. UnicodeReader ur = new UnicodeReader(loc.getInputStream(), defaultEnc); // Remove listener so dirty flag doesn't get set when loading a file. Document doc = getDocument(); doc.removeDocumentListener(this); try (BufferedReader r = new BufferedReader(ur)) { read(r, null); } finally { doc.addDocumentListener(this); } // No IOException thrown, so we can finally change the location. charSet = ur.getEncoding(); String old = getFileFullPath(); this.loc = loc; setDirty(false); setCaretPosition(0); discardAllEdits(); firePropertyChange(FULL_PATH_PROPERTY, old, getFileFullPath()); } /** * Reloads this file from disk. The file must exist for this operation * to not throw an exception.

* * The file's "dirty" state will be set to false after this * operation. If this is a local file, its "last modified" time is * updated to reflect that of the actual file.

* * Note that if the file has been modified on disk, and is now a Unicode * encoding when before it wasn't (or if it is a different Unicode now), * this will cause this {@link TextEditorPane}'s encoding to change. * Otherwise, the file's encoding will stay the same. * * @throws IOException If the file does not exist, or if an IO error * occurs reading the file. * @see #isLocalAndExists() */ public void reload() throws IOException { String oldEncoding = getEncoding(); UnicodeReader ur = new UnicodeReader(loc.getInputStream(), oldEncoding); String encoding = ur.getEncoding(); try (BufferedReader r = new BufferedReader(ur)) { read(r, null); // Dumps old contents. } setEncoding(encoding); setDirty(false); syncLastSaveOrLoadTimeToActualFile(); discardAllEdits(); // Prevent user from being able to undo the reload } /** * Called whenever text is removed from this editor. * * @param e The document event. */ @Override public void removeUpdate(DocumentEvent e) { if (!dirty) { setDirty(true); } } /** * Saves the file in its current encoding.

* * The text area's "dirty" state is set to false, and if * this is a local file, its "last modified" time is updated. * * @throws IOException If an IO error occurs. * @see #saveAs(FileLocation) * @see #load(FileLocation, String) */ public void save() throws IOException { saveImpl(loc); setDirty(false); syncLastSaveOrLoadTimeToActualFile(); } /** * Saves this file in a new local location. This method fires a property * change event of type {@link #FULL_PATH_PROPERTY}. * * @param loc The location to save to. * @throws IOException If an IO error occurs. * @see #save() * @see #load(FileLocation, String) */ public void saveAs(FileLocation loc) throws IOException { saveImpl(loc); // No exception thrown - we can "rename" the file. String old = getFileFullPath(); this.loc = loc; setDirty(false); lastSaveOrLoadTime = loc.getActualLastModified(); firePropertyChange(FULL_PATH_PROPERTY, old, getFileFullPath()); } /** * Saves the text in this editor to the specified location. * * @param loc The location to save to. * @throws IOException If an IO error occurs. */ private void saveImpl(FileLocation loc) throws IOException { OutputStream out = loc.getOutputStream(); try (BufferedWriter w = new BufferedWriter( new UnicodeWriter(out, getEncoding()))) { write(w); } } /** * Sets whether or not this text in this editor has unsaved changes. * This fires a property change event of type {@link #DIRTY_PROPERTY}.

* * Applications will usually have no need to call this method directly; the * only time you might have a need to call this method directly is if you * have to initialize an instance of TextEditorPane with content that does * not come from a file. TextEditorPane automatically sets its * own dirty flag when its content is edited, when its encoding is changed, * or when its line ending property is changed. It is cleared whenever * load(), reload(), save(), or * saveAs() are called. * * @param dirty Whether or not the text has been modified. * @see #isDirty() */ public void setDirty(boolean dirty) { if (this.dirty!=dirty) { this.dirty = dirty; firePropertyChange(DIRTY_PROPERTY, !dirty, dirty); } } /** * Sets the document for this editor. * * @param doc The new document. */ @Override public void setDocument(Document doc) { Document old = getDocument(); if (old!=null) { old.removeDocumentListener(this); } super.setDocument(doc); doc.addDocumentListener(this); } /** * Sets the encoding to use when reading or writing this file. This * method sets the editor's dirty flag when the encoding is changed, and * fires a property change event of type {@link #ENCODING_PROPERTY}. * * @param encoding The new encoding. * @throws UnsupportedCharsetException If the encoding is not supported. * @throws NullPointerException If encoding is * null. * @see #getEncoding() */ public void setEncoding(String encoding) { if (encoding==null) { throw new NullPointerException("encoding cannot be null"); } else if (!Charset.isSupported(encoding)) { throw new UnsupportedCharsetException(encoding); } if (charSet==null || !charSet.equals(encoding)) { String oldEncoding = charSet; charSet = encoding; firePropertyChange(ENCODING_PROPERTY, oldEncoding, charSet); setDirty(true); } } /** * Sets the line separator sequence to use when this file is saved (e.g. * "\n", "\r\n" or "\r"). * * Besides parameter checking, this method is preferred over * getDocument().putProperty() because it sets the editor's * dirty flag when the line separator is changed. * * @param separator The new line separator. * @throws NullPointerException If separator is * null. * @throws IllegalArgumentException If separator is not one * of "\n", "\r\n" or "\r". * @see #getLineSeparator() */ public void setLineSeparator(String separator) { setLineSeparator(separator, true); } /** * Sets the line separator sequence to use when this file is saved (e.g. * "\n", "\r\n" or "\r"). * * Besides parameter checking, this method is preferred over * getDocument().putProperty() because can set the editor's * dirty flag when the line separator is changed. * * @param separator The new line separator. * @param setDirty Whether the dirty flag should be set if the line * separator is changed. * @throws NullPointerException If separator is * null. * @throws IllegalArgumentException If separator is not one * of "\n", "\r\n" or "\r". * @see #getLineSeparator() */ public void setLineSeparator(String separator, boolean setDirty) { if (separator==null) { throw new NullPointerException("terminator cannot be null"); } if (!"\r\n".equals(separator) && !"\n".equals(separator) && !"\r".equals(separator)) { throw new IllegalArgumentException("Invalid line terminator"); } Document doc = getDocument(); Object old = doc.getProperty( RTextAreaEditorKit.EndOfLineStringProperty); if (!separator.equals(old)) { doc.putProperty(RTextAreaEditorKit.EndOfLineStringProperty, separator); if (setDirty) { setDirty(true); } } } /** * Sets whether or not this text area should be treated as read-only. * This fires a property change event of type {@link #READ_ONLY_PROPERTY}. * * @param readOnly Whether or not the document is read-only. * @see #isReadOnly() */ public void setReadOnly(boolean readOnly) { if (this.readOnly!=readOnly) { this.readOnly = readOnly; firePropertyChange(READ_ONLY_PROPERTY, !readOnly, readOnly); } } /** * Syncs this text area's "last saved or loaded" time to that of the file * being edited, if that file is local and exists. If the file is * remote or is local but does not yet exist, nothing happens.

* * You normally do not have to call this method, as the "last saved or * loaded" time for {@link TextEditorPane}s is kept up-to-date internally * during such operations as {@link #save()}, {@link #reload()}, etc. * * @see #getLastSaveOrLoadTime() * @see #isModifiedOutsideEditor() */ public void syncLastSaveOrLoadTimeToActualFile() { if (loc.isLocalAndExists()) { lastSaveOrLoadTime = loc.getActualLastModified(); } } public static void main(String[] args) throws Exception { try { TextEditorPane textArea = new TextEditorPane(); textArea.load(FileLocation.create("d:/temp/test.txt"), "UTF-8"); JPanel cp = new JPanel(); cp.setPreferredSize(new java.awt.Dimension(300, 300)); cp.setLayout(new java.awt.BorderLayout()); cp.add(new JScrollPane(textArea)); JFrame frame = new JFrame(); frame.setContentPane(cp); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationByPlatform(true); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy