0001: // Copyright (c) 2000, 2005 BlueJ Group, Deakin University
0002: //
0003: // This software is made available under the terms of the "MIT License"
0004: // A copy of this license is included with this source distribution
0005: // in "license.txt" and is also available at:
0006: // http://www.opensource.org/licenses/mit-license.html
0007: // Any queries should be directed to Michael Kolling mik@bluej.org
0008:
0009: package bluej.editor.moe;
0010:
0011: import java.awt.Container;
0012: import java.awt.Event;
0013: import java.awt.Toolkit;
0014: import java.awt.datatransfer.*;
0015: import java.awt.event.ActionEvent;
0016: import java.awt.event.KeyAdapter;
0017: import java.awt.event.KeyEvent;
0018: import java.io.*;
0019: import java.util.ArrayList;
0020: import java.util.Hashtable;
0021: import java.util.Iterator;
0022:
0023: import javax.swing.*;
0024: import javax.swing.event.DocumentEvent;
0025: import javax.swing.text.*;
0026: import javax.swing.undo.CannotRedoException;
0027: import javax.swing.undo.CannotUndoException;
0028:
0029: import bluej.Config;
0030: import bluej.prefmgr.PrefMgr;
0031: import bluej.prefmgr.PrefMgrDialog;
0032: import bluej.utility.Debug;
0033: import bluej.utility.DialogManager;
0034:
0035: /**
0036: * A set of actions supported by the Moe editor. This is a singleton: the
0037: * actions are shared between all editor instances.
0038: *
0039: * Actions are stored both in a hashtable and in an array. The hashtable is used
0040: * for fast lookup by name, whereas the array is needed to support complete,
0041: * ordered access.
0042: *
0043: * @author Michael Kolling
0044: * @author Bruce Quig
0045: */
0046:
0047: public final class MoeActions {
0048: // -------- CONSTANTS --------
0049:
0050: private static final String KEYS_FILE = "editor.keys";
0051:
0052: private static int SHORTCUT_MASK;
0053: private static int ALT_SHORTCUT_MASK;
0054: private static int SHIFT_SHORTCUT_MASK;
0055: private static int SHIFT_ALT_SHORTCUT_MASK;
0056: private static int DOUBLE_SHORTCUT_MASK; // two masks (ie. CTRL + META)
0057:
0058: private static final int tabSize = Config.getPropInteger(
0059: "bluej.editor.tabsize", 4);
0060: private static final String spaces = " ";
0061: private static final char TAB_CHAR = '\t';
0062:
0063: // -------- INSTANCE VARIABLES --------
0064:
0065: private Action[] actionTable; // table of all known actions
0066: private Hashtable actions; // the same actions in a hashtable
0067: private String[] categories;
0068: private int[] categoryIndex;
0069:
0070: private Keymap keymap; // the editor's keymap
0071: private KeyCatcher keyCatcher;
0072:
0073: private boolean lastActionWasCut; // true if last action was a cut action
0074: // undo helpers
0075: public UndoAction undoAction;
0076: public RedoAction redoAction;
0077:
0078: // frequently needed actions
0079: public Action compileAction;
0080:
0081: // for bug workaround:
0082: private InputMap componentInputMap;
0083:
0084: // =========================== STATIC METHODS ===========================
0085:
0086: private static MoeActions moeActions;
0087:
0088: /**
0089: * Get the actions object (a singleton) and, at the same time, install the
0090: * action keymap as the main keymap for the given textComponent..
0091: */
0092: public static MoeActions getActions(JTextComponent textComponent) {
0093: if (moeActions == null)
0094: moeActions = new MoeActions(textComponent);
0095:
0096: if (textComponent != null)
0097: textComponent.setKeymap(moeActions.keymap);
0098: return moeActions;
0099: }
0100:
0101: // ========================== INSTANCE METHODS ==========================
0102:
0103: /**
0104: * Constructor. Singleton, thus private.
0105: */
0106: private MoeActions(JTextComponent textComponent) {
0107: // sort out modifier keys...
0108: SHORTCUT_MASK = Toolkit.getDefaultToolkit()
0109: .getMenuShortcutKeyMask();
0110:
0111: if (SHORTCUT_MASK == Event.CTRL_MASK)
0112: ALT_SHORTCUT_MASK = Event.META_MASK; // alternate (second) modifier
0113: else
0114: ALT_SHORTCUT_MASK = Event.CTRL_MASK;
0115:
0116: SHIFT_SHORTCUT_MASK = SHORTCUT_MASK + Event.SHIFT_MASK;
0117: SHIFT_ALT_SHORTCUT_MASK = Event.SHIFT_MASK + ALT_SHORTCUT_MASK;
0118: DOUBLE_SHORTCUT_MASK = SHORTCUT_MASK + ALT_SHORTCUT_MASK;
0119:
0120: // install our own keymap, with the existing one as parent
0121: keymap = JTextComponent.addKeymap("BlueJ map", textComponent
0122: .getKeymap());
0123:
0124: createActionTable(textComponent);
0125: keyCatcher = new KeyCatcher();
0126: if (!load())
0127: setDefaultKeyBindings();
0128: lastActionWasCut = false;
0129:
0130: // for bug workaround (below)
0131: componentInputMap = textComponent.getInputMap();
0132: }
0133:
0134: public void setUndoEnabled(boolean enabled) {
0135: undoAction.setEnabled(enabled);
0136: }
0137:
0138: public void setRedoEnabled(boolean enabled) {
0139: redoAction.setEnabled(enabled);
0140: }
0141:
0142: /**
0143: * Return an action with a given name.
0144: */
0145: public Action getActionByName(String name) {
0146: return (Action) (actions.get(name));
0147: }
0148:
0149: /**
0150: * Get a keystroke for an action. Return null is there is none.
0151: */
0152: public KeyStroke[] getKeyStrokesForAction(Action action) {
0153: KeyStroke[] keys = keymap.getKeyStrokesForAction(action);
0154: keys = addComponentKeyStrokes(action, keys); // BUG workaround
0155: if (keys != null && keys.length > 0)
0156: return keys;
0157: else
0158: return null;
0159: }
0160:
0161: /**
0162: * BUG WORKAROUND: currently, keymap.getKeyStrokesForAction() misses
0163: * keystrokes that come from JComponents inputMap. Here, we add those
0164: * ourselves...
0165: */
0166: public KeyStroke[] addComponentKeyStrokes(Action action,
0167: KeyStroke[] keys) {
0168: ArrayList keyStrokes = null;
0169: KeyStroke[] componentKeys = componentInputMap.allKeys();
0170:
0171: // find all component keys that bind to this action
0172: for (int i = 0; i < componentKeys.length; i++) {
0173: if (componentInputMap.get(componentKeys[i]).equals(
0174: action.getValue(Action.NAME))) {
0175: if (keyStrokes == null)
0176: keyStrokes = new ArrayList();
0177: keyStrokes.add(componentKeys[i]);
0178: }
0179: }
0180:
0181: // test whether this keyStroke was redefined in keymap
0182: if (keyStrokes != null) {
0183: for (Iterator i = keyStrokes.iterator(); i.hasNext();) {
0184: if (keymap.getAction((KeyStroke) i.next()) != null) {
0185: i.remove();
0186: }
0187: }
0188: }
0189:
0190: // merge found keystrokes into key array
0191: if ((keyStrokes == null) || (keyStrokes.size() == 0))
0192: return keys;
0193:
0194: KeyStroke[] allKeys;
0195: if (keys == null) {
0196: allKeys = new KeyStroke[keyStrokes.size()];
0197: keyStrokes.toArray(allKeys);
0198: } else { // merge new keystrokes into keys
0199: allKeys = new KeyStroke[keyStrokes.size() + keys.length];
0200: keyStrokes.toArray(allKeys);
0201: System.arraycopy(allKeys, 0, allKeys, keys.length,
0202: keyStrokes.size());
0203: System.arraycopy(keys, 0, allKeys, 0, keys.length);
0204: }
0205: return allKeys;
0206: }
0207:
0208: /**
0209: * Add a new key binding into the action table.
0210: */
0211: public void addActionForKeyStroke(KeyStroke key, Action a) {
0212: keymap.addActionForKeyStroke(key, a);
0213: }
0214:
0215: /**
0216: * Remove a key binding from the action table.
0217: */
0218: public void removeKeyStrokeBinding(KeyStroke key) {
0219: keymap.removeKeyStrokeBinding(key);
0220: }
0221:
0222: /**
0223: * Save the key bindings. Return true if successful.
0224: */
0225: public boolean save() {
0226: try {
0227: File file = Config.getUserConfigFile(KEYS_FILE);
0228: FileOutputStream ostream = new FileOutputStream(file);
0229: ObjectOutputStream stream = new ObjectOutputStream(ostream);
0230: KeyStroke[] keys = keymap.getBoundKeyStrokes();
0231: stream.writeInt(MoeEditor.version);
0232: stream.writeInt(keys.length);
0233: for (int i = 0; i < keys.length; i++) {
0234: stream.writeObject(keys[i]);
0235: stream.writeObject(keymap.getAction(keys[i]).getValue(
0236: Action.NAME));
0237: }
0238: stream.flush();
0239: ostream.close();
0240: return true;
0241: } catch (Exception exc) {
0242: Debug.message("Cannot save key bindings: " + exc);
0243: return false;
0244: }
0245: }
0246:
0247: /**
0248: * Load the key bindings. Return true if successful.
0249: */
0250: public boolean load() {
0251: try {
0252: File file = Config.getUserConfigFile(KEYS_FILE);
0253: FileInputStream istream = new FileInputStream(file);
0254: ObjectInputStream stream = new ObjectInputStream(istream);
0255: //KeyStroke[] keys = keymap.getBoundKeyStrokes();
0256: int version = 0;
0257: int count = stream.readInt();
0258: if (count > 100) { // it was new format: version number stored first
0259: version = count;
0260: count = stream.readInt();
0261: }
0262: if (Config.isMacOS() && (version < 140)) {
0263: // do not attempt to load old bindings on MacOS when switching
0264: // to jdk 1.4.1
0265: return false;
0266: }
0267:
0268: for (int i = 0; i < count; i++) {
0269: KeyStroke key = (KeyStroke) stream.readObject();
0270: String actionName = (String) stream.readObject();
0271: Action action = (Action) (actions.get(actionName));
0272: if (action != null) {
0273: keymap.addActionForKeyStroke(key, action);
0274: }
0275: }
0276: istream.close();
0277:
0278: // set up bindings for new actions in recent releases
0279:
0280: if (version < 130) {
0281: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0282: KeyEvent.VK_TAB, 0), (Action) (actions
0283: .get("indent")));
0284: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0285: KeyEvent.VK_TAB, Event.SHIFT_MASK),
0286: (Action) (actions.get("insert-tab")));
0287: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0288: KeyEvent.VK_ENTER, 0), (Action) (actions
0289: .get("new-line")));
0290: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0291: KeyEvent.VK_ENTER, Event.SHIFT_MASK),
0292: (Action) (actions.get("insert-break")));
0293: }
0294: if (version < 200) {
0295: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0296: KeyEvent.VK_TAB, Event.SHIFT_MASK),
0297: (Action) (actions.get("de-indent")));
0298: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
0299: KeyEvent.VK_I, SHORTCUT_MASK),
0300: (Action) (actions.get("insert-tab")));
0301: }
0302: return true;
0303: } catch (Exception exc) {
0304: // ignore - file probably didn't exist (yet)
0305: return false;
0306: }
0307: }
0308:
0309: /**
0310: * Called to inform that any one of the user actions (text edit or caret
0311: * move) was executed.
0312: */
0313: public void userAction() {
0314: lastActionWasCut = false;
0315: }
0316:
0317: /**
0318: * Called at every insertion of text into the document.
0319: */
0320: public void textInsertAction(DocumentEvent evt,
0321: JTextComponent textPane) {
0322: try {
0323: if (evt.getLength() == 1) { // single character inserted
0324: Document doc = evt.getDocument();
0325: int offset = evt.getOffset();
0326: char ch = doc.getText(offset, 1).charAt(0);
0327:
0328: // 'ch' is the character that was just typed
0329: // currently, the only character upon which we act is the
0330: // closing brace ('}')
0331:
0332: if (ch == '}') {
0333: closingBrace(textPane, doc, offset);
0334: }
0335: }
0336: } catch (BadLocationException e) {
0337: }
0338: }
0339:
0340: /**
0341: * We just typed a closing brace character - indent appropriately.
0342: */
0343: private void closingBrace(JTextComponent textPane, Document doc,
0344: int offset) throws BadLocationException {
0345: int lineIndex = getCurrentLineIndex(textPane);
0346: Element line = getLine(textPane, lineIndex);
0347: int lineStart = line.getStartOffset();
0348: String prefix = doc.getText(lineStart, offset - lineStart);
0349:
0350: if (prefix.trim().length() == 0) { // only if there is no other text before '}'
0351: textPane.setCaretPosition(lineStart);
0352: doIndent(textPane, true);
0353: textPane.setCaretPosition(textPane.getCaretPosition() + 1);
0354: }
0355: }
0356:
0357: /**
0358: * Get a keystroke for an action by action name. Return null is there is
0359: * none.
0360: */
0361: // public KeyStroke[] getKeyStrokesForName(String actionName)
0362: // {
0363: // Action action = getActionByName(actionName);
0364: // KeyStroke[] keys = keymap.getKeyStrokesForAction(action);
0365: // if (keys != null && keys.length > 0)
0366: // return keys;
0367: // else
0368: // return null;
0369: // }
0370: // ============================ USER ACTIONS =============================
0371: abstract class MoeAbstractAction extends TextAction {
0372:
0373: public MoeAbstractAction(String name) {
0374: super (name);
0375: }
0376:
0377: /* side effect: clears message in editor! */
0378: protected final MoeEditor getEditor(ActionEvent e) {
0379: MoeEditor ed = null;
0380:
0381: // the source of the event is the first place to look
0382: Object source = e.getSource();
0383: if (source instanceof JComponent) {
0384: Container c = ((JComponent) source)
0385: .getTopLevelAncestor();
0386: if (c instanceof MoeEditor)
0387: ed = (MoeEditor) c;
0388: }
0389:
0390: // otherwise use 'getTextComponent'
0391: if (ed == null) {
0392: JTextComponent textComponent = getTextComponent(e);
0393: if (textComponent != null) {
0394: Container c = textComponent.getTopLevelAncestor();
0395: if (c instanceof MoeEditor)
0396: ed = (MoeEditor) c;
0397: }
0398: }
0399:
0400: if (ed != null)
0401: ed.clearMessage();
0402: return ed;
0403: }
0404: }
0405:
0406: // === File: ===
0407: // --------------------------------------------------------------------
0408:
0409: class SaveAction extends MoeAbstractAction {
0410:
0411: public SaveAction() {
0412: super ("save");
0413: }
0414:
0415: public void actionPerformed(ActionEvent e) {
0416: getEditor(e).userSave();
0417: }
0418: }
0419:
0420: // --------------------------------------------------------------------
0421:
0422: /**
0423: * Reload has been chosen. Ask "Really?" and call "doReload" if the answer
0424: * is yes.
0425: */
0426: class ReloadAction extends MoeAbstractAction {
0427:
0428: public ReloadAction() {
0429: super ("reload");
0430: }
0431:
0432: public void actionPerformed(ActionEvent e) {
0433: getEditor(e).reload();
0434: }
0435: }
0436:
0437: // --------------------------------------------------------------------
0438:
0439: class PrintAction extends MoeAbstractAction {
0440:
0441: public PrintAction() {
0442: super ("print");
0443: }
0444:
0445: public void actionPerformed(ActionEvent e) {
0446: getEditor(e).print();
0447: }
0448: }
0449:
0450: // --------------------------------------------------------------------
0451:
0452: class PageSetupAction extends MoeAbstractAction {
0453:
0454: public PageSetupAction() {
0455: super ("page-setup");
0456: }
0457:
0458: public void actionPerformed(ActionEvent e) {
0459: getEditor(e).pageSetup();
0460: }
0461: }
0462:
0463: // --------------------------------------------------------------------
0464:
0465: class CloseAction extends MoeAbstractAction {
0466:
0467: public CloseAction() {
0468: super ("close");
0469: }
0470:
0471: public void actionPerformed(ActionEvent e) {
0472: getEditor(e).close();
0473: }
0474: }
0475:
0476: // === Edit: ===
0477: // --------------------------------------------------------------------
0478:
0479: class UndoAction extends MoeAbstractAction {
0480:
0481: public UndoAction() {
0482: super ("undo");
0483: this .setEnabled(false);
0484: }
0485:
0486: public void actionPerformed(ActionEvent e) {
0487: MoeEditor editor = getEditor(e);
0488: try {
0489: editor.undoManager.undo();
0490: } catch (CannotUndoException ex) {
0491: Debug.message("moe: cannot undo...");
0492: }
0493: editor.updateUndoControls();
0494: editor.updateRedoControls();
0495: }
0496: }
0497:
0498: // --------------------------------------------------------------------
0499:
0500: class RedoAction extends MoeAbstractAction {
0501:
0502: public RedoAction() {
0503: super ("redo");
0504: this .setEnabled(false);
0505: }
0506:
0507: public void actionPerformed(ActionEvent e) {
0508: MoeEditor editor = getEditor(e);
0509: try {
0510: editor.undoManager.redo();
0511: } catch (CannotRedoException ex) {
0512: Debug.message("moe: cannot redo...");
0513: }
0514: editor.updateUndoControls();
0515: editor.updateRedoControls();
0516: }
0517: }
0518:
0519: // --------------------------------------------------------------------
0520:
0521: class CommentBlockAction extends MoeAbstractAction {
0522:
0523: public CommentBlockAction() {
0524: super ("comment-block");
0525: }
0526:
0527: public void actionPerformed(ActionEvent e) {
0528: MoeEditor editor = getEditor(e);
0529: editor.undoManager.beginCompoundEdit();
0530: blockAction(getTextComponent(e), new CommentLineAction());
0531: editor.undoManager.endCompoundEdit();
0532: }
0533: }
0534:
0535: // --------------------------------------------------------------------
0536:
0537: class UncommentBlockAction extends MoeAbstractAction {
0538:
0539: public UncommentBlockAction() {
0540: super ("uncomment-block");
0541: }
0542:
0543: public void actionPerformed(ActionEvent e) {
0544: MoeEditor editor = getEditor(e);
0545: editor.undoManager.beginCompoundEdit();
0546: blockAction(getTextComponent(e), new UncommentLineAction());
0547: editor.undoManager.endCompoundEdit();
0548: }
0549: }
0550:
0551: // --------------------------------------------------------------------
0552:
0553: class IndentBlockAction extends MoeAbstractAction {
0554:
0555: public IndentBlockAction() {
0556: super ("indent-block");
0557: }
0558:
0559: public void actionPerformed(ActionEvent e) {
0560: MoeEditor editor = getEditor(e);
0561: editor.undoManager.beginCompoundEdit();
0562: blockAction(getTextComponent(e), new IndentLineAction());
0563: editor.undoManager.endCompoundEdit();
0564: }
0565: }
0566:
0567: // --------------------------------------------------------------------
0568:
0569: class DeindentBlockAction extends MoeAbstractAction {
0570:
0571: public DeindentBlockAction() {
0572: super ("deindent-block");
0573: }
0574:
0575: public void actionPerformed(ActionEvent e) {
0576: MoeEditor editor = getEditor(e);
0577: editor.undoManager.beginCompoundEdit();
0578: blockAction(getTextComponent(e), new DeindentLineAction());
0579: editor.undoManager.endCompoundEdit();
0580: }
0581: }
0582:
0583: // --------------------------------------------------------------------
0584:
0585: class InsertMethodAction extends MoeAbstractAction {
0586:
0587: public InsertMethodAction() {
0588: super ("insert-method");
0589: }
0590:
0591: public void actionPerformed(ActionEvent e) {
0592: MoeEditor editor = getEditor(e);
0593: editor.undoManager.beginCompoundEdit();
0594: insertTemplate(getTextComponent(e), "method");
0595: editor.undoManager.endCompoundEdit();
0596: }
0597: }
0598:
0599: // --------------------------------------------------------------------
0600:
0601: class IndentAction extends MoeAbstractAction {
0602:
0603: public IndentAction() {
0604: super ("indent");
0605: }
0606:
0607: public void actionPerformed(ActionEvent e) {
0608: JTextComponent textPane = getTextComponent(e);
0609: MoeEditor ed = getEditor(e);
0610:
0611: // if necessary, convert all TABs in the current editor to spaces
0612: int converted = 0;
0613: if (ed.checkExpandTabs()) // do TABs need expanding?
0614: converted = convertTabsToSpaces(textPane);
0615:
0616: if (PrefMgr.getFlag(PrefMgr.AUTO_INDENT))
0617: doIndent(textPane, false);
0618: else
0619: insertSpacedTab(textPane);
0620:
0621: if (converted > 0)
0622: ed.writeMessage(Config
0623: .getString("editor.info.tabsExpanded"));
0624: }
0625: }
0626:
0627: // --------------------------------------------------------------------
0628:
0629: class DeIndentAction extends MoeAbstractAction {
0630:
0631: public DeIndentAction() {
0632: super ("de-indent");
0633: }
0634:
0635: public void actionPerformed(ActionEvent e) {
0636: JTextComponent textPane = getTextComponent(e);
0637: MoeEditor ed = getEditor(e);
0638:
0639: // if necessary, convert all TABs in the current editor to spaces
0640: if (ed.checkExpandTabs()) { // do TABs need expanding?
0641: int converted = convertTabsToSpaces(textPane);
0642:
0643: if (converted > 0)
0644: ed.writeMessage(Config
0645: .getString("editor.info.tabsExpanded"));
0646: }
0647: doDeIndent(textPane);
0648: }
0649: }
0650:
0651: // --------------------------------------------------------------------
0652:
0653: class NewLineAction extends MoeAbstractAction {
0654:
0655: public NewLineAction() {
0656: super ("new-line");
0657: }
0658:
0659: public void actionPerformed(ActionEvent e) {
0660:
0661: Action action = (Action) (actions
0662: .get(DefaultEditorKit.insertBreakAction));
0663: action.actionPerformed(e);
0664:
0665: if (PrefMgr.getFlag(PrefMgr.AUTO_INDENT)) {
0666: JTextComponent textPane = getTextComponent(e);
0667: doIndent(textPane, true);
0668: }
0669: }
0670: }
0671:
0672: // --------------------------------------------------------------------
0673:
0674: class CopyLineAction extends MoeAbstractAction {
0675:
0676: public CopyLineAction() {
0677: super ("copy-line");
0678: }
0679:
0680: public void actionPerformed(ActionEvent e) {
0681: boolean addToClipboard = lastActionWasCut;
0682: getActionByName("caret-begin-line").actionPerformed(e);
0683: getActionByName("selection-down").actionPerformed(e);
0684: if (addToClipboard)
0685: addSelectionToClipboard(getTextComponent(e));
0686: else
0687: getActionByName("copy-to-clipboard").actionPerformed(e);
0688: lastActionWasCut = true;
0689: }
0690: }
0691:
0692: // --------------------------------------------------------------------
0693:
0694: class CutLineAction extends MoeAbstractAction {
0695:
0696: public CutLineAction() {
0697: super ("cut-line");
0698: }
0699:
0700: public void actionPerformed(ActionEvent e) {
0701: boolean addToClipboard = lastActionWasCut;
0702: getActionByName("caret-begin-line").actionPerformed(e);
0703: getActionByName("selection-down").actionPerformed(e);
0704: if (addToClipboard) {
0705: addSelectionToClipboard(getTextComponent(e));
0706: getActionByName("delete-previous").actionPerformed(e);
0707: } else
0708: getActionByName("cut-to-clipboard").actionPerformed(e);
0709: lastActionWasCut = true;
0710: }
0711: }
0712:
0713: // --------------------------------------------------------------------
0714:
0715: class CutEndOfLineAction extends MoeAbstractAction {
0716:
0717: public CutEndOfLineAction() {
0718: super ("cut-end-of-line");
0719: }
0720:
0721: public void actionPerformed(ActionEvent e) {
0722: boolean addToClipboard = lastActionWasCut;
0723:
0724: getActionByName("selection-end-line").actionPerformed(e);
0725: JTextComponent textComponent = getTextComponent(e);
0726: String selection = textComponent.getSelectedText();
0727: if (selection == null)
0728: getActionByName("selection-forward").actionPerformed(e);
0729:
0730: if (addToClipboard) {
0731: addSelectionToClipboard(textComponent);
0732: getActionByName("delete-previous").actionPerformed(e);
0733: } else
0734: getActionByName("cut-to-clipboard").actionPerformed(e);
0735: lastActionWasCut = true;
0736: }
0737: }
0738:
0739: // --------------------------------------------------------------------
0740:
0741: class CutWordAction extends MoeAbstractAction {
0742:
0743: public CutWordAction() {
0744: super ("cut-word");
0745: }
0746:
0747: public void actionPerformed(ActionEvent e) {
0748: boolean addToClipboard = lastActionWasCut;
0749: getActionByName("caret-previous-word").actionPerformed(e);
0750: getActionByName("selection-next-word").actionPerformed(e);
0751: if (addToClipboard) {
0752: addSelectionToClipboard(getTextComponent(e));
0753: getActionByName("delete-previous").actionPerformed(e);
0754: } else
0755: getActionByName("cut-to-clipboard").actionPerformed(e);
0756: lastActionWasCut = true;
0757: }
0758: }
0759:
0760: // --------------------------------------------------------------------
0761:
0762: class CutEndOfWordAction extends MoeAbstractAction {
0763:
0764: public CutEndOfWordAction() {
0765: super ("cut-end-of-word");
0766: }
0767:
0768: public void actionPerformed(ActionEvent e) {
0769: boolean addToClipboard = lastActionWasCut;
0770: getActionByName("selection-next-word").actionPerformed(e);
0771: if (addToClipboard) {
0772: addSelectionToClipboard(getTextComponent(e));
0773: getActionByName("delete-previous").actionPerformed(e);
0774: } else
0775: getActionByName("cut-to-clipboard").actionPerformed(e);
0776: lastActionWasCut = true;
0777: }
0778: }
0779:
0780: // === Tools: ===
0781:
0782: // --------------------------------------------------------------------
0783:
0784: class FindAction extends MoeAbstractAction {
0785:
0786: public FindAction() {
0787: super ("find");
0788: }
0789:
0790: public void actionPerformed(ActionEvent e) {
0791: getEditor(e).find();
0792: }
0793: }
0794:
0795: // --------------------------------------------------------------------
0796:
0797: class FindNextAction extends MoeAbstractAction {
0798:
0799: public FindNextAction() {
0800: super ("find-next");
0801: }
0802:
0803: public void actionPerformed(ActionEvent e) {
0804: getEditor(e).findNext();
0805: }
0806: }
0807:
0808: // --------------------------------------------------------------------
0809:
0810: class FindNextBackwardAction extends MoeAbstractAction {
0811:
0812: public FindNextBackwardAction() {
0813: super ("find-next-backward");
0814: }
0815:
0816: public void actionPerformed(ActionEvent e) {
0817: getEditor(e).findNextBackward();
0818: }
0819: }
0820:
0821: // --------------------------------------------------------------------
0822:
0823: class ReplaceAction extends MoeAbstractAction {
0824:
0825: public ReplaceAction() {
0826: super ("replace");
0827: }
0828:
0829: public void actionPerformed(ActionEvent e) {
0830: getEditor(e).replace();
0831: }
0832: }
0833:
0834: // --------------------------------------------------------------------
0835:
0836: class CompileAction extends MoeAbstractAction {
0837:
0838: public CompileAction() {
0839: super ("compile");
0840: }
0841:
0842: public void actionPerformed(ActionEvent e) {
0843: getEditor(e).compile();
0844: }
0845: }
0846:
0847: // --------------------------------------------------------------------
0848:
0849: class ToggleInterfaceAction extends MoeAbstractAction {
0850:
0851: public ToggleInterfaceAction() {
0852: super ("toggle-interface-view");
0853: }
0854:
0855: public void actionPerformed(ActionEvent e) {
0856: Object source = e.getSource();
0857: if (source instanceof JComboBox)
0858: getEditor(e).toggleInterface();
0859: else
0860: getEditor(e).toggleInterfaceMenu();
0861: }
0862: }
0863:
0864: // === Debug: ===
0865: // --------------------------------------------------------------------
0866:
0867: class ToggleBreakPointAction extends MoeAbstractAction {
0868:
0869: public ToggleBreakPointAction() {
0870: super ("toggle-breakpoint");
0871: }
0872:
0873: public void actionPerformed(ActionEvent e) {
0874: getEditor(e).toggleBreakpoint();
0875: }
0876: }
0877:
0878: // === Options: ===
0879: // --------------------------------------------------------------------
0880:
0881: class KeyBindingsAction extends MoeAbstractAction {
0882:
0883: public KeyBindingsAction() {
0884: super ("key-bindings");
0885: }
0886:
0887: public void actionPerformed(ActionEvent e) {
0888: FunctionDialog dlg = new FunctionDialog(getEditor(e),
0889: actionTable, categories, categoryIndex);
0890:
0891: dlg.setVisible(true);
0892: }
0893: }
0894:
0895: // --------------------------------------------------------------------
0896:
0897: class PreferencesAction extends MoeAbstractAction {
0898:
0899: public PreferencesAction() {
0900: super ("preferences");
0901: }
0902:
0903: public void actionPerformed(ActionEvent e) {
0904: PrefMgrDialog.showDialog(0); // 0 is the index of the editor pane in
0905: // the pref dialog
0906: }
0907: }
0908:
0909: // === Help: ===
0910: // --------------------------------------------------------------------
0911:
0912: class AboutAction extends MoeAbstractAction {
0913:
0914: public AboutAction() {
0915: super ("about-editor");
0916: }
0917:
0918: public void actionPerformed(ActionEvent e) {
0919: JOptionPane
0920: .showMessageDialog(
0921: getEditor(e),
0922: new String[] {
0923: "Moe",
0924: "Version "
0925: + MoeEditor.versionString,
0926: " ",
0927: "Moe is the editor of the BlueJ programming environment.",
0928: "Written by Michael K\u00F6lling (mik@bluej.org)." },
0929: "About Moe",
0930: JOptionPane.INFORMATION_MESSAGE);
0931: }
0932: }
0933:
0934: // --------------------------------------------------------------------
0935:
0936: class DescribeKeyAction extends MoeAbstractAction {
0937:
0938: public DescribeKeyAction() {
0939: super ("describe-key");
0940: }
0941:
0942: public void actionPerformed(ActionEvent e) {
0943: JTextComponent textComponent = getTextComponent(e);
0944: textComponent.addKeyListener(keyCatcher);
0945: MoeEditor ed = getEditor(e);
0946: keyCatcher.setEditor(ed);
0947: ed.writeMessage("Describe key: ");
0948: }
0949: }
0950:
0951: // --------------------------------------------------------------------
0952:
0953: class HelpMouseAction extends MoeAbstractAction {
0954:
0955: public HelpMouseAction() {
0956: super ("help-mouse");
0957: }
0958:
0959: public void actionPerformed(ActionEvent e) {
0960: JOptionPane.showMessageDialog(getEditor(e), new String[] {
0961: "Moe Mouse Buttons:", " ", "left button:",
0962: " click: place cursor",
0963: " double-click: select word",
0964: " triple-click: select line",
0965: " drag: make selection", " ", "right button:",
0966: " (currently unused)", }, "Moe Mouse Buttons",
0967: JOptionPane.INFORMATION_MESSAGE);
0968: }
0969: }
0970:
0971: // --------------------------------------------------------------------
0972:
0973: class ShowManualAction extends MoeAbstractAction {
0974:
0975: public ShowManualAction() {
0976: super ("show-manual");
0977: }
0978:
0979: public void actionPerformed(ActionEvent e) {
0980: DialogManager.NYI(getEditor(e));
0981: }
0982: }
0983:
0984: // --------------------------------------------------------------------
0985:
0986: class GoToLineAction extends MoeAbstractAction {
0987:
0988: public GoToLineAction() {
0989: super ("go-to-line");
0990: }
0991:
0992: public void actionPerformed(ActionEvent e) {
0993: getEditor(e).goToLine();
0994: }
0995: }
0996:
0997: // --------------------------------------------------------------------
0998: // class Action extends MoeAbstractAction {
0999: //
1000: // public Action() {
1001: // super("");
1002: // }
1003: //
1004: // public void actionPerformed(ActionEvent e) {
1005: // DialogManager.NYI(editor);
1006: // }
1007: // }
1008:
1009: // ========================= SUPPORT ROUTINES ==========================
1010:
1011: /**
1012: * Add the current selection of the text component to the clipboard.
1013: */
1014: public void addSelectionToClipboard(JTextComponent textComponent) {
1015: Clipboard clipboard = textComponent.getToolkit()
1016: .getSystemClipboard();
1017:
1018: // get text from clipboard
1019: Transferable content = clipboard.getContents(this );
1020: String clipContent = "";
1021: if (content != null) {
1022: try {
1023: clipContent = (String) (content
1024: .getTransferData(DataFlavor.stringFlavor));
1025: } catch (Exception exc) {
1026: } // content was not string
1027: }
1028:
1029: // add current selection and store back in clipboard
1030: StringSelection contents = new StringSelection(clipContent
1031: + textComponent.getSelectedText());
1032: clipboard.setContents(contents, contents);
1033: }
1034:
1035: // --------------------------------------------------------------------
1036: /**
1037: * Return the current line.
1038: */
1039: // private Element getCurrentLine(JTextComponent text)
1040: // {
1041: // MoeSyntaxDocument document = (MoeSyntaxDocument)text.getDocument();
1042: // return document.getParagraphElement(text.getCaretPosition());
1043: // }
1044: // --------------------------------------------------------------------
1045: /**
1046: * Return the current column number.
1047: */
1048: private int getCurrentColumn(JTextComponent textPane) {
1049: Caret caret = textPane.getCaret();
1050: int pos = Math.min(caret.getMark(), caret.getDot());
1051: AbstractDocument doc = (AbstractDocument) textPane
1052: .getDocument();
1053: int lineStart = doc.getParagraphElement(pos).getStartOffset();
1054: return (pos - lineStart);
1055: }
1056:
1057: // --------------------------------------------------------------------
1058: /**
1059: * Find and return a line by line number
1060: */
1061: private Element getLine(JTextComponent text, int lineNo) {
1062: return text.getDocument().getDefaultRootElement().getElement(
1063: lineNo);
1064: }
1065:
1066: // -------------------------------------------------------------------
1067: /**
1068: * Find and return a line by text position
1069: */
1070: private Element getLineAt(JTextComponent text, int pos) {
1071: MoeSyntaxDocument document = (MoeSyntaxDocument) text
1072: .getDocument();
1073: return document.getParagraphElement(pos);
1074: }
1075:
1076: // -------------------------------------------------------------------
1077: /**
1078: * Return the number of the current line.
1079: */
1080: private int getCurrentLineIndex(JTextComponent text) {
1081: MoeSyntaxDocument document = (MoeSyntaxDocument) text
1082: .getDocument();
1083: return document.getDefaultRootElement().getElementIndex(
1084: text.getCaretPosition());
1085: }
1086:
1087: // ===================== ACTION IMPLEMENTATION ======================
1088:
1089: /**
1090: * Do some semi-intelligent indentation. That is: indent the current line to
1091: * the same depth, using the same characters (TABs or spaces) as the line
1092: * immediately above.
1093: */
1094: private void doIndent(JTextComponent textPane, boolean isNewLine) {
1095: int lineIndex = getCurrentLineIndex(textPane);
1096: if (lineIndex == 0) { // first line
1097: if (!isNewLine)
1098: insertSpacedTab(textPane);
1099: return;
1100: }
1101:
1102: MoeSyntaxDocument doc = (MoeSyntaxDocument) textPane
1103: .getDocument();
1104:
1105: Element line = getLine(textPane, lineIndex);
1106: int lineStart = line.getStartOffset();
1107: int pos = textPane.getCaretPosition();
1108:
1109: try {
1110: boolean isOpenBrace = false;
1111: boolean isCommentEnd = false, isCommentEndOnly = false;
1112:
1113: // if there is any text before the cursor, just insert a tab
1114:
1115: String prefix = doc.getText(lineStart, pos - lineStart);
1116: if (prefix.trim().length() > 0) {
1117: insertSpacedTab(textPane);
1118: return;
1119: }
1120:
1121: // get indentation string from previous line
1122:
1123: boolean foundLine = false;
1124: int lineOffset = 1;
1125: String prevLineText = null;
1126: while ((lineIndex - lineOffset >= 0) && !foundLine) {
1127: Element prevline = getLine(textPane, lineIndex
1128: - lineOffset);
1129: int prevLineStart = prevline.getStartOffset();
1130: int prevLineEnd = prevline.getEndOffset();
1131: prevLineText = doc.getText(prevLineStart, prevLineEnd
1132: - prevLineStart);
1133: if (!isWhiteSpaceOnly(prevLineText)) {
1134: foundLine = true;
1135: } else {
1136: lineOffset++;
1137: }
1138: }
1139: if (!foundLine) {
1140: if (!isNewLine)
1141: insertSpacedTab(textPane);
1142: return;
1143: }
1144:
1145: if (isOpenBrace(prevLineText))
1146: isOpenBrace = true;
1147: else {
1148: isCommentEnd = prevLineText.trim().endsWith("*/");
1149: isCommentEndOnly = prevLineText.trim().equals("*/");
1150: }
1151:
1152: int indentPos = findFirstNonIndentChar(prevLineText,
1153: isCommentEnd);
1154:
1155: // if the cursor is already past the indentation point, insert tab
1156: // (unless we just did a line break, then we just stop)
1157:
1158: int caretColumn = getCurrentColumn(textPane);
1159: if (caretColumn >= indentPos) {
1160: if (!isNewLine)
1161: insertSpacedTab(textPane);
1162: return;
1163: }
1164:
1165: String indent = prevLineText.substring(0, indentPos);
1166:
1167: if (isNewLine && isCommentStart(indent)) {
1168: completeNewCommentBlock(textPane, indent);
1169: return;
1170: }
1171:
1172: // find and replace indentation of current line
1173:
1174: int lineEnd = line.getEndOffset();
1175: String lineText = doc.getText(lineStart, lineEnd
1176: - lineStart);
1177: indentPos = findFirstNonIndentChar(lineText, true);
1178: char firstChar = lineText.charAt(indentPos);
1179: doc.remove(lineStart, indentPos);
1180: doc.insertString(lineStart, nextIndent(indent, isOpenBrace,
1181: isCommentEndOnly), null);
1182: if (firstChar == '}')
1183: removeTab(textPane, doc);
1184: } catch (BadLocationException exc) {
1185: }
1186: }
1187:
1188: /**
1189: * Return true if s contains only whitespace (or nothing).
1190: */
1191: private boolean isWhiteSpaceOnly(String s) {
1192: return s.trim().length() == 0;
1193: }
1194:
1195: /**
1196: * Do some semi-intelligent de-indentation. That is: indent the current line
1197: * one indentation level less that the line above, or less than it currently
1198: * is.
1199: */
1200: private void doDeIndent(JTextComponent textPane) {
1201: // set cursor to first non-blank character (or eol if none)
1202: // if indentation is more than line above: indent as line above
1203: // if indentation is same or less than line above: indent one level back
1204:
1205: int lineIndex = getCurrentLineIndex(textPane);
1206: MoeSyntaxDocument doc = (MoeSyntaxDocument) textPane
1207: .getDocument();
1208:
1209: try {
1210: Element line = getLine(textPane, lineIndex);
1211: int lineStart = line.getStartOffset();
1212: int lineEnd = line.getEndOffset();
1213: String lineText = doc.getText(lineStart, lineEnd
1214: - lineStart);
1215:
1216: int currentIndentPos = findFirstNonIndentChar(lineText,
1217: true);
1218: char firstChar = lineText.charAt(currentIndentPos);
1219:
1220: textPane.setCaretPosition(lineStart + currentIndentPos);
1221:
1222: if (lineIndex == 0) { // first line
1223: removeTab(textPane, doc);
1224: return;
1225: }
1226:
1227: // get indentation details from previous line
1228:
1229: Element prevline = getLine(textPane, lineIndex - 1);
1230: int prevLineStart = prevline.getStartOffset();
1231: int prevLineEnd = prevline.getEndOffset();
1232: String prevLineText = doc.getText(prevLineStart,
1233: prevLineEnd - prevLineStart);
1234:
1235: int targetIndentPos = findFirstNonIndentChar(prevLineText,
1236: true);
1237:
1238: if (currentIndentPos > targetIndentPos) {
1239: // indent same as line above
1240: String indent = prevLineText.substring(0,
1241: targetIndentPos);
1242: doc.remove(lineStart, currentIndentPos);
1243: doc.insertString(lineStart, indent, null);
1244: if (firstChar == '}')
1245: removeTab(textPane, doc);
1246: } else {
1247: // we are at same level as line above or less - go one indentation
1248: // level back
1249: removeTab(textPane, doc);
1250: }
1251: } catch (BadLocationException exc) {
1252: }
1253: }
1254:
1255: /**
1256: * Check whether the indentation s opens a new multi-line comment
1257: */
1258: private boolean isCommentStart(String s) {
1259: s = s.trim();
1260: return s.endsWith("/**") || s.endsWith("/*");
1261: }
1262:
1263: /**
1264: * Insert text to complete a new, started block comment and place the cursor
1265: * appropriately.
1266: *
1267: * The indentString passed in always ends with "/*".
1268: */
1269: private void completeNewCommentBlock(JTextComponent textPane,
1270: String indentString) {
1271: String nextIndent = indentString.substring(0, indentString
1272: .length() - 2);
1273: textPane.replaceSelection(nextIndent + " * ");
1274: int pos = textPane.getCaretPosition();
1275: textPane.replaceSelection("\n");
1276: textPane.replaceSelection(nextIndent + " */");
1277: textPane.setCaretPosition(pos);
1278: }
1279:
1280: /**
1281: * Check whether the given line ends with an opening brace.
1282: */
1283: private boolean isOpenBrace(String s) {
1284: int index = s.lastIndexOf('{');
1285: if (index == -1)
1286: return false;
1287:
1288: return s.indexOf('}', index + 1) == -1;
1289: }
1290:
1291: /**
1292: * Find the position of the first non-indentation character in a string.
1293: * Indentation characters are <whitespace>, //, *, /*, /**.
1294: */
1295: private int findFirstNonIndentChar(String s, boolean whitespaceOnly) {
1296: int cnt = 0;
1297: char ch = s.charAt(0);
1298:
1299: // if this line ends a comment, indent whitepace only;
1300: // otherwise indent across whitespace, asterisks and comment starts
1301:
1302: if (whitespaceOnly) {
1303: while (ch == ' ' || ch == '\t') { // SPACE or TAB
1304: cnt++;
1305: ch = s.charAt(cnt);
1306: }
1307: } else {
1308: while (ch == ' ' || ch == '\t' || ch == '*') { // SPACE, TAB or *
1309: cnt++;
1310: ch = s.charAt(cnt);
1311: }
1312: if ((s.charAt(cnt) == '/') && (s.charAt(cnt + 1) == '*'))
1313: cnt += 2;
1314: }
1315: return cnt;
1316: }
1317:
1318: /**
1319: * Transform indentation string to ensure: after " / *" follows " *" after " / * *"
1320: * follows " *" after " * /" follows ""
1321: */
1322: private String nextIndent(String s, boolean openBrace,
1323: boolean commentEndOnly) {
1324: // after an opening brace, add some spaces to the indentation
1325: if (openBrace)
1326: return s + spaces.substring(0, tabSize);
1327:
1328: if (commentEndOnly)
1329: return s.substring(0, s.length() - 1);
1330:
1331: if (s.endsWith("/*"))
1332: return s.substring(0, s.length() - 2) + " * ";
1333:
1334: return s;
1335: }
1336:
1337: /**
1338: * Insert a spaced tab at the current caret position in to the textPane.
1339: */
1340: private void insertSpacedTab(JTextComponent textPane) {
1341: int numSpaces = tabSize
1342: - (getCurrentColumn(textPane) % tabSize);
1343: textPane.replaceSelection(spaces.substring(0, numSpaces));
1344: }
1345:
1346: /**
1347: * Remove characters before the current caret position to take the
1348: * caret back to the previous TAB position. No check is made what kind
1349: * of characters those are - the caller should make sure they can be
1350: * removed (usually they should be whitespace).
1351: */
1352: private void removeTab(JTextComponent textPane, Document doc)
1353: throws BadLocationException {
1354: int col = getCurrentColumn(textPane);
1355: if (col > 0) {
1356: int remove = col % tabSize;
1357: if (remove == 0)
1358: remove = tabSize;
1359: int pos = textPane.getCaretPosition();
1360: doc.remove(pos - remove, remove);
1361: }
1362: }
1363:
1364: /**
1365: * Convert all tabs in this text to spaces, maintaining the current
1366: * indentation.
1367: *
1368: * @param textPane The text pane to convert
1369: * @return The number of tab characters converted
1370: */
1371: private int convertTabsToSpaces(JTextComponent textPane) {
1372: int count = 0;
1373: int lineNo = 0;
1374: AbstractDocument doc = (AbstractDocument) textPane
1375: .getDocument();
1376: Element root = doc.getDefaultRootElement();
1377: Element line = root.getElement(lineNo);
1378: try {
1379: while (line != null) {
1380: int start = line.getStartOffset();
1381: int length = line.getEndOffset() - start;
1382: String text = doc.getText(start, length);
1383: int startCount = count;
1384: int tabIndex = text.indexOf('\t');
1385: while (tabIndex != -1) {
1386: text = expandTab(text, tabIndex);
1387: count++;
1388: tabIndex = text.indexOf('\t');
1389: }
1390: if (count != startCount) { // there was a TAB in this line...
1391: doc.remove(start, length);
1392: doc.insertString(start, text, null);
1393: }
1394: lineNo++;
1395: line = root.getElement(lineNo);
1396: }
1397: } catch (BadLocationException exc) {
1398: Debug.reportError("stuffed up in 'convertTabsToSpaces'");
1399: }
1400: return count;
1401: }
1402:
1403: private String expandTab(String s, int idx) {
1404: int numSpaces = tabSize - (idx % tabSize);
1405: return s.substring(0, idx) + spaces.substring(0, numSpaces)
1406: + s.substring(idx + 1);
1407: }
1408:
1409: /**
1410: * Insert text from a named template into the editor at the current cursor
1411: * position. Every line in the template will be indented to the current
1412: * cursor position (in addition to possible indentation in the template
1413: * itself), and TAB characters at beginnings of lines in the template will
1414: * be converted to a spaced tab according to the current tabsize.
1415: *
1416: * @param textPane
1417: * The editor pane to enter the text into
1418: * @param templateName
1419: * The name of the template (without path or suffix)
1420: */
1421: private void insertTemplate(JTextComponent textPane,
1422: String templateName) {
1423: try {
1424: File template = Config.getTemplateFile(templateName);
1425: BufferedReader in = new BufferedReader(new FileReader(
1426: template));
1427: int pos = textPane.getCaretPosition();
1428: int column = getCurrentColumn(textPane);
1429: if (column > 40)
1430: column = 40;
1431: String line = in.readLine();
1432: while (line != null) {
1433: while ((line.length() > 0) && (line.charAt(0) == '\t')) {
1434: insertSpacedTab(textPane);
1435: line = line.substring(1);
1436: }
1437: textPane.replaceSelection(line);
1438: textPane.replaceSelection("\n");
1439: textPane.replaceSelection(spaces.substring(0, column)); // indent
1440: line = in.readLine();
1441: }
1442: textPane.setCaretPosition(pos);
1443: } catch (IOException exc) {
1444: Debug.reportError("Could not read method template.");
1445: Debug.reportError("Exception: " + exc);
1446: }
1447: }
1448:
1449: /**
1450: *
1451: */
1452: private void blockAction(JTextComponent textPane,
1453: LineAction lineAction) {
1454: Caret caret = textPane.getCaret();
1455: int selectionStart = caret.getMark();
1456: int selectionEnd = caret.getDot();
1457: if (selectionStart > selectionEnd) {
1458: int tmp = selectionStart;
1459: selectionStart = selectionEnd;
1460: selectionEnd = tmp;
1461: }
1462: if (selectionStart != selectionEnd)
1463: selectionEnd = selectionEnd - 1; // skip last position
1464:
1465: MoeSyntaxDocument doc = (MoeSyntaxDocument) textPane
1466: .getDocument();
1467: Element text = doc.getDefaultRootElement();
1468:
1469: int firstLineIndex = text.getElementIndex(selectionStart);
1470: int lastLineIndex = text.getElementIndex(selectionEnd);
1471: for (int i = firstLineIndex; i <= lastLineIndex; i++) {
1472: Element line = text.getElement(i);
1473: lineAction.apply(line, doc);
1474: }
1475:
1476: textPane.setCaretPosition(text.getElement(firstLineIndex)
1477: .getStartOffset());
1478: textPane.moveCaretPosition(text.getElement(lastLineIndex)
1479: .getEndOffset());
1480: }
1481:
1482: // --------------------------------------------------------------------
1483:
1484: /**
1485: * Create the table of action supported by this editor
1486: */
1487: private void createActionTable(JTextComponent textComponent) {
1488: undoAction = new UndoAction();
1489: redoAction = new RedoAction();
1490: compileAction = new CompileAction();
1491:
1492: // get all actions into arrays
1493:
1494: Action[] textActions = textComponent.getActions();
1495: Action[] myActions = { new SaveAction(), new ReloadAction(),
1496: new PageSetupAction(), new PrintAction(),
1497: new CloseAction(),
1498:
1499: undoAction, redoAction, new CommentBlockAction(),
1500: new UncommentBlockAction(), new IndentBlockAction(),
1501: new DeindentBlockAction(), new InsertMethodAction(),
1502: new IndentAction(), new DeIndentAction(),
1503: new NewLineAction(), new CopyLineAction(),
1504: new CutLineAction(), new CutEndOfLineAction(),
1505: new CutWordAction(), new CutEndOfWordAction(),
1506:
1507: new FindAction(), new FindNextAction(),
1508: new FindNextBackwardAction(), new ReplaceAction(),
1509: compileAction, new GoToLineAction(),
1510: new ToggleInterfaceAction(),
1511: new ToggleBreakPointAction(),
1512:
1513: new KeyBindingsAction(), new PreferencesAction(),
1514:
1515: new AboutAction(), new DescribeKeyAction(),
1516: new HelpMouseAction(), new ShowManualAction(), };
1517:
1518: // insert all actions into a hashtable
1519:
1520: actions = new Hashtable();
1521:
1522: Action action;
1523: for (int i = 0; i < textActions.length; i++) {
1524: action = textActions[i];
1525: //Debug.message("a: " + action.getValue(Action.NAME));
1526: actions.put(action.getValue(Action.NAME), action);
1527: }
1528: for (int i = 0; i < myActions.length; i++) {
1529: action = myActions[i];
1530: actions.put(action.getValue(Action.NAME), action);
1531: }
1532:
1533: // sort all actions into a big, ordered table
1534:
1535: actionTable = new Action[] {
1536:
1537: // edit functions
1538:
1539: (Action) (actions
1540: .get(DefaultEditorKit.deletePrevCharAction)), // 0
1541: (Action) (actions
1542: .get(DefaultEditorKit.deleteNextCharAction)),
1543: (Action) (actions.get(DefaultEditorKit.copyAction)),
1544: (Action) (actions.get(DefaultEditorKit.cutAction)),
1545: (Action) (actions.get("copy-line")),
1546: (Action) (actions.get("cut-line")),
1547: (Action) (actions.get("cut-end-of-line")),
1548: (Action) (actions.get("cut-word")),
1549: (Action) (actions.get("cut-end-of-word")),
1550: (Action) (actions.get(DefaultEditorKit.pasteAction)),
1551: (Action) (actions.get("indent")),
1552: (Action) (actions.get("de-indent")),
1553: (Action) (actions.get(DefaultEditorKit.insertTabAction)),
1554: (Action) (actions.get("new-line")),
1555: (Action) (actions
1556: .get(DefaultEditorKit.insertBreakAction)),
1557: (Action) (actions.get("insert-method")),
1558: (Action) (actions.get("comment-block")),
1559: (Action) (actions.get("uncomment-block")),
1560: (Action) (actions.get("indent-block")),
1561: (Action) (actions.get("deindent-block")),
1562:
1563: (Action) (actions
1564: .get(DefaultEditorKit.selectWordAction)), // 20
1565: (Action) (actions
1566: .get(DefaultEditorKit.selectLineAction)),
1567: (Action) (actions
1568: .get(DefaultEditorKit.selectParagraphAction)),
1569: (Action) (actions.get(DefaultEditorKit.selectAllAction)),
1570: (Action) (actions
1571: .get(DefaultEditorKit.selectionBackwardAction)),
1572: (Action) (actions
1573: .get(DefaultEditorKit.selectionForwardAction)),
1574: (Action) (actions
1575: .get(DefaultEditorKit.selectionUpAction)),
1576: (Action) (actions
1577: .get(DefaultEditorKit.selectionDownAction)),
1578: (Action) (actions
1579: .get(DefaultEditorKit.selectionBeginWordAction)),
1580: (Action) (actions
1581: .get(DefaultEditorKit.selectionEndWordAction)),
1582: (Action) (actions
1583: .get(DefaultEditorKit.selectionPreviousWordAction)), // 30
1584: (Action) (actions
1585: .get(DefaultEditorKit.selectionNextWordAction)),
1586: (Action) (actions
1587: .get(DefaultEditorKit.selectionBeginLineAction)),
1588: (Action) (actions
1589: .get(DefaultEditorKit.selectionEndLineAction)),
1590: (Action) (actions
1591: .get(DefaultEditorKit.selectionBeginParagraphAction)),
1592: (Action) (actions
1593: .get(DefaultEditorKit.selectionEndParagraphAction)),
1594: (Action) (actions.get("selection-page-up")),
1595: (Action) (actions.get("selection-page-down")),
1596: (Action) (actions
1597: .get(DefaultEditorKit.selectionBeginAction)),
1598: (Action) (actions
1599: .get(DefaultEditorKit.selectionEndAction)),
1600: (Action) (actions.get("unselect")),
1601:
1602: // move and scroll functions
1603:
1604: (Action) (actions.get(DefaultEditorKit.backwardAction)), // 41
1605: (Action) (actions.get(DefaultEditorKit.forwardAction)),
1606: (Action) (actions.get(DefaultEditorKit.upAction)),
1607: (Action) (actions.get(DefaultEditorKit.downAction)),
1608: (Action) (actions.get(DefaultEditorKit.beginWordAction)),
1609: (Action) (actions.get(DefaultEditorKit.endWordAction)),
1610: (Action) (actions
1611: .get(DefaultEditorKit.previousWordAction)),
1612: (Action) (actions.get(DefaultEditorKit.nextWordAction)),
1613: (Action) (actions.get(DefaultEditorKit.beginLineAction)),
1614: (Action) (actions.get(DefaultEditorKit.endLineAction)), // 50
1615: (Action) (actions
1616: .get(DefaultEditorKit.beginParagraphAction)),
1617: (Action) (actions
1618: .get(DefaultEditorKit.endParagraphAction)),
1619: (Action) (actions.get(DefaultEditorKit.pageUpAction)),
1620: (Action) (actions.get(DefaultEditorKit.pageDownAction)),
1621: (Action) (actions.get(DefaultEditorKit.beginAction)),
1622: (Action) (actions.get(DefaultEditorKit.endAction)),
1623:
1624: // class functions
1625: (Action) (actions.get("save")), // 57
1626: (Action) (actions.get("reload")),
1627: (Action) (actions.get("close")),
1628: (Action) (actions.get("print")),
1629: (Action) (actions.get("page-setup")),
1630:
1631: // customisation functions
1632: (Action) (actions.get("key-bindings")), // 62
1633: (Action) (actions.get("preferences")),
1634:
1635: // help functions
1636: (Action) (actions.get("describe-key")), // 64
1637: (Action) (actions.get("help-mouse")),
1638: (Action) (actions.get("show-manual")),
1639: (Action) (actions.get("about-editor")),
1640:
1641: // misc functions
1642: undoAction, // 68
1643: redoAction, (Action) (actions.get("find")),
1644: (Action) (actions.get("find-next")),
1645: (Action) (actions.get("find-next-backward")),
1646: (Action) (actions.get("replace")),
1647: (Action) (actions.get("compile")),
1648: (Action) (actions.get("toggle-interface-view")),
1649: (Action) (actions.get("toggle-breakpoint")),
1650: (Action) (actions.get("go-to-line")), }; // 78
1651:
1652: categories = new String[] {
1653: Config.getString("editor.functions.editFunctions"),
1654: Config.getString("editor.functions.moveScroll"),
1655: Config.getString("editor.functions.classFunctions"),
1656: Config.getString("editor.functions.customisation"),
1657: Config.getString("editor.functions.help"),
1658: Config.getString("editor.functions.misc") };
1659:
1660: categoryIndex = new int[] { 0, 41, 57, 62, 64, 68, 78 };
1661: }
1662:
1663: /**
1664: * Set up the default key bindings. Used for initial setup, or restoring the
1665: * default later on.
1666: */
1667: public void setDefaultKeyBindings() {
1668: keymap.removeBindings();
1669:
1670: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1671: KeyEvent.VK_S, SHORTCUT_MASK), (Action) (actions
1672: .get("save")));
1673: // "reload" not bound
1674: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1675: KeyEvent.VK_P, SHORTCUT_MASK), (Action) (actions
1676: .get("print")));
1677: // "page-setup" not bound
1678: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1679: KeyEvent.VK_W, SHORTCUT_MASK), (Action) (actions
1680: .get("close")));
1681: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1682: KeyEvent.VK_Z, SHORTCUT_MASK), (Action) (actions
1683: .get("undo")));
1684: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1685: KeyEvent.VK_Y, SHORTCUT_MASK), (Action) (actions
1686: .get("redo")));
1687: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1688: KeyEvent.VK_F8, 0), (Action) (actions
1689: .get("comment-block")));
1690: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1691: KeyEvent.VK_F7, 0), (Action) (actions
1692: .get("uncomment-block")));
1693: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1694: KeyEvent.VK_F6, 0), (Action) (actions
1695: .get("indent-block")));
1696: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1697: KeyEvent.VK_F5, 0), (Action) (actions
1698: .get("deindent-block")));
1699: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1700: KeyEvent.VK_M, SHORTCUT_MASK), (Action) (actions
1701: .get("insert-method")));
1702: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1703: KeyEvent.VK_TAB, 0), (Action) (actions.get("indent")));
1704: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1705: KeyEvent.VK_TAB, Event.SHIFT_MASK), (Action) (actions
1706: .get("de-indent")));
1707: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1708: KeyEvent.VK_I, SHORTCUT_MASK), (Action) (actions
1709: .get("insert-tab")));
1710: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1711: KeyEvent.VK_ENTER, 0), (Action) (actions
1712: .get("new-line")));
1713: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1714: KeyEvent.VK_ENTER, Event.SHIFT_MASK), (Action) (actions
1715: .get("insert-break")));
1716: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1717: KeyEvent.VK_F, SHORTCUT_MASK), (Action) (actions
1718: .get("find")));
1719: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1720: KeyEvent.VK_G, SHORTCUT_MASK), (Action) (actions
1721: .get("find-next")));
1722: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1723: KeyEvent.VK_G, SHIFT_SHORTCUT_MASK), (Action) (actions
1724: .get("find-next-backward")));
1725: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1726: KeyEvent.VK_R, SHORTCUT_MASK), (Action) (actions
1727: .get("replace")));
1728: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1729: KeyEvent.VK_L, SHORTCUT_MASK), (Action) (actions
1730: .get("go-to-line")));
1731: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1732: KeyEvent.VK_K, SHORTCUT_MASK), (Action) (actions
1733: .get("compile")));
1734: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1735: KeyEvent.VK_J, SHORTCUT_MASK), (Action) (actions
1736: .get("toggle-interface-view")));
1737: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1738: KeyEvent.VK_B, SHORTCUT_MASK), (Action) (actions
1739: .get("toggle-breakpoint")));
1740: // "key-bindings" not bound
1741: // "preferences" not bound
1742: // "about-editor" not bound
1743: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1744: KeyEvent.VK_D, SHORTCUT_MASK), (Action) (actions
1745: .get("describe-key")));
1746: // "help-mouse" not bound
1747: // "show-manual" not bound
1748:
1749: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1750: KeyEvent.VK_C, SHORTCUT_MASK), (Action) (actions
1751: .get(DefaultEditorKit.copyAction)));
1752: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1753: KeyEvent.VK_X, SHORTCUT_MASK), (Action) (actions
1754: .get(DefaultEditorKit.cutAction)));
1755: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1756: KeyEvent.VK_V, SHORTCUT_MASK), (Action) (actions
1757: .get(DefaultEditorKit.pasteAction)));
1758:
1759: // F2, F3, F4
1760: keymap
1761: .addActionForKeyStroke(KeyStroke.getKeyStroke(
1762: KeyEvent.VK_F2, 0), (Action) (actions
1763: .get("copy-line")));
1764: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1765: KeyEvent.VK_F3, 0), (Action) (actions
1766: .get(DefaultEditorKit.pasteAction)));
1767: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1768: KeyEvent.VK_F4, 0), (Action) (actions.get("cut-line")));
1769:
1770: // cursor block
1771: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1772: KeyEvent.VK_UP, ALT_SHORTCUT_MASK), (Action) (actions
1773: .get(DefaultEditorKit.pasteAction)));
1774: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1775: KeyEvent.VK_LEFT, ALT_SHORTCUT_MASK), (Action) (actions
1776: .get(DefaultEditorKit.deletePrevCharAction)));
1777: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1778: KeyEvent.VK_RIGHT, ALT_SHORTCUT_MASK),
1779: (Action) (actions
1780: .get(DefaultEditorKit.deleteNextCharAction)));
1781: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1782: KeyEvent.VK_LEFT, SHIFT_ALT_SHORTCUT_MASK),
1783: (Action) (actions.get("cut-line")));
1784: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1785: KeyEvent.VK_RIGHT, SHIFT_ALT_SHORTCUT_MASK),
1786: (Action) (actions.get("cut-end-of-line")));
1787: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1788: KeyEvent.VK_LEFT, DOUBLE_SHORTCUT_MASK),
1789: (Action) (actions.get("cut-word")));
1790: keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(
1791: KeyEvent.VK_RIGHT, DOUBLE_SHORTCUT_MASK),
1792: (Action) (actions.get("cut-end-of-word")));
1793: }
1794:
1795: /**
1796: * Interface LineAction - a superclass for all line actions. Line actions
1797: * manipulate a single line of text and are used by the blockAction method.
1798: * The blockAction applies a LineAction to each line in a block of text.
1799: */
1800: interface LineAction {
1801: /**
1802: * Apply some action to a line in the document.
1803: */
1804: public void apply(Element line, MoeSyntaxDocument doc);
1805: }
1806:
1807: /**
1808: * Class CommentLineAction - add a comment symbol to the given line.
1809: */
1810: class CommentLineAction implements LineAction {
1811: /**
1812: * Comment the given line
1813: */
1814: public void apply(Element line, MoeSyntaxDocument doc) {
1815: int lineStart = line.getStartOffset();
1816: try {
1817: doc.insertString(lineStart, "// ", null);
1818: } catch (Exception exc) {
1819: }
1820: }
1821: }
1822:
1823: /**
1824: * Class UncommentLineAction - remove the comment symbol (if any) from the
1825: * given line.
1826: */
1827: class UncommentLineAction implements LineAction {
1828: public void apply(Element line, MoeSyntaxDocument doc) {
1829: int lineStart = line.getStartOffset();
1830: int lineEnd = line.getEndOffset();
1831: try {
1832: String lineText = doc.getText(lineStart, lineEnd
1833: - lineStart);
1834: if (lineText.trim().startsWith("//")) {
1835: int cnt = 0;
1836: while (lineText.charAt(cnt) != '/')
1837: // whitespace chars
1838: cnt++;
1839: if (lineText.charAt(cnt + 2) == ' ')
1840: doc.remove(lineStart, cnt + 3);
1841: else
1842: doc.remove(lineStart, cnt + 2);
1843: }
1844: } catch (Exception exc) {
1845: }
1846: }
1847: }
1848:
1849: /**
1850: * Class IndentLineAction - add one level of indentation to the given line.
1851: */
1852: class IndentLineAction implements LineAction {
1853: public void apply(Element line, MoeSyntaxDocument doc) {
1854: int lineStart = line.getStartOffset();
1855: try {
1856: doc.insertString(lineStart, spaces
1857: .substring(0, tabSize), null);
1858: } catch (Exception exc) {
1859: }
1860: }
1861: }
1862:
1863: /**
1864: * Class DeindentLineAction - remove one indentation level from the given
1865: * line.
1866: */
1867: class DeindentLineAction implements LineAction {
1868: public void apply(Element line, MoeSyntaxDocument doc) {
1869: int lineStart = line.getStartOffset();
1870: int lineEnd = line.getEndOffset();
1871: try {
1872: String lineText = doc.getText(lineStart, lineEnd
1873: - lineStart);
1874: String spacedTab = spaces.substring(0, tabSize);
1875: if (lineText.startsWith(spacedTab))
1876: doc.remove(lineStart, tabSize); // remove spaced tab
1877: else if (lineText.charAt(0) == TAB_CHAR)
1878: doc.remove(lineStart, 1); // remove hard tab
1879: else {
1880: int cnt = 0;
1881: while (lineText.charAt(cnt) == ' ')
1882: // remove spaces
1883: cnt++;
1884: doc.remove(lineStart, cnt);
1885: }
1886: } catch (Exception exc) {
1887: }
1888: }
1889: }
1890:
1891: /**
1892: * Class KeyCatcher - used for implementation of "describe-key" command to
1893: * catch the next key press so that we can see what it does.
1894: */
1895: class KeyCatcher extends KeyAdapter {
1896: MoeEditor editor;
1897:
1898: public void keyPressed(KeyEvent e) {
1899: int keyCode = e.getKeyCode();
1900:
1901: if (keyCode == KeyEvent.VK_CAPS_LOCK
1902: || // the keys we want to
1903: // ignore...
1904: keyCode == KeyEvent.VK_SHIFT
1905: || keyCode == KeyEvent.VK_CONTROL
1906: || keyCode == KeyEvent.VK_META
1907: || keyCode == KeyEvent.VK_ALT
1908: || keyCode == KeyEvent.VK_ALT_GRAPH
1909: || keyCode == KeyEvent.VK_COMPOSE
1910: || keyCode == KeyEvent.VK_NUM_LOCK
1911: || keyCode == KeyEvent.VK_SCROLL_LOCK
1912: || keyCode == KeyEvent.VK_UNDEFINED)
1913: return;
1914:
1915: KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
1916: String modifierName = KeyEvent.getKeyModifiersText(key
1917: .getModifiers());
1918: String keyName = KeyEvent.getKeyText(keyCode);
1919: if (modifierName.length() > 0)
1920: keyName = modifierName + "+" + keyName;
1921:
1922: Keymap map = keymap;
1923: Action action = null;
1924:
1925: while (map != null && action == null) {
1926: action = map.getAction(key);
1927: map = map.getResolveParent();
1928: }
1929:
1930: if (action == null) {
1931: // BUG workaround: bindings inhertited from component are not
1932: // found
1933: // through the keymap. we search for them explicitly here...
1934: Object binding = componentInputMap.get(key);
1935: if (binding == null) {
1936: //editor.writeMessage(keyName + " is not bound to a function.");
1937: editor
1938: .writeMessage(keyName
1939: + Config
1940: .getString("editor.keypressed.keyIsNotBound"));
1941: } else {
1942: editor
1943: .writeMessage(keyName
1944: + Config
1945: .getString("editor.keypressed.callsTheFunction")
1946: + binding + "\"");
1947: }
1948: } else {
1949: String name = (String) action.getValue(Action.NAME);
1950: editor
1951: .writeMessage(keyName
1952: + Config
1953: .getString("editor.keypressed.callsTheFunction")
1954: + name + "\"");
1955: }
1956: e.getComponent().removeKeyListener(keyCatcher);
1957: e.consume();
1958: }
1959:
1960: public void setEditor(MoeEditor ed) {
1961: editor = ed;
1962: }
1963:
1964: }
1965:
1966: } // end class MoeActions
|