package com.devdaily.justwrite.view; import java.awt.Color; import java.awt.Dimension; import java.awt.Event; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.File; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollBar; import javax.swing.KeyStroke; import javax.swing.event.DocumentEvent; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.text.Document; import javax.swing.text.StyledDocument; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import com.apple.mrj.MRJAboutHandler; import com.apple.mrj.MRJApplicationUtils; import com.apple.mrj.MRJPrefsHandler; import com.apple.mrj.MRJQuitHandler; import com.devdaily.justwrite.JustWrite; import com.devdaily.justwrite.controller.MainFrameController; import com.devdaily.justwrite.view.actions.CloseFileAction; import com.devdaily.justwrite.view.actions.ColorChooserAction; import com.devdaily.justwrite.view.actions.CompleteWordAction; import com.devdaily.justwrite.view.actions.LargerFontSizeAction; import com.devdaily.justwrite.view.actions.OpenFileAction; import com.devdaily.justwrite.view.actions.OpenRecentFileAction; import com.devdaily.justwrite.view.actions.QuitAction; import com.devdaily.justwrite.view.actions.SaveFileAction; import com.devdaily.justwrite.view.actions.SmallerFontSizeAction; /** * Copyright 2010, Alvin Alexander, http://devdaily.com. * This software is distributed under the terms of the * GNU General Public License. * * This file is part of an application named JustWrite. * * JustWrite 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. * * JustWrite 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 JustWrite. If not, see <http://www.gnu.org/licenses/>. * */ public class JustWriteMainFrame extends FullScreenEditorFrame implements KeyListener, MRJAboutHandler, MRJQuitHandler, MRJPrefsHandler { private MainFrameController mainFrameController; private static final Color DEFAULT_EDITOR_BG_COLOR = new Color(5, 5, 5); private int height; private int width; private JMenu dirtyMenu = new JMenu(""); private Action openRecentFileAction; private Action openFileAction; private Action saveFileAction; private Action closeFileAction; private Action colorChooserAction; private Action quitAction; private Action smallerFontSizeAction; private Action largerFontSizeAction; private Action completeWordAction; // don't really want a keystroke here: private KeyStroke openRecentFileKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_R, Event.META_MASK); private KeyStroke openFileKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.META_MASK); private KeyStroke saveFileKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.META_MASK); private KeyStroke closeFileKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.META_MASK); private KeyStroke colorChooserKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_T, Event.META_MASK); private KeyStroke quitKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.META_MASK); private KeyStroke undoKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.META_MASK); private KeyStroke redoKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y, Event.META_MASK); private KeyStroke smallerFontSizeKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Event.META_MASK); // handle [meta][equals], [meta][equals][shift], and [meta][plus] private KeyStroke largerFontSizeKeystroke1 = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, Event.META_MASK); private KeyStroke largerFontSizeKeystroke2 = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, Event.META_MASK + Event.SHIFT_MASK); private KeyStroke largerFontSizeKeystroke3 = KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Event.META_MASK); // the auto-complete feature private KeyStroke completeWordKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); // undo and redo // can jack this up to a StyledDocument or DefaultStyledDocument as long as we use // a jtextpane: private StyledDocument document; protected UndoableEditListener undoHandler = new UndoHandler(); protected UndoManager undoManager = new UndoManager(); private UndoAction undoAction = null; private RedoAction redoAction = null; private boolean dirty; public JustWriteMainFrame(MainFrameController mainFrameController) { this.mainFrameController = mainFrameController; // no adornments on the mainframe this.setUndecorated(true); this.setResizable(false); this.getContentPane().setBackground(Color.black); handleStandardMacMenuActions(); configureEditorPane(); configureVerticalScrollbar(); configureActionsAndKeystrokes(); // add our menubar (after creating actions) setJMenuBar(createMenuBar()); makeFrameFillScreen(); } private void makeFrameFillScreen() { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); height = screenSize.height; width = screenSize.width; setPreferredSize(new Dimension(width, height)); pack(); } private void handleStandardMacMenuActions() { boolean isMacOS = System.getProperty("mrj.version") != null; if (isMacOS) { MRJApplicationUtils.registerAboutHandler(this); MRJApplicationUtils.registerPrefsHandler(this); MRJApplicationUtils.registerQuitHandler(this); } } /** * Note: Changing from a JEditorPane to a JTextPane solved my word-wrapping * problem. Until this switch, the JEditorPane was wrapping words mid-word, * i.e., any time I hit the end of the line. */ private void configureEditorPane() { getEditorPane().setBackground(DEFAULT_EDITOR_BG_COLOR); getEditorPane().setMargin(new Insets(12, 12, 12, 12)); getEditorPane().updateUI(); // add a listener to the document, mostly so we can tell if it is dirty document = (StyledDocument)getEditorPane().getDocument(); document.addUndoableEditListener(undoHandler); document.addDocumentListener(new javax.swing.event.DocumentListener() { public void insertUpdate(DocumentEvent e) { documentInsertUpdate(e); } public void removeUpdate(DocumentEvent e) { documentRemoveUpdate(e); } public void changedUpdate(DocumentEvent e) { documentChangedUpdate(e); } }); } private void configureActionsAndKeystrokes() { openFileAction = new OpenFileAction(mainFrameController, this, "Open", openFileKeystroke.getKeyCode()); getEditorPane().getInputMap().put(openFileKeystroke, "openFile"); getEditorPane().getActionMap().put("openFile", openFileAction); openRecentFileAction = new OpenRecentFileAction(mainFrameController, this, "Open Recent ...", openRecentFileKeystroke.getKeyCode()); getEditorPane().getInputMap().put(openRecentFileKeystroke, "openRecentFile"); getEditorPane().getActionMap().put("openRecentFile", openRecentFileAction); saveFileAction = new SaveFileAction(mainFrameController, "Save", null); getEditorPane().getInputMap().put(saveFileKeystroke, "saveFile"); getEditorPane().getActionMap().put("saveFile", saveFileAction); closeFileAction = new CloseFileAction(mainFrameController, this, "Close", closeFileKeystroke.getKeyCode()); getEditorPane().getInputMap().put(closeFileKeystroke, "closeFile"); getEditorPane().getActionMap().put("closeFile", closeFileAction); colorChooserAction = new ColorChooserAction(mainFrameController, this, "Select Colors", colorChooserKeystroke.getKeyCode()); getEditorPane().getInputMap().put(colorChooserKeystroke, "colorChooser"); getEditorPane().getActionMap().put("colorChooser", colorChooserAction); quitAction = new QuitAction(this, "Quit", null); getEditorPane().getInputMap().put(quitKeystroke, "quitKeystroke"); getEditorPane().getActionMap().put("quitKeystroke", quitAction); undoAction = new UndoAction(); getEditorPane().getInputMap().put(undoKeystroke, "undoKeystroke"); getEditorPane().getActionMap().put("undoKeystroke", undoAction); redoAction = new RedoAction(); getEditorPane().getInputMap().put(redoKeystroke, "redoKeystroke"); getEditorPane().getActionMap().put("redoKeystroke", redoAction); smallerFontSizeAction = new SmallerFontSizeAction(mainFrameController, this, "Select Colors", smallerFontSizeKeystroke.getKeyCode()); getEditorPane().getInputMap().put(smallerFontSizeKeystroke, "smallerFontSizeKeystroke"); getEditorPane().getActionMap().put("smallerFontSizeKeystroke", smallerFontSizeAction); largerFontSizeAction = new LargerFontSizeAction(mainFrameController, this, "Select Colors", largerFontSizeKeystroke1.getKeyCode()); getEditorPane().getInputMap().put(largerFontSizeKeystroke1, "largerFontSizeKeystroke"); getEditorPane().getActionMap().put("largerFontSizeKeystroke", largerFontSizeAction); getEditorPane().getInputMap().put(largerFontSizeKeystroke2, "largerFontSizeKeystroke2"); getEditorPane().getActionMap().put("largerFontSizeKeystroke2", largerFontSizeAction); getEditorPane().getInputMap().put(largerFontSizeKeystroke3, "largerFontSizeKeystroke3"); getEditorPane().getActionMap().put("largerFontSizeKeystroke3", largerFontSizeAction); completeWordAction = new CompleteWordAction(mainFrameController, this, "Complete Word", completeWordKeystroke.getKeyCode()); getEditorPane().getInputMap().put(completeWordKeystroke, "completeWord"); getEditorPane().getActionMap().put("completeWord", completeWordAction); } private JMenuBar createMenuBar() { // create the menubar JMenuBar menuBar = new JMenuBar(); // File menu JMenu fileMenu = new JMenu("File"); JMenuItem openFileMenuItem = new JMenuItem(openFileAction); JMenuItem openRecentFileMenuItem = new JMenuItem(openRecentFileAction); JMenuItem saveFileMenuItem = new JMenuItem(saveFileAction); JMenuItem closeFileMenuItem = new JMenuItem(closeFileAction); fileMenu.add(openFileMenuItem); fileMenu.add(openRecentFileMenuItem); fileMenu.add(saveFileMenuItem); fileMenu.add(closeFileMenuItem); // Edit menu JMenu editMenu = new JMenu("Edit"); JMenuItem colorChooserMenuItem = new JMenuItem(colorChooserAction); JMenuItem undoMenuItem = new JMenuItem(undoAction); JMenuItem redoMenuItem = new JMenuItem(redoAction); editMenu.add(undoMenuItem); editMenu.add(redoMenuItem); editMenu.add(colorChooserMenuItem); // add the menus to the menubar menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(dirtyMenu); return menuBar; } /** * TODO this doesn't actually work */ private void configureVerticalScrollbar() { JScrollBar verticalScrollBar = getScrollPane().createVerticalScrollBar(); verticalScrollBar.setBackground(Color.black); verticalScrollBar.setForeground(Color.black); // see // http://developer.apple.com/mac/library/technotes/tn2007/tn2196.html#//apple_ref/doc/uid/DTS10004439 verticalScrollBar.putClientProperty("JComponent.sizeVariant", "mini"); getScrollPane().setVerticalScrollBar(verticalScrollBar); getScrollPane().updateUI(); } /** * Places the given text in our text-editing area. */ public void setEditorText(String text) { // TODO need to do this with SwingWorker or SwingUtils? getEditorPane().setText(text); getEditorPane().setCaretPosition(0); setFocusInEditingArea(); dirty = false; } /** * Returns the text that is currently in our text-editing area. */ public String getEditorText() { return getEditorPane().getText(); } public StyledDocument getDocument() { return this.document; } // /////////// handle the key-related events ////////// public void keyPressed(KeyEvent e) { KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } // ////////// code to handle the "dirty" state of our document /////////// public boolean isDirty() { return dirty; } /** * Update the UI widget that tells the user the current editor content is dirty. */ private void updateUIDirtyWidget(String s) { dirtyMenu.setText(s); dirtyMenu.updateUI(); } /** * Mark the editor content as dirty, and update the UI appropriately. */ public void setDirty(boolean b) { this.dirty = b; String filename = mainFrameController.getCurrentFilename(); if (filename == null) { filename = ""; } else { filename = new File(filename).getName(); } if (dirty) { updateUIDirtyWidget("* " + filename); } else { updateUIDirtyWidget(" " + filename); } } void documentChangedUpdate(DocumentEvent e) { setDirty(true); } void documentInsertUpdate(DocumentEvent e) { setDirty(true); } void documentRemoveUpdate(DocumentEvent e) { setDirty(true); } // /////////// handle undo and redo actions ////////////////// class UndoHandler implements UndoableEditListener { /** * Messaged when the Document has created an edit, the edit is added to * <code>undoManager</code>, an instance of UndoManager. */ public void undoableEditHappened(UndoableEditEvent e) { undoManager.addEdit(e.getEdit()); undoAction.update(); redoAction.update(); } } class UndoAction extends AbstractAction { public UndoAction() { super("Undo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undoManager.undo(); } catch (CannotUndoException ex) { // TODO log this or ignore it //ex.printStackTrace(); } update(); redoAction.update(); } protected void update() { if (undoManager.canUndo()) { setEnabled(true); putValue(Action.NAME, undoManager.getUndoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Undo"); } } } class RedoAction extends AbstractAction { public RedoAction() { super("Redo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undoManager.redo(); } catch (CannotRedoException ex) { // TODO log this or ignore it //ex.printStackTrace(); } update(); undoAction.update(); } protected void update() { if (undoManager.canRedo()) { setEnabled(true); putValue(Action.NAME, undoManager.getRedoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Redo"); } } } // //////////////// code to handle the standard mac os x menu actions //////////////// public void handleAbout() { JOptionPane.showMessageDialog(null, "JustWrite, a devdaily.com creation", "About", JOptionPane.INFORMATION_MESSAGE); } public void handlePrefs() throws IllegalStateException { JOptionPane.showMessageDialog(null, "Sorry, no prefs yet", "Sorry, no prefs yet", JOptionPane.INFORMATION_MESSAGE); } public void handleQuit() throws IllegalStateException { quitAction.actionPerformed(null); } public void setFocusInEditingArea() { // TODO do i need to do this in SwingUtilities??? // this did not work getEditorPane().requestFocusInWindow(); // this did work getEditorPane().requestFocus(); } }