/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ketUI;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.FileDialog;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.TransferHandler;
import javax.swing.filechooser.FileNameExtensionFilter;
import ket.*;
import ket.display.HTMLTools;
import ket.display.ColourScheme;
import ket.display.ColourSchemeDecorator;
import ket.display.LatexTools;
import ket.display.box.Box;
import ket.math.*;
import ket.math.convert.ArgumentParser;
import ketUI.Clipboard;
import ketUI.chord.Chord;
import ketUI.chord.ChordEventHandler;
import ketUI.chord.KeyboardEventHandler;
import ketUI.modes.*;
import ketUI.panel.AnimatedDisplay;
import ketUI.panel.FocusRequestDaemon;
import ketUI.panel.FullScreenFocusListener;
import ketUI.panel.KetPanel;
import ketUI.panel.KetPanelFocusListener;
import ketUI.panel.KetFrame;
/**
* The top-level model of a collection of equations including associated
* graphical user interface components, methods to load and save documents as
* well as to initialize such documents.
*/
public class FrameManager implements Runnable {
public static final int SCREEN_WIDTH = 600;
public static final int SCREEN_HEIGHT = 600;
final DocumentManager documentManager;
final Document document;
final JFrame frame;
FullScreenFocusListener fullScreen;
FullScreenFrame fullScreenFrame;
boolean readOnly;
String latexFilename;
String clojureFilename;
String htmlFilename;
String filename;
/**
* There are three types of document sources: blank documents,
* templates that are stored within the Ket.jar file and regular input
* files that are stored elsewhere. To distinguish blank documents,
* pass in a null filename and to indicate a template, use *.template
* while regular documents are *.txt.
*/
public FrameManager(DocumentManager documentManager, Document document, JFrame existingFrame) {
this.documentManager = documentManager;
this.document = document;
this.frame = existingFrame==null ? new KetFrame(documentManager, document) : existingFrame;
fullScreenFrame = null;
latexFilename = null;
clojureFilename = null;
htmlFilename = null;
}
public void invokeLater() {
if (isStandAlone()) {
SwingUtilities.invokeLater(this);
}
}
public void run() {
boolean hasFocus = getKetPanel().requestFocusInWindow();
if (!hasFocus) {
FocusRequestDaemon focusRequestDeamon = new FocusRequestDaemon(getKetPanel());
focusRequestDeamon.start();
getKetPanel().addFocusListener(new KetPanelFocusListener(focusRequestDeamon));
}
frame.setVisible(true);
MenuEventHandler menuEventHandler = getMenuEventHandler();
if (menuEventHandler!=null) {
menuEventHandler.updateMultipleEquations();
}
getKetPanel().updateAndRepaint();
}
/*-
public Document(DocumentManager documentManager, JFrame existingFrame, String filename, String label) {
this(documentManager, existingFrame, filename);
getCursor().selectEquation(label);
int index = getCursor().getEquation().getEquationIndex();
getKetPanel().fromMiddle(index);
}
public Document(DocumentManager documentManager, JFrame existingFrame, String filename, int equationIndex) {
this(documentManager, existingFrame, filename);
getCursor().selectEquation(equationIndex);
int index = getCursor().getEquation().getEquationIndex();
getKetPanel().fromMiddle(index);
}
*/
public boolean isStandAlone() {
return frame instanceof KetFrame;
}
public KetFrame getKetFrame() {
return isStandAlone() ? (KetFrame) frame : null ;
}
public void toggleSavingImages() {
ChordEventHandler chordEventHandler = getKeyboardEventHandler().getChordEventHandler();
if (chordEventHandler.imagesAreSaved()) {
chordEventHandler.stopSavingImages();
} else {
startSavingImages();
}
MenuEventHandler menuEventHandler = getMenuEventHandler();
if (menuEventHandler!=null) {
menuEventHandler.saveImages(chordEventHandler.imagesAreSaved());
}
}
public void startSavingImages() {
// TODO: Also update the menuitem if not already done so. (Toggle save/stop).
JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home")); // TODO: If filename is non-null then use its path.
fileChooser.setDialogTitle("Save future edits as images...");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Image files (*.png)", "png");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showSaveDialog(frame);
if(returnVal!=JFileChooser.APPROVE_OPTION) return;
File file = fileChooser.getSelectedFile();
if (file==null) return;
String filename = file.getPath();
ChordEventHandler chordEventHandler = getKeyboardEventHandler().getChordEventHandler();
chordEventHandler.startSavingImages(filename);
//!> chordEventHandler.stopSavingImages();
}
private void loadIfPossible(String filename) {
boolean loadFile = filename!=null;
if (loadFile) {
loadFile = initialFileSetup(filename);
}
if ( ! loadFile ) {
// Default to a blank document.
blankDocumentSetup();
}
}
/**
* Load a specified filename.
*/
private boolean initialFileSetup(String filename) {
readOnly = false;
// Load a file, returning if true and defaulting to a blank
// document (below) if an error is encountered.
boolean readSuccess = false;
if (filename.matches(".+\\.template")) {
// A template (i.e. within Ket.jar).
readSuccess = getMathCollection().readJar(filename);
if (readSuccess) {
// Don't overwrite template files.
readOnly = true;
}
} else {
// A regular file (i.e. outside Ket.jar).
File file = new File(filename);
if (file.exists()) {
readSuccess = getMathCollection().read(filename);
}
}
if (readSuccess) {
// View from the top of the equation.
Argument visibleRoot = getEquationList().getFirstEquation().getVisibleRoot();
getSelection().setOnly(visibleRoot);
getKetPanel().fromTop(0);
getKeyboardEventHandler().setToInitialState();
getKetPanel().updateAndRepaint();
setFilename(filename);
return true;
} else {
return false;
}
}
private void blankDocumentSetup() {
Equation blankEquation = new Equation();
Selection selection = getMathCollection().getSelection();
EquationList el = selection.getEquationList();
el.addLast(blankEquation);
selection.setCurrent(blankEquation.getRoot());
getMathCollection().getUndo().initHistory(el);
getKeyboardEventHandler().setToInitialState();
getKetPanel().updateAndRepaint();
setFilename(null);
}
/**
* Return the JFrame in which the document is displayed.
*/
public JFrame getFrame() {
return frame;
}
public ToolBarEventHandler getToolBarEventHandler() {
return isStandAlone() ? getKetFrame().getToolBarEventHandler() : null;
}
public void setFilename(String filename) {
this.filename = filename;
updateFilename();
}
public void updateFilename() {
if (!isStandAlone()) {
return;
}
String title;
if (filename==null) {
title = "Unknown";
} else {
title = filename;
if (containsUnsavedChanges()) {
title += "*";
}
}
if (readOnly) {
title += " [read only]";
}
if (title.length()!=0) {
title += " - ";
}
title += "Ket";
frame.setTitle(title);
}
public boolean save(boolean saveAs) {
if (saveAs || filename==null || filename.matches(".+\\.template")) {
String text = saveAs ? "Save As" : "Save";
JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));
fileChooser.setDialogTitle(text);
FileNameExtensionFilter filter = new FileNameExtensionFilter("Plain text files (*.txt, *.ket)", "txt", "ket");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showSaveDialog(frame); //! showSaveDialog | showOpenDialog
if(returnVal!=JFileChooser.APPROVE_OPTION) return false;
File file = fileChooser.getSelectedFile();
if (file==null) return false;
String path = file.getPath();
setFilename(path);
}
if (filename==null) {
Ket.out.println(" !!! No filename !!! ");
return false;
}
boolean ok = getMathCollection().writeText(filename);
updateFilename();
if (ok) {
return true;
} else {
Ket.out.println(" !!! Failed to save the file !!! ");
return false;
}
}
/**
* Handle mouse wheel scroll events.
*/
public void scrollDown() {
if (getKetPanel().isListDisplayMode()) {
getKetPanel().moveViewHalfPageDown();
} else if (getKetPanel().isGridDisplayMode()) {
getKetPanel().moveViewDown();
} else {
getCursor().selectDown();
}
getKetPanel().updateAndRepaint();
}
/**
* Handle mouse wheel scroll events.
*/
public void scrollUp() {
if (getKetPanel().isListDisplayMode()) {
getKetPanel().moveViewHalfPageUp();
} else if (getKetPanel().isGridDisplayMode()) {
getKetPanel().moveViewUp();
} else {
getCursor().selectUp();
}
getKetPanel().updateAndRepaint();
}
private GraphicsDevice getScreenDevice() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
}
public boolean isFullScreen() {
return fullScreenFrame!=null;
}
class FullScreenFrame extends JFrame implements Runnable {
FullScreenFocusListener listener;
public FullScreenFrame(KetPanel ketPanel) {
super();
listener = new FullScreenFocusListener(FrameManager.this);
addWindowListener(listener);
addFocusListener(listener);
setUndecorated(true);
add(ketPanel);
setResizable(false);
}
public void run() {
getScreenDevice().setFullScreenWindow(fullScreenFrame);
this.setVisible(true);
}
}
public void enterFullScreen() {
if (!isStandAlone()) {
return;
}
fullScreenFrame = new FullScreenFrame(getKetPanel());
frame.setVisible(false);
SwingUtilities.invokeLater(fullScreenFrame);
}
public void exitFullScreen() {
if (fullScreenFrame==null) return;
//! Window window = getScreenDevice().getFullScreenWindow();
//! where window==fullScreenFrame
frame.setResizable(true);
//- frame.setJMenuBar(getMenuBar());
getKetFrame().refreshMenuBar();
frame.add(getKetPanel());
getScreenDevice().setFullScreenWindow(null);
SwingUtilities.invokeLater(this);
fullScreenFrame.dispose();
int index = getCursor().getEquationIndex();
getKetPanel().fromMiddle(index);
fullScreenFrame = null;
SwingUtilities.invokeLater(this);
}
public void toggleFullScreen() {
if (isFullScreen()) {
exitFullScreen();
} else {
enterFullScreen();
}
}
/**
* Check if the user wants to save the current equations before
* proceeding. True is returned if the user wants to proceed and false
* if save failed or they selected cancel.
*/
private boolean confirmDocumentCanBeChanged() {
if (containsUnsavedChanges()) {
String name = getFilename();
int response = JOptionPane.showConfirmDialog(
frame,
"Save changes to '"+name+"'",
"Unsaved changes",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
switch (response) {
case JOptionPane.YES_OPTION:
boolean ok = this.save(false);
return ok;
case JOptionPane.NO_OPTION:
return true;
case JOptionPane.CLOSED_OPTION:
case JOptionPane.CANCEL_OPTION:
default:
return false;
}
} else {
return true;
}
}
/**
* If required, display a dialog checking if the user wants to save
* before closing then close this window and if no other windows exist
* then close all of them.
*/
public void confirmDispose() {
if (confirmDocumentCanBeChanged() ) {
this.dispose();
}
}
/**
* Clear the current equation list and replace it with a single,
* unknown equation.
*/
public void clear() {
if ( isStandAlone() && confirmDocumentCanBeChanged() ) {
getSelection().deleteAllEquations();
readOnly = false;
frame.setTitle(null);
setFilename(null); //?
getKetPanel().fromTop(0);
getKeyboardEventHandler().setToInitialState();
getModes().setDocumentState(DocumentState.UPDATE_REPLACE);
getKetPanel().updateAndRepaint();
updateFilename();
}
}
public void setTitle(String title) { // This may be useful elsewhere.
if (isStandAlone()) {
frame.setTitle(title);
}
}
/**
* Dispose of this window and if there are no other windows then exit
* the program.
*/
public void dispose() {
if (documentManager==null) return;
int numberOfWindows = documentManager.getNumberOfWindows() - 1;
documentManager.removeDocument(document);
if (numberOfWindows>0) {
frame.dispose();
} else {
System.exit(0);
}
}
/**
* Return either the filename of the current list or null otherwise.
*/
public String getFilename() {
return filename!=null ? filename : "Unknown";
}
/**
* Return the filename if defined or null otherwise.
*/
public String getRawFilename() {
return filename;
}
/**
* Check for any unsaved changes.
*/
public boolean containsUnsavedChanges() {
return getMathCollection().containsUnsavedChanges();
}
/**
* If this equation list is changed then open in a new window and if
* not then open here.
*/
public void open() {
String filename = openFileDialog();
if (filename==null) {
return;
} else if (documentManager!=null && containsUnsavedChanges()) {
Document additionalDocument = new Document(documentManager, null, filename);
documentManager.addDocument(additionalDocument);
updateFilename();
} else {
// Note: this block doesn't require a new document and hence a new document manager:
int oldSize = getEquationList().size();
boolean ok = getMathCollection().read(filename);
if (!ok) {
Ket.out.println(" !!! Reading failed !!! ");
return;
}
int newSize = getEquationList().size();
if (oldSize<newSize) {
for (int i=0; i<oldSize; i++) {
getEquationList().deleteEquation(0);
}
}
getKeyboardEventHandler().setToInitialState();
Argument root = getEquationList().getFirstEquation().getVisibleRoot();
getSelection().setOnly(root);
getKetPanel().fromTop(0);
getKetPanel().updateAndRepaint();
updateFilename();
}
}
public KeyboardEventHandler getKeyboardEventHandler() {
return document.getKeyboardEventHandler();
}
/**
* Display the open-file dialog and return the filename or null if
* cancelled.
*/
public String openFileDialog() {
JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));
fileChooser.setDialogTitle("Open");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Plain text files (*.txt, *.ket)", "txt", "ket");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showOpenDialog(frame);
if(returnVal!=JFileChooser.APPROVE_OPTION) return null;
File file = fileChooser.getSelectedFile();
if (file==null) return null;
if (!file.exists()) return null;
return file.getPath();
}
public void exportToHTML() {
/*-
FileDialog fileDialog = new FileDialog(frame, "Export to HTML (*.html)", FileDialog.SAVE);
if (htmlFilename!=null) {
fileDialog.setFile(htmlFilename);
}
fileDialog.setVisible(true);
String filename = fileDialog.getDirectory() + fileDialog.getFile();
if (fileDialog.getFile()==null) {
return;
}
*/
JFileChooser fileChooser = new JFileChooser(htmlFilename!=null?htmlFilename:System.getProperty("user.home"));
fileChooser.setDialogTitle("Export to HTML (*.html)");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Web pages (*.html, *.htm)", "html", "htm");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showSaveDialog(frame);
if(returnVal!=JFileChooser.APPROVE_OPTION) return;
File file = fileChooser.getSelectedFile();
if (file==null) return;
htmlFilename = file.getPath();
boolean ok = getMathCollection().writeHTML(htmlFilename);
if (!ok) return;
HTMLTools.openURI(htmlFilename);
}
public void exportToLatex() {
JFileChooser fileChooser = new JFileChooser(latexFilename!=null?latexFilename:System.getProperty("user.home"));
fileChooser.setDialogTitle("Export to LaTeX (*.tex)");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Export to Latex (*.tex)", "tex");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showSaveDialog(frame);
if(returnVal!=JFileChooser.APPROVE_OPTION) return;
File file = fileChooser.getSelectedFile();
if (file==null) return;
latexFilename = file.getPath();
LatexTools.convertToPDF(latexFilename, getModes());
}
public void exportToClojure() {
JFileChooser fileChooser = new JFileChooser(clojureFilename!=null?clojureFilename:System.getProperty("user.home"));
fileChooser.setDialogTitle("Export to Clojure (*.clj)");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Clojure program source file (*.clj)", "clj");
fileChooser.setFileFilter(filter);
int returnVal = fileChooser.showSaveDialog(frame);
if(returnVal!=JFileChooser.APPROVE_OPTION) return;
File file = fileChooser.getSelectedFile();
if (file==null) return;
clojureFilename = file.getPath();
getMathCollection().writeClojure(clojureFilename);
}
public void displayAboutMessage() {
String text = "<H1><FONT color=\"RED\">KET</FONT></H1><BR>";
text += "<FONT color=\"BLUE\">";
text += "version "+Ket.VERSION+"<BR>";
text += "by Alasdair C. Hamilton<BR>";
text += "Ket is open source and freely distributable";
text += "</FONT>";
String html = "<HTML> <BODY> <CENTER> "+text+" <CENTER> </BODY> </HTML>";
JOptionPane.showMessageDialog(frame, html, "About", JOptionPane.PLAIN_MESSAGE);
getKeyboardEventHandler().setToInitialState();
}
////////////////////
// HELPER METHODS //
////////////////////
public Modes getModes() {
return document.getModes();
}
public Cursor getCursor() {
return getSelection().getCursor();
}
public EquationList getEquationList() {
return getSelection().getEquationList();
}
public Selection getSelection() {
return getMathCollection().getSelection();
}
public MathCollection getMathCollection() {
return document.getMathCollection();
}
public KetPanel getKetPanel() {
return document.getKetPanel();
}
public DocumentManager getDocumentManager() {
return documentManager;
}
public String toString() { // Not used.
String string = "<Document filename='"+filename+"'>\n";
string += getMathCollection().toString();
string += "</Document>\n";
return string;
}
public MenuEventHandler getMenuEventHandler() {
return isStandAlone() ? getKetFrame().getMenuEventHandler() : null;
}
public static int WINDOW_SIZE = 10;
public void resize(int dx, int dy) {
int x = frame.getWidth() + WINDOW_SIZE*dx;
int y = frame.getHeight() + WINDOW_SIZE*dy;
Point p = frame.getLocation();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
x = bound(x, 0, screenSize.width-p.x);
y = bound(y, 0, screenSize.height-p.y);
frame.setSize(x, y);
}
public void shift(int dx, int dy) {
Point p = frame.getLocation();
int x = p.x + WINDOW_SIZE*dx;
int y = p.y + WINDOW_SIZE*dy;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
x = bound(x, 0, screenSize.width - frame.getWidth());
y = bound(y, 0, screenSize.height - frame.getHeight());
frame.setLocation(x, y);
}
private int bound(int x, int xMin, int xMax) {
if (x<xMin) return xMin;
if (x>xMax) return xMax;
return x;
}
}