package menalea;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import menalea.profile.ProfileListDialog;
import menalea.profile.ProfileManager;
import menalea.profile.ProfileMenuSwitcher;
@SuppressWarnings("serial")
public class MainWindow extends JFrame implements Editor.Listener {
private final MessageBundle msgs;
public final AbstractAction newAction, openAction, saveAction,
saveAsAction, exportAction, exitAction;
public final AbstractAction showProfileDialogAction;
public final AbstractAction zoomInAction, zoomOutAction,
convertToHTMLAction;
public final AbstractAction markdownHelpAction, aboutAction;
private final ProfileMenuSwitcher profileMenuSwitcher;
private final MTextArea textArea;
private final Editor editor;
private final JToolBar toolBar;
private final Preferences preferences;
private final ProfileManager profileManager;
public MainWindow(MessageBundle msgs) {
this.msgs = msgs;
preferences = Preferences.userNodeForPackage(getClass())
.node("mainwindow");
// Boring action creation...
newAction = new NewAction();
openAction = new OpenAction();
saveAction = new SaveAction();
saveAsAction = new SaveAsAction();
exportAction = new ExportAction();
exitAction = new ExitAction();
showProfileDialogAction = new ShowProfileDialogAction();
zoomInAction = new ZoomInAction();
zoomOutAction = new ZoomOutAction();
convertToHTMLAction = new ConvertToHTMLAction();
markdownHelpAction = new MarkdownHelpAction();
aboutAction = new AboutAction();
profileManager = ProfileManager.readFromPreferences(msgs);
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setSize(
preferences.getInt("width", 1000),
preferences.getInt("height", 600)
);
setMinimumSize(new Dimension(400, 200));
int state = preferences.getInt("state", NORMAL);
if (state == ICONIFIED)
state = NORMAL;
setExtendedState(state);
setLocationRelativeTo(null);
textArea = new MTextArea(this, msgs);
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
JScrollPane scrollPane = new JScrollPane(textArea,
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane, BorderLayout.CENTER);
editor = new Editor(textArea);
editor.addListener(this);
profileMenuSwitcher = new ProfileMenuSwitcher(profileManager, msgs);
buildMenu();
buildPopupMenu();
toolBar = new JToolBar();
buildToolBar();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
if (confirmIfUnsavedChanges())
exit();
}
});
addWindowStateListener(new WindowStateListener() {
@Override
public void windowStateChanged(WindowEvent e) {
preferences.putInt("state", getExtendedState());
}
});
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
preferences.putInt("width", getWidth());
preferences.putInt("height", getHeight());
}
});
updateTitle();
}
private void buildMenu() {
JMenuBar menuBar = new JMenuBar();
{
JMenu menu = new JMenu(msgs.get(Message.menuFile));
menu.add(newAction);
menu.add(openAction);
menu.add(saveAction);
menu.add(saveAsAction);
menu.add(exportAction);
menu.addSeparator();
menu.add(convertToHTMLAction);
menu.addSeparator();
menu.add(exitAction);
menuBar.add(menu);
}
{
JMenu menu = new JMenu(msgs.get(Message.menuEdit));
menu.add(textArea.undoAction);
menu.add(textArea.redoAction);
menu.addSeparator();
menu.add(textArea.cutAction);
menu.add(textArea.copyAction);
menu.add(textArea.pasteAction);
menu.addSeparator();
menu.add(textArea.searchAction);
menu.addSeparator();
menu.add(textArea.selectAllAction);
menuBar.add(menu);
}
{
JMenu menu = new JMenu(msgs.get(Message.menuFormat));
menu.add(textArea.boldAction);
menu.add(textArea.italicAction);
menu.add(textArea.strikethroughAction);
menu.addSeparator();
menu.add(textArea.headingAction);
menu.addSeparator();
menu.add(profileMenuSwitcher);
menu.add(showProfileDialogAction);
menuBar.add(menu);
}
{
JMenu menu = new JMenu(msgs.get(Message.menuView));
menu.add(zoomInAction);
menu.add(zoomOutAction);
menuBar.add(menu);
}
{
JMenu menu = new JMenu(msgs.get(Message.menuHelp));
menu.add(markdownHelpAction);
menu.add(aboutAction);
menuBar.add(menu);
}
setJMenuBar(menuBar);
}
private void buildPopupMenu() {
JPopupMenu popup = new JPopupMenu();
popup.add(textArea.undoAction);
popup.add(textArea.redoAction);
popup.addSeparator();
popup.add(textArea.cutAction);
popup.add(textArea.copyAction);
popup.add(textArea.pasteAction);
popup.addSeparator();
popup.add(textArea.selectAllAction);
MouseListener popupListener = new PopupListener(popup);
textArea.addMouseListener(popupListener);
}
private void buildToolBar() {
toolBar.add(saveAction);
toolBar.addSeparator();
toolBar.add(textArea.undoAction);
toolBar.add(textArea.redoAction);
toolBar.addSeparator();
toolBar.add(textArea.cutAction);
toolBar.add(textArea.copyAction);
toolBar.add(textArea.pasteAction);
toolBar.addSeparator();
toolBar.add(textArea.selectAllAction);
toolBar.addSeparator();
toolBar.add(textArea.boldAction);
toolBar.add(textArea.italicAction);
toolBar.addSeparator();
toolBar.add(zoomInAction);
toolBar.add(zoomOutAction);
toolBar.addSeparator();
toolBar.add(convertToHTMLAction);
toolBar.addSeparator();
toolBar.add(markdownHelpAction);
add(toolBar, BorderLayout.NORTH);
}
private void exit() {
profileManager.writeToPreferences();
System.exit(0);
}
private void updateTitle() {
String title = "";
if (editor != null) {
String modified = "(" + msgs.get(Message.modifiedFile) + ")";
title += getFilePath();
title += editor.isSaved() ? "" : " " + modified;
title += " - ";
}
title += "Menalea";
setTitle(title);
}
public String getFilePath() {
String path = editor.getFilePath();
if (path == null)
return msgs.get(Message.untitledFile);
else
return path;
}
public String getFileName() {
String path = editor.getFilePath();
if (path == null)
return msgs.get(Message.untitledFile);
else
return new File(path).getName();
}
/**
*
* @return <code>true</code> if the action is confirmed
*/
private boolean confirmIfUnsavedChanges() {
if (editor.isSaved())
return true;
String msg = msgs.get(Message.saveConfirmationDialogText,
getFileName());
int r = JOptionPane.showConfirmDialog(
this,
msg, "Menalea",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
switch (r) {
case JOptionPane.YES_OPTION:
return save();
case JOptionPane.NO_OPTION:
return true;
default:
return false;
}
}
public void open(String filePath) {
try {
editor.open(filePath);
textArea.discardAllEdits();
} catch (IOException e) {
e.printStackTrace();
showErrorMessage("Une erreur est survenue lors de " +
"l'ouverture :\n" + e);
}
}
private void open() {
if (! confirmIfUnsavedChanges())
return;
File file = Tools.chooseFileOpen(MainWindow.this);
if (file == null)
return;
open(file.getPath());
}
/**
*
* @return <code>true</code> if the document has been saved.
*/
private boolean saveAs() {
try {
File file = Tools.chooseFileSave(this);
if (file == null)
return false;
editor.saveAs(file.getPath());
return true;
} catch (IOException e) {
e.printStackTrace();
showErrorMessage("Une erreur est survenue lors de " +
"l'enregistrement :\n" + e);
return false;
}
}
/**
*
* @return <code>true</code> if the document has been saved.
*/
private boolean save() {
if (editor.getFilePath() == null) {
return saveAs();
} else {
try {
editor.save();
return true;
} catch (IOException e) {
e.printStackTrace();
showErrorMessage("Une erreur est survenue lors de " +
"l'enregistrement :\n" + e);
return false;
}
}
}
/**
*
* @return <code>true</code> if the document has been saved.
*/
private boolean export() {
try {
File file = Tools.chooseFileExport(this);
if (file == null)
return false;
String html = MarkdownConverter.convert(getFileName(),
textArea.getText(), profileManager);
Tools.writeUTF8File(file, html);
return true;
} catch (IOException e) {
e.printStackTrace();
showErrorMessage("Une erreur est survenue lors de " +
"l'exportation :\n" + e);
return false;
}
}
private void showHTMLPreview() {
try {
Tools.compileAndBrowseMarkdownPreview(getFileName(),
textArea.getText(), profileManager);
} catch (IOException e) {
e.printStackTrace();
showErrorMessage("Une erreur est survenue lors de la création " +
"de l'aperçu :\n" + e);
}
}
public void showErrorMessage(String message) {
JOptionPane.showMessageDialog(this, message, "Menalea",
JOptionPane.ERROR_MESSAGE);
}
private void showProfileDialog() {
ProfileListDialog dialog = new ProfileListDialog(this, profileManager, msgs);
dialog.setVisible(true);
}
@Override
public void editorSaved(Editor editor) {
updateTitle();
}
@Override
public void editorTextChanged(Editor editor) {
updateTitle();
}
@Override
public void editorOpened(Editor editor) {
updateTitle();
}
@Override
public void editorNewDocumentCreated(Editor editor) {
updateTitle();
}
///////////////////////////////////////////////////////////////////////////
private class NewAction extends MAction {
public NewAction() {
super(msgs.get(Message.menuFileNew),
KeyStroke.getKeyStroke("control N"),
Tools.getPNGIcon("document-new"));
}
@Override
public void actionPerformed(ActionEvent e) {
if (confirmIfUnsavedChanges()) {
editor.newDocument();
textArea.discardAllEdits();
}
}
}
private class OpenAction extends MAction {
public OpenAction() {
super(msgs.get(Message.menuFileOpen),
KeyStroke.getKeyStroke("control O"),
Tools.getPNGIcon("document-open"));
}
@Override
public void actionPerformed(ActionEvent event) {
open();
}
}
private class SaveAction extends MAction {
public SaveAction() {
super(msgs.get(Message.menuFileSave),
KeyStroke.getKeyStroke("control S"),
Tools.getPNGIcon("document-save"));
}
@Override
public void actionPerformed(ActionEvent e) {
save();
}
}
private class SaveAsAction extends MAction {
public SaveAsAction() {
super(msgs.get(Message.menuFileSaveAs),
KeyStroke.getKeyStroke("control shift S"),
Tools.getPNGIcon("document-save-as"));
}
@Override
public void actionPerformed(ActionEvent e) {
saveAs();
}
}
private class ExportAction extends MAction {
public ExportAction() {
super(msgs.get(Message.menuFileExport),
KeyStroke.getKeyStroke("control E"));
}
@Override
public void actionPerformed(ActionEvent e) {
export();
}
}
private class ExitAction extends MAction {
public ExitAction() {
super(msgs.get(Message.menuFileExit),
KeyStroke.getKeyStroke("alt F4"));
}
@Override
public void actionPerformed(ActionEvent e) {
if (! confirmIfUnsavedChanges())
return;
System.exit(0);
}
}
///////////////////////////////////////////////////////////////////////////
private class ConvertToHTMLAction extends MAction {
public ConvertToHTMLAction() {
super(msgs.get(Message.menuFileHtmlPreview),
KeyStroke.getKeyStroke("F5"),
Tools.getPNGIcon("go-next"));
}
@Override
public void actionPerformed(ActionEvent event) {
showHTMLPreview();
}
}
///////////////////////////////////////////////////////////////////////////
private class ShowProfileDialogAction extends MAction {
public ShowProfileDialogAction() {
super(msgs.get(Message.menuFormatShowProfileDialog));
}
@Override
public void actionPerformed(ActionEvent event) {
showProfileDialog();
}
}
///////////////////////////////////////////////////////////////////////////
private class ZoomInAction extends MAction {
public ZoomInAction() {
super(msgs.get(Message.menuViewZoomIn),
Tools.getPNGIcon("list-add"));
}
@Override
public void actionPerformed(ActionEvent e) {
textArea.zoomIn();
}
}
private class ZoomOutAction extends MAction {
public ZoomOutAction() {
super(msgs.get(Message.menuViewZoomOut),
Tools.getPNGIcon("list-remove"));
}
@Override
public void actionPerformed(ActionEvent e) {
textArea.zoomOut();
}
}
///////////////////////////////////////////////////////////////////////////
private class MarkdownHelpAction extends MAction {
public MarkdownHelpAction() {
super(msgs.get(Message.menuHelpMarkdown),
Tools.getPNGIcon("help"));
}
@Override
public void actionPerformed(ActionEvent e) {
URI uri;
try {
uri = new URI(msgs.get(Message.menuHelpMarkdownURL));
Tools.openWebpage(uri);
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
private class AboutAction extends MAction {
public AboutAction() {
super(msgs.get(Message.menuHelpAbout));
}
@Override
public void actionPerformed(ActionEvent e) {
AboutDialog aboutDialog = new AboutDialog(MainWindow.this, msgs);
aboutDialog.setVisible(true);
}
}
///////////////////////////////////////////////////////////////////////////
private static class PopupListener extends MouseAdapter {
private final JPopupMenu popup;
public PopupListener(JPopupMenu popup) {
this.popup = popup;
}
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(),
e.getX(), e.getY());
}
}
}
}