/*
Copyright 2012 Shane Phillips
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.jdraw;
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.WindowListener;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
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.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.EtchedBorder;
import org.jdraw.swing.TaggedMenuItem;
import org.jdraw.utils.Dialogs;
import org.jdraw.utils.FileExtensionFilters;
import org.jdraw.utils.FileIO;
import org.jdraw.utils.RecentFiles;
import org.jdraw.utils.Dialogs.FileSelectionMode;
public class MainWindow extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane, statusBar;
private JMenuBar menuBar;
private JMenu menuFile, menuCanvas, menuRecentFiles, menuHelp;
private DrawingPanel drawingPanel;
private JSplitPane splitPane;
private JTextArea textArea;
private JLabel statusLabel;
// File Menu
private JMenuItem miFileNew, miFileOpen, miFileSave, miFileSaveAs, miClose;
// Canvas Menu
private JMenuItem miRefresh, miExportImage;
// Help Menu
private JMenuItem miReference;
JScrollPane panelScrollPane;
private MenuActionListener menuActionListener;
private ResourceBundle rb;
private Locale locale;
private Logger log;
private boolean fileModified;
private File currentFile;
private String jsCode = "";
private ScriptEngineManager sem;
private ScriptEngine se;
private Canvas canvas;
private RecentFiles recentFiles;
private int top, left, width, height, state;
private final int RECENT_FILE_COUNT = 10;
/**
* Application entry point
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow frame = new MainWindow();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Constructor for the MainWindow
*/
public MainWindow() {
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
loadPreferences();
setExtendedState(state);
setBounds(left, top, width, height);
MainWindowListener windowListener = new MainWindowListener();
addWindowListener(windowListener);
addWindowStateListener(windowListener);
addComponentListener(windowListener);
// Misc stuff
locale = Locale.getDefault();
rb = ResourceBundle.getBundle("resources", locale);
log = Logger.getLogger(this.getClass().getName());
// TODO: move this to configuration
log.setUseParentHandlers(false);
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINE);
log.addHandler(consoleHandler);
log.setLevel(Level.FINE);
// SE
sem = new ScriptEngineManager();
se = sem.getEngineByExtension("js");
canvas = new Canvas();
se.put("canvas", canvas);
// Components
menuBar = new JMenuBar();
setJMenuBar(menuBar);
menuFile = new JMenu(rb.getString("menu.file"));
menuBar.add(menuFile);
miFileNew = new JMenuItem(rb.getString("menu.file.new"));
miFileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
InputEvent.CTRL_MASK));
menuFile.add(miFileNew);
miFileOpen = new JMenuItem(rb.getString("menu.file.open"));
miFileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
InputEvent.CTRL_MASK));
menuFile.add(miFileOpen);
miFileSave = new JMenuItem(rb.getString("menu.file.save"));
miFileSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
InputEvent.CTRL_MASK));
miFileSave.setEnabled(false);
menuFile.add(miFileSave);
miFileSaveAs = new JMenuItem(rb.getString("menu.file.saveas"));
menuFile.add(miFileSaveAs);
JSeparator sep = new JSeparator();
menuFile.add(sep);
menuRecentFiles = new JMenu(rb.getString("menu.file.recent"));
menuFile.add(menuRecentFiles);
JSeparator sep2 = new JSeparator();
menuFile.add(sep2);
miClose = new JMenuItem(rb.getString("menu.file.close"));
menuFile.add(miClose);
menuCanvas = new JMenu(rb.getString("menu.canvas"));
menuBar.add(menuCanvas);
miRefresh = new JMenuItem(rb.getString("menu.canvas.refresh"));
miRefresh.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
menuCanvas.add(miRefresh);
miExportImage = new JMenuItem(rb.getString("menu.canvas.export"));
miExportImage.setEnabled(false);
menuCanvas.add(miExportImage);
menuHelp = new JMenu(rb.getString("menu.help"));
miReference = new JMenuItem(rb.getString("menu.help.online"));
menuHelp.add(miReference);
menuBar.add(menuHelp);
contentPane = new JPanel();
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
statusBar = new JPanel();
contentPane.add(statusBar, BorderLayout.SOUTH);
statusBar.setBorder(new EtchedBorder(3));
splitPane = new JSplitPane();
splitPane.setResizeWeight(0.8);
splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
contentPane.add(splitPane, BorderLayout.CENTER);
drawingPanel = new DrawingPanel();
panelScrollPane = new JScrollPane(drawingPanel);
panelScrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
panelScrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
// splitPane.setLeftComponent(drawingPanel);
splitPane.setLeftComponent(panelScrollPane);
textArea = new JTextArea();
Font font = new Font(Font.MONOSPACED, Font.PLAIN, 14);
textArea.setFont(font);
textArea.setEditable(true);
textArea.addKeyListener(new TextAreaListener());
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
splitPane.setRightComponent(scrollPane);
statusLabel = new JLabel();
statusBar.add(statusLabel, 0);
statusLabel.setFont(font);
setFileModified(false);
setCurrentFile(null);
menuActionListener = new MenuActionListener();
addMenuActionListeners();
refreshRecentFiles();
String labelText = String.format(
rb.getString("label.status.canvas.size"), canvas.getWidth(),
canvas.getHeight());
statusLabel.setText(labelText);
}
// Getters / Setters
public boolean isFileModified() {
return fileModified;
}
public void setFileModified(boolean fileModified) {
if (fileModified == this.fileModified) {
return;
}
this.fileModified = fileModified;
miFileSave.setEnabled(fileModified);
miRefresh.setEnabled(true);
if (fileModified) {
String title = String.format(rb.getString("window.main.title"),
rb.getString("document.untitled") + "*");
if (currentFile != null) {
title = String.format(rb.getString("window.main.title"),
currentFile.getAbsolutePath() + "*");
}
setTitle(title);
}
}
public File getCurrentFile() {
return currentFile;
}
public void setCurrentFile(File currentFile) {
this.currentFile = currentFile;
if (currentFile != null) {
String title = String.format(rb.getString("window.main.title"),
currentFile.getAbsolutePath());
setTitle(title);
miFileSaveAs.setEnabled(true);
miRefresh.setEnabled(true);
} else {
String title = String.format(rb.getString("window.main.title"),
rb.getString("document.untitled"));
setTitle(title);
miFileSaveAs.setEnabled(false);
miRefresh.setEnabled(false);
}
setFileModified(false);
jsCode = textArea.getText();
if (currentFile != null) {
recentFiles.put(currentFile);
}
refreshRecentFiles();
}
// Methods
private void addMenuActionListeners() {
for (int menuCount = 0; menuCount < menuBar.getMenuCount(); menuCount++) {
JMenu menu = menuBar.getMenu(menuCount);
for (int item = 0; item < menu.getItemCount(); item++) {
if (menu.getItem(item) != null
&& !TaggedMenuItem.class.isInstance(menu.getItem(item))) {
menu.getItem(item).addActionListener(menuActionListener);
}
}
}
}
private boolean newFile() {
if (isFileModified()) {
if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
if (!saveFile(false)) {
return false;
}
}
}
setCurrentFile(null);
textArea.setText(null);
jsCode = "";
refreshCanvas();
miExportImage.setEnabled(false);
return true;
}
private boolean saveFile(boolean saveAs) {
byte[] buffer = textArea.getText().getBytes();
File tempFile = null;
if (currentFile != null) {
tempFile = new File(currentFile.getAbsolutePath());
}
if (saveAs || currentFile == null) {
try {
tempFile = Dialogs.selectFile(
FileExtensionFilters.getJSFileFilter(),
FileSelectionMode.SAVE);
if (tempFile == null) {
return false;
}
if (FileIO.getFileExtension(tempFile.getName()) == null) {
tempFile = new File(tempFile.getAbsolutePath() + ".js");
}
if (tempFile.exists()) {
String message = String.format(
rb.getString("warning.file.exists"),
tempFile.getAbsolutePath());
if (JOptionPane.showConfirmDialog(null, message, null,
JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
return false;
}
}
} catch (Exception ex) {
return false;
}
}
try {
FileIO.save(tempFile, buffer);
} catch (IOException ex) {
String message = String.format(rb.getString("error.file.save"),
ex.getMessage());
Dialogs.showError(rb.getString("error.title"), message);
return false;
}
setCurrentFile(tempFile);
return true;
}
private boolean openFile(String fileName) {
if (isFileModified()) {
if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
if (!saveFile(false)) {
return false;
}
}
}
File f = null;
if (fileName == null) {
try {
f = Dialogs.selectFile(FileExtensionFilters.getJSFileFilter(),
FileSelectionMode.OPEN);
if (f == null) {
return false;
}
} catch (Exception ex) {
return false;
}
} else {
f = new File(fileName);
}
if (!f.exists()) {
String message = String.format(
rb.getString("error.file.not.found"), f.getAbsolutePath());
Dialogs.showError(rb.getString("error.title"), message);
return false;
}
try {
byte[] buffer = FileIO.read(f);
textArea.setText(new String(buffer));
} catch (IOException ex) {
String message = String.format(rb.getString(f.getAbsolutePath()),
ex.getMessage());
Dialogs.showError(rb.getString("error.title"), message);
return false;
}
setCurrentFile(f);
return true;
}
private void refreshCanvas() {
try {
canvas.reset();
se.eval(textArea.getText());
drawingPanel.setPreferredSize(new Dimension(
(int) canvas.getWidth(), (int) canvas.getHeight()));
drawingPanel.setImage(canvas.getImage());
drawingPanel.repaint();
String labelText = String.format(
rb.getString("label.status.canvas.size"),
canvas.getWidth(), canvas.getHeight());
statusLabel.setText(labelText);
miExportImage.setEnabled(true);
} catch (Exception ex) {
miExportImage.setEnabled(false);
String exceptionMessage = null;
if (ex.getCause() != null) {
exceptionMessage = ex.getCause().getMessage();
} else {
exceptionMessage = ex.getMessage();
}
String message = String.format(rb.getString("error.script"),
exceptionMessage);
log.warning(message);
Dialogs.showError(rb.getString("error.title"), message);
}
}
private void loadPreferences() {
Preferences prefs = Preferences.userNodeForPackage(this.getClass());
top = prefs.getInt("window.top", 100);
left = prefs.getInt("window.left", 100);
width = prefs.getInt("window.width", 640);
height = prefs.getInt("window.height", 480);
state = prefs.getInt("window.state", 0);
String r = prefs.get("recent.files", null);
recentFiles = new RecentFiles(RECENT_FILE_COUNT);
if (r != null && r.length() > 0) {
String[] ra = r.split(";");
for (int i = ra.length - 1; i >= 0; i--) {
recentFiles.put(new File(ra[i]));
}
}
}
private void savePreferences() {
Preferences prefs = Preferences.userNodeForPackage(this.getClass());
prefs.putInt("window.state", state);
if (state == JFrame.MAXIMIZED_BOTH) {
top = 100;
left = 100;
width = 640;
height = 480;
}
prefs.putInt("window.top", top);
prefs.putInt("window.left", left);
prefs.putInt("window.width", width);
prefs.putInt("window.height", height);
String rf = "";
for (File f : recentFiles.getRecentFiles()) {
if (!rf.equals("")) {
rf += ";";
}
rf += f.getAbsolutePath();
}
prefs.put("recent.files", rf);
}
private void refreshRecentFiles() {
log.fine("refreshRecentFiles()");
menuRecentFiles.removeAll();
if (recentFiles.getRecentFiles().size() > 0) {
for (File f : recentFiles.getRecentFiles()) {
TaggedMenuItem mi = new TaggedMenuItem();
mi.setTag(f);
mi.setText(f.getName());
mi.setToolTipText(f.getAbsolutePath());
mi.addActionListener(menuActionListener);
menuRecentFiles.add(mi);
}
}
}
private void exportImage() {
File imageFile = Dialogs.selectFile(
FileExtensionFilters.getPNGFilter(), FileSelectionMode.SAVE);
if (imageFile == null) {
return;
}
try {
if (FileIO.getFileExtension(imageFile.getName()) == null) {
imageFile = new File(imageFile.getAbsolutePath() + ".png");
}
if (imageFile.exists()) {
String message = String.format(
rb.getString("warning.file.exists"),
imageFile.getAbsolutePath());
if (JOptionPane.showConfirmDialog(null, message, null,
JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
return;
}
}
log.fine("Exporting image to: " + imageFile.getAbsolutePath());
ImageIO.write(canvas.getImage(), "png", imageFile);
} catch (IOException ex) {
log.warning("exportImage(): " + ex.getMessage());
String message = String.format(rb.getString("error.image.export"),
ex.getMessage());
Dialogs.showError(rb.getString("error.title"), message);
}
}
private void launchBrowser() {
try {
Desktop deskTop = Desktop.getDesktop();
deskTop.browse(new URI(rb.getString("help.url")));
} catch (Exception ex) {
Dialogs.showError(rb.getString("error.title"), ex.getMessage());
}
}
class MenuActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent event) {
log.fine("Menu Action: " + event.getActionCommand());
if (event.getActionCommand()
.equals(rb.getString("menu.file.close"))) {
WindowEvent closingEvent = new WindowEvent(MainWindow.this,
WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit().getSystemEventQueue()
.postEvent(closingEvent);
} else if (event.getActionCommand().equals(
rb.getString("menu.file.new"))) {
log.fine("New File: " + newFile());
}
else if (event.getActionCommand().equals(
rb.getString("menu.file.open"))) {
log.fine("Open File: " + openFile(null));
}
else if (event.getActionCommand().equals(
rb.getString("menu.file.save"))) {
saveFile(false);
}
else if (event.getActionCommand().equals(
rb.getString("menu.file.saveas"))) {
saveFile(true);
}
else if (event.getActionCommand().equals(
rb.getString("menu.canvas.refresh"))) {
refreshCanvas();
}
else if (event.getActionCommand().equals(
rb.getString("menu.canvas.export"))) {
exportImage();
}
else if (event.getActionCommand().equals(
rb.getString("menu.help.online"))) {
launchBrowser();
}
else if (TaggedMenuItem.class.isInstance(event.getSource())) {
TaggedMenuItem mi = (TaggedMenuItem) event.getSource();
openFile(((File) mi.getTag()).getAbsolutePath());
}
}
}
class TextAreaListener implements KeyListener {
@Override
public void keyPressed(KeyEvent event) {
}
@Override
public void keyReleased(KeyEvent event) {
if (!textArea.getText().equals(jsCode)) {
setFileModified(true);
}
}
@Override
public void keyTyped(KeyEvent event) {
}
}
class MainWindowListener implements WindowStateListener, WindowListener,
WindowFocusListener, ComponentListener {
@Override
public void windowStateChanged(WindowEvent e) {
log.fine("New state: " + e.getNewState());
state = e.getNewState();
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
log.fine("Window Closing");
savePreferences();
if (isFileModified()) {
if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
if (!saveFile(false)) {
return;
}
log.fine("File saved during close");
} else {
log.fine("File changes abandoned by user during close");
}
}
System.exit(0);
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowGainedFocus(WindowEvent e) {
}
@Override
public void windowLostFocus(WindowEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
@Override
public void componentMoved(ComponentEvent e) {
width = getWidth();
height = getHeight();
top = (int) getBounds().getY();
left = (int) getBounds().getX();
}
@Override
public void componentResized(ComponentEvent e) {
}
@Override
public void componentShown(ComponentEvent e) {
}
}
}