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.*;
0012: import java.awt.event.ActionEvent;
0013: import java.awt.event.WindowAdapter;
0014: import java.awt.event.WindowEvent;
0015: import java.awt.print.PageFormat;
0016: import java.awt.print.PrinterJob;
0017: import java.beans.PropertyChangeEvent;
0018: import java.beans.PropertyChangeListener;
0019: import java.io.*;
0020: import java.net.URL;
0021: import java.util.ArrayList;
0022: import java.util.HashMap;
0023: import java.util.List;
0024: import java.util.Properties;
0025: import java.util.StringTokenizer;
0026:
0027: import javax.swing.*;
0028: import javax.swing.border.EmptyBorder;
0029: import javax.swing.event.*;
0030: import javax.swing.text.AbstractDocument;
0031: import javax.swing.text.BadLocationException;
0032: import javax.swing.text.Caret;
0033: import javax.swing.text.Element;
0034: import javax.swing.text.SimpleAttributeSet;
0035: import javax.swing.text.html.HTMLDocument;
0036: import javax.swing.text.html.HTMLEditorKit;
0037: import javax.swing.text.html.HTMLFrameHyperlinkEvent;
0038:
0039: import org.syntax.jedit.tokenmarker.JavaTokenMarker;
0040:
0041: import bluej.BlueJEvent;
0042: import bluej.BlueJEventListener;
0043: import bluej.Config;
0044: import bluej.editor.EditorWatcher;
0045: import bluej.editor.LineColumn;
0046: import bluej.pkgmgr.PkgMgrFrame;
0047: import bluej.prefmgr.PrefMgr;
0048: import bluej.utility.Debug;
0049: import bluej.utility.DialogManager;
0050: import bluej.utility.FileUtility;
0051: import bluej.utility.Utility;
0052:
0053: /**
0054: * Moe is the editor of the BlueJ environment. This class is the main class of
0055: * this editor and implements the top-level functionality.
0056: *
0057: * MoeEditor implements the Editor interface, which defines the interface to the
0058: * rest of the BlueJ system.
0059: *
0060: * @author Michael Kolling
0061: * @author Bruce Quig
0062: * @author Damiano Bolla
0063: */
0064:
0065: public final class MoeEditor extends JFrame implements
0066: bluej.editor.Editor, BlueJEventListener, HyperlinkListener,
0067: DocumentListener {
0068: // -------- CONSTANTS --------
0069:
0070: // version number
0071: final static int version = 200;
0072: final static String versionString = "2.0";
0073:
0074: // colours
0075: final static Color cursorColor = new Color(255, 0, 100); // cursor
0076:
0077: final static Color frameBgColor = new Color(196, 196, 196);
0078: final static Color infoColor = new Color(240, 240, 240);
0079: final static Color lightGrey = new Color(224, 224, 224);
0080: final static Color selectionColour = Config.getSelectionColour();
0081: final static Color titleCol = Config
0082: .getItemColour("colour.text.fg");
0083: final static Color envOpColour = Config
0084: .getItemColour("colour.menu.environOp");
0085:
0086: // Icons
0087: final static Image iconImage = Config.getImageAsIcon(
0088: "image.icon.editor").getImage();
0089:
0090: // Fonts
0091: public static int printFontSize = Config.getPropInteger(
0092: "bluej.fontsize.printText", 10);
0093: public static Font printFont = new Font("Monospaced", Font.PLAIN,
0094: printFontSize);
0095:
0096: // Strings
0097: String implementationString = Config
0098: .getString("editor.implementationLabel");
0099: String interfaceString = Config.getString("editor.interfaceLabel");
0100:
0101: // suffixes for resources
0102: final static String LabelSuffix = "Label";
0103: final static String ActionSuffix = "Action";
0104: final static String TooltipSuffix = "Tooltip";
0105: final static String AcceleratorSuffix = "Accelerator";
0106:
0107: // file suffixes
0108: final static String CRASHFILE_SUFFIX = "#";
0109: final static String BACKUP_SUFFIX = "~";
0110:
0111: final static String spaces = " ";
0112:
0113: final static String COMPILED = "compiled";
0114:
0115: private static boolean matchBrackets = false;
0116:
0117: // -------- INSTANCE VARIABLES --------
0118:
0119: private EditorWatcher watcher;
0120: private Properties resources;
0121:
0122: private AbstractDocument document;
0123: private MoeSyntaxDocument sourceDocument;
0124: private HTMLDocument htmlDocument;
0125:
0126: private MoeActions actions;
0127: public MoeUndoManager undoManager;
0128:
0129: JEditorPane currentTextPane; // text component currently dislayed
0130: private JEditorPane sourcePane; // the component holding the source text
0131:
0132: private JEditorPane htmlPane; // the component holding the javadoc html
0133: private MoeCaret moeCaret;
0134:
0135: private Info info; // the info number label
0136: private JPanel statusArea; // the status area
0137: private StatusLabel saveState; // the status label
0138: private JComboBox interfaceToggle;
0139: private GoToLineDialog goToLineDialog;
0140:
0141: private JScrollPane scrollPane;
0142: private JComponent toolbar; // The toolbar
0143:
0144: private String filename; // name of file or null
0145: private long lastModified; // time of last modification of file
0146: private String windowTitle; // title of editor window
0147: private String docFilename; // path to javadoc html file
0148:
0149: private boolean sourceIsCode; // true if current buffer is code
0150: private boolean viewingHTML;
0151:
0152: private int currentStepPos; // position of step mark (or -1)
0153: private boolean mayHaveBreakpoints; // true if there were BP here
0154: private boolean ignoreChanges = false;
0155: private boolean tabsAreExpanded = false;
0156:
0157: private MoePrinter printer;
0158:
0159: private TextInsertNotifier doTextInsert = new TextInsertNotifier();
0160:
0161: /**
0162: * Property map, allows BlueJ extensions to assosciate property values with
0163: * this editor instance; otherwise unused.
0164: */
0165: private HashMap propertyMap = new HashMap();
0166:
0167: /**
0168: * Constructor. Title may be null
0169: */
0170: public MoeEditor(String title, boolean isCode,
0171: EditorWatcher watcher, boolean showToolbar,
0172: boolean showLineNum, Properties resources) {
0173: super ("Moe");
0174: this .watcher = watcher;
0175: this .resources = resources;
0176:
0177: filename = null;
0178: windowTitle = title;
0179: sourceIsCode = isCode;
0180: viewingHTML = false;
0181: currentStepPos = -1;
0182: mayHaveBreakpoints = false;
0183: matchBrackets = PrefMgr.getFlag(PrefMgr.MATCH_BRACKETS);
0184: undoManager = new MoeUndoManager(this );
0185:
0186: initWindow();
0187: }
0188:
0189: // --------------------------------------------------------------------
0190:
0191: /**
0192: * Update the state of controls bound to "undo".
0193: */
0194: public void updateUndoControls() {
0195: actions.setUndoEnabled(undoManager.canUndo());
0196: }
0197:
0198: /**
0199: * Update the state of controls bound to "redo".
0200: */
0201: public void updateRedoControls() {
0202: actions.setRedoEnabled(undoManager.canRedo());
0203: }
0204:
0205: /**
0206: * Load the file "filename" and show the editor window.
0207: */
0208: public boolean showFile(String filename, boolean compiled, // inherited from Editor, redefined
0209: String docFilename, Rectangle bounds) {
0210: this .filename = filename;
0211: this .docFilename = docFilename;
0212:
0213: if (bounds != null) {
0214: setBounds(bounds);
0215: }
0216:
0217: boolean loaded = false;
0218: boolean readError = false;
0219:
0220: if (filename != null) {
0221:
0222: try {
0223: // check for crash file
0224: String crashFilename = filename + CRASHFILE_SUFFIX;
0225: String backupFilename = crashFilename + "backup";
0226: File crashFile = new File(crashFilename);
0227: if (crashFile.exists()) {
0228: File backupFile = new File(backupFilename);
0229: backupFile.delete();
0230: crashFile.renameTo(backupFile);
0231: DialogManager.showMessage(this , "editor-crashed");
0232: }
0233:
0234: FileReader reader = new FileReader(filename);
0235: sourcePane.read(reader, null);
0236: reader.close();
0237: File file = new File(filename);
0238: lastModified = file.lastModified();
0239:
0240: sourceDocument = (MoeSyntaxDocument) sourcePane
0241: .getDocument();
0242:
0243: // set TokenMarker for syntax highlighting if desired
0244: checkSyntaxStatus();
0245:
0246: sourceDocument.addDocumentListener(this );
0247: sourceDocument.addUndoableEditListener(undoManager);
0248: document = sourceDocument;
0249: loaded = true;
0250: } catch (FileNotFoundException ex) {
0251: clear();
0252: } catch (IOException ex) {
0253: readError = true;
0254: }
0255: }
0256:
0257: if (!loaded) // should exist, but didn't
0258: return false;
0259:
0260: // if (loaded) ## NYI
0261: // if (newFile.canWrite()) { // have write permission
0262: // save_state = Saved;
0263: // statusLabel.setText("saved");
0264: // }
0265: // else {
0266: // save_state = ReadOnly;
0267: // statusLabel.setText("read only");
0268: // }
0269: // else
0270: // save_state = Saved;
0271:
0272: if (loaded)
0273: info.message(Config.getString("editor.info.version") + " "
0274: + versionString);
0275: else if (readError)
0276: info.warning(
0277: Config.getString("editor.info.readingProblem"),
0278: Config.getString("editor.info.regularFile"));
0279: else
0280: info.message(Config.getString("editor.info.version"
0281: + versionString), Config
0282: .getString("editor.info.newFile"));
0283:
0284: setWindowTitle();
0285: sourcePane.setFont(PrefMgr.getStandardEditorFont());
0286: sourcePane.setSelectionColor(selectionColour);
0287:
0288: setCompileStatus(compiled);
0289:
0290: return true;
0291: }
0292:
0293: /**
0294: * Reload the editor content from the associated file, discarding unsaved
0295: * edits.
0296: */
0297: public void reloadFile() // inherited from Editor, redefined
0298: {
0299: doReload();
0300: }
0301:
0302: /**
0303: * Wipe out contents of the editor.
0304: */
0305: public void clear() // inherited from Editor, redefined
0306: {
0307: ignoreChanges = true;
0308: sourcePane.setText("");
0309: ignoreChanges = false;
0310: }
0311:
0312: /**
0313: * Insert a string into the buffer. The editor is not immediately
0314: * redisplayed. This function is typically used in a sequence "clear;
0315: * [insertText]*; setVisible(true)". If the selection is on, it is replaced
0316: * by the new text.
0317: *
0318: * @param text the text to be inserted
0319: * @param caretBack move the caret to the beginning of the inserted text
0320: */
0321: public void insertText(String text, boolean caretBack) // inherited from Editor, redefined
0322: {
0323: sourcePane.replaceSelection(text);
0324: if (caretBack) {
0325: sourcePane.setCaretPosition(sourcePane.getCaretPosition()
0326: - text.length());
0327: }
0328: }
0329:
0330: /**
0331: * Show the editor window. This includes whatever is necessary of the
0332: * following: make visible, de-iconify, bring to front of window stack.
0333: *
0334: * @param vis The new visible value
0335: */
0336: public void setVisible(boolean vis) // inherited from Editor, redefined
0337: {
0338: if (vis) {
0339: sourcePane.setFont(PrefMgr.getStandardEditorFont());
0340: checkSyntaxStatus();
0341: checkBracketStatus();
0342: setState(Frame.NORMAL); // de-iconify
0343: toFront(); // window to front
0344: }
0345: Utility.bringToFront();
0346: super .setVisible(vis); // show the window
0347: }
0348:
0349: /**
0350: * Refresh the editor window.
0351: */
0352: public void refresh() // inherited from Editor, redefined
0353: {
0354: sourcePane.setFont(PrefMgr.getStandardEditorFont());
0355: checkBracketStatus();
0356: checkSyntaxStatus();
0357: currentTextPane.repaint();
0358: }
0359:
0360: /**
0361: * True is the editor is on screen.
0362: *
0363: * @return The showing value
0364: */
0365: public boolean isShowing() // inherited from Editor, redefined
0366: {
0367: if (isVisible() != super .isShowing()) {
0368: Debug.message("isVisible is not isShowing!");
0369: }
0370: return super .isShowing();
0371: }
0372:
0373: /**
0374: * Save the buffer to disk under current filename. This is often called from
0375: * the outside - just in case. Save only if really necessary, otherwise we
0376: * save much too often. PRE: filename != null
0377: */
0378: public void save() // inherited from Editor, redefined
0379: throws IOException {
0380: IOException failureException = null;
0381: if (saveState.isChanged()) {
0382: BufferedWriter writer = null;
0383: try {
0384: // The crash file is used during writing and will remain in
0385: // case of a crash during the write operation. The backup
0386: // file always contains the last version.
0387: String crashFilename = filename + CRASHFILE_SUFFIX;
0388: String backupFilename = filename + BACKUP_SUFFIX;
0389:
0390: // make a backup to the crash file
0391: FileUtility.copyFile(filename, crashFilename);
0392:
0393: writer = new BufferedWriter(new FileWriter(filename));
0394: sourcePane.write(writer);
0395: writer.close();
0396: setSaved();
0397: File file = new File(filename);
0398: lastModified = file.lastModified();
0399:
0400: if (PrefMgr.getFlag(PrefMgr.MAKE_BACKUP)) {
0401: // if all went well, rename the crash file as a normal
0402: // backup
0403: File crashFile = new File(crashFilename);
0404: File backupFile = new File(backupFilename);
0405: backupFile.delete();
0406: crashFile.renameTo(backupFile);
0407: } else {
0408: File crashFile = new File(crashFilename);
0409: crashFile.delete();
0410: }
0411: } catch (IOException ex) {
0412: failureException = ex;
0413: info.warning(Config
0414: .getString("editor.info.errorSaving")
0415: + " - " + ex.getLocalizedMessage());
0416: } finally {
0417: try {
0418: if (writer != null)
0419: writer.close();
0420: } catch (IOException ex) {
0421: failureException = ex;
0422: }
0423: }
0424: }
0425:
0426: // If an error occurred, set a message in the editor status bar, and
0427: // re-throw the exception.
0428: if (failureException != null) {
0429: info.warning(Config.getString("editor.info.errorSaving")
0430: + " - " + failureException.getLocalizedMessage());
0431: throw failureException;
0432: }
0433: }
0434:
0435: /**
0436: * The editor wants to close. Do this through the EditorManager so that we
0437: * can be removed from the list of open editors.
0438: */
0439: public void close() // inherited from Editor, redefined
0440: {
0441: try {
0442: save();
0443: } catch (IOException ioe) {
0444: }
0445: // temporary - should really be done by watcher from outside
0446: doClose();
0447: }
0448:
0449: /**
0450: * Display a message (used for compile/runtime errors). An editor must
0451: * support at least two lines of message text, so the message can contain a
0452: * newline character.
0453: *
0454: * @param message the message to be displayed
0455: * @param lineNumber The line to highlight
0456: * @param column the column to move the cursor to
0457: * @param beep if true, do a system beep
0458: * @param setStepMark if true, set step mark (for single stepping)
0459: * @param help name of help group (may be null)
0460: */
0461: public void displayMessage(String message, int lineNumber,
0462: int column, boolean beep, boolean setStepMark, String help) // inherited from Editor
0463: {
0464: switchToSourceView();
0465:
0466: Element line = getLine(lineNumber);
0467: int pos = line.getStartOffset();
0468:
0469: if (setStepMark) {
0470: setStepMark(pos);
0471: }
0472:
0473: // highlight the line
0474:
0475: sourcePane.setCaretPosition(pos);
0476: sourcePane.moveCaretPosition(line.getEndOffset() - 1);
0477: moeCaret.setPersistentHighlight();
0478: // w/o line break
0479:
0480: // display the message
0481:
0482: if (beep)
0483: info.warning(message);
0484: else
0485: info.message(message);
0486:
0487: if (help != null)
0488: info.setHelp(help);
0489: }
0490:
0491: /**
0492: * Set the selection of the editor to be a len characters on the line
0493: * lineNumber, starting with column columnNumber
0494: *
0495: * @param lineNumber the line to select characters on
0496: * @param columnNumber the column to start selection at (1st column is 1 - not 0)
0497: * @param len the number of characters to select
0498: */
0499: public void setSelection(int lineNumber, int columnNumber, int len) {
0500: Element line = getLine(lineNumber);
0501:
0502: sourcePane.select(line.getStartOffset() + columnNumber - 1,
0503: line.getStartOffset() + columnNumber + len - 1);
0504: }
0505:
0506: /**
0507: * Select a specified area of text.
0508: *
0509: * @param lineNumber1 The new selection value
0510: * @param columnNumber1 The new selection value
0511: * @param lineNumber2 The new selection value
0512: * @param columnNumber2 The new selection value
0513: */
0514: public void setSelection(int lineNumber1, int columnNumber1,
0515: int lineNumber2, int columnNumber2) {
0516: /*
0517: * if (lineNumber2 < lineNumber1) return; if (lineNumber2 == lineNumber1 &&
0518: * (columnNumber2 < columnNumber1)) return;
0519: */
0520: Element line1 = getLine(lineNumber1);
0521: Element line2 = getLine(lineNumber2);
0522:
0523: sourcePane.select(line1.getStartOffset() + columnNumber1 - 1,
0524: line2.getStartOffset() + columnNumber2 - 1);
0525: }
0526:
0527: /**
0528: * Get the text currently selected.
0529: *
0530: * @return The selected text.
0531: */
0532: public String getSelectedText() {
0533: return sourcePane.getSelectedText();
0534: }
0535:
0536: /**
0537: * Remove the step mark (the mark that shows the current line when
0538: * single-stepping through code). If it is not currently displayed, do
0539: * nothing.
0540: */
0541: public void removeStepMark() // inherited from Editor
0542: {
0543: if (currentStepPos != -1) {
0544: SimpleAttributeSet a = new SimpleAttributeSet();
0545: a.addAttribute(MoeSyntaxView.STEPMARK, Boolean.FALSE);
0546: sourceDocument.setParagraphAttributes(currentStepPos, a);
0547: currentStepPos = -1;
0548: // remove highlight as well
0549: sourcePane.setCaretPosition(sourcePane.getCaretPosition());
0550: // force an update of UI
0551: repaint();
0552: }
0553: }
0554:
0555: /**
0556: * Change class name.
0557: *
0558: * @param title new window title
0559: * @param filename new file name
0560: */
0561: public void changeName(String title, String filename,
0562: String docFilename) {
0563: this .filename = filename;
0564: this .docFilename = docFilename;
0565: // error ## - need to add full path
0566: windowTitle = title;
0567: setWindowTitle();
0568: }
0569:
0570: /**
0571: * Set the "compiled" status
0572: *
0573: * @param compiled True if the class has been compiled.
0574: */
0575: public void setCompiled(boolean compiled) {
0576: setCompileStatus(compiled);
0577: if (compiled) {
0578: info.message(Config.getString("editor.info.compiled"));
0579: }
0580: }
0581:
0582: /**
0583: * Called when all the breakpoints have been cleared. The editor should
0584: * update its display to show that no breakpoints are set.
0585: */
0586: public void removeBreakpoints() {
0587: // This may be a callback in response to a modification event.
0588: // If we try to remove breakpoints during the modification notification,
0589: // AbstractDocument throws an exception.
0590: EventQueue.invokeLater(new Runnable() {
0591: public void run() {
0592: clearAllBreakpoints();
0593: }
0594: });
0595: }
0596:
0597: /**
0598: * The editor must re-set all its breakpoints via the EditorWatcher
0599: * interface.
0600: */
0601: public void reInitBreakpoints() {
0602: if (mayHaveBreakpoints) {
0603: mayHaveBreakpoints = false;
0604: for (int i = 1; i <= numberOfLines(); i++) {
0605: if (lineHasBreakpoint(i)) {
0606: watcher.breakpointToggleEvent(this , i, true);
0607: mayHaveBreakpoints = true;
0608: }
0609: }
0610: }
0611: }
0612:
0613: /**
0614: * Determine whether this buffer has been modified.
0615: *
0616: * @return a boolean indicating whether the file is modified
0617: */
0618: public boolean isModified() // inherited from Editor
0619: {
0620: return (saveState.isChanged());
0621: }
0622:
0623: /**
0624: * Set this editor to read-only.
0625: *
0626: * @param readOnly The new readOnly value
0627: */
0628: public void setReadOnly(boolean readOnly) {
0629: if (readOnly) {
0630: saveState.setState(StatusLabel.READONLY);
0631: updateUndoControls();
0632: updateRedoControls();
0633: }
0634: sourcePane.setEditable(!readOnly);
0635: }
0636:
0637: /**
0638: * Returns if this editor is read-only. Accessor for the setReadOnly
0639: * property.
0640: *
0641: * @return a boolean indicating whether the editor is read-only.
0642: */
0643: public boolean isReadOnly() {
0644: return !sourcePane.isEditable();
0645: }
0646:
0647: /**
0648: * Set this editor to display either the interface or the source code of
0649: * this class
0650: *
0651: * @param interfaceStatus If true, display class interface, otherwise source.
0652: */
0653: public void showInterface(boolean interfaceStatus) {
0654: interfaceToggle.setSelectedIndex(interfaceStatus ? 1 : 0);
0655: }
0656:
0657: /**
0658: * Tell whether the editor is currently displaying the interface or the
0659: * source of the class.
0660: *
0661: * @return True, if interface is currently shown, false otherwise.
0662: */
0663: public boolean isShowingInterface() {
0664: return viewingHTML;
0665: }
0666:
0667: /**
0668: * Check whether the source file has changed on disk. If it has, reload.
0669: */
0670: private void checkForChangeOnDisk() {
0671: File file = new File(filename);
0672: long modified = file.lastModified();
0673: if (modified != lastModified) {
0674: if (saveState.isChanged()) {
0675: int answer = DialogManager.askQuestion(this ,
0676: "changed-on-disk");
0677: if (answer == 0)
0678: doReload();
0679: else
0680: lastModified = modified; // don't ask again for this change
0681: } else {
0682: doReload();
0683: }
0684: }
0685: }
0686:
0687: /**
0688: * Returns the current caret location within the edited text.
0689: *
0690: * @return An object describing the current caret location.
0691: */
0692: public LineColumn getCaretLocation() {
0693: int caretOffset = sourcePane.getCaretPosition();
0694: return getLineColumnFromOffset(caretOffset);
0695: }
0696:
0697: /**
0698: * Returns the LineColumn object from the given offset in the text.
0699: *
0700: * @param offset The number of characters from the beginning of text (startng
0701: * from zero)
0702: * @return the LineColumn object or null if the offset points outside the
0703: * text.
0704: */
0705: public LineColumn getLineColumnFromOffset(int offset) {
0706: int lineNumber = sourceDocument.getDefaultRootElement()
0707: .getElementIndex(offset);
0708:
0709: if (lineNumber < 0) {
0710: return null;
0711: }
0712:
0713: Element lineElement = getLineAt(offset);
0714: int column = offset - lineElement.getStartOffset();
0715:
0716: if (column < 0) {
0717: return null;
0718: }
0719:
0720: return new LineColumn(lineNumber, column);
0721: }
0722:
0723: /**
0724: * Sets the current Caret location within the edited text.
0725: *
0726: * @param location The location in the text to set the Caret to.
0727: * @throws IllegalArgumentException
0728: * if the specified TextLocation represents a position which
0729: * does not exist in the text.
0730: */
0731: public void setCaretLocation(LineColumn location) {
0732: sourcePane.setCaretPosition(getOffsetFromLineColumn(location));
0733: }
0734:
0735: /**
0736: * Returns the location where the current selection begins.
0737: *
0738: * @return the current beginning of the selection or null if no text is
0739: * selected.
0740: */
0741: public LineColumn getSelectionBegin() {
0742: Caret aCaret = sourcePane.getCaret();
0743:
0744: // If the dot is == as the mark then there is no selection.
0745: if (aCaret.getDot() == aCaret.getMark()) {
0746: return null;
0747: }
0748:
0749: int beginOffset = Math.min(aCaret.getDot(), aCaret.getMark());
0750:
0751: return getLineColumnFromOffset(beginOffset);
0752: }
0753:
0754: /**
0755: * Returns the location where the current selection ends.
0756: *
0757: * @return the current end of the selection or null if no text is selected.
0758: */
0759: public LineColumn getSelectionEnd() {
0760: Caret aCaret = sourcePane.getCaret();
0761:
0762: // If the dot is == as the mark then there is no selection.
0763: if (aCaret.getDot() == aCaret.getMark()) {
0764: return null;
0765: }
0766:
0767: int endOffset = Math.max(aCaret.getDot(), aCaret.getMark());
0768:
0769: return getLineColumnFromOffset(endOffset);
0770: }
0771:
0772: /**
0773: * Returns the text which lies between the two LineColumn.
0774: *
0775: * @param begin The beginning of the text to get
0776: * @param end The end of the text to get
0777: * @return The text between the 'begin' and 'end' positions.
0778: * @throws IllegalArgumentException
0779: * if either of the specified TextLocations represent a position
0780: * which does not exist in the text.
0781: */
0782: public String getText(LineColumn begin, LineColumn end) {
0783: int first = getOffsetFromLineColumn(begin);
0784: int last = getOffsetFromLineColumn(end);
0785: int beginOffset = Math.min(first, last);
0786: int endOffset = Math.max(first, last);
0787:
0788: try {
0789: return sourceDocument.getText(beginOffset, endOffset
0790: - beginOffset);
0791: } catch (BadLocationException exc) {
0792: throw new IllegalArgumentException(exc.getMessage());
0793: }
0794: }
0795:
0796: /**
0797: * Request to the editor to replace the text between 'begin' and 'end' with
0798: * the given newText. If begin and end point to the same location, the text
0799: * is inserted.
0800: *
0801: * @param begin The start position of text to replace
0802: * @param end The end position of text to replace
0803: * @param newText The text to insert
0804: * @throws IllegalArgumentException
0805: * if either of the specified LineColumn represent a position
0806: * which does not exist in the text.
0807: * @throws BadLocationException
0808: * if internally the text points outside a location in the text.
0809: */
0810: public void setText(LineColumn begin, LineColumn end, String newText)
0811: throws BadLocationException {
0812: int start = getOffsetFromLineColumn(begin);
0813: int finish = getOffsetFromLineColumn(end);
0814:
0815: int beginOffset = Math.min(start, finish);
0816: int endOffset = Math.max(start, finish);
0817:
0818: if (beginOffset != endOffset) {
0819: sourceDocument.remove(beginOffset, endOffset - beginOffset);
0820: }
0821:
0822: sourceDocument.insertString(beginOffset, newText, null);
0823: }
0824:
0825: /**
0826: * Request to the editor to mark the text between begin and end as selected.
0827: *
0828: * @param begin The start position of the selection
0829: * @param end The end position of the selection
0830: * @throws IllegalArgumentException
0831: * if either of the specified TextLocations represent a position
0832: * which does not exist in the text.
0833: */
0834: public void setSelection(LineColumn begin, LineColumn end) {
0835: int start = getOffsetFromLineColumn(begin);
0836: int finish = getOffsetFromLineColumn(end);
0837:
0838: int selectionStart = Math.min(start, finish);
0839: int selectionEnd = Math.max(start, finish);
0840:
0841: sourcePane.setCaretPosition(selectionStart);
0842: sourcePane.moveCaretPosition(selectionEnd);
0843: }
0844:
0845: /**
0846: * Translates a LineColumn into an offset into the text held by the editor.
0847: *
0848: * @param location position to be translated
0849: * @return the offset into the content of this editor
0850: * @throws IllegalArgumentException
0851: * if the specified LineColumn represent a position which does
0852: * not exist in the text.
0853: */
0854: public int getOffsetFromLineColumn(LineColumn location) {
0855: if (location.getLine() < 0) {
0856: throw new IllegalArgumentException("line < 0");
0857: }
0858:
0859: Element lineElement = sourceDocument.getDefaultRootElement()
0860: .getElement(location.getLine());
0861: if (lineElement == null) {
0862: throw new IllegalArgumentException("line="
0863: + location.getLine() + " is out of bound");
0864: }
0865:
0866: int lineOffset = lineElement.getStartOffset();
0867:
0868: if (location.getColumn() < 0) {
0869: throw new IllegalArgumentException("column < 0 ");
0870: }
0871:
0872: int lineLen = lineElement.getEndOffset() - lineOffset;
0873:
0874: if (location.getColumn() >= lineLen) {
0875: throw new IllegalArgumentException("column="
0876: + location.getColumn() + " greater than line len="
0877: + lineLen);
0878: }
0879:
0880: return lineOffset + location.getColumn();
0881: }
0882:
0883: /**
0884: * Returns a property of the current editor.
0885: *
0886: * @param propertyKey The propertyKey of the property to retrieve.
0887: * @return the property value or null if it is not found
0888: */
0889: public Object getProperty(String propertyKey) {
0890: return propertyMap.get(propertyKey);
0891: }
0892:
0893: /**
0894: * Set a property for the current editor. Any existing property with
0895: * this key will be overwritten.
0896: *
0897: * @param propertyKey The property key of the new property
0898: * @param value The new property value
0899: */
0900: public void setProperty(String propertyKey, Object value) {
0901: if (propertyKey == null) {
0902: return;
0903: }
0904:
0905: propertyMap.put(propertyKey, value);
0906: }
0907:
0908: /**
0909: * Returns the length of the line indicated in the edited text.
0910: * Zero is a valid value if the given line has no characters in it.
0911: *
0912: * @param line the line in the text for which the length should be calculated, starting from 0
0913: * @return the length of the line, -1 if line is invalid
0914: */
0915: public int getLineLength(int line) {
0916: if (line < 0) {
0917: return -1;
0918: }
0919:
0920: Element lineElement = sourceDocument.getDefaultRootElement()
0921: .getElement(line);
0922: if (lineElement == null) {
0923: return -1;
0924: }
0925:
0926: int startOffset = lineElement.getStartOffset();
0927:
0928: return lineElement.getEndOffset() - startOffset;
0929: }
0930:
0931: /**
0932: * Returns the length of the data. This is the number of
0933: * characters of content that represents the users data.
0934: *
0935: * It is possible to obtain the line and column of the last character of text by using
0936: * the getLineColumnFromOffset() method.
0937: *
0938: * @return the length >= 0
0939: */
0940: public int getTextLength() {
0941: return sourceDocument.getLength();
0942: }
0943:
0944: /**
0945: * Return the number of lines in the documant.
0946: */
0947: public int numberOfLines() {
0948: return sourceDocument.getDefaultRootElement().getElementCount();
0949: }
0950:
0951: // --------------------------------------------------------------------
0952: // ------------ end of interface inherited from Editor ----------------
0953: // --------------------------------------------------------------------
0954:
0955: // ---- BlueJEventListener interface ----
0956:
0957: /**
0958: * A BlueJEvent was raised. Check whether it is one that we're interested
0959: * in.
0960: */
0961: public void blueJEvent(int eventId, Object arg) {
0962: switch (eventId) {
0963: case BlueJEvent.DOCU_GENERATED:
0964: BlueJEvent.removeListener(this );
0965: refreshHtmlDisplay();
0966: break;
0967: case BlueJEvent.DOCU_ABORTED:
0968: BlueJEvent.removeListener(this );
0969: info.warning(Config.getString("editor.info.docAborted"));
0970: break;
0971: }
0972: }
0973:
0974: // -------- DocumentListener interface --------
0975:
0976: /**
0977: * A text insertion has taken place.
0978: */
0979: public void insertUpdate(DocumentEvent e) {
0980: if (!saveState.isChanged()) {
0981: saveState.setState(StatusLabel.CHANGED);
0982: setChanged();
0983: }
0984: actions.userAction();
0985: doTextInsert.setEvent(e, sourcePane);
0986: SwingUtilities.invokeLater(doTextInsert);
0987: }
0988:
0989: /**
0990: * A text removal has taken place.
0991: */
0992: public void removeUpdate(DocumentEvent e) {
0993: if (!saveState.isChanged()) {
0994: saveState.setState(StatusLabel.CHANGED);
0995: setChanged();
0996: }
0997: actions.userAction();
0998: }
0999:
1000: /**
1001: * Document properties have changed - ignore
1002: */
1003: public void changedUpdate(DocumentEvent e) {
1004: }
1005:
1006: // --------------------------------------------------------------------
1007: /**
1008: * Clear the message in the info area.
1009: */
1010: public void clearMessage() {
1011: info.clear();
1012: }
1013:
1014: /**
1015: * Display a message into the info area.
1016: *
1017: * @param msg the message to display
1018: */
1019: public void writeMessage(String msg) {
1020: info.message(msg);
1021: }
1022:
1023: /**
1024: * Write a warning message into the info area. Typically some form of
1025: * unexpected behaviour has occurred.
1026: *
1027: * @param msg Description of the Parameter
1028: */
1029: public void writeWarningMessage(String msg) {
1030: info.warning(msg);
1031: }
1032:
1033: // ==================== USER ACTION IMPLEMENTATIONS ===================
1034:
1035: // --------------------------------------------------------------------
1036: /**
1037: */
1038: public void userSave() {
1039: if (saveState.isSaved())
1040: info.message(Config.getString("editor.info.noChanges"));
1041: else {
1042: try {
1043: save();
1044: } catch (IOException ioe) {
1045: }
1046: // Note we can safely ignore the exception here: a message has
1047: // already been displayed in the editor status bar
1048: }
1049: }
1050:
1051: // --------------------------------------------------------------------
1052: /**
1053: */
1054: public void reload() {
1055: if (filename == null) {
1056: info.warning(Config.getString("editor.info.cannotReload"),
1057: Config.getString("editor.info.reload"));
1058: } else if (saveState.isChanged()) {
1059: int answer = DialogManager.askQuestion(this ,
1060: "really-reload");
1061: if (answer == 0)
1062: doReload();
1063: } else {
1064: doReload();
1065: }
1066: }
1067:
1068: // --------------------------------------------------------------------
1069:
1070: /**
1071: * Prints source code from Editor
1072: *
1073: * @param printerJob A PrinterJob to print to.
1074: */
1075: public void print(PrinterJob printerJob) {
1076: PrintHandler pt = new PrintHandler(printerJob,
1077: getPageFormat(printerJob));
1078: pt.print();
1079: }
1080:
1081: /**
1082: * Return a validated version of the global PageFormat for BlueJ
1083: */
1084: public PageFormat getPageFormat(PrinterJob job) {
1085: return job.validatePage(PkgMgrFrame.getPageFormat());
1086: }
1087:
1088: /**
1089: * Generalised version of print function. This is what is typically called
1090: * when print is initiated from within the source code editor menu. This
1091: * sets up and runs the print process as a separate lower priority thread.
1092: */
1093: public void print() {
1094: // create a printjob
1095: PrinterJob job = PrinterJob.getPrinterJob();
1096: if (job.printDialog()) {
1097: PrintHandler pt = new PrintHandler(job, getPageFormat(job));
1098: Thread printJobThread = new Thread(pt);
1099: printJobThread.setPriority((Thread.currentThread()
1100: .getPriority() - 1));
1101: printJobThread.start();
1102: }
1103: }
1104:
1105: // --------------------------------------------------------------------
1106: /**
1107: * Implementation of the "page setup" user function. This provides a dialog
1108: * for print page setup. PageSetup is global to BlueJ. Calling this from the
1109: * Editor is effectively the same as calling from PkgMgrFrame as this saves
1110: * back to PkgMgrFrame's global page format object.
1111: */
1112: public void pageSetup() {
1113: PrinterJob job = PrinterJob.getPrinterJob();
1114: PageFormat pageFormat = job.pageDialog(PkgMgrFrame
1115: .getPageFormat());
1116: PkgMgrFrame.setPageFormat(pageFormat);
1117: }
1118:
1119: // --------------------------------------------------------------------
1120: /**
1121: * The editor has been closed. Hide the editor window now.
1122: */
1123: public void doClose() {
1124: setVisible(false);
1125: if (watcher != null) {
1126: watcher.closeEvent(this );
1127: }
1128: }
1129:
1130: // --------------------------------------------------------------------
1131: /**
1132: * Check whether TABs need expanding in this editor. If they do, return
1133: * true. At the same time, set this flag to true.
1134: *
1135: * @return Description of the Return Value
1136: */
1137: public boolean checkExpandTabs() {
1138: if (tabsAreExpanded)
1139: return false;
1140:
1141: else {
1142: tabsAreExpanded = true;
1143: return true;
1144: }
1145: }
1146:
1147: // --------------------------------------------------------------------
1148: /**
1149: * Implementation of "find" user function.
1150: */
1151: public void find() {
1152: Finder finder = MoeEditorManager.editorManager.getFinder();
1153: finder.show(this , currentTextPane.getSelectedText(), false);
1154: }
1155:
1156: // --------------------------------------------------------------------
1157: /**
1158: * Implementation of "replace" user function. Replace adds extra
1159: * functionality to that of a find dialog, as well as altered behaviour. It
1160: * can remain open for multiple functions.
1161: */
1162: public void replace() {
1163: Finder finder = MoeEditorManager.editorManager.getFinder();
1164: finder.show(this , currentTextPane.getSelectedText(), true);
1165: }
1166:
1167: // --------------------------------------------------------------------
1168: /**
1169: * Implementation of "find-next" user function.
1170: */
1171: public void findNext() {
1172: Finder finder = MoeEditorManager.editorManager.getFinder();
1173: String s = currentTextPane.getSelectedText();
1174: if (s == null) {
1175: s = finder.getSearchString();
1176: if (s == null) {
1177: info.warning(DialogManager
1178: .getMessage("no-search-string"));
1179: return;
1180: }
1181: }
1182: findNextString(finder, s, false);
1183: }
1184:
1185: // --------------------------------------------------------------------
1186: /**
1187: * Implementation of "find-next-reverse" user function.
1188: */
1189: public void findNextBackward() {
1190: Finder finder = MoeEditorManager.editorManager.getFinder();
1191: String s = currentTextPane.getSelectedText();
1192: if (s == null) {
1193: s = finder.getSearchString();
1194: if (s == null) {
1195: info.warning(DialogManager
1196: .getMessage("no-search-string"));
1197: return;
1198: }
1199: }
1200: findNextString(finder, s, true);
1201: }
1202:
1203: // --------------------------------------------------------------------
1204:
1205: /**
1206: * Finds the first cvs-style conflict and selects it
1207: */
1208: public void findFirstConflict() {
1209: setCaretLocation(new LineColumn(0, 0));
1210: findNextConflict();
1211: }
1212:
1213: private void findNextConflict() {
1214: findString("=======", false, false, true, false);
1215: findString("<<<<<<<", true, false, false, false);
1216: LineColumn startPos = getCaretLocation();
1217: findString(">>>>>>>", false, false, false, false);
1218: LineColumn endPos = getCaretLocation();
1219: setSelection(startPos, endPos);
1220: }
1221:
1222: /**
1223: * Do a find with info in the info area.
1224: */
1225: private void findNextString(Finder finder, String s,
1226: boolean backward) {
1227: boolean found = findString(s, backward, finder.getIgnoreCase(),
1228: finder.getWholeWord(), (!finder.getSearchFound()));
1229:
1230: finder.setSearchString(s);
1231: finder.setSearchFound(found);
1232: }
1233:
1234: // --------------------------------------------------------------------
1235: /**
1236: * Do a find with info in the info area.
1237: */
1238: boolean findString(String s, boolean backward, boolean ignoreCase,
1239: boolean wholeWord, boolean wrap) {
1240: if (s.length() == 0) {
1241: info.warning(Config
1242: .getString("editor.info.emptySearchString"));
1243: return false;
1244: }
1245:
1246: boolean found;
1247: if (backward)
1248: found = doFindBackward(s, ignoreCase, wholeWord, wrap);
1249: else
1250: found = doFind(s, ignoreCase, wholeWord, wrap);
1251:
1252: StringBuffer msg = new StringBuffer(Config
1253: .getString("editor.find.find.label")
1254: + " ");
1255: msg.append(backward ? Config.getString("editor.find.backward")
1256: : Config.getString("editor.find.forward"));
1257: if (ignoreCase || wholeWord || wrap)
1258: msg.append(" (");
1259:
1260: if (ignoreCase)
1261: msg.append(Config.getString("editor.find.ignoreCase")
1262: .toLowerCase()
1263: + ", ");
1264:
1265: if (wholeWord)
1266: msg.append(Config.getString("editor.find.wholeWord")
1267: .toLowerCase()
1268: + ", ");
1269:
1270: if (wrap)
1271: msg.append(Config.getString("editor.find.wrapAround")
1272: .toLowerCase()
1273: + ", ");
1274:
1275: if (ignoreCase || wholeWord || wrap)
1276: msg.replace(msg.length() - 2, msg.length(), "): ");
1277: else
1278: msg.append(": ");
1279:
1280: msg.append(s);
1281:
1282: if (found)
1283: info.message(msg.toString());
1284: else
1285: info.warning(msg.toString(), Config
1286: .getString("editor.info.notFound"));
1287:
1288: return found;
1289: }
1290:
1291: // --------------------------------------------------------------------
1292: /**
1293: * doFind - do a find without visible feedback. Returns false if not found.
1294: */
1295: boolean doFind(String s, boolean ignoreCase, boolean wholeWord,
1296: boolean wrap) {
1297: int docLength = document.getLength();
1298: int startPosition = currentTextPane.getCaretPosition();
1299: int endPos = docLength;
1300:
1301: boolean found = false;
1302: boolean finished = false;
1303:
1304: // first line searched starts from current caret position
1305: int start = startPosition;
1306: Element line = getLineAt(start);
1307: int lineEnd = Math.min(line.getEndOffset(), endPos);
1308:
1309: // following lines search from start of line
1310: try {
1311: while (!found && !finished) {
1312: String lineText = document.getText(start, lineEnd
1313: - start);
1314: if (lineText != null && lineText.length() > 0) {
1315: int foundPos = findSubstring(lineText, s,
1316: ignoreCase, wholeWord, false);
1317: if (foundPos != -1) {
1318: currentTextPane.select(start + foundPos, start
1319: + foundPos + s.length());
1320: found = true;
1321: }
1322: }
1323: if (lineEnd >= endPos) {
1324: if (wrap) {
1325: // do the wrapping
1326: endPos = startPosition;
1327: line = document.getParagraphElement(0);
1328: start = line.getStartOffset();
1329: lineEnd = Math.min(line.getEndOffset(), endPos);
1330: wrap = false;
1331: // don't wrap again
1332: } else {
1333: finished = true;
1334: }
1335: } else {
1336: // go to next line
1337: line = document.getParagraphElement(lineEnd + 1);
1338: start = line.getStartOffset();
1339: lineEnd = Math.min(line.getEndOffset(), endPos);
1340: }
1341: }
1342: } catch (BadLocationException ex) {
1343: Debug.message("error in editor find operation");
1344: }
1345: return found;
1346: }
1347:
1348: // --------------------------------------------------------------------
1349: /**
1350: * doFindBackward - do a find backwards without visible feedback. Returns
1351: * false if not found.
1352: */
1353: boolean doFindBackward(String s, boolean ignoreCase,
1354: boolean wholeWord, boolean wrap) {
1355: int docLength = document.getLength();
1356: int startPosition = currentTextPane.getCaretPosition() - 1;
1357: if (startPosition < 0) {
1358: startPosition = docLength;
1359: }
1360: int endPos = 0; // where the search ends
1361:
1362: boolean found = false;
1363: boolean finished = false;
1364:
1365: int start = startPosition; // start of next partial search
1366: Element line = getLineAt(start);
1367: int lineStart = Math.max(line.getStartOffset(), endPos);
1368:
1369: try {
1370: while (!found && !finished) {
1371: String lineText = document.getText(lineStart, start
1372: - lineStart);
1373: if (lineText != null && lineText.length() > 0) {
1374: int foundPos = findSubstring(lineText, s,
1375: ignoreCase, wholeWord, true);
1376: if (foundPos != -1) {
1377: currentTextPane.select(lineStart + foundPos,
1378: lineStart + foundPos + s.length());
1379: found = true;
1380: }
1381: }
1382: if (lineStart <= endPos) { // reached end of search
1383: if (wrap) { // do the wrapping around
1384: endPos = startPosition;
1385: line = document.getParagraphElement(docLength);
1386: start = line.getEndOffset();
1387: lineStart = Math.max(line.getStartOffset(),
1388: endPos);
1389: wrap = false; // don't wrap again
1390: } else {
1391: finished = true;
1392: }
1393: } else { // go to next line
1394: line = document.getParagraphElement(lineStart - 1);
1395: start = line.getEndOffset();
1396: lineStart = Math.max(line.getStartOffset(), endPos);
1397: }
1398: }
1399: } catch (BadLocationException ex) {
1400: Debug.reportError("error in editor find operation");
1401: ex.printStackTrace();
1402: }
1403: return found;
1404: }
1405:
1406: /**
1407: * Transfers caret to user specified line number location.
1408: */
1409: public void goToLine() {
1410: if (goToLineDialog == null) {
1411: goToLineDialog = new GoToLineDialog(this );
1412: }
1413:
1414: DialogManager.centreDialog(goToLineDialog);
1415: goToLineDialog.showDialog(numberOfLines());
1416: int newPosition = goToLineDialog.getLineNumber();
1417: if (newPosition > 0) {
1418: setSelection(newPosition, 1, 0);
1419: }
1420: }
1421:
1422: /**
1423: * Find the position of a substring in a given string, ignoring case or searching for
1424: * whole words if desired. Return the position of the substring or -1.
1425: *
1426: * @param text the full string to be searched
1427: * @param sub the substring that we're looking for
1428: * @param ignoreCase if true, case is ignored
1429: * @param wholeWord if true, and the search string resembles something like a word,
1430: * find only whole-word ocurrences
1431: * @param backwards Description of the Parameter
1432: * @return Description of the Return Value
1433: * @returns the index of the substring, or -1 if not found
1434: */
1435: private int findSubstring(String text, String sub,
1436: boolean ignoreCase, boolean wholeWord, boolean backwards) {
1437: int strlen = text.length();
1438: int sublen = sub.length();
1439:
1440: if (sublen == 0) {
1441: return -1;
1442: }
1443:
1444: // 'wholeWord' search does not make much sense when the search string is
1445: // not a word
1446: // (ar at least the first and last character is a letter). Check that.
1447: if (!Character.isJavaIdentifierPart(sub.charAt(0))
1448: || !Character.isJavaIdentifierPart(sub
1449: .charAt(sublen - 1))) {
1450: wholeWord = false;
1451: }
1452:
1453: boolean found = false;
1454: int pos = (backwards ? strlen - sublen : 0);
1455: boolean itsOver = (backwards ? (pos < 0)
1456: : (pos + sublen > strlen));
1457:
1458: while (!found && !itsOver) {
1459: found = text.regionMatches(ignoreCase, pos, sub, 0, sublen);
1460: if (found && wholeWord) {
1461: found = ((pos == 0) || !Character
1462: .isJavaIdentifierPart(text.charAt(pos - 1)))
1463: && ((pos + sublen >= strlen) || !Character
1464: .isJavaIdentifierPart(text.charAt(pos
1465: + sublen)));
1466: }
1467: if (!found) {
1468: pos = (backwards ? pos - 1 : pos + 1);
1469: itsOver = (backwards ? (pos < 0)
1470: : (pos + sublen > strlen));
1471: }
1472: }
1473: if (found) {
1474: return pos;
1475: } else {
1476: return -1;
1477: }
1478: }
1479:
1480: // --------------------------------------------------------------------
1481: /**
1482: * Implementation of "compile" user function.
1483: */
1484: public void compile() {
1485: if (watcher == null) {
1486: return;
1487: }
1488: if (!viewingCode()) {
1489: info.warning(" ");
1490: return;
1491: }
1492:
1493: info.message(Config.getString("editor.info.compiling"));
1494: watcher.compile(this );
1495: }
1496:
1497: // --------------------------------------------------------------------
1498: /**
1499: * Toggle the interface popup menu. This is used when using keys to toggle
1500: * the interface view. Toggling the menu will result in invoking the action.
1501: */
1502: public void toggleInterfaceMenu() {
1503: if (!sourceIsCode)
1504: return;
1505:
1506: if (interfaceToggle.getSelectedIndex() == 0)
1507: interfaceToggle.setSelectedIndex(1);
1508: else
1509: interfaceToggle.setSelectedIndex(0);
1510: }
1511:
1512: // --------------------------------------------------------------------
1513: /**
1514: * Implementation of "toggle-interface-view" user function. The menu has
1515: * already been changed - now see what it is and do it.
1516: */
1517: public void toggleInterface() {
1518: if (!sourceIsCode) {
1519: return;
1520: }
1521:
1522: boolean wantHTML = (interfaceToggle.getSelectedItem() == interfaceString);
1523: if (wantHTML && !viewingHTML) {
1524: switchToInterfaceView();
1525: } else if (!wantHTML && viewingHTML) {
1526: switchToSourceView();
1527: }
1528: }
1529:
1530: /**
1531: * Allow the enabling/disabling of print menu option. Added to disable the
1532: * printing og javadoc html for the time being until until implemented.
1533: * (This is reliant on the use of j2sdk1.4 and Java Unified Print Service
1534: * implementation JSR 6)
1535: *
1536: * @param flag true to enable printing from menu.
1537: */
1538: public void enablePrinting(boolean flag) {
1539: Action printAction = actions.getActionByName("print");
1540: if (printAction != null) {
1541: printAction.setEnabled(flag);
1542: }
1543: Action pageSetupAction = actions.getActionByName("page-setup");
1544: if (pageSetupAction != null) {
1545: pageSetupAction.setEnabled(flag);
1546: }
1547:
1548: }
1549:
1550: // --------------------------------------------------------------------
1551: /**
1552: * Switch on the source view (it it isn't showing already).
1553: */
1554: private void switchToSourceView() {
1555: if (!viewingHTML) {
1556: return;
1557: }
1558:
1559: // enable print option
1560: enablePrinting(true);
1561: document = sourceDocument;
1562: currentTextPane = sourcePane;
1563: viewingHTML = false;
1564: scrollPane.setViewportView(currentTextPane);
1565: checkSyntaxStatus();
1566: currentTextPane.requestFocus();
1567: }
1568:
1569: // --------------------------------------------------------------------
1570: /**
1571: * Switch on the javadoc interface view (it it isn't showing already). If
1572: * necessary, generate it first.
1573: */
1574: private void switchToInterfaceView() {
1575: if (viewingHTML) {
1576: return;
1577: }
1578:
1579: // disable print menu option until implemented
1580: enablePrinting(false);
1581: try {
1582: save();
1583: displayInterface();
1584: } catch (IOException ioe) {
1585: // Could display a dialog here. However, the error message
1586: // (from save() call) will already be displayed in the editor
1587: // status bar.
1588: }
1589: }
1590:
1591: // --------------------------------------------------------------------
1592: /**
1593: * Refresh the HTML display.
1594: */
1595: private void refreshHtmlDisplay() {
1596: try {
1597: File urlFile = new File(getDocPath());
1598: URL myURL = urlFile.toURI().toURL();
1599: htmlPane.setPage(myURL);
1600: htmlDocument = (HTMLDocument) htmlPane.getDocument();
1601: htmlDocument.setBase(myURL);
1602: info.message(Config.getString("editor.info.docLoaded"));
1603: } catch (Exception exc) {
1604: info.warning(
1605: Config.getString("editor.info.docDisappeared"),
1606: getDocPath());
1607: Debug.reportError("loading class interface failed: " + exc);
1608: }
1609: }
1610:
1611: // --------------------------------------------------------------------
1612: /**
1613: * Check whether javadoc file is up to date.
1614: *
1615: * @return True is the currently existing documentation is up-to-date.
1616: */
1617: private boolean docUpToDate() {
1618: try {
1619: File src = new File(filename);
1620: File doc = new File(docFilename);
1621:
1622: if (!doc.exists()
1623: || (src.exists() && (src.lastModified() > doc
1624: .lastModified()))) {
1625: return false;
1626: }
1627: } catch (Exception e) {
1628: e.printStackTrace();
1629: return false;
1630: }
1631: return true;
1632: }
1633:
1634: // --------------------------------------------------------------------
1635: /**
1636: * We want to display the interface view. This will generate the
1637: * documentation if necessary.
1638: *
1639: * Don't call this directly to switch to the interface view. Call
1640: * switchToInterfaceView() instead.
1641: */
1642: private void displayInterface() {
1643: info.message(Config.getString("editor.info.loadingDoc"));
1644: boolean generateDoc = !docUpToDate();
1645:
1646: // The following all used to be done in a separate thread, but this is not
1647: // necessary - setPage() operates asynchronously anyway.
1648: if (htmlPane == null) {
1649: createHTMLPane();
1650: if (!generateDoc) {
1651: refreshHtmlDisplay();
1652: }
1653: } else if (!generateDoc) {
1654: info.message(Config.getString("editor.info.docLoaded"));
1655: }
1656:
1657: if (generateDoc) {
1658: // clear the existing document
1659: htmlDocument = new HTMLDocument();
1660: htmlPane.setDocument(htmlDocument);
1661:
1662: // interface needs to be re-generated
1663: info.message(Config.getString("editor.info.generatingDoc"));
1664: BlueJEvent.addListener(this );
1665: watcher.generateDoc();
1666: }
1667:
1668: document = htmlDocument;
1669: currentTextPane = htmlPane;
1670: viewingHTML = true;
1671: scrollPane.setViewportView(htmlPane);
1672: currentTextPane.requestFocus();
1673: }
1674:
1675: // --------------------------------------------------------------------
1676: /**
1677: */
1678: public void createHTMLPane() {
1679: htmlPane = new JEditorPane();
1680: htmlPane.setEditorKit(new HTMLEditorKit());
1681: htmlPane.setEditable(false);
1682: htmlPane.addHyperlinkListener(this );
1683: }
1684:
1685: // --------------------------------------------------------------------
1686: /**
1687: * A hyperlink was activated in the document. Do something appropriate.
1688: */
1689: public void hyperlinkUpdate(HyperlinkEvent e) {
1690: info.clear();
1691: if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
1692: JEditorPane pane = (JEditorPane) e.getSource();
1693: if (e instanceof HTMLFrameHyperlinkEvent) {
1694: HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e;
1695: HTMLDocument doc = (HTMLDocument) pane.getDocument();
1696: doc.processHTMLFrameHyperlinkEvent(evt);
1697: } else {
1698: try {
1699: pane.setPage(e.getURL());
1700: } catch (Throwable t) {
1701: info.warning("cannot display hyperlink: "
1702: + e.getURL());
1703: Debug.reportError("hyperlink failed: " + t);
1704: }
1705: }
1706: }
1707: }
1708:
1709: // --------------------------------------------------------------------
1710: /**
1711: * Implementation of "toggle-breakpoint" user function.
1712: */
1713: public void toggleBreakpoint() {
1714: if (!viewingCode()) {
1715: info.warning(" "); // cause a beep
1716: return;
1717: }
1718: toggleBreakpoint(sourcePane.getCaretPosition());
1719: }
1720:
1721: // --------------------------------------------------------------------
1722: /**
1723: * Toggle a breakpoint at a given position.
1724: */
1725: public void toggleBreakpoint(int pos) {
1726: if (positionHasBreakpoint(pos))
1727: setUnsetBreakpoint(pos, false); // remove
1728: else
1729: setUnsetBreakpoint(pos, true); // set
1730: }
1731:
1732: // --------------------------------------------------------------------
1733: /**
1734: * Clear all known breakpoints.
1735: */
1736: private void clearAllBreakpoints() {
1737: if (mayHaveBreakpoints) {
1738:
1739: for (int i = 1; i <= numberOfLines(); i++) {
1740: if (lineHasBreakpoint(i)) {
1741: doRemoveBreakpoint(getPositionInLine(i));
1742: }
1743: }
1744: mayHaveBreakpoints = false;
1745: }
1746: }
1747:
1748: // --------------------------------------------------------------------
1749: /**
1750: * Check weather a position has a breakpoint set
1751: */
1752: private boolean positionHasBreakpoint(int pos) {
1753: Element line = getLineAt(pos);
1754: return Boolean.TRUE.equals(line.getAttributes().getAttribute(
1755: MoeSyntaxView.BREAKPOINT));
1756: }
1757:
1758: // --------------------------------------------------------------------
1759: /**
1760: * Check weather a line has a breakpoint set
1761: */
1762: private boolean lineHasBreakpoint(int lineNo) {
1763: Element line = getLine(lineNo);
1764: return (Boolean.TRUE.equals(line.getAttributes().getAttribute(
1765: MoeSyntaxView.BREAKPOINT)));
1766: }
1767:
1768: // --------------------------------------------------------------------
1769: /**
1770: * Try to set or remove a breakpoint (depending on the parameter) at the
1771: * given position. Informs the watcher.
1772: */
1773: private void setUnsetBreakpoint(int pos, boolean set) {
1774: if (watcher != null) {
1775: int line = getLineNumberAt(pos);
1776: String result = watcher.breakpointToggleEvent(this , line,
1777: set);
1778:
1779: if (result == null) {
1780: // no problem, go ahead
1781: SimpleAttributeSet a = new SimpleAttributeSet();
1782: if (set) {
1783: a.addAttribute(MoeSyntaxView.BREAKPOINT,
1784: Boolean.TRUE);
1785: mayHaveBreakpoints = true;
1786: } else {
1787: a.addAttribute(MoeSyntaxView.BREAKPOINT,
1788: Boolean.FALSE);
1789: }
1790:
1791: sourceDocument.setParagraphAttributes(pos, a);
1792: } else {
1793: info.warning(result);
1794: }
1795:
1796: // force an update of UI
1797: repaint();
1798: } else {
1799: info
1800: .warning(Config
1801: .getString("editor.info.cannotSetBreak"));
1802: }
1803:
1804: }
1805:
1806: // --------------------------------------------------------------------
1807: /**
1808: * Remove a breakpoint without question.
1809: */
1810: private void doRemoveBreakpoint(int pos) {
1811: SimpleAttributeSet a = new SimpleAttributeSet();
1812: a.addAttribute(MoeSyntaxView.BREAKPOINT, Boolean.FALSE);
1813: sourceDocument.setParagraphAttributes(pos, a);
1814: repaint();
1815: }
1816:
1817: // --------------------------------------------------------------------
1818: /**
1819: * Try to set or remove a step mark (depending on the parameter) at the
1820: * given position.
1821: *
1822: * @param pos A position in the line where we'd like the step mark.
1823: */
1824: private void setStepMark(int pos) {
1825: removeStepMark();
1826: SimpleAttributeSet a = new SimpleAttributeSet();
1827: a.addAttribute(MoeSyntaxView.STEPMARK, Boolean.TRUE);
1828: sourceDocument.setParagraphAttributes(pos, a);
1829: currentStepPos = pos;
1830: // force an update of UI
1831: repaint();
1832: }
1833:
1834: // ========================= SUPPORT ROUTINES ==========================
1835:
1836: // --------------------------------------------------------------------
1837: /**
1838: * return a boolean representing whether in source editing view
1839: */
1840: private boolean viewingCode() {
1841: return sourceIsCode && (!viewingHTML);
1842: }
1843:
1844: // --------------------------------------------------------------------
1845: /**
1846: * Return the current line.
1847: */
1848: // private Element getCurrentLine()
1849: // {
1850: // return document.getParagraphElement(currentTextPane.getCaretPosition());
1851: // }
1852: // --------------------------------------------------------------------
1853: /**
1854: * Find and return a line by line number
1855: */
1856: private Element getLine(int lineNo) {
1857: return sourceDocument.getDefaultRootElement().getElement(
1858: lineNo - 1);
1859: }
1860:
1861: // --------------------------------------------------------------------
1862: /**
1863: * Find and return a line by text position
1864: */
1865: private Element getLineAt(int pos) {
1866: return sourceDocument.getParagraphElement(pos);
1867: }
1868:
1869: // --------------------------------------------------------------------
1870: /**
1871: * Find and return a position in a line.
1872: */
1873: private int getPositionInLine(int lineNo) {
1874: return getLine(lineNo).getStartOffset();
1875: }
1876:
1877: // --------------------------------------------------------------------
1878: /**
1879: * Return the number of the current line.
1880: */
1881: // private int getCurrentLineNo()
1882: // {
1883: // return document.getDefaultRootElement().getElementIndex(
1884: // currentTextPane.getCaretPosition()) + 1;
1885: // }
1886: // --------------------------------------------------------------------
1887: /**
1888: * Return the number of the line containing position 'pos'.
1889: */
1890: private int getLineNumberAt(int pos) {
1891: return sourceDocument.getDefaultRootElement().getElementIndex(
1892: pos) + 1;
1893: }
1894:
1895: // --------------------------------------------------------------------
1896: /**
1897: * Revert the buffer contents to the last saved version. Do not ask any
1898: * question - just do it. Must have a file name.
1899: */
1900: public void doReload() {
1901: FileReader reader = null;
1902: try {
1903: reader = new FileReader(filename);
1904: sourcePane.read(reader, null);
1905: reader.close();
1906: File file = new File(filename);
1907: lastModified = file.lastModified();
1908:
1909: sourceDocument = (MoeSyntaxDocument) sourcePane
1910: .getDocument();
1911:
1912: // flag document type as a java file by associating a
1913: // JavaTokenMarker for syntax colouring if specified
1914: checkSyntaxStatus();
1915: sourceDocument.addDocumentListener(this );
1916: sourceDocument.addUndoableEditListener(undoManager);
1917:
1918: // We want to inform the watcher that the editor content has changed,
1919: // and then inform it that we are in "saved" state (synced with file).
1920: // But first set state to saved to avoid unnecessary writes to disk.
1921: saveState.setState(StatusLabel.SAVED);
1922: setChanged(); // contents may have changed - notify watcher
1923: setSaved(); // notify watcher that we are saved
1924: } catch (FileNotFoundException ex) {
1925: info.warning(Config
1926: .getString("editor.info.fileDisappeared"));
1927: } catch (IOException ex) {
1928: info.warning(Config.getString("editor.info.fileReadError"));
1929: setChanged();
1930: } finally {
1931: try {
1932: if (reader != null)
1933: reader.close();
1934: } catch (IOException ioe) {
1935: }
1936: }
1937: }
1938:
1939: // --------------------------------------------------------------------
1940: /**
1941: * Checks that current status of syntax highlighting option is consistent
1942: * with desired option eg off/on.
1943: */
1944: private void checkSyntaxStatus() {
1945: if (sourceDocument != null) {
1946:
1947: // flag document type as a java file by associating a
1948: // JavaTokenMarker for syntax colouring if specified
1949: if (viewingCode() && PrefMgr.getFlag(PrefMgr.HILIGHTING)) {
1950: if (sourceDocument.getTokenMarker() == null) {
1951: sourceDocument
1952: .setTokenMarker(new JavaTokenMarker());
1953: }
1954: } else {
1955: sourceDocument.setTokenMarker(null);
1956: }
1957: }
1958: // else ??
1959: }
1960:
1961: /**
1962: * Checks that current status of syntax highlighting option is consistent
1963: * with desired option eg off/on. Called when refreshing or making visible
1964: * to pick up any Preference Manager changes to this functionality
1965: */
1966: private void checkBracketStatus() {
1967: matchBrackets = PrefMgr.getFlag(PrefMgr.MATCH_BRACKETS);
1968: // tidies up leftover highlight if matching is switched off
1969: // while highlighting a valid bracket or refreshes bracket in open
1970: // editor
1971: if (matchBrackets)
1972: doBracketMatch();
1973: else
1974: moeCaret.removeBracket();
1975: }
1976:
1977: /**
1978: * Tell whether we are currently matching brackets.
1979: *
1980: * @return True, if we are matching brackets, otherwise false.
1981: */
1982: public boolean matchBrackets() {
1983: return matchBrackets;
1984: }
1985:
1986: // --------------------------------------------------------------------
1987: /**
1988: * Toggle the editor's 'compiled' status. If compiled, enable the breakpoint
1989: * function.
1990: */
1991: private void setCompileStatus(boolean compiled) {
1992: actions.getActionByName("toggle-breakpoint").setEnabled(
1993: compiled && viewingCode());
1994: if (compiled) {
1995: sourceDocument.putProperty(COMPILED, Boolean.TRUE);
1996: } else {
1997: sourceDocument.putProperty(COMPILED, Boolean.FALSE);
1998: }
1999:
2000: currentTextPane.repaint();
2001: }
2002:
2003: // --------------------------------------------------------------------
2004: /**
2005: * Set the saved/changed status of this buffer to SAVED.
2006: */
2007: private void setSaved() {
2008: info.message(Config.getString("editor.info.saved"));
2009: saveState.setState(StatusLabel.SAVED);
2010: if (watcher != null) {
2011: watcher.saveEvent(this );
2012: }
2013: }
2014:
2015: // --------------------------------------------------------------------
2016: /**
2017: * Buffer just went from saved to changed state (called by StatusLabel)
2018: */
2019: private void setChanged() {
2020: if (ignoreChanges) {
2021: return;
2022: }
2023: setCompileStatus(false);
2024: if (watcher != null) {
2025: watcher.modificationEvent(this );
2026: }
2027: }
2028:
2029: // --------------------------------------------------------------------
2030: /**
2031: * Clear the message in the info area.
2032: */
2033: void caretMoved() {
2034: clearMessage();
2035: if (matchBrackets) {
2036: doBracketMatch();
2037: }
2038: actions.userAction();
2039: }
2040:
2041: /**
2042: * returns the position of the matching bracket for the source pane's
2043: * current caret position. Returns -1 if not found or not valid/appropriate
2044: *
2045: * @return the int representing bracket position
2046: */
2047: public int getBracketMatch() {
2048: int pos = -1;
2049: try {
2050: int caretPos = sourcePane.getCaretPosition();
2051: if (caretPos != 0) {
2052: caretPos--;
2053: }
2054: pos = TextUtilities.findMatchingBracket(sourceDocument,
2055: caretPos);
2056: } catch (BadLocationException ble) {
2057: Debug
2058: .reportError("Bad document location reached while trying to match brackets");
2059: }
2060: return pos;
2061: }
2062:
2063: /**
2064: * delegates bracket matching to the source pane's caret
2065: */
2066: private void doBracketMatch() {
2067: Caret caret = sourcePane.getCaret();
2068: if (caret instanceof MoeCaret) {
2069: ((MoeCaret) caret).paintMatchingBracket();
2070: }
2071: }
2072:
2073: /**
2074: * Set the window title to show the defined title, or else the file name.
2075: */
2076: private void setWindowTitle() {
2077: String title = windowTitle;
2078:
2079: if (title == null) {
2080: if (filename == null)
2081: title = "Moe: <no name>";
2082: else
2083: title = "Moe: " + filename;
2084: }
2085: setTitle(title);
2086: }
2087:
2088: // --------------------------------------------------------------------
2089: /**
2090: * Return the path to the class documentation.
2091: */
2092: private String getDocPath() {
2093: return docFilename;
2094: }
2095:
2096: // --------------------------------------------------------------------
2097:
2098: /**
2099: * Gets the resource attribute of the MoeEditor object
2100: */
2101: private String getResource(String name) {
2102: return Config.getPropString(name, null, resources);
2103: }
2104:
2105: // --------------------------------------------------------------------
2106:
2107: /**
2108: * Tokenize a string.
2109: */
2110: private String[] tokenize(String input) {
2111: List list = new ArrayList();
2112: StringTokenizer t = new StringTokenizer(input);
2113: String tokens[];
2114:
2115: while (t.hasMoreTokens()) {
2116: list.add(t.nextToken());
2117: }
2118:
2119: tokens = new String[list.size()];
2120: list.toArray(tokens);
2121: return tokens;
2122: }
2123:
2124: // ======================= WINDOW INITIALISATION =======================
2125:
2126: // --------------------------------------------------------------------
2127: /**
2128: * Create all the Window components
2129: */
2130: private void initWindow() {
2131: setIconImage(iconImage);
2132:
2133: // prepare the content pane
2134:
2135: JPanel contentPane = new JPanel(new BorderLayout(5, 5));
2136: contentPane.setBorder(BorderFactory.createEmptyBorder(6, 6, 6,
2137: 6));
2138: setContentPane(contentPane);
2139:
2140: // create and add info and status areas
2141:
2142: JPanel bottomArea = new JPanel();
2143:
2144: // create panel for info/status
2145: bottomArea.setLayout(new BorderLayout(5, 5));
2146:
2147: info = new Info();
2148: bottomArea.add(info, BorderLayout.CENTER);
2149:
2150: statusArea = new JPanel();
2151: statusArea.setLayout(new GridLayout(0, 1));
2152: // one column, many rows
2153: statusArea.setBackground(infoColor);
2154: statusArea.setBorder(BorderFactory
2155: .createLineBorder(Color.black));
2156:
2157: saveState = new StatusLabel(StatusLabel.SAVED);
2158: statusArea.add(saveState);
2159: bottomArea.add(statusArea, BorderLayout.EAST);
2160:
2161: contentPane.add(bottomArea, BorderLayout.SOUTH);
2162:
2163: // create the text document
2164:
2165: sourceDocument = new MoeSyntaxDocument();
2166: sourceDocument.addDocumentListener(this );
2167: sourceDocument.addUndoableEditListener(undoManager);
2168:
2169: // create the text pane
2170:
2171: MoeSyntaxEditorKit kit = new MoeSyntaxEditorKit(false);
2172: sourcePane = new MoeEditorPane();
2173:
2174: sourcePane.setDocument(sourceDocument);
2175: sourcePane.setCaretPosition(0);
2176: sourcePane.setMargin(new Insets(2, 2, 2, 2));
2177: sourcePane.setOpaque(true);
2178: sourcePane.setEditorKit(kit);
2179: moeCaret = new MoeCaret(this );
2180: sourcePane.setCaret(moeCaret);
2181: sourcePane
2182: .setBackground(MoeSyntaxDocument.getBackgroundColor());
2183: // sourcePane.setSelectionColor(selectionColour);
2184: sourcePane.setCaretColor(cursorColor);
2185:
2186: // default showing:
2187: currentTextPane = sourcePane;
2188:
2189: scrollPane = new JScrollPane(currentTextPane);
2190: scrollPane.setPreferredSize(new Dimension(598, 400));
2191: scrollPane.getVerticalScrollBar().setUnitIncrement(16);
2192:
2193: contentPane.add(scrollPane, BorderLayout.CENTER);
2194:
2195: // get table of edit actions
2196:
2197: actions = MoeActions.getActions(sourcePane);
2198: actions.setUndoEnabled(false);
2199: actions.setRedoEnabled(false);
2200:
2201: // **** temporary: disable all unimplemented actions ****
2202: actions.getActionByName("show-manual").setEnabled(false);
2203: // ****
2204:
2205: // create menubar and menus
2206:
2207: JMenuBar menubar = createMenuBar();
2208: setJMenuBar(menubar);
2209:
2210: // create toolbar
2211:
2212: toolbar = createToolbar();
2213: contentPane.add(toolbar, BorderLayout.NORTH);
2214:
2215: // add event listener to handle the window close requests
2216:
2217: addWindowListener(new WindowAdapter() {
2218: public void windowClosing(WindowEvent e) {
2219: close();
2220: }
2221:
2222: public void windowActivated(WindowEvent e) {
2223: checkForChangeOnDisk();
2224: }
2225: });
2226:
2227: setFocusTraversalPolicy(new MoeFocusTraversalPolicy());
2228:
2229: setWindowTitle();
2230: pack();
2231: }
2232:
2233: // --------------------------------------------------------------------
2234:
2235: /**
2236: * Create the editor's menu bar.
2237: */
2238: private JMenuBar createMenuBar() {
2239: JMenuBar menubar = new JMenuBar();
2240: JMenu menu = null;
2241:
2242: String[] menuKeys = tokenize(getResource("menubar"));
2243: for (int i = 0; i < menuKeys.length; i++) {
2244: menu = createMenu(menuKeys[i]);
2245: if (menu != null) {
2246: menubar.add(menu);
2247: }
2248: }
2249: return menubar;
2250: }
2251:
2252: // --------------------------------------------------------------------
2253:
2254: /**
2255: * Create a single menu for the editor's menu bar. The key for the menu (as
2256: * defined in moe.properties) is supplied.
2257: */
2258: private JMenu createMenu(String key) {
2259: JMenuItem item;
2260: String label;
2261:
2262: // get menu title
2263: JMenu menu = new JMenu(Config.getString("editor." + key
2264: + LabelSuffix));
2265:
2266: // get menu definition
2267: String itemString = getResource(key);
2268: if (itemString == null) {
2269: Debug
2270: .message("Moe: cannot find menu definition for "
2271: + key);
2272: return null;
2273: }
2274:
2275: // cut menu definition into separate items
2276: String[] itemKeys = tokenize(itemString);
2277:
2278: // create menu item for each item
2279: for (int i = 0; i < itemKeys.length; i++) {
2280: if (itemKeys[i].equals("-")) {
2281: menu.addSeparator();
2282: } else {
2283: Action action = actions.getActionByName(itemKeys[i]);
2284: if (action == null) {
2285: Debug.message("Moe: cannot find action "
2286: + itemKeys[i]);
2287: } else {
2288: item = menu.add(action);
2289: label = Config.getString("editor." + itemKeys[i]
2290: + LabelSuffix);
2291: if (label != null) {
2292: item.setText(label);
2293: }
2294: KeyStroke[] keys = actions
2295: .getKeyStrokesForAction(action);
2296: if (keys != null) {
2297: item.setAccelerator(chooseKey(keys));
2298: }
2299: }
2300: }
2301: }
2302: return menu;
2303: }
2304:
2305: /**
2306: * Choose a key to use in the menu from all defined keys.
2307: */
2308: private KeyStroke chooseKey(KeyStroke[] keys) {
2309: if (keys.length == 1) {
2310: return keys[0];
2311: } else {
2312: KeyStroke key = keys[0];
2313: // give preference to shortcuts using letter keys (CTRL-V, rather
2314: // than F2)
2315: for (int i = 1; i < keys.length; i++) {
2316: if (keys[i].getKeyCode() >= 'A'
2317: && keys[i].getKeyCode() <= 'Z') {
2318: key = keys[i];
2319: }
2320: }
2321: return key;
2322: }
2323: }
2324:
2325: // --------------------------------------------------------------------
2326:
2327: /**
2328: * Create the toolbar.
2329: *
2330: * @return The toolbar component, ready made.
2331: */
2332: private JComponent createToolbar() {
2333: JPanel toolbar = new JPanel();
2334: toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
2335: //((FlowLayout)toolbar.getLayout()).setAlignment(FlowLayout.LEFT);
2336:
2337: String[] toolKeys = tokenize(getResource("toolbar"));
2338: for (int i = 0; i < toolKeys.length; i++) {
2339: toolbar.add(createToolbarButton(toolKeys[i], false));
2340: toolbar.add(Box.createHorizontalStrut(4));
2341: }
2342:
2343: toolbar.add(Box.createHorizontalGlue());
2344: toolbar.add(Box.createHorizontalGlue());
2345: toolbar.add(createInterfaceSelector());
2346:
2347: return toolbar;
2348: }
2349:
2350: // --------------------------------------------------------------------
2351:
2352: /**
2353: * Create a button on the toolbar.
2354: */
2355: private AbstractButton createToolbarButton(String key,
2356: boolean isToggle) {
2357: final String label = Config.getString("editor." + key
2358: + LabelSuffix);
2359: AbstractButton button;
2360:
2361: String actionName = getResource(key + ActionSuffix);
2362: if (actionName == null) {
2363: actionName = key;
2364: }
2365: Action action = actions.getActionByName(actionName);
2366: Action tbAction = new ToolbarAction(action, label);
2367:
2368: if (isToggle) {
2369: button = new JToggleButton(tbAction);
2370: } else {
2371: button = new JButton(tbAction);
2372: }
2373:
2374: if (action == null) {
2375: button.setEnabled(false);
2376: Debug.message("Moe: action not found for button " + label);
2377: }
2378:
2379: button.setRequestFocusEnabled(false);
2380: // never get keyboard focus
2381:
2382: if (!Config.isMacOS()) {
2383: // on all other platforms than MacOS, the default insets needs to
2384: // be changed to make the buttons smaller
2385: Insets margin = button.getMargin();
2386: button
2387: .setMargin(new Insets(margin.top, 3, margin.bottom,
2388: 3));
2389: } else {
2390: Utility.changeToMacButton(button);
2391: }
2392:
2393: button.setFont(PrefMgr.getStandardFont());
2394: return button;
2395: }
2396:
2397: // --------------------------------------------------------------------
2398:
2399: /**
2400: * Create a combo box for the toolbar
2401: */
2402: private JComboBox createInterfaceSelector() {
2403: String[] choiceStrings = { implementationString,
2404: interfaceString };
2405: interfaceToggle = new JComboBox(choiceStrings);
2406:
2407: interfaceToggle.setRequestFocusEnabled(false);
2408: interfaceToggle.setFont(PrefMgr.getStandardFont());
2409: interfaceToggle.setBorder(new EmptyBorder(2, 2, 2, 2));
2410: interfaceToggle.setForeground(envOpColour);
2411:
2412: String actionName = "toggle-interface-view";
2413: Action action = actions.getActionByName(actionName);
2414: if (action != null) { // should never be null...
2415: interfaceToggle.setAction(action);
2416: } else {
2417: interfaceToggle.setEnabled(false);
2418: Debug.message("Moe: action not found: " + actionName);
2419: }
2420: if (!sourceIsCode) {
2421: interfaceToggle.setEnabled(false);
2422: }
2423: return interfaceToggle;
2424: }
2425:
2426: // --------------------------------------------------------------------
2427:
2428: /**
2429: * Inner class for printing thread to allow printing to occur as a
2430: * background operation.
2431: *
2432: * @author Bruce Quig
2433: */
2434: class PrintHandler implements Runnable {
2435: PrinterJob printJob;
2436: PageFormat pageFormat;
2437:
2438: /**
2439: * Construct the PrintHandler.
2440: */
2441: public PrintHandler(PrinterJob pj, PageFormat format) {
2442: super ();
2443: printJob = pj;
2444: pageFormat = format;
2445: }
2446:
2447: /**
2448: * Implementation of Runnable interface
2449: */
2450: public void run() {
2451: print();
2452: }
2453:
2454: /**
2455: * Create MoePrinter and then invoke print method
2456: */
2457: public void print() {
2458: if (printer == null) {
2459: printer = new MoePrinter();
2460: }
2461:
2462: // print document, using new pageformat object at present
2463: info.message(Config.getString("editor.info.printing"));
2464: if (printer.printDocument(printJob, sourceDocument,
2465: windowTitle, printFont, pageFormat)) {
2466: info.message(Config.getString("editor.info.printed"));
2467: } else {
2468: info.message(Config.getString("editor.info.cancelled"));
2469: }
2470:
2471: }
2472:
2473: }
2474:
2475: // --------------------------------------------------------------------
2476:
2477: /**
2478: * Class for thread listening to edit changes.
2479: */
2480: class TextInsertNotifier implements Runnable {
2481: private DocumentEvent evt;
2482: private JEditorPane editorPane;
2483:
2484: /**
2485: * Sets the event attribute of the TextInsertNotifier object
2486: */
2487: public void setEvent(DocumentEvent e, JEditorPane editorPane) {
2488: evt = e;
2489: this .editorPane = editorPane;
2490: }
2491:
2492: /**
2493: * Main processing method for the TextInsertNotifier object
2494: */
2495: public void run() {
2496: actions.textInsertAction(evt, editorPane);
2497: }
2498: }
2499:
2500: /**
2501: * Custom focus traversal implementation to make sure that the text area
2502: * gets and never loses focus.
2503: */
2504: class MoeFocusTraversalPolicy extends FocusTraversalPolicy {
2505: public Component getComponentAfter(Container focusCycleRoot,
2506: Component aComponent) {
2507: return currentTextPane;
2508: }
2509:
2510: public Component getComponentBefore(Container focusCycleRoot,
2511: Component aComponent) {
2512: return currentTextPane;
2513: }
2514:
2515: public Component getDefaultComponent(Container focusCycleRoot) {
2516: return currentTextPane;
2517: }
2518:
2519: public Component getFirstComponent(Container focusCycleRoot) {
2520: return currentTextPane;
2521: }
2522:
2523: public Component getInitialComponent(Window window) {
2524: return currentTextPane;
2525: }
2526:
2527: public Component getLastComponent(Container focusCycleRoot) {
2528: return currentTextPane;
2529: }
2530: }
2531:
2532: /**
2533: * An abstract action which delegates to a sub-action, and which
2534: * mirrors the "enabled" state of the sub-action. This allows having
2535: * actions with alternative labels.
2536: *
2537: * @author Davin McCall
2538: */
2539: class ToolbarAction extends AbstractAction implements
2540: PropertyChangeListener {
2541: private Action subAction;
2542:
2543: public ToolbarAction(Action subAction, String label) {
2544: super (label);
2545: this .subAction = subAction;
2546: subAction.addPropertyChangeListener(this );
2547: setEnabled(subAction.isEnabled());
2548: }
2549:
2550: public void actionPerformed(ActionEvent e) {
2551: subAction.actionPerformed(e);
2552: }
2553:
2554: public void propertyChange(PropertyChangeEvent evt) {
2555: // If the enabled state of the sub-action changed,
2556: // then we should change our own state.
2557: if (evt.getPropertyName().equals("enabled")) {
2558: Object newVal = evt.getNewValue();
2559: if (newVal instanceof Boolean) {
2560: boolean state = ((Boolean) newVal).booleanValue();
2561: setEnabled(state);
2562: }
2563: }
2564: }
2565: }
2566: }
|