/*
* License: source-license.txt
* If this code is used independently, copy the license here.
*/
package wombat.util.files;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.*;
import java.net.InetAddress;
import java.util.*;
import javax.swing.JOptionPane;
import javax.swing.text.BadLocationException;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import wombat.gui.frames.FindReplaceDialog;
import wombat.gui.frames.MainFrame;
import wombat.gui.text.SchemeTextArea;
import wombat.gui.text.sta.SharedTextArea;
import wombat.util.Options;
import wombat.util.errors.ErrorManager;
import net.infonode.docking.*;
import net.infonode.docking.util.*;
/**
* Manage open documents.
*/
public final class DocumentManager implements FocusListener {
static DocumentManager me;
// Next file to create.
int lastIndex;
// GUI references.
MainFrame Main;
RootWindow Root;
TabWindow Documents;
StringViewMap Views = new StringViewMap();
// Active documents.
List<SchemeTextArea> allDocuments;
SchemeTextArea activeDocument;
// Hide constructor.
private DocumentManager() {}
/**
* Manage documents.
*
* @param main The main frame.
* @param root The root window (for splitting when strange things happen).
* @param views View map (holds all of the documents).
* @param documents Document tab manager (holds open documents at first).
*/
public static DocumentManager init(MainFrame main, RootWindow root, StringViewMap views, TabWindow documents) {
if (me == null) {
me = new DocumentManager();
me.lastIndex = 0;
me.Main = main;
me.Root = root;
me.Views = views;
me.Documents = documents;
me.allDocuments = new ArrayList<SchemeTextArea>();
}
return me;
}
/**
* Create a new document.
*/
public static boolean New() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
// Create the document ID.
me.lastIndex++;
String id = "document-" + me.lastIndex;
// Create the actual document and add it to the GUI.
SchemeTextArea ss = new SchemeTextArea(true, true);
me.allDocuments.add(ss);
ss.code.addFocusListener(me);
me.Views.addView(id, new View("<new document>", null, ss));
ss.myView = me.Views.getView(id);
ss.myView.addListener(new ViewCloseListener(ss));
me.Documents.addTab(me.Views.getView(id));
// If everything is set up correctly, make sure that the document correctly displays.
// This fixes issues with closing all of the documents then making a new one.
if (me.Root != null && !me.Documents.isShowing())
me.Root.setWindow(new SplitWindow(false, 0.6f, me.Documents, me.Root.getWindow()));
// Focus the new window.
ss.code.requestFocusInWindow();
me.activeDocument = ss;
return true;
}
/**
* Load a file from a dialog.
* @return If the load worked.
*/
public static boolean Open() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
// Choose a file.
FileDialog fc = new FileDialog(me.Main, "Open...", FileDialog.LOAD);
fc.setVisible(true);
if (fc.getFile() == null)
return false;
// Sanity check.
File file = new File(fc.getDirectory(), fc.getFile());
if (!file.exists())
{
ErrorManager.logError("Unable to load file (does not exist): " + fc.getFile());
return false;
}
// Actually open it.
return Open(file);
}
/**
* Load a specific file.
* @param file The file to load.
* @return If the load worked.
*/
public static boolean Open(File file) {
if (me == null) throw new RuntimeException("Document manager not initialized.");
// Check if the document was already opened, it it was use that one.
for (SchemeTextArea ss : me.allDocuments) {
if (ss.myFile != null && ss.myFile.equals(file)) {
for (int i = 0; i < me.Documents.getChildWindowCount(); i++) {
if (me.Documents.getChildWindow(i).equals(ss.myView)) {
me.Documents.setSelectedTab(i);
ss.code.requestFocusInWindow();
return true;
}
}
}
}
// Otherwise, load the document.
me.lastIndex++;
// Generate an internal ID for it.
String id = "document-" + me.lastIndex;
String filename = file.getName();
// Try to load it.
try {
// Opened files that no longer exist, just make a new one.
if (!file.exists())
file.createNewFile();
// Create the text area and add it to the GUI.
SchemeTextArea ss = new SchemeTextArea(file, true, true);
me.allDocuments.add(ss);
ss.myFile = file;
ss.code.addFocusListener(me);
me.Views.addView(id, new View(filename, null, ss));
ss.myView = me.Views.getView(id);
ss.myView.addListener(new ViewCloseListener(ss));
me.Documents.addTab(me.Views.getView(id));
// Sanity check for if we closed all of the documents and want a new one.
if (me.Root != null && !me.Documents.isShowing())
me.Root.setWindow(new SplitWindow(false, 0.6f, me.Documents, me.Root.getWindow()));
// If there is an empty <new document> open, replace that one.
if (me.activeDocument != null &&
me.activeDocument.isEmpty() &&
me.activeDocument.myFile == null &&
me.activeDocument.myView != null &&
"<new document>".equals(me.activeDocument.myView.getTitle())) {
me.activeDocument.close();
}
// Finally, focus on the new one.
ss.code.requestFocusInWindow();
me.activeDocument = ss;
// Add it to the document manager.
RecentDocumentManager.addFile(file);
// It worked.
return true;
}
catch(IOException ex) {
ErrorManager.logError("Unable to load file (" + file.getName() + "): " + ex.getMessage());
return false;
}
}
/**
* Save the current file.
* @return If the save worked.
*/
public static boolean Save() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument()) return false;
if (me.activeDocument.myFile == null) return SaveAs();
try {
me.activeDocument.save();
RecentDocumentManager.addFile(me.activeDocument.myFile);
return true;
} catch(FileNotFoundException ex) {
return false;
} catch(IOException ex) {
return false;
}
}
/**
* Save the active file with a new name.
* @return If it worked.
*/
public static boolean SaveAs() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument()) return false;
FileDialog fc = new FileDialog(me.Main, "Save as...", FileDialog.SAVE);
fc.setVisible(true);
if (fc.getFile() == null) return false;
File file = new File(fc.getDirectory(), fc.getFile());
me.activeDocument.myFile = file;
if (me.activeDocument instanceof SharedTextArea && ((SharedTextArea) me.activeDocument).isShared()) {
String docName = ((SharedTextArea) me.activeDocument).getDocumentName();
me.activeDocument.myView.getViewProperties().setTitle(file.getName() + " (" + docName + ")");
} else {
me.activeDocument.myView.getViewProperties().setTitle(file.getName());
}
return Save();
}
/**
* Close the active document.
* @return If it worked.
*/
public static boolean Close(boolean force) {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (verifyActiveDocument()) return false;
if (!me.activeDocument.isEmpty()) {
String name = me.activeDocument.myView.getViewProperties().getTitle();
if (me.activeDocument.isDirty()) {
if (Options.ConfirmOnClose) {
int result = JOptionPane.showConfirmDialog(
me.activeDocument,
"Save " + name + " before closing?\n\n(If you select no, any unsaved work will be lost.)",
"Close...",
(force ? JOptionPane.YES_NO_OPTION : JOptionPane.YES_NO_CANCEL_OPTION));
if (result == JOptionPane.YES_OPTION){
if (!Save())
return false;
} else if (result == JOptionPane.CANCEL_OPTION) {
return false;
}
} else {
Save();
}
}
}
// Close it.
int i = me.allDocuments.indexOf(me.activeDocument);
me.allDocuments.remove(me.activeDocument);
me.activeDocument.close();
me.activeDocument.myView.close();
// Get a new active document.
try {
me.activeDocument = me.allDocuments.get(Math.max(0, i - 1));
me.activeDocument.requestFocusInWindow();
} catch(Exception e) {
}
return true;
}
/**
* Close all documents.
* @return If it worked.
*/
public static boolean CloseAll() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
boolean closedAll = true;
while (verifyActiveDocument() && closedAll) {
me.activeDocument = me.allDocuments.get(0);
closedAll &= Close(true);
}
return closedAll;
}
/**
* Reload all documents (to update formatting).
* @return If it worked.
*/
public static boolean ReloadAll() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
for (SchemeTextArea ss : me.allDocuments)
ss.refresh();
return me.Main.updateDisplay();
}
/**
* Run the active document.
* @return If it worked.
*/
public static boolean Run() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument()) return false;
String name = me.activeDocument.myView.getViewProperties().getTitle();
if (me.activeDocument.myFile == null || me.activeDocument.isDirty()) {
if (Options.ConfirmOnRun) {
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
me.activeDocument,
"Save " + name + " before running?\n\n(You must save your code to run it.)",
"Save...",
JOptionPane.YES_NO_OPTION)) {
if (!Save())
return false;
} else {
return false;
}
} else {
if (!Save()) return false;
}
}
try {
me.Main.doCommand("(load \"" + me.activeDocument.myFile.getCanonicalPath().replace("\\", "/") + "\")");
me.Main.focusREPL();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/**
* Check that the active document is really active and that we haven't removed it.
* @return True if we have a valid active document.
*/
private static boolean verifyActiveDocument() {
if (!me.allDocuments.contains(me.activeDocument))
me.activeDocument = null;
if (me.allDocuments.isEmpty())
return false;
if (me.activeDocument == null) {
me.activeDocument = me.allDocuments.get(0);
me.activeDocument.requestFocus();
verifyActiveDocument();
}
return true;
}
/**
* Format the active document.
* @return If it worked.
*/
public static boolean Format() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument()) return false;
me.activeDocument.format();
return true;
}
/**
* Insert a tab / return.
*
* @param insertReturn Insert a newline before tabbing.
* @return If it worked.
*/
public static boolean Tab(boolean insertReturn) {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SchemeTextArea doc = me.activeDocument;
if (doc == null)
return false;
if (insertReturn) {
try {
doc.code.getDocument().insertString(doc.code.getCaretPosition(), "\n", null);
} catch (BadLocationException ble) {
ErrorManager.logError("Unable to add a new line on ENTER.");
}
}
doc.tab();
return true;
}
/**
* Undo on the active document.
* @return If it worked.
*/
public static boolean Undo() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SchemeTextArea doc = me.activeDocument;
try {
if (doc.Undo.canUndo())
doc.Undo.undo();
return true;
} catch (CannotUndoException e) {
return false;
}
}
/**
* Redo on the active document.
* @return If it worked.
*/
public static boolean Redo() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SchemeTextArea doc = me.activeDocument;
try {
if (doc.Undo.canRedo())
doc.Undo.redo();
return true;
} catch (CannotRedoException e) {
return false;
}
}
/**
* Show the find/replace dialog for the current document.
*/
public static void FindReplace() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SchemeTextArea doc = me.activeDocument;
new FindReplaceDialog(me.Main, doc.code).setVisible(true);
}
/**
* Keep track of which text area last had focus.
* @param e The event.
*/
@Override
public void focusGained(FocusEvent e) {
if (!(e.getSource() instanceof Component))
return;
Component c = (Component) e.getSource();
while (c != null)
{
if (c instanceof SchemeTextArea)
{
activeDocument = (SchemeTextArea) c;
return;
}
c = c.getParent();
}
}
/**
* Ignore this.
* @param e
*/
@Override public void focusLost(FocusEvent e) {
}
/**
* Create a new shared document.
* @throws Exception If we cannot host.
*/
public static boolean HostShared() throws Exception {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SharedTextArea ss = new SharedTextArea(InetAddress.getLocalHost(), SharedTextArea.NEXT_PORT++, true);
me.allDocuments.add(ss);
ss.code.addFocusListener(me);
me.lastIndex++;
String id = "document-" + me.lastIndex;
me.Views.addView(id, new View("<new document> (" + ss.getDocumentName() + ")", null, ss));
ss.myView = me.Views.getView(id);
me.Documents.addTab(me.Views.getView(id));
if (me.Root != null && !me.Documents.isShowing())
me.Root.setWindow(new SplitWindow(false, 0.6f, me.Documents, me.Root.getWindow()));
ss.code.requestFocusInWindow();
return true;
}
/**
* Create a new shared document.
* @param host The name of the server
* @param port The port on the server
* @throws Exception If we cannot host.
*/
public static boolean JoinShared(InetAddress host, int port) throws Exception {
if (me == null) throw new RuntimeException("Document manager not initialized.");
SharedTextArea ss = new SharedTextArea(host, port, false);
me.allDocuments.add(ss);
ss.code.addFocusListener(me);
me.lastIndex++;
String id = "document-" + me.lastIndex;
me.Views.addView(id, new View("<new document> (" + ss.getDocumentName() + ")", null, ss));
ss.myView = me.Views.getView(id);
me.Documents.addTab(me.Views.getView(id));
if (me.Root != null && !me.Documents.isShowing())
me.Root.setWindow(new SplitWindow(false, 0.6f, me.Documents, me.Root.getWindow()));
ss.code.requestFocusInWindow();
return true;
}
/**
* Disconnect from an active shared document.
*/
public static void DisconnectShared() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument()) return;
if (!(me.activeDocument instanceof SharedTextArea)) return;
SharedTextArea sta = (SharedTextArea) me.activeDocument;
sta.disconnect();
if (sta.myFile == null)
sta.myView.getViewProperties().setTitle("<new document>");
else
sta.myView.getViewProperties().setTitle(sta.myFile.getName());
}
/**
* Get the file that the active document is using.
* @return The filename.
*/
public static File getActiveFile() {
if (me == null) throw new RuntimeException("Document manager not initialized.");
if (!verifyActiveDocument())
return null;
else
return me.activeDocument.myFile;
}
/**
* True if the active document is a shared text area.
* @return True/false
*/
public static boolean isActiveShared() {
return (me != null
&& me.activeDocument != null
&& me.activeDocument instanceof SharedTextArea
&& ((SharedTextArea) me.activeDocument).isShared());
}
}
/**
* Remove documents from the scheme text area when they're closed.
*/
class ViewCloseListener implements DockingWindowListener {
SchemeTextArea SS;
public ViewCloseListener(SchemeTextArea ss) {
SS = ss;
}
/**
* When the window is closed.
* @param event Event parameters.
*/
@Override public void windowClosed(DockingWindow event) {
SS.close();
DocumentManager.me.allDocuments.remove(SS);
}
@Override public void windowUndocking(DockingWindow event) throws OperationAbortedException {}
@Override public void windowUndocked(DockingWindow event) {}
@Override public void windowShown(DockingWindow event) {}
@Override public void windowRestoring(DockingWindow event) throws OperationAbortedException {}
@Override public void windowRestored(DockingWindow event) {}
@Override public void windowRemoved(DockingWindow event, DockingWindow arg1) {}
@Override public void windowMinimizing(DockingWindow event) throws OperationAbortedException {}
@Override public void windowMinimized(DockingWindow event) {}
@Override public void windowMaximizing(DockingWindow event) throws OperationAbortedException {}
@Override public void windowMaximized(DockingWindow event) {}
@Override public void windowHidden(DockingWindow event) {}
@Override public void windowDocking(DockingWindow event) throws OperationAbortedException {}
@Override public void windowDocked(DockingWindow event) {}
@Override public void windowClosing(DockingWindow event) throws OperationAbortedException {}
@Override public void windowAdded(DockingWindow event, DockingWindow arg1) {}
@Override public void viewFocusChanged(View event, View arg1) {}
}