0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Picrosystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor;
0043:
0044: import java.awt.Point;
0045: import java.awt.Rectangle;
0046: import java.awt.event.ActionEvent;
0047: import java.io.Reader;
0048: import java.io.Writer;
0049: import java.io.IOException;
0050: import java.util.Map;
0051: import java.util.HashMap;
0052: import java.util.Iterator;
0053: import java.util.List;
0054: import java.util.ArrayList;
0055: import java.util.Collections;
0056: import javax.swing.Action;
0057: import javax.swing.JEditorPane;
0058: import javax.swing.SwingConstants;
0059: import javax.swing.SwingUtilities;
0060: import javax.swing.text.Document;
0061: import javax.swing.text.DefaultEditorKit;
0062: import javax.swing.text.BadLocationException;
0063: import javax.swing.text.Element;
0064: import javax.swing.text.ViewFactory;
0065: import javax.swing.text.Caret;
0066: import javax.swing.text.JTextComponent;
0067: import java.io.CharArrayWriter;
0068: import java.lang.reflect.Method;
0069: import java.util.Vector;
0070: import java.util.logging.Level;
0071: import java.util.logging.Logger;
0072: import javax.swing.text.EditorKit;
0073: import javax.swing.text.Position;
0074: import org.netbeans.api.editor.mimelookup.MimeLookup;
0075: import org.netbeans.api.editor.mimelookup.MimePath;
0076: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
0077: import org.netbeans.modules.editor.lib.NavigationHistory;
0078: import org.openide.awt.StatusDisplayer;
0079: import org.openide.util.HelpCtx;
0080: import org.openide.util.Lookup;
0081: import org.openide.util.NbBundle;
0082:
0083: /**
0084: * Editor kit implementation for base document
0085: *
0086: * @author Miloslav Metelka
0087: * @version 1.00
0088: */
0089:
0090: public class BaseKit extends DefaultEditorKit {
0091:
0092: private static final Logger LOG = Logger.getLogger(BaseKit.class
0093: .getName());
0094:
0095: /** split the current line at cursor position */
0096: public static final String splitLineAction = "split-line"; // NOI18N
0097:
0098: /** Cycle through annotations on the current line */
0099: public static final String annotationsCyclingAction = "annotations-cycling"; // NOI18N
0100:
0101: /** Collapse a fold. Depends on the current caret position. */
0102: public static final String collapseFoldAction = "collapse-fold"; //NOI18N
0103:
0104: /** Expand a fold. Depends on the current caret position. */
0105: public static final String expandFoldAction = "expand-fold"; //NOI18N
0106:
0107: /** Collapse all existing folds in the document. */
0108: public static final String collapseAllFoldsAction = "collapse-all-folds"; //NOI18N
0109:
0110: /** Expand all existing folds in the document. */
0111: public static final String expandAllFoldsAction = "expand-all-folds"; //NOI18N
0112:
0113: /** Move one page up and make or extend selection */
0114: public static final String selectionPageUpAction = "selection-page-up"; // NOI18N
0115:
0116: /** Move one page down and make or extend selection */
0117: public static final String selectionPageDownAction = "selection-page-down"; // NOI18N
0118:
0119: /** Remove indentation */
0120: public static final String removeTabAction = "remove-tab"; // NOI18N
0121:
0122: /** Remove selected block or do nothing - useful for popup menu */
0123: public static final String removeSelectionAction = "remove-selection"; // NOI18N
0124:
0125: /** Expand the abbreviation */
0126: public static final String abbrevExpandAction = "abbrev-expand"; // NOI18N
0127:
0128: /** Reset the abbreviation accounting string */
0129: public static final String abbrevResetAction = "abbrev-reset"; // NOI18N
0130:
0131: /** Remove the word */
0132: //public static final String removeWordAction = "remove-word"; #47709
0133: /** Remove characters to the begining of the word or
0134: * the previous word if caret is not directly at word */
0135: public static final String removePreviousWordAction = "remove-word-previous"; // NOI18N
0136:
0137: /** Remove characters to the end of the word or
0138: * the next word if caret is not directly at word */
0139: public static final String removeNextWordAction = "remove-word-next"; // NOI18N
0140:
0141: /** Remove to the begining of the line */
0142: public static final String removeLineBeginAction = "remove-line-begin"; // NOI18N
0143:
0144: /** Remove line */
0145: public static final String removeLineAction = "remove-line"; // NOI18N
0146:
0147: /** Move selection else line up */
0148: /* package */static final String moveSelectionElseLineUpAction = "move-selection-else-line-up"; // NOI18N
0149:
0150: /** Move selection else line down */
0151: /* package */static final String moveSelectionElseLineDownAction = "move-selection-else-line-down"; // NOI18N
0152:
0153: /** Copy selection else line up */
0154: /* package */static final String copySelectionElseLineUpAction = "copy-selection-else-line-up"; // NOI18N
0155:
0156: /** Copy selection else line down */
0157: /* package */static final String copySelectionElseLineDownAction = "copy-selection-else-line-down"; // NOI18N
0158:
0159: /** Toggle the typing mode to overwrite mode or back to insert mode */
0160: public static final String toggleTypingModeAction = "toggle-typing-mode"; // NOI18N
0161:
0162: /** Change the selected text or current character to uppercase */
0163: public static final String toUpperCaseAction = "to-upper-case"; // NOI18N
0164:
0165: /** Change the selected text or current character to lowercase */
0166: public static final String toLowerCaseAction = "to-lower-case"; // NOI18N
0167:
0168: /** Switch the case of the selected text or current character */
0169: public static final String switchCaseAction = "switch-case"; // NOI18N
0170:
0171: /** Find next occurence action */
0172: public static final String findNextAction = "find-next"; // NOI18N
0173:
0174: /** Find previous occurence action */
0175: public static final String findPreviousAction = "find-previous"; // NOI18N
0176:
0177: /** Toggle highlight search action */
0178: public static final String toggleHighlightSearchAction = "toggle-highlight-search"; // NOI18N
0179:
0180: /** Find current word */
0181: public static final String findSelectionAction = "find-selection"; // NOI18N
0182:
0183: /** Undo action */
0184: public static final String undoAction = "undo"; // NOI18N
0185:
0186: /** Redo action */
0187: public static final String redoAction = "redo"; // NOI18N
0188:
0189: /** Word match next */
0190: public static final String wordMatchNextAction = "word-match-next"; // NOI18N
0191:
0192: /** Word match prev */
0193: public static final String wordMatchPrevAction = "word-match-prev"; // NOI18N
0194:
0195: /** Reindent Line action */
0196: public static final String reindentLineAction = "reindent-line"; // NOI18N
0197:
0198: /** Shift line right action */
0199: public static final String shiftLineRightAction = "shift-line-right"; // NOI18N
0200:
0201: /** Shift line left action */
0202: public static final String shiftLineLeftAction = "shift-line-left"; // NOI18N
0203:
0204: /** Action that scrolls the window so that caret is at the center of the window */
0205: public static final String adjustWindowCenterAction = "adjust-window-center"; // NOI18N
0206:
0207: /** Action that scrolls the window so that caret is at the top of the window */
0208: public static final String adjustWindowTopAction = "adjust-window-top"; // NOI18N
0209:
0210: /** Action that scrolls the window so that caret is at the bottom of the window */
0211: public static final String adjustWindowBottomAction = "adjust-window-bottom"; // NOI18N
0212:
0213: /** Action that moves the caret so that caret is at the center of the window */
0214: public static final String adjustCaretCenterAction = "adjust-caret-center"; // NOI18N
0215:
0216: /** Action that moves the caret so that caret is at the top of the window */
0217: public static final String adjustCaretTopAction = "adjust-caret-top"; // NOI18N
0218:
0219: /** Action that moves the caret so that caret is at the bottom of the window */
0220: public static final String adjustCaretBottomAction = "adjust-caret-bottom"; // NOI18N
0221:
0222: /** Format part of the document text using Indent */
0223: public static final String formatAction = "format"; // NOI18N
0224:
0225: /** First non-white character on the line */
0226: public static final String firstNonWhiteAction = "first-non-white"; // NOI18N
0227:
0228: /** Last non-white character on the line */
0229: public static final String lastNonWhiteAction = "last-non-white"; // NOI18N
0230:
0231: /** First non-white character on the line */
0232: public static final String selectionFirstNonWhiteAction = "selection-first-non-white"; // NOI18N
0233:
0234: /** Last non-white character on the line */
0235: public static final String selectionLastNonWhiteAction = "selection-last-non-white"; // NOI18N
0236:
0237: /** Select the nearest identifier around caret */
0238: public static final String selectIdentifierAction = "select-identifier"; // NOI18N
0239:
0240: /** Select the next parameter (after the comma) in the given context */
0241: public static final String selectNextParameterAction = "select-next-parameter"; // NOI18N
0242:
0243: /** Go to the previous position stored in the jump-list */
0244: public static final String jumpListNextAction = "jump-list-next"; // NOI18N
0245:
0246: /** Go to the next position stored in the jump-list */
0247: public static final String jumpListPrevAction = "jump-list-prev"; // NOI18N
0248:
0249: /** Go to the last position in the previous component stored in the jump-list */
0250: public static final String jumpListNextComponentAction = "jump-list-next-component"; // NOI18N
0251:
0252: /** Go to the next position in the previous component stored in the jump-list */
0253: public static final String jumpListPrevComponentAction = "jump-list-prev-component"; // NOI18N
0254:
0255: /** Scroll window one line up */
0256: public static final String scrollUpAction = "scroll-up"; // NOI18N
0257:
0258: /** Scroll window one line down */
0259: public static final String scrollDownAction = "scroll-down"; // NOI18N
0260:
0261: /** Prefix of all macro-based actions */
0262: public static final String macroActionPrefix = "macro-"; // NOI18N
0263:
0264: /** Start recording of macro. Only one macro recording can be active at the time */
0265: public static final String startMacroRecordingAction = "start-macro-recording"; //NOI18N
0266:
0267: /** Stop the active recording */
0268: public static final String stopMacroRecordingAction = "stop-macro-recording"; //NOI18N
0269:
0270: /** Name of the action moving caret to the first column on the line */
0271: public static final String lineFirstColumnAction = "caret-line-first-column"; // NOI18N
0272:
0273: /** Insert the current Date and Time */
0274: public static final String insertDateTimeAction = "insert-date-time"; // NOI18N
0275:
0276: /** Name of the action moving caret to the first
0277: * column on the line and extending the selection
0278: */
0279: public static final String selectionLineFirstColumnAction = "selection-line-first-column"; // NOI18N
0280:
0281: /** Name of the action for generating of Glyph Gutter popup menu*/
0282: public static final String generateGutterPopupAction = "generate-gutter-popup"; // NOI18N
0283:
0284: /** Toggle visibility of line numbers*/
0285: public static final String toggleLineNumbersAction = "toggle-line-numbers"; // NOI18N
0286:
0287: /** Paste and reformat code */
0288: public static final String pasteFormatedAction = "paste-formated"; // NOI18N
0289:
0290: /** Starts a new line in code */
0291: public static final String startNewLineAction = "start-new-line"; // NOI18N
0292:
0293: /** Cut text from caret position to line begining action. */
0294: public static final String cutToLineBeginAction = "cut-to-line-begin"; // NOI18N
0295:
0296: /** Cut text from caret position to line end action. */
0297: public static final String cutToLineEndAction = "cut-to-line-end"; // NOI18N
0298:
0299: /** Remove all trailing spaces in the document. */
0300: public static final String removeTrailingSpacesAction = "remove-trailing-spaces"; //NOI18N
0301:
0302: public static final String DOC_REPLACE_SELECTION_PROPERTY = "doc-replace-selection-property"; //NOI18N
0303:
0304: private static final int KIT_CNT_PREALLOC = 7;
0305:
0306: static final long serialVersionUID = -8570495408376659348L;
0307:
0308: /** [kit-class, kit-instance] pairs are stored here */
0309: private static final Map kits = new HashMap(KIT_CNT_PREALLOC);
0310:
0311: /** [kit-class, keymap] pairs */
0312: private static final Map kitKeymaps = new HashMap(KIT_CNT_PREALLOC);
0313:
0314: /** [kit, action[]] pairs */
0315: private static final Map kitActions = new HashMap(KIT_CNT_PREALLOC);
0316:
0317: /** [kit, action-map] pairs */
0318: private static final Map kitActionMaps = new HashMap(
0319: KIT_CNT_PREALLOC);
0320:
0321: private static CopyAction copyActionDef = new CopyAction();
0322: private static CutAction cutActionDef = new CutAction();
0323: private static PasteAction pasteActionDef = new PasteAction(false);
0324: private static DeleteCharAction deletePrevCharActionDef = new DeleteCharAction(
0325: deletePrevCharAction, false);
0326: private static DeleteCharAction deleteNextCharActionDef = new DeleteCharAction(
0327: deleteNextCharAction, true);
0328: private static ActionFactory.RemoveSelectionAction removeSelectionActionDef = new ActionFactory.RemoveSelectionAction();
0329:
0330: private static ActionFactory.UndoAction undoActionDef = new ActionFactory.UndoAction();
0331: private static ActionFactory.RedoAction redoActionDef = new ActionFactory.RedoAction();
0332:
0333: public static final int MAGIC_POSITION_MAX = Integer.MAX_VALUE - 1;
0334:
0335: static SettingsChangeListener settingsListener = new SettingsChangeListener() {
0336: public void settingsChange(SettingsChangeEvent evt) {
0337: String settingName = (evt != null) ? evt.getSettingName()
0338: : null;
0339:
0340: boolean clearActions = (settingName == null
0341: || SettingsNames.CUSTOM_ACTION_LIST
0342: .equals(settingName) || SettingsNames.MACRO_MAP
0343: .equals(settingName));
0344:
0345: if (clearActions
0346: || SettingsNames.KEY_BINDING_LIST
0347: .equals(settingName)) {
0348: kitKeymaps.clear();
0349: }
0350:
0351: if (clearActions) {
0352: kitActions.clear();
0353: kitActionMaps.clear();
0354: } else { // only refresh action settings
0355: Iterator i = kitActions.entrySet().iterator();
0356: while (i.hasNext()) {
0357: Map.Entry me = (Map.Entry) i.next();
0358: updateActionSettings((Action[]) me.getValue(), evt,
0359: (Class) me.getKey());
0360: }
0361: }
0362: }
0363: };
0364:
0365: static {
0366: Settings.addSettingsChangeListener(settingsListener);
0367: }
0368:
0369: private static void updateActionSettings(Action[] actions,
0370: SettingsChangeEvent evt, Class kitClass) {
0371: for (int i = 0; i < actions.length; i++) {
0372: if (actions[i] instanceof BaseAction) {
0373: ((BaseAction) actions[i]).settingsChange(evt, kitClass);
0374: }
0375: }
0376: }
0377:
0378: /**
0379: * Gets an editor kit from its implemetation class.
0380: *
0381: * <p>Please be careful when using this method and make sure that you understand
0382: * how it works and what the deference is from using <code>MimeLookup</code>.
0383: * This method simply creates an instance of <code>BaseKit</code> from
0384: * its implementation class passed in as a parameter. It completely ignores
0385: * the registry of editor kits in <code>MimeLookup</code>, which has severe
0386: * consequences.
0387: *
0388: * <div class="nonnormative">
0389: * <p>The usuall pattern for using editor kits is to start with a mime type
0390: * of a document (ie. file) that you want to edit, then use some registry
0391: * for editor kits to look up the kit for your mime type and finally set the
0392: * kit in a <code>JTextComponent</code>, let it create a <code>Document</code>
0393: * and load it with data. The registry can generally be anything, but in Netbeans
0394: * we use <code>MimeLookup</code> (JDK for example uses
0395: * <code>JEditorPane.createEditorKitForContentType</code>).
0396: *
0397: * <p>The editor kits are registered in <code>MimeLookup</code> for each
0398: * particular mime type and the registry itself does not impose any rules on
0399: * the editor kit implementations other than extending the <code>EditorKit</code>
0400: * class. This for example means that the same implemantation of <code>EditorKit</code>
0401: * can be used for multiple mime types. This is exactly how XML editor kit
0402: * is reused for various flavors of XML documents (e.g. ant build scripts,
0403: * web app descriptors, etc).
0404: *
0405: * <p>Netbeans did not always have <code>MimeLookup</code>
0406: * and it also used a different approach for registering and retrieving
0407: * editor kits. This old approach was based on implemetation classes rather than on mime
0408: * types and while it is still more or less functional for the old kit
0409: * implementations, it is fundamentally broken and should not be used any more.
0410: * The code below demonstrates probably the biggest mistake when thinking
0411: * in the old ways.
0412: *
0413: * <pre>
0414: * // WARNING: The code below is a demonstration of a common mistake that
0415: * // people do when using <code>BaseKit.getKit</code>.
0416: *
0417: * JTextComponent component = ...; // Let's say we have a component
0418: * Class kitClass = Utilities.getKitClass(component);
0419: * String mimeType = BaseKit.getKit(kitClass).getContentType();
0420: * </pre>
0421: *
0422: * <p>The problem with the above code is that it blindely assumes that each
0423: * kit class can be uniquely mapped to a mime type. This is not true! The
0424: * same can be achieved in much easier way, which always works.
0425: *
0426: * <pre>
0427: * JTextComponent component = ...; // Let's say we have a component
0428: * String mimeType = component.getUI().getEditorKit(component).getContentType();
0429: * </pre>
0430: * </div>
0431: *
0432: * @param kitClass An implementation class of the editor kit that should
0433: * be returned. If the <code>kitClass</code> is not <code>BaseKit</code> or
0434: * its subclass the instance of bare <code>BaseKit</code> will be returned.
0435: *
0436: * @return An instance of the <code>kitClass</code> or <code>BaseKit</code>.
0437: * @deprecated Use <code>CloneableEditorSupport.getEditorKit</code> or
0438: * <code>MimeLookup</code> instead to find <code>EditorKit</code> for a mime
0439: * type.
0440: */
0441: public static BaseKit getKit(Class kitClass) {
0442: if (kitClass != null
0443: && BaseKit.class.isAssignableFrom(kitClass)
0444: && BaseKit.class != kitClass) {
0445: if (!noKitsTracker) {
0446: String mimeType = kitsTracker_FindMimeType(kitClass);
0447: if (mimeType != null) {
0448: EditorKit kit = MimeLookup.getLookup(
0449: MimePath.parse(mimeType)).lookup(
0450: EditorKit.class);
0451: if (kit instanceof BaseKit) {
0452: return (BaseKit) kit;
0453: }
0454: }
0455: }
0456: } else {
0457: kitClass = BaseKit.class;
0458: }
0459:
0460: synchronized (kits) {
0461: BaseKit kit = (BaseKit) kits.get(kitClass);
0462: if (kit == null) {
0463: try {
0464: kit = (BaseKit) kitClass.newInstance();
0465: } catch (IllegalAccessException e) {
0466: LOG.log(Level.WARNING, null, e);
0467: } catch (InstantiationException e) {
0468: LOG.log(Level.WARNING, null, e);
0469: }
0470: kits.put(kitClass, kit);
0471: }
0472: return kit;
0473: }
0474: }
0475:
0476: private static volatile boolean noKitsTracker = false;
0477:
0478: /* package */static String kitsTracker_FindMimeType(Class kitClass) {
0479: String mimeType = null;
0480:
0481: if (!noKitsTracker) {
0482: try {
0483: ClassLoader cl = Lookup.getDefault().lookup(
0484: ClassLoader.class);
0485: Class clazz = cl
0486: .loadClass("org.netbeans.modules.editor.impl.KitsTracker"); //NOI18N
0487: Method getInstanceMethod = clazz
0488: .getDeclaredMethod("getInstance"); //NOI18N
0489: Method findMimeTypeMethod = clazz.getDeclaredMethod(
0490: "findMimeType", Class.class); //NOI18N
0491: Object kitsTracker = getInstanceMethod.invoke(null);
0492: mimeType = (String) findMimeTypeMethod.invoke(
0493: kitsTracker, kitClass);
0494: } catch (Exception e) {
0495: // ignore
0496: noKitsTracker = true;
0497: }
0498: }
0499:
0500: return mimeType;
0501: }
0502:
0503: /**
0504: * Creates a new instance of <code>BaseKit</code>.
0505: *
0506: * <div class="nonnormative">
0507: * <p>You should not need to instantiate editor kits
0508: * directly under normal circumstances. There is a few ways how you can get
0509: * instance of <code>EditorKit</code> depending on what you already have
0510: * available:
0511: *
0512: * <ul>
0513: * <li><b>mime type</b> - Use <code>CloneableEditorSupport.getEditorKit(yourMimeType)</code>
0514: * to get the <code>EditorKit</code> registered for your mime type or use
0515: * the following code <code>MimeLookup.getLookup(MimePath.parse(yourMimeType)).lookup(EditorKit.class)</code>
0516: * and check for <code>null</code>.
0517: * <li><b>JTextComponent</b> - Simply call
0518: * <code>JTextComponent.getUI().getEditorKit(JTextComponent)</code> passing
0519: * in the same component.
0520: * </ul>
0521: * </div>
0522: */
0523: public BaseKit() {
0524: // possibly register
0525: synchronized (kits) {
0526: if (kits.get(this .getClass()) == null) {
0527: kits.put(this .getClass(), this ); // register itself
0528: }
0529: }
0530: }
0531:
0532: /** Clone this editor kit */
0533: public Object clone() {
0534: return this ; // no need to create another instance
0535: }
0536:
0537: /** Fetches a factory that is suitable for producing
0538: * views of any models that are produced by this
0539: * kit. The default is to have the UI produce the
0540: * factory, so this method has no implementation.
0541: *
0542: * @return the view factory
0543: */
0544: public ViewFactory getViewFactory() {
0545: return null;
0546: }
0547:
0548: /** Create caret to navigate through document */
0549: public Caret createCaret() {
0550: return new BaseCaret();
0551: }
0552:
0553: /** Create empty document */
0554: public Document createDefaultDocument() {
0555: return new BaseDocument(this .getClass(), true);
0556: }
0557:
0558: /** Create new instance of syntax coloring scanner
0559: * @param doc document to operate on. It can be null in the cases the syntax
0560: * creation is not related to the particular document
0561: */
0562: public Syntax createSyntax(Document doc) {
0563: return new DefaultSyntax();
0564: }
0565:
0566: /** Create the syntax used for formatting */
0567: public Syntax createFormatSyntax(Document doc) {
0568: return createSyntax(doc);
0569: }
0570:
0571: /** Create syntax support */
0572: public SyntaxSupport createSyntaxSupport(BaseDocument doc) {
0573: return new SyntaxSupport(doc);
0574: }
0575:
0576: /** Create the formatter appropriate for this kit */
0577: public Formatter createFormatter() {
0578: return new Formatter(this .getClass());
0579: }
0580:
0581: /** Create text UI */
0582: protected BaseTextUI createTextUI() {
0583: return new BaseTextUI();
0584: }
0585:
0586: /** Create extended UI */
0587: protected EditorUI createEditorUI() {
0588: return new EditorUI();
0589: }
0590:
0591: /**
0592: * Create extended UI for printing a document.
0593: * @deprecated this method is no longer being called by {@link EditorUI}.
0594: * {@link #createPrintEditorUI(BaseDocument, boolean, boolean)} is being
0595: * called instead.
0596: */
0597: protected EditorUI createPrintEditorUI(BaseDocument doc) {
0598: return new EditorUI(doc);
0599: }
0600:
0601: /**
0602: * Create extended UI for printing a document.
0603: *
0604: * @param doc document for which the extended UI is being created.
0605: * @param usePrintColoringMap use printing coloring settings instead
0606: * of the regular ones.
0607: * @param lineNumberEnabled if set to false the line numbers will not be printed.
0608: * If set to true the visibility of line numbers depends on the settings
0609: * for the line number visibility.
0610: */
0611: protected EditorUI createPrintEditorUI(BaseDocument doc,
0612: boolean usePrintColoringMap, boolean lineNumberEnabled) {
0613:
0614: return new EditorUI(doc, usePrintColoringMap, lineNumberEnabled);
0615: }
0616:
0617: public MultiKeymap getKeymap() {
0618: synchronized (Settings.class) {
0619: MultiKeymap km = (MultiKeymap) kitKeymaps.get(this
0620: .getClass());
0621: if (km == null) { // keymap not yet constructed
0622: // construct new keymap
0623: km = new MultiKeymap("Keymap for " + this .getClass()); // NOI18N
0624: // retrieve key bindings for this kit and super kits
0625: Settings.KitAndValue kv[] = Settings
0626: .getValueHierarchy(this .getClass(),
0627: SettingsNames.KEY_BINDING_LIST);
0628: // go through all levels and collect key bindings
0629: for (int i = kv.length - 1; i >= 0; i--) {
0630: List keyList = (List) kv[i].value;
0631: JTextComponent.KeyBinding[] keys = new JTextComponent.KeyBinding[keyList
0632: .size()];
0633: keyList.toArray(keys);
0634: km.load(keys, getActionMap());
0635: }
0636:
0637: km.setDefaultAction((Action) getActionMap().get(
0638: defaultKeyTypedAction));
0639:
0640: kitKeymaps.put(this .getClass(), km);
0641: }
0642: return km;
0643: }
0644: }
0645:
0646: /** Inserts content from the given stream. */
0647: public void read(Reader in, Document doc, int pos)
0648: throws IOException, BadLocationException {
0649: if (doc instanceof BaseDocument) {
0650: ((BaseDocument) doc).read(in, pos); // delegate it to document
0651: } else {
0652: super .read(in, doc, pos);
0653: }
0654: }
0655:
0656: /** Writes content from a document to the given stream */
0657: public void write(Writer out, Document doc, int pos, int len)
0658: throws IOException, BadLocationException {
0659: if (doc instanceof BaseDocument) {
0660: ((BaseDocument) doc).write(out, pos, len);
0661: } else {
0662: super .write(out, doc, pos, len);
0663: }
0664: }
0665:
0666: /** Creates map with [name, action] pairs from the given
0667: * array of actions.
0668: */
0669: public static Map actionsToMap(Action[] actions) {
0670: Map map = new HashMap();
0671: for (int i = 0; i < actions.length; i++) {
0672: Action a = actions[i];
0673: String name = (String) a.getValue(Action.NAME);
0674: map.put(((name != null) ? name : ""), a); // NOI18N
0675: }
0676: return map;
0677: }
0678:
0679: /** Converts map with [name, action] back
0680: * to array of actions.
0681: */
0682: public static Action[] mapToActions(Map map) {
0683: Action[] actions = new Action[map.size()];
0684: int i = 0;
0685: for (Iterator iter = map.values().iterator(); iter.hasNext();) {
0686: actions[i++] = (Action) iter.next();
0687: }
0688: return actions;
0689: }
0690:
0691: /** Called after the kit is installed into JEditorPane */
0692: public void install(JEditorPane c) {
0693:
0694: assert (SwingUtilities.isEventDispatchThread()) // expected in AWT only
0695: : "BaseKit.install() incorrectly called from non-AWT thread."; // NOI18N
0696:
0697: BaseTextUI ui = createTextUI();
0698: c.setUI(ui);
0699:
0700: String propName = "netbeans.editor.noinputmethods"; // NOI18N
0701: Object noInputMethods = System.getProperty(propName);
0702: boolean enableIM;
0703: if (noInputMethods != null) {
0704: enableIM = !Boolean.getBoolean(propName);
0705: } else {
0706: enableIM = SettingsUtil.getBoolean(this .getClass(),
0707: SettingsNames.INPUT_METHODS_ENABLED, true);
0708: }
0709:
0710: c.enableInputMethods(enableIM);
0711: executeInstallActions(c);
0712:
0713: c.putClientProperty("hyperlink-operation", // NOI18N
0714: org.netbeans.lib.editor.hyperlink.HyperlinkOperation
0715: .create(c, getContentType()));
0716:
0717: // Mark that the editor's multi keymap adheres to context API in status displayer
0718: c.putClientProperty("context-api-aware", Boolean.TRUE); // NOI18N
0719:
0720: // Add default help IDs derived from the kit's mime type, #61618.
0721: // If the kit itself is HelpCtx.Provider it will be called from CloneableEditor.getHelpCtx()
0722: if (!(this instanceof HelpCtx.Provider)) {
0723: HelpCtx.setHelpIDString(c, getContentType().replace('/',
0724: '.').replace('+', '.')); //NOI18N
0725: }
0726: }
0727:
0728: protected void executeInstallActions(JEditorPane c) {
0729: Settings.KitAndValue[] kv = Settings
0730: .getValueHierarchy(this .getClass(),
0731: SettingsNames.KIT_INSTALL_ACTION_NAME_LIST);
0732: for (int i = kv.length - 1; i >= 0; i--) {
0733: List actList = (List) kv[i].value;
0734: actList = translateActionNameList(actList); // translate names to actions
0735: if (actList != null) {
0736: for (Iterator iter = actList.iterator(); iter.hasNext();) {
0737: Action a = (Action) iter.next();
0738: a.actionPerformed(new ActionEvent(c,
0739: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
0740: }
0741: }
0742: }
0743: }
0744:
0745: public void deinstall(JEditorPane c) {
0746:
0747: assert (SwingUtilities.isEventDispatchThread()); // expected in AWT only
0748:
0749: BaseTextUI.uninstallUIWatcher(c);
0750: executeDeinstallActions(c);
0751: c.updateUI();
0752:
0753: // #41209: reset ancestor override flag if previously set
0754: if (c.getClientProperty("ancestorOverride") != null) { // NOI18N
0755: c.putClientProperty("ancestorOverride", Boolean.FALSE); // NOI18N
0756: }
0757: }
0758:
0759: protected void executeDeinstallActions(JEditorPane c) {
0760: Settings.KitAndValue[] kv = Settings.getValueHierarchy(this
0761: .getClass(),
0762: SettingsNames.KIT_DEINSTALL_ACTION_NAME_LIST);
0763: for (int i = kv.length - 1; i >= 0; i--) {
0764: List actList = (List) kv[i].value;
0765: actList = translateActionNameList(actList); // translate names to actions
0766: if (actList != null) {
0767: for (Iterator iter = actList.iterator(); iter.hasNext();) {
0768: Action a = (Action) iter.next();
0769: a.actionPerformed(new ActionEvent(c,
0770: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
0771: }
0772: }
0773: }
0774: }
0775:
0776: /** Initialize document by adding the draw-layers for example. */
0777: protected void initDocument(BaseDocument doc) {
0778: }
0779:
0780: /** Create actions that this kit supports. To use the actions of the parent kit
0781: * it's better instead of using super.createActions() to use
0782: * getKit(super.getClass()).getActions() because it can reuse existing
0783: * parent actions.
0784: */
0785: protected Action[] createActions() {
0786: return new Action[] {
0787: new DefaultKeyTypedAction(),
0788: new InsertContentAction(),
0789: new InsertBreakAction(),
0790: new SplitLineAction(),
0791: new InsertTabAction(),
0792: deletePrevCharActionDef,
0793: deleteNextCharActionDef,
0794: new ReadOnlyAction(),
0795: new WritableAction(),
0796: cutActionDef,
0797: copyActionDef,
0798: pasteActionDef,
0799: new PasteAction(true),
0800: new BeepAction(),
0801: new UpAction(upAction, false),
0802: new UpAction(selectionUpAction, true),
0803: new PageUpAction(pageUpAction, false),
0804: new PageUpAction(selectionPageUpAction, true),
0805: new DownAction(downAction, false),
0806: new DownAction(selectionDownAction, true),
0807: new PageDownAction(selectionPageDownAction, true),
0808: new PageDownAction(pageDownAction, false),
0809: new ForwardAction(forwardAction, false),
0810: new ForwardAction(selectionForwardAction, true),
0811: new BackwardAction(backwardAction, false),
0812: new BackwardAction(selectionBackwardAction, true),
0813: new BeginLineAction(lineFirstColumnAction, false, true),
0814: new BeginLineAction(selectionLineFirstColumnAction,
0815: true, true),
0816: new BeginLineAction(beginLineAction, false),
0817: new BeginLineAction(selectionBeginLineAction, true),
0818: new EndLineAction(endLineAction, false),
0819: new EndLineAction(selectionEndLineAction, true),
0820: new BeginAction(beginAction, false),
0821: new BeginAction(selectionBeginAction, true),
0822: new EndAction(endAction, false),
0823: new EndAction(selectionEndAction, true),
0824: new NextWordAction(nextWordAction, false),
0825: new NextWordAction(selectionNextWordAction, true),
0826: new PreviousWordAction(previousWordAction, false),
0827: new PreviousWordAction(selectionPreviousWordAction,
0828: true),
0829: new BeginWordAction(beginWordAction, false),
0830: new BeginWordAction(selectionBeginWordAction, true),
0831: new EndWordAction(endWordAction, false),
0832: new EndWordAction(selectionEndWordAction, true),
0833: new SelectWordAction(),
0834: new SelectLineAction(),
0835: new SelectAllAction(),
0836: new RemoveTrailingSpacesAction(),
0837: new ActionFactory.RemoveTabAction(),
0838: //new ActionFactory.RemoveWordAction(), #47709
0839: new ActionFactory.RemoveWordPreviousAction(),
0840: new ActionFactory.RemoveWordNextAction(),
0841: new ActionFactory.RemoveLineBeginAction(),
0842: new ActionFactory.RemoveLineAction(),
0843: new ActionFactory.MoveSelectionElseLineUpAction(),
0844: new ActionFactory.MoveSelectionElseLineDownAction(),
0845: new ActionFactory.CopySelectionElseLineUpAction(),
0846: new ActionFactory.CopySelectionElseLineDownAction(),
0847: removeSelectionActionDef,
0848: new ActionFactory.ToggleTypingModeAction(),
0849: new ActionFactory.ChangeCaseAction(toUpperCaseAction,
0850: Utilities.CASE_UPPER),
0851: new ActionFactory.ChangeCaseAction(toLowerCaseAction,
0852: Utilities.CASE_LOWER),
0853: new ActionFactory.ChangeCaseAction(switchCaseAction,
0854: Utilities.CASE_SWITCH),
0855: new ActionFactory.FindNextAction(),
0856: new ActionFactory.FindPreviousAction(),
0857: new ActionFactory.FindSelectionAction(),
0858: new ActionFactory.ToggleHighlightSearchAction(),
0859: undoActionDef,
0860: redoActionDef,
0861: new ActionFactory.WordMatchAction(wordMatchNextAction,
0862: true),
0863: new ActionFactory.WordMatchAction(wordMatchPrevAction,
0864: false),
0865: new ActionFactory.ReindentLineAction(),
0866: new ActionFactory.ShiftLineAction(shiftLineLeftAction,
0867: false),
0868: new ActionFactory.ShiftLineAction(shiftLineRightAction,
0869: true),
0870: new ActionFactory.AdjustWindowAction(
0871: adjustWindowTopAction, 0),
0872: new ActionFactory.AdjustWindowAction(
0873: adjustWindowCenterAction, 50),
0874: new ActionFactory.AdjustWindowAction(
0875: adjustWindowBottomAction, 100),
0876: new ActionFactory.AdjustCaretAction(
0877: adjustCaretTopAction, 0),
0878: new ActionFactory.AdjustCaretAction(
0879: adjustCaretCenterAction, 50),
0880: new ActionFactory.AdjustCaretAction(
0881: adjustCaretBottomAction, 100),
0882: new ActionFactory.FormatAction(),
0883: new ActionFactory.FirstNonWhiteAction(
0884: firstNonWhiteAction, false),
0885: new ActionFactory.FirstNonWhiteAction(
0886: selectionFirstNonWhiteAction, true),
0887: new ActionFactory.LastNonWhiteAction(
0888: lastNonWhiteAction, false),
0889: new ActionFactory.LastNonWhiteAction(
0890: selectionLastNonWhiteAction, true),
0891: new ActionFactory.SelectIdentifierAction(),
0892: new ActionFactory.SelectNextParameterAction(),
0893: new ActionFactory.ScrollUpAction(),
0894: new ActionFactory.ScrollDownAction(),
0895: new ActionFactory.InsertDateTimeAction(),
0896: new ActionFactory.GenerateGutterPopupAction(),
0897: new ActionFactory.ToggleLineNumbersAction(),
0898: new ActionFactory.AnnotationsCyclingAction(),
0899: new ActionFactory.CollapseFold(),
0900: new ActionFactory.ExpandFold(),
0901: new ActionFactory.CollapseAllFolds(),
0902: new ActionFactory.ExpandAllFolds(),
0903: new ActionFactory.DumpViewHierarchyAction(),
0904: new ActionFactory.StartNewLine(),
0905: new ActionFactory.CutToLineBeginOrEndAction(false),
0906: new ActionFactory.CutToLineBeginOrEndAction(true),
0907:
0908: // Self test actions
0909: // new EditorDebug.SelfTestAction(),
0910: // new EditorDebug.DumpPlanesAction(),
0911: // new EditorDebug.DumpSyntaxMarksAction()
0912: };
0913: }
0914:
0915: protected Action[] getCustomActions() {
0916: Settings.KitAndValue kv[] = Settings.getValueHierarchy(this
0917: .getClass(), SettingsNames.CUSTOM_ACTION_LIST);
0918: if (kv.length == 0) {
0919: return null;
0920: }
0921: if (kv.length == 1) {
0922: List l = (List) kv[0].value;
0923: return (Action[]) l.toArray(new Action[l.size()]);
0924: }
0925: // more than one list of actions
0926: List l = new ArrayList();
0927: for (int i = kv.length - 1; i >= 0; i--) { // from BaseKit down
0928: l.addAll((List) kv[i].value);
0929: }
0930: return (Action[]) l.toArray(new Action[l.size()]);
0931: }
0932:
0933: /**
0934: * @deprecated Without any replacement.
0935: */
0936: protected Action[] getMacroActions() {
0937: return new Action[0];
0938: }
0939:
0940: /** Get actions associated with this kit. createActions() is called
0941: * to get basic list and then customActions are added.
0942: */
0943: public final Action[] getActions() {
0944: synchronized (Settings.class) { // possibly long running code follows
0945: Class this Class = this .getClass();
0946: Action[] actions = (Action[]) kitActions.get(this Class);
0947: if (actions == null) {
0948: // create map of actions
0949: Action[] createdActions = createActions();
0950: updateActionSettings(createdActions, null, this Class);
0951: Map actionMap = actionsToMap(createdActions);
0952: // add custom actions
0953: Action[] customActions = getCustomActions();
0954: if (customActions != null) {
0955: updateActionSettings(customActions, null, this Class);
0956: actionMap.putAll(actionsToMap(customActions));
0957: }
0958:
0959: // store for later use
0960: kitActionMaps.put(this Class, actionMap);
0961: // create action array and store for later use
0962: actions = mapToActions(actionMap);
0963: kitActions.put(this Class, actions);
0964:
0965: // At this moment the actions are constructed completely
0966: // The actions will be updated now if necessary
0967: updateActions();
0968: }
0969: return actions;
0970: }
0971: }
0972:
0973: Map getActionMap() {
0974: Map actionMap = (Map) kitActionMaps.get(this .getClass());
0975: if (actionMap == null) {
0976: getActions(); // init action map
0977: actionMap = (Map) kitActionMaps.get(this .getClass());
0978:
0979: // Fix of #27418
0980: if (actionMap == null) {
0981: actionMap = Collections.EMPTY_MAP;
0982: }
0983: }
0984: return actionMap;
0985: }
0986:
0987: /** Update the actions right after their creation was finished.
0988: * The <code>getActions()</code> and <code>getActionByName()</code>
0989: * can be used safely in this method.
0990: * The implementation must call <code>super.updateActions()</code> so that
0991: * the updating in parent is performed too.
0992: */
0993: protected void updateActions() {
0994: }
0995:
0996: /** Get action from its name. */
0997: public Action getActionByName(String name) {
0998: return (name != null) ? (Action) getActionMap().get(name)
0999: : null;
1000: }
1001:
1002: public List translateActionNameList(List actionNameList) {
1003: List ret = new ArrayList();
1004: if (actionNameList != null) {
1005: Iterator i = actionNameList.iterator();
1006: while (i.hasNext()) {
1007: Action a = getActionByName((String) i.next());
1008: if (a != null) {
1009: ret.add(a);
1010: }
1011: }
1012: }
1013: return ret;
1014: }
1015:
1016: /** Default typed action */
1017: public static class DefaultKeyTypedAction extends LocalBaseAction {
1018:
1019: static final long serialVersionUID = 3069164318144463899L;
1020:
1021: public DefaultKeyTypedAction() {
1022: super (defaultKeyTypedAction, MAGIC_POSITION_RESET
1023: | CLEAR_STATUS_TEXT);
1024: putValue(BaseAction.NO_KEYBINDING, Boolean.TRUE);
1025: }
1026:
1027: private static final boolean isMac = System
1028: .getProperty("mrj.version") != null; //NOI18N
1029:
1030: public void actionPerformed(ActionEvent evt,
1031: JTextComponent target) {
1032: if ((target != null) && (evt != null)) {
1033:
1034: // Check whether the modifiers are OK
1035: int mod = evt.getModifiers();
1036: boolean ctrl = ((mod & ActionEvent.CTRL_MASK) != 0);
1037: // On the mac, norwegian and french keyboards use Alt to do bracket characters.
1038: // This replicates Apple's modification DefaultEditorKit.DefaultKeyTypedAction
1039: boolean alt = isMac ? ((mod & ActionEvent.META_MASK) != 0)
1040: : ((mod & ActionEvent.ALT_MASK) != 0);
1041:
1042: if (alt || ctrl) {
1043: return;
1044: }
1045:
1046: // Check whether the target is enabled and editable
1047: if (!target.isEditable() || !target.isEnabled()) {
1048: target.getToolkit().beep();
1049: return;
1050: }
1051:
1052: Caret caret = target.getCaret();
1053: BaseDocument doc = (BaseDocument) target.getDocument();
1054: EditorUI editorUI = Utilities.getEditorUI(target);
1055: // determine if typed char is valid
1056: String cmd = evt.getActionCommand();
1057: if ((cmd != null) && (cmd.length() == 1)) {
1058: // Utilities.clearStatusText(target);
1059:
1060: try {
1061: NavigationHistory.getEdits().markWaypoint(
1062: target, caret.getDot(), false, true);
1063: } catch (BadLocationException e) {
1064: LOG
1065: .log(
1066: Level.WARNING,
1067: "Can't add position to the history of edits.",
1068: e); //NOI18N
1069: }
1070:
1071: doc.atomicLock();
1072: DocumentUtilities.setTypingModification(doc, true);
1073: try {
1074: char ch = cmd.charAt(0);
1075: if ((ch >= 0x20) && (ch != 0x7F)) { // valid character
1076: editorUI.getWordMatch().clear(); // reset word matching
1077: Boolean overwriteMode = (Boolean) editorUI
1078: .getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1079: try {
1080: boolean doInsert = true; // editorUI.getAbbrev().checkAndExpand(ch, evt);
1081: if (doInsert) {
1082: if (Utilities
1083: .isSelectionShowing(caret)) { // valid selection
1084: boolean ovr = (overwriteMode != null && overwriteMode
1085: .booleanValue());
1086: try {
1087: doc
1088: .putProperty(
1089: DOC_REPLACE_SELECTION_PROPERTY,
1090: true);
1091: replaceSelection(target,
1092: caret.getDot(),
1093: caret, cmd, ovr);
1094: } finally {
1095: doc
1096: .putProperty(
1097: DOC_REPLACE_SELECTION_PROPERTY,
1098: null);
1099: }
1100: } else { // no selection
1101: int dotPos = caret.getDot();
1102: if (overwriteMode != null
1103: && overwriteMode
1104: .booleanValue()
1105: && dotPos < doc
1106: .getLength()
1107: && doc.getChars(dotPos,
1108: 1)[0] != '\n') { // overwrite current char
1109: doc.atomicLock();
1110: try {
1111: insertString(doc,
1112: dotPos, caret,
1113: cmd, true);
1114: } finally {
1115: doc.atomicUnlock();
1116: }
1117: } else { // insert mode
1118: doc.atomicLock();
1119: try {
1120: insertString(doc,
1121: dotPos, caret,
1122: cmd, false);
1123: } finally {
1124: doc.atomicUnlock();
1125: }
1126: }
1127: }
1128: }
1129: } catch (BadLocationException e) {
1130: target.getToolkit().beep();
1131: }
1132: }
1133:
1134: checkIndent(target, cmd);
1135: } finally {
1136: DocumentUtilities.setTypingModification(doc,
1137: false);
1138: doc.atomicUnlock();
1139: }
1140: }
1141:
1142: }
1143: }
1144:
1145: /**
1146: * Hook to insert the given string at the given position into
1147: * the given document in insert-mode, no selection, writeable
1148: * document. Designed to be overridden by subclasses that want
1149: * to intercept inserted characters.
1150: */
1151: protected void insertString(BaseDocument doc, int dotPos,
1152: Caret caret, String str, boolean overwrite)
1153: throws BadLocationException {
1154: if (overwrite)
1155: doc.remove(dotPos, 1);
1156: doc.insertString(dotPos, str, null);
1157: }
1158:
1159: /**
1160: * Hook to insert the given string at the given position into
1161: * the given document in insert-mode with selection visible
1162: * Designed to be overridden by subclasses that want
1163: * to intercept inserted characters.
1164: */
1165: protected void replaceSelection(JTextComponent target,
1166: int dotPos, Caret caret, String str, boolean overwrite)
1167: throws BadLocationException {
1168: target.replaceSelection(str);
1169: }
1170:
1171: /** Check whether there was any important character typed
1172: * so that the line should be possibly reformatted.
1173: */
1174: protected void checkIndent(JTextComponent target,
1175: String typedText) {
1176: }
1177:
1178: }
1179:
1180: public static class InsertBreakAction extends LocalBaseAction {
1181:
1182: static final long serialVersionUID = 7966576342334158659L;
1183:
1184: public InsertBreakAction() {
1185: super (insertBreakAction, MAGIC_POSITION_RESET
1186: | ABBREV_RESET | WORD_MATCH_RESET);
1187: }
1188:
1189: public void actionPerformed(ActionEvent evt,
1190: JTextComponent target) {
1191: if (target != null) {
1192: if (!target.isEditable() || !target.isEnabled()) {
1193: target.getToolkit().beep();
1194: return;
1195: }
1196:
1197: BaseDocument doc = (BaseDocument) target.getDocument();
1198: Formatter formatter = doc.getFormatter();
1199: formatter.indentLock();
1200: try {
1201: doc.atomicLock();
1202: DocumentUtilities.setTypingModification(doc, true);
1203: try {
1204: target.replaceSelection("");
1205: Caret caret = target.getCaret();
1206: Object cookie = beforeBreak(target, doc, caret);
1207:
1208: int dotPos = caret.getDot();
1209: int newDotPos = formatter.indentNewLine(doc,
1210: dotPos);
1211: caret.setDot(newDotPos);
1212:
1213: afterBreak(target, doc, caret, cookie);
1214: } finally {
1215: DocumentUtilities.setTypingModification(doc,
1216: false);
1217: doc.atomicUnlock();
1218: }
1219: } finally {
1220: formatter.indentUnlock();
1221: }
1222: }
1223: }
1224:
1225: /**
1226: * Hook called before any changes to the document. The value
1227: * returned is passed intact to the other hook.
1228: */
1229: protected Object beforeBreak(JTextComponent target,
1230: BaseDocument doc, Caret caret) {
1231: return null;
1232: }
1233:
1234: /**
1235: * Hook called after the enter was inserted and cursor
1236: * repositioned. *data* is the object returned previously by
1237: * *beforeBreak* hook. By default null.
1238: */
1239: protected void afterBreak(JTextComponent target,
1240: BaseDocument doc, Caret caret, Object data) {
1241: }
1242: }
1243:
1244: public static class SplitLineAction extends LocalBaseAction {
1245:
1246: static final long serialVersionUID = 7966576342334158659L;
1247:
1248: public SplitLineAction() {
1249: super (splitLineAction, MAGIC_POSITION_RESET | ABBREV_RESET
1250: | WORD_MATCH_RESET);
1251: }
1252:
1253: public void actionPerformed(ActionEvent evt,
1254: JTextComponent target) {
1255: if (target != null) {
1256: if (!target.isEditable() || !target.isEnabled()) {
1257: target.getToolkit().beep();
1258: return;
1259: }
1260:
1261: BaseDocument doc = (BaseDocument) target.getDocument();
1262: Caret caret = target.getCaret();
1263:
1264: Formatter formatter = doc.getFormatter();
1265: formatter.indentLock();
1266: doc.atomicLock();
1267: DocumentUtilities.setTypingModification(doc, true);
1268: try {
1269: target.replaceSelection("");
1270: final int dotPos = caret.getDot(); // dot stays where it was
1271: formatter.indentNewLine(doc, dotPos); // newline
1272: caret.setDot(dotPos);
1273: } finally {
1274: DocumentUtilities.setTypingModification(doc, false);
1275: doc.atomicUnlock();
1276: formatter.indentUnlock();
1277: }
1278: }
1279: }
1280:
1281: }
1282:
1283: public static class InsertTabAction extends LocalBaseAction {
1284:
1285: static final long serialVersionUID = -3379768531715989243L;
1286:
1287: public InsertTabAction() {
1288: super (insertTabAction, MAGIC_POSITION_RESET | ABBREV_RESET
1289: | WORD_MATCH_RESET);
1290: }
1291:
1292: public void actionPerformed(ActionEvent evt,
1293: JTextComponent target) {
1294: if (target != null) {
1295: if (!target.isEditable() || !target.isEnabled()) {
1296: target.getToolkit().beep();
1297: return;
1298: }
1299:
1300: Caret caret = target.getCaret();
1301: BaseDocument doc = (BaseDocument) target.getDocument();
1302: doc.atomicLock();
1303: DocumentUtilities.setTypingModification(doc, true);
1304: try {
1305: if (Utilities.isSelectionShowing(caret)) { // block selected
1306: try {
1307: doc.getFormatter().changeBlockIndent(doc,
1308: target.getSelectionStart(),
1309: target.getSelectionEnd(), +1);
1310: } catch (GuardedException e) {
1311: target.getToolkit().beep();
1312: } catch (BadLocationException e) {
1313: e.printStackTrace();
1314: }
1315: } else { // no selected text
1316: int dotPos = caret.getDot();
1317: int caretCol;
1318: // find caret column
1319: try {
1320: caretCol = doc.getVisColFromPos(dotPos);
1321: } catch (BadLocationException e) {
1322: LOG.log(Level.WARNING, null, e);
1323: caretCol = 0;
1324: }
1325:
1326: try {
1327: // find indent of the first previous non-white row
1328: int upperCol = Utilities.getRowIndent(doc,
1329: dotPos, false);
1330: if (upperCol == -1) { // no prev line with indent
1331: upperCol = 0;
1332: }
1333: // is there any char on this line before cursor?
1334: int indent = Utilities.getRowIndent(doc,
1335: dotPos);
1336: // test whether we should indent
1337: if (indent == -1) {
1338: if (upperCol > caretCol) { // upper indent is greater
1339: indent = upperCol;
1340: } else { // simulate insert tab by changing indent
1341: indent = Utilities
1342: .getNextTabColumn(doc,
1343: dotPos);
1344: }
1345:
1346: // Fix of #32240 - #1 of 2
1347: int rowStart = Utilities.getRowStart(
1348: doc, dotPos);
1349:
1350: doc.getFormatter().changeRowIndent(doc,
1351: dotPos, indent);
1352:
1353: // Fix of #32240 - #2 of 2
1354: int newDotPos = doc
1355: .getOffsetFromVisCol(indent,
1356: rowStart);
1357: if (newDotPos >= 0) {
1358: caret.setDot(newDotPos);
1359: }
1360:
1361: } else { // already chars on the line
1362: doc.getFormatter().insertTabString(doc,
1363: dotPos);
1364:
1365: }
1366: } catch (BadLocationException e) {
1367: // use the same pos
1368: }
1369: }
1370: } finally {
1371: DocumentUtilities.setTypingModification(doc, false);
1372: doc.atomicUnlock();
1373: }
1374: }
1375:
1376: }
1377:
1378: }
1379:
1380: /** Compound action that encapsulates several actions */
1381: public static class CompoundAction extends LocalBaseAction {
1382:
1383: Action[] actions;
1384:
1385: static final long serialVersionUID = 1649688300969753758L;
1386:
1387: public CompoundAction(String nm, Action actions[]) {
1388: this (nm, 0, actions);
1389: }
1390:
1391: public CompoundAction(String nm, int resetMask,
1392: Action actions[]) {
1393: super (nm, resetMask);
1394: this .actions = actions;
1395: }
1396:
1397: public void actionPerformed(ActionEvent evt,
1398: JTextComponent target) {
1399: if (target != null) {
1400: for (int i = 0; i < actions.length; i++) {
1401: Action a = actions[i];
1402: if (a instanceof BaseAction) {
1403: ((BaseAction) a).actionPerformed(evt, target);
1404: } else {
1405: a.actionPerformed(evt);
1406: }
1407: }
1408: }
1409: }
1410: }
1411:
1412: /** Compound action that gets and executes its actions
1413: * depending on the kit of the component.
1414: * The other advantage is that it doesn't create additional
1415: * instances of compound actions.
1416: */
1417: public static class KitCompoundAction extends LocalBaseAction {
1418:
1419: private String[] actionNames;
1420:
1421: static final long serialVersionUID = 8415246475764264835L;
1422:
1423: public KitCompoundAction(String nm, String actionNames[]) {
1424: this (nm, 0, actionNames);
1425: }
1426:
1427: public KitCompoundAction(String nm, int resetMask,
1428: String actionNames[]) {
1429: super (nm, resetMask);
1430: this .actionNames = actionNames;
1431: }
1432:
1433: public void actionPerformed(ActionEvent evt,
1434: JTextComponent target) {
1435: if (target != null) {
1436: BaseKit kit = Utilities.getKit(target);
1437: if (kit != null) {
1438: for (int i = 0; i < actionNames.length; i++) {
1439: Action a = kit.getActionByName(actionNames[i]);
1440: if (a != null) {
1441: if (a instanceof BaseAction) {
1442: ((BaseAction) a).actionPerformed(evt,
1443: target);
1444: } else {
1445: a.actionPerformed(evt);
1446: }
1447: }
1448: }
1449: }
1450: }
1451: }
1452: }
1453:
1454: public static class InsertContentAction extends LocalBaseAction {
1455:
1456: static final long serialVersionUID = 5647751370952797218L;
1457:
1458: public InsertContentAction() {
1459: super (insertContentAction, MAGIC_POSITION_RESET
1460: | ABBREV_RESET | WORD_MATCH_RESET);
1461: putValue(BaseAction.NO_KEYBINDING, Boolean.TRUE);
1462: }
1463:
1464: public void actionPerformed(ActionEvent evt,
1465: JTextComponent target) {
1466: if ((target != null) && (evt != null)) {
1467: if (!target.isEditable() || !target.isEnabled()) {
1468: target.getToolkit().beep();
1469: return;
1470: }
1471:
1472: String content = evt.getActionCommand();
1473: if (content != null) {
1474: target.replaceSelection(content);
1475: } else {
1476: target.getToolkit().beep();
1477: }
1478: }
1479: }
1480: }
1481:
1482: /** Insert text specified in constructor */
1483: public static class InsertStringAction extends LocalBaseAction {
1484:
1485: String text;
1486:
1487: static final long serialVersionUID = -2755852016584693328L;
1488:
1489: public InsertStringAction(String nm, String text) {
1490: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
1491: | WORD_MATCH_RESET);
1492: this .text = text;
1493: }
1494:
1495: public void actionPerformed(ActionEvent evt,
1496: JTextComponent target) {
1497: if (target != null) {
1498: if (!target.isEditable() || !target.isEnabled()) {
1499: target.getToolkit().beep();
1500: return;
1501: }
1502:
1503: target.replaceSelection(text);
1504: }
1505: }
1506: }
1507:
1508: /** Remove previous or next character */
1509: public static class DeleteCharAction extends LocalBaseAction {
1510:
1511: protected boolean nextChar;
1512:
1513: static final long serialVersionUID = -4321971925753148556L;
1514:
1515: public DeleteCharAction(String nm, boolean nextChar) {
1516: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
1517: | WORD_MATCH_RESET);
1518: this .nextChar = nextChar;
1519: }
1520:
1521: public void actionPerformed(ActionEvent evt,
1522: JTextComponent target) {
1523: if (target != null) {
1524: if (!target.isEditable() || !target.isEnabled()) {
1525: target.getToolkit().beep();
1526: return;
1527: }
1528:
1529: BaseDocument doc = (BaseDocument) target.getDocument();
1530: Caret caret = target.getCaret();
1531: int dot = caret.getDot();
1532: int mark = caret.getMark();
1533:
1534: doc.atomicLock();
1535: DocumentUtilities.setTypingModification(doc, true);
1536:
1537: try {
1538: if (dot != mark) { // remove selection
1539: doc.remove(Math.min(dot, mark), Math.abs(dot
1540: - mark));
1541: } else {
1542: if (nextChar) { // remove next char
1543: char ch = doc.getChars(dot, 1)[0];
1544: doc.remove(dot, 1);
1545: charDeleted(doc, dot, caret, ch);
1546: } else { // remove previous char
1547: char ch = doc.getChars(dot - 1, 1)[0];
1548: doc.remove(dot - 1, 1);
1549: charBackspaced(doc, dot - 1, caret, ch);
1550: }
1551: }
1552: } catch (BadLocationException e) {
1553: target.getToolkit().beep();
1554: } finally {
1555: DocumentUtilities.setTypingModification(doc, false);
1556: doc.atomicUnlock();
1557: }
1558:
1559: }
1560: }
1561:
1562: protected void charBackspaced(BaseDocument doc, int dotPos,
1563: Caret caret, char ch) throws BadLocationException {
1564: }
1565:
1566: protected void charDeleted(BaseDocument doc, int dotPos,
1567: Caret caret, char ch) throws BadLocationException {
1568: }
1569: }
1570:
1571: public static class ReadOnlyAction extends LocalBaseAction {
1572:
1573: static final long serialVersionUID = 9204335480208463193L;
1574:
1575: public ReadOnlyAction() {
1576: super (readOnlyAction);
1577: putValue(BaseAction.NO_KEYBINDING, Boolean.TRUE);
1578: }
1579:
1580: public void actionPerformed(ActionEvent evt,
1581: JTextComponent target) {
1582: if (target != null) {
1583: target.setEditable(false);
1584: }
1585: }
1586: }
1587:
1588: public static class WritableAction extends LocalBaseAction {
1589:
1590: static final long serialVersionUID = -5982547952800937954L;
1591:
1592: public WritableAction() {
1593: super (writableAction);
1594: putValue(BaseAction.NO_KEYBINDING, Boolean.TRUE);
1595: }
1596:
1597: public void actionPerformed(ActionEvent evt,
1598: JTextComponent target) {
1599: if (target != null) {
1600: target.setEditable(true);
1601: }
1602: }
1603: }
1604:
1605: public static class CutAction extends LocalBaseAction {
1606:
1607: static final long serialVersionUID = 6377157040901778853L;
1608:
1609: public CutAction() {
1610: super (cutAction, ABBREV_RESET | UNDO_MERGE_RESET
1611: | WORD_MATCH_RESET);
1612: setEnabled(false);
1613: //#54893 putValue ("helpID", CutAction.class.getName ()); // NOI18N
1614: }
1615:
1616: public void actionPerformed(ActionEvent evt,
1617: JTextComponent target) {
1618: if (target != null) {
1619: if (!target.isEditable() || !target.isEnabled()) {
1620: target.getToolkit().beep();
1621: return;
1622: }
1623:
1624: try {
1625: NavigationHistory.getEdits().markWaypoint(target,
1626: target.getCaret().getDot(), false, true);
1627: } catch (BadLocationException e) {
1628: LOG
1629: .log(
1630: Level.WARNING,
1631: "Can't add position to the history of edits.",
1632: e); //NOI18N
1633: }
1634:
1635: BaseDocument doc = (BaseDocument) target.getDocument();
1636: doc.atomicLock();
1637: DocumentUtilities.setTypingModification(doc, true);
1638: try {
1639: target.cut();
1640: } finally {
1641: DocumentUtilities.setTypingModification(doc, false);
1642: doc.atomicUnlock();
1643: }
1644: }
1645: }
1646: }
1647:
1648: public static class CopyAction extends LocalBaseAction {
1649:
1650: static final long serialVersionUID = -5119779005431986964L;
1651:
1652: public CopyAction() {
1653: super (copyAction, ABBREV_RESET | UNDO_MERGE_RESET
1654: | WORD_MATCH_RESET);
1655: setEnabled(false);
1656: //#54893 putValue ("helpID", CopyAction.class.getName ()); // NOI18N
1657: }
1658:
1659: public void actionPerformed(ActionEvent evt,
1660: JTextComponent target) {
1661: if (target != null) {
1662: target.copy();
1663: }
1664: }
1665: }
1666:
1667: public static class PasteAction extends LocalBaseAction {
1668:
1669: static final long serialVersionUID = 5839791453996432149L;
1670: private boolean formatted;
1671:
1672: public PasteAction(boolean formated) {
1673: super (formated ? pasteFormatedAction : pasteAction,
1674: ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET);
1675: //#54893 putValue ("helpID", PasteAction.class.getName ()); // NOI18N
1676: this .formatted = formated;
1677: }
1678:
1679: public void actionPerformed(ActionEvent evt,
1680: JTextComponent target) {
1681: if (target != null) {
1682: if (!target.isEditable() || !target.isEnabled()) {
1683: target.getToolkit().beep();
1684: return;
1685: }
1686:
1687: BaseDocument doc = Utilities.getDocument(target);
1688: if (doc == null)
1689: return;
1690:
1691: try {
1692: NavigationHistory.getEdits().markWaypoint(target,
1693: target.getCaret().getDot(), false, true);
1694: } catch (BadLocationException e) {
1695: LOG
1696: .log(
1697: Level.WARNING,
1698: "Can't add position to the history of edits.",
1699: e); //NOI18N
1700: }
1701:
1702: Formatter formatter = doc.getFormatter();
1703: if (formatted) {
1704: formatter.reformatLock();
1705: }
1706: try {
1707: doc.atomicLock();
1708: DocumentUtilities.setTypingModification(doc, true);
1709: try {
1710: Caret caret = target.getCaret();
1711: int startOffset = target.getSelectionStart();
1712: target.paste();
1713: int endOffset = caret.getDot();
1714: if (formatted) {
1715: formatter.reformat(doc, startOffset,
1716: endOffset);
1717: }
1718: } catch (Exception e) {
1719: target.getToolkit().beep();
1720: } finally {
1721: DocumentUtilities.setTypingModification(doc,
1722: false);
1723: doc.atomicUnlock();
1724: }
1725: } finally {
1726: if (formatted) {
1727: formatter.reformatUnlock();
1728: }
1729: }
1730: }
1731: }
1732:
1733: public static void indentBlock(BaseDocument doc,
1734: int startOffset, int endOffset)
1735: throws BadLocationException {
1736: char[] text = doc.getChars(startOffset, endOffset
1737: - startOffset);
1738: String[] lines = toLines(new String(text));
1739:
1740: doc.remove(startOffset, endOffset - startOffset);
1741: // System.out.println("Lines:\n"); // NOI18N
1742: // for (int j = 0 ; j < lines.length; j++) System.out.println(lines[j] + "<"); // NOI18N
1743:
1744: int offset = startOffset;
1745: // handle the full lines
1746: for (int i = 0; i < lines.length - 1; i++) {
1747: String indent = getIndentString(doc, offset, lines[i]);
1748: String fragment = indent + lines[i].trim() + '\n';
1749: // System.out.println(fragment + "|"); // NOI18N
1750: doc.insertString(offset, fragment, null);
1751: offset += fragment.length();
1752: }
1753:
1754: // the rest just paste without indenting
1755: doc.insertString(offset, lines[lines.length - 1], null);
1756:
1757: }
1758:
1759: /** Break string to lines */
1760: private static String[] toLines(String str) {
1761: Vector v = new Vector();
1762: int p = 0, p0 = 0;
1763: for (; p < str.length(); p++) {
1764: if (str.charAt(p) == '\n') {
1765: v.add(str.substring(p0, p + 1));
1766: p0 = p + 1;
1767: }
1768: }
1769: if (p0 < str.length())
1770: v.add(str.substring(p0, str.length()));
1771: else
1772: v.add("");
1773:
1774: return (String[]) v.toArray(new String[0]);
1775: }
1776:
1777: private static String getIndentString(BaseDocument doc,
1778: int startOffset, String str) {
1779: try {
1780: Formatter f = doc.getFormatter();
1781: CharArrayWriter cw = new CharArrayWriter();
1782: Writer w = f.createWriter(doc, startOffset, cw);
1783: w.write(str, 0, str.length());
1784: w.close();
1785: String out = new String(cw.toCharArray());
1786: int i = 0;
1787: for (; i < out.length(); i++) {
1788: if (out.charAt(i) != ' ' && out.charAt(i) != '\t')
1789: break;
1790: }
1791: // System.out.println(out+"|"); // NOI18N
1792: // System.out.println(out.substring(0,i)+"^"); // NOI18N
1793:
1794: return out.substring(0, i);
1795: } catch (java.io.IOException e) {
1796: return "";
1797: }
1798: }
1799: }
1800:
1801: public static class BeepAction extends LocalBaseAction {
1802:
1803: static final long serialVersionUID = -4474054576633223968L;
1804:
1805: public BeepAction() {
1806: super (beepAction);
1807: putValue(BaseAction.NO_KEYBINDING, Boolean.TRUE);
1808: }
1809:
1810: public void actionPerformed(ActionEvent evt,
1811: JTextComponent target) {
1812: if (target != null) {
1813: target.getToolkit().beep();
1814: }
1815: }
1816: }
1817:
1818: public static class UpAction extends LocalBaseAction {
1819:
1820: boolean select;
1821:
1822: static final long serialVersionUID = 4621760742646981563L;
1823:
1824: public UpAction(String nm, boolean select) {
1825: super (nm, ABBREV_RESET | UNDO_MERGE_RESET
1826: | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
1827: this .select = select;
1828: }
1829:
1830: public void actionPerformed(ActionEvent evt,
1831: JTextComponent target) {
1832: if (target != null) {
1833: try {
1834: Caret caret = target.getCaret();
1835: int dot = caret.getDot();
1836: Point p = caret.getMagicCaretPosition();
1837: if (p == null) {
1838: Rectangle r = target.modelToView(dot);
1839: if (r != null) {
1840: p = new Point(r.x, r.y);
1841: caret.setMagicCaretPosition(p);
1842: } else {
1843: return; // model to view failed
1844: }
1845: }
1846: try {
1847: dot = Utilities.getPositionAbove(target, dot,
1848: p.x);
1849: if (select) {
1850: caret.moveDot(dot);
1851: } else {
1852: caret.setDot(dot);
1853: }
1854: } catch (BadLocationException e) {
1855: // the position stays the same
1856: }
1857: } catch (BadLocationException ex) {
1858: target.getToolkit().beep();
1859: }
1860: }
1861: }
1862: }
1863:
1864: public static class DownAction extends LocalBaseAction {
1865:
1866: boolean select;
1867:
1868: static final long serialVersionUID = -5635702355125266822L;
1869:
1870: public DownAction(String nm, boolean select) {
1871: super (nm, ABBREV_RESET | UNDO_MERGE_RESET
1872: | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
1873: this .select = select;
1874: }
1875:
1876: public void actionPerformed(ActionEvent evt,
1877: JTextComponent target) {
1878: if (target != null) {
1879: try {
1880: Caret caret = target.getCaret();
1881: int dot = caret.getDot();
1882: Point p = caret.getMagicCaretPosition();
1883: if (p == null) {
1884: Rectangle r = target.modelToView(dot);
1885: if (r != null) {
1886: p = new Point(r.x, r.y);
1887: caret.setMagicCaretPosition(p);
1888: } else {
1889: return; // model to view failed
1890: }
1891: }
1892: try {
1893: dot = Utilities.getPositionBelow(target, dot,
1894: p.x);
1895: if (select) {
1896: caret.moveDot(dot);
1897: } else {
1898: caret.setDot(dot);
1899: }
1900: } catch (BadLocationException e) {
1901: // position stays the same
1902: }
1903: } catch (BadLocationException ex) {
1904: target.getToolkit().beep();
1905: }
1906: }
1907: }
1908: }
1909:
1910: /** Go one page up */
1911: public static class PageUpAction extends LocalBaseAction {
1912:
1913: boolean select;
1914:
1915: static final long serialVersionUID = -3107382148581661079L;
1916:
1917: public PageUpAction(String nm, boolean select) {
1918: super (nm, ABBREV_RESET | UNDO_MERGE_RESET
1919: | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
1920: this .select = select;
1921: }
1922:
1923: public void actionPerformed(ActionEvent evt,
1924: JTextComponent target) {
1925: if (target != null) {
1926: try {
1927: Caret caret = target.getCaret();
1928: BaseDocument doc = (BaseDocument) target
1929: .getDocument();
1930: int caretOffset = caret.getDot();
1931: Rectangle caretBounds = ((BaseTextUI) target
1932: .getUI()).modelToView(target, caretOffset);
1933: if (caretBounds == null) {
1934: return; // Cannot continue reasonably
1935: }
1936:
1937: // Retrieve caret magic position and attempt to retain
1938: // the x-coordinate information and use it
1939: // for setting of the new caret position
1940: Point magicCaretPosition = caret
1941: .getMagicCaretPosition();
1942: if (magicCaretPosition == null) {
1943: magicCaretPosition = new Point(caretBounds.x,
1944: caretBounds.y);
1945: }
1946:
1947: Rectangle visibleBounds = target.getVisibleRect();
1948: int newCaretOffset;
1949: Rectangle newCaretBounds;
1950:
1951: // Check whether caret was contained in the original visible window
1952: if (visibleBounds.contains(caretBounds)) {
1953: // Clone present view bounds
1954: Rectangle newVisibleBounds = new Rectangle(
1955: visibleBounds);
1956: // Do viewToModel() and modelToView() with the left top corner
1957: // of the currently visible view. If that line is not fully visible
1958: // then it should be the bottom line of the previous page
1959: // (if it's fully visible then the line above it).
1960: int topLeftOffset = target
1961: .viewToModel(new Point(visibleBounds.x,
1962: visibleBounds.y));
1963: Rectangle topLeftLineBounds = target
1964: .modelToView(topLeftOffset);
1965:
1966: // newVisibleBounds.y will hold bottom of new view
1967: if (topLeftLineBounds.y != visibleBounds.y) {
1968: newVisibleBounds.y = topLeftLineBounds.y
1969: + topLeftLineBounds.height;
1970: } // Component view starts right at the line boundary
1971: // Go back by the view height
1972: newVisibleBounds.y -= visibleBounds.height;
1973:
1974: // Find the new caret bounds by using relative y position
1975: // on the original caret bounds. If the caret's new relative bounds
1976: // would be visually above the old bounds
1977: // the view should be shifted so that the relative bounds
1978: // are the same (user's eyes do not need to move).
1979: int caretRelY = caretBounds.y - visibleBounds.y;
1980: int caretNewY = newVisibleBounds.y + caretRelY;
1981: newCaretOffset = target.viewToModel(new Point(
1982: magicCaretPosition.x, caretNewY));
1983: newCaretBounds = target
1984: .modelToView(newCaretOffset);
1985: if (newCaretBounds.y < caretNewY) {
1986: // Need to go one line down to retain the top line
1987: // of the present newVisibleBounds to be fully visible.
1988: // Attempt to go forward by height of caret
1989: newCaretOffset = target
1990: .viewToModel(new Point(
1991: magicCaretPosition.x,
1992: newCaretBounds.y
1993: + newCaretBounds.height));
1994: newCaretBounds = target
1995: .modelToView(newCaretOffset);
1996: }
1997:
1998: // Shift the new visible bounds so that the caret
1999: // does not visually move
2000: newVisibleBounds.y = newCaretBounds.y
2001: - caretRelY;
2002:
2003: // Scroll the window to the requested rectangle
2004: target.scrollRectToVisible(newVisibleBounds);
2005:
2006: } else { // Caret outside of originally visible window
2007: // Shift the dot by the visible bounds height
2008: Point newCaretPoint = new Point(
2009: magicCaretPosition.x, caretBounds.y
2010: - visibleBounds.height);
2011: newCaretOffset = target
2012: .viewToModel(newCaretPoint);
2013: newCaretBounds = target
2014: .modelToView(newCaretOffset);
2015: }
2016:
2017: if (select) {
2018: caret.moveDot(newCaretOffset);
2019: } else {
2020: caret.setDot(newCaretOffset);
2021: }
2022:
2023: // Update magic caret position
2024: magicCaretPosition.y = newCaretBounds.y;
2025: caret.setMagicCaretPosition(magicCaretPosition);
2026:
2027: } catch (BadLocationException ex) {
2028: target.getToolkit().beep();
2029: }
2030: }
2031: }
2032: }
2033:
2034: public static class ForwardAction extends LocalBaseAction {
2035:
2036: boolean select;
2037:
2038: static final long serialVersionUID = 8007293230193334414L;
2039:
2040: public ForwardAction(String nm, boolean select) {
2041: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2042: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2043: | CLEAR_STATUS_TEXT);
2044: this .select = select;
2045: }
2046:
2047: public void actionPerformed(ActionEvent evt,
2048: JTextComponent target) {
2049: if (target != null) {
2050: Caret caret = target.getCaret();
2051: try {
2052: int pos;
2053: if (!select && Utilities.isSelectionShowing(caret)) {
2054: pos = target.getSelectionEnd();
2055: if (pos != caret.getDot()) {
2056: pos--;
2057: } else {
2058: // clear the selection, but do not move the cursor
2059: caret.setDot(pos);
2060: return;
2061: }
2062: } else
2063: pos = caret.getDot();
2064: int dot = target.getUI().getNextVisualPositionFrom(
2065: target, pos, Position.Bias.Forward,
2066: SwingConstants.EAST, null);
2067: if (select) {
2068: caret.moveDot(dot);
2069: } else {
2070: caret.setDot(dot);
2071: }
2072: } catch (BadLocationException ex) {
2073: target.getToolkit().beep();
2074: }
2075: }
2076: }
2077: }
2078:
2079: /** Go one page down */
2080: public static class PageDownAction extends LocalBaseAction {
2081:
2082: boolean select;
2083:
2084: static final long serialVersionUID = 8942534850985048862L;
2085:
2086: public PageDownAction(String nm, boolean select) {
2087: super (nm, ABBREV_RESET | UNDO_MERGE_RESET
2088: | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2089: this .select = select;
2090: }
2091:
2092: public void actionPerformed(ActionEvent evt,
2093: JTextComponent target) {
2094: if (target != null) {
2095: try {
2096: Caret caret = target.getCaret();
2097: BaseDocument doc = (BaseDocument) target
2098: .getDocument();
2099: int caretOffset = caret.getDot();
2100: Rectangle caretBounds = ((BaseTextUI) target
2101: .getUI()).modelToView(target, caretOffset);
2102: if (caretBounds == null) {
2103: return; // Cannot continue reasonably
2104: }
2105:
2106: // Retrieve caret magic position and attempt to retain
2107: // the x-coordinate information and use it
2108: // for setting of the new caret position
2109: Point magicCaretPosition = caret
2110: .getMagicCaretPosition();
2111: if (magicCaretPosition == null) {
2112: magicCaretPosition = new Point(caretBounds.x,
2113: caretBounds.y);
2114: }
2115:
2116: Rectangle visibleBounds = target.getVisibleRect();
2117: int newCaretOffset;
2118: Rectangle newCaretBounds;
2119:
2120: // Check whether caret was contained in the original visible window
2121: if (visibleBounds.contains(caretBounds)) {
2122: // Clone present view bounds
2123: Rectangle newVisibleBounds = new Rectangle(
2124: visibleBounds);
2125: // Do viewToModel() and modelToView() with the left bottom corner
2126: // of the currently visible view.
2127: // That line should be the top line of the next page.
2128: int bottomLeftOffset = target
2129: .viewToModel(new Point(visibleBounds.x,
2130: visibleBounds.y
2131: + visibleBounds.height));
2132: Rectangle bottomLeftLineBounds = target
2133: .modelToView(bottomLeftOffset);
2134:
2135: // newVisibleBounds.y will hold bottom of new view
2136: newVisibleBounds.y = bottomLeftLineBounds.y;
2137:
2138: // Find the new caret bounds by using relative y position
2139: // on the original caret bounds. If the caret's new relative bounds
2140: // would be visually below the old bounds
2141: // the view should be shifted so that the relative bounds
2142: // are the same (user's eyes do not need to move).
2143: int caretRelY = caretBounds.y - visibleBounds.y;
2144: int caretNewY = newVisibleBounds.y + caretRelY;
2145: newCaretOffset = target.viewToModel(new Point(
2146: magicCaretPosition.x, caretNewY));
2147: newCaretBounds = target
2148: .modelToView(newCaretOffset);
2149: if (newCaretBounds.y > caretNewY) {
2150: // Need to go one line above to retain the top line
2151: // of the present newVisibleBounds to be fully visible.
2152: // Attempt to go up by height of caret.
2153: newCaretOffset = target
2154: .viewToModel(new Point(
2155: magicCaretPosition.x,
2156: newCaretBounds.y
2157: - newCaretBounds.height));
2158: newCaretBounds = target
2159: .modelToView(newCaretOffset);
2160: }
2161:
2162: // Shift the new visible bounds so that the caret
2163: // does not visually move
2164: newVisibleBounds.y = newCaretBounds.y
2165: - caretRelY;
2166:
2167: // Scroll the window to the requested rectangle
2168: target.scrollRectToVisible(newVisibleBounds);
2169:
2170: } else { // Caret outside of originally visible window
2171: // Shift the dot by the visible bounds height
2172: Point newCaretPoint = new Point(
2173: magicCaretPosition.x, caretBounds.y
2174: + visibleBounds.height);
2175: newCaretOffset = target
2176: .viewToModel(newCaretPoint);
2177: newCaretBounds = target
2178: .modelToView(newCaretOffset);
2179: }
2180:
2181: if (select) {
2182: caret.moveDot(newCaretOffset);
2183: } else {
2184: caret.setDot(newCaretOffset);
2185: }
2186:
2187: // Update magic caret position
2188: magicCaretPosition.y = newCaretBounds.y;
2189: caret.setMagicCaretPosition(magicCaretPosition);
2190:
2191: } catch (BadLocationException ex) {
2192: target.getToolkit().beep();
2193: }
2194: }
2195: }
2196: }
2197:
2198: public static class BackwardAction extends LocalBaseAction {
2199:
2200: boolean select;
2201:
2202: static final long serialVersionUID = -3048379822817847356L;
2203:
2204: public BackwardAction(String nm, boolean select) {
2205: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2206: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2207: | CLEAR_STATUS_TEXT);
2208: this .select = select;
2209: }
2210:
2211: public void actionPerformed(ActionEvent evt,
2212: JTextComponent target) {
2213: if (target != null) {
2214: Caret caret = target.getCaret();
2215: try {
2216: int pos;
2217: if (!select && Utilities.isSelectionShowing(caret)) {
2218: pos = target.getSelectionStart();
2219: if (pos != caret.getDot()) {
2220: pos++;
2221: } else {
2222: // clear the selection, but do not move the cursor
2223: caret.setDot(pos);
2224: return;
2225: }
2226: } else
2227: pos = caret.getDot();
2228: int dot = target.getUI().getNextVisualPositionFrom(
2229: target, pos, Position.Bias.Backward,
2230: SwingConstants.WEST, null);
2231: if (select) {
2232: caret.moveDot(dot);
2233: } else {
2234: caret.setDot(dot);
2235: }
2236: } catch (BadLocationException ex) {
2237: target.getToolkit().beep();
2238: }
2239: }
2240: }
2241: }
2242:
2243: public static class BeginLineAction extends LocalBaseAction {
2244:
2245: protected boolean select;
2246:
2247: /** Whether the action should go to the begining of
2248: * the text on the line or to the first column on the line*/
2249: boolean homeKeyColumnOne;
2250:
2251: static final long serialVersionUID = 3269462923524077779L;
2252:
2253: public BeginLineAction(String nm, boolean select) {
2254: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2255: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2256: | CLEAR_STATUS_TEXT);
2257: this .select = select;
2258: homeKeyColumnOne = false;
2259: }
2260:
2261: public BeginLineAction(String nm, boolean select,
2262: boolean columnOne) {
2263: this (nm, select);
2264: homeKeyColumnOne = columnOne;
2265: }
2266:
2267: public void actionPerformed(ActionEvent evt,
2268: JTextComponent target) {
2269: if (target != null) {
2270: Caret caret = target.getCaret();
2271: BaseDocument doc = (BaseDocument) target.getDocument();
2272: try {
2273: int dot = caret.getDot();
2274: int lineStartPos = Utilities.getRowStart(target,
2275: dot);
2276: if (homeKeyColumnOne) { // to first column
2277: dot = lineStartPos;
2278: } else { // either to line start or text start
2279: int textStartPos = Utilities
2280: .getRowFirstNonWhite(doc, lineStartPos);
2281: if (textStartPos < 0) { // no text on the line
2282: textStartPos = Utilities.getRowEnd(target,
2283: lineStartPos);
2284: }
2285: if (dot == lineStartPos) { // go to the text start pos
2286: dot = textStartPos;
2287: } else if (dot <= textStartPos) {
2288: dot = lineStartPos;
2289: } else {
2290: dot = textStartPos;
2291: }
2292: }
2293: if (select) {
2294: caret.moveDot(dot);
2295: } else {
2296: caret.setDot(dot);
2297: }
2298: } catch (BadLocationException e) {
2299: target.getToolkit().beep();
2300: }
2301: }
2302: }
2303: }
2304:
2305: public static class EndLineAction extends LocalBaseAction {
2306:
2307: protected boolean select;
2308:
2309: static final long serialVersionUID = 5216077634055190170L;
2310:
2311: public EndLineAction(String nm, boolean select) {
2312: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2313: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2314: | CLEAR_STATUS_TEXT);
2315: this .select = select;
2316: }
2317:
2318: public void actionPerformed(ActionEvent evt,
2319: JTextComponent target) {
2320: if (target != null) {
2321: Caret caret = target.getCaret();
2322: try {
2323: int dot = Utilities.getRowEnd(target, caret
2324: .getDot());
2325: if (select) {
2326: caret.moveDot(dot);
2327: } else {
2328: caret.setDot(dot);
2329: }
2330: // now move the magic caret position far to the right
2331: Rectangle r = target.modelToView(dot);
2332: if (r != null) {
2333: Point p = new Point(MAGIC_POSITION_MAX, r.y);
2334: caret.setMagicCaretPosition(p);
2335: }
2336: } catch (BadLocationException e) {
2337: e.printStackTrace();
2338: target.getToolkit().beep();
2339: }
2340: }
2341: }
2342: }
2343:
2344: public static class BeginAction extends LocalBaseAction {
2345:
2346: boolean select;
2347:
2348: static final long serialVersionUID = 3463563396210234361L;
2349:
2350: public BeginAction(String nm, boolean select) {
2351: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2352: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2353: | SAVE_POSITION | CLEAR_STATUS_TEXT);
2354: this .select = select;
2355: }
2356:
2357: public void actionPerformed(ActionEvent evt,
2358: JTextComponent target) {
2359: if (target != null) {
2360: Caret caret = target.getCaret();
2361: int dot = 0; // begin of document
2362: if (select) {
2363: caret.moveDot(dot);
2364: } else {
2365: caret.setDot(dot);
2366: }
2367: }
2368: }
2369: }
2370:
2371: public static class EndAction extends LocalBaseAction {
2372:
2373: boolean select;
2374:
2375: static final long serialVersionUID = 8547506353130203657L;
2376:
2377: public EndAction(String nm, boolean select) {
2378: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2379: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2380: | SAVE_POSITION | CLEAR_STATUS_TEXT);
2381: this .select = select;
2382: }
2383:
2384: public void actionPerformed(ActionEvent evt,
2385: JTextComponent target) {
2386: if (target != null) {
2387: Caret caret = target.getCaret();
2388: int dot = target.getDocument().getLength(); // end of document
2389: if (select) {
2390: caret.moveDot(dot);
2391: } else {
2392: caret.setDot(dot);
2393: }
2394: }
2395: }
2396: }
2397:
2398: public static class NextWordAction extends LocalBaseAction {
2399:
2400: boolean select;
2401:
2402: static final long serialVersionUID = -5909906947175434032L;
2403:
2404: public NextWordAction(String nm, boolean select) {
2405: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2406: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2407: | CLEAR_STATUS_TEXT);
2408: this .select = select;
2409: }
2410:
2411: public void actionPerformed(ActionEvent evt,
2412: JTextComponent target) {
2413: if (target != null) {
2414: Caret caret = target.getCaret();
2415: try {
2416: int dotPos = caret.getDot();
2417: dotPos = Utilities.getNextWord(target, dotPos);
2418: if (caret instanceof BaseCaret) {
2419: BaseCaret bCaret = (BaseCaret) caret;
2420: if (select) {
2421: bCaret.moveDot(dotPos);
2422: } else {
2423: bCaret.setDot(dotPos, false);
2424: }
2425: } else {
2426: if (select) {
2427: caret.moveDot(dotPos);
2428: } else {
2429: caret.setDot(dotPos);
2430: }
2431: }
2432: } catch (BadLocationException ex) {
2433: target.getToolkit().beep();
2434: }
2435: }
2436: }
2437: }
2438:
2439: public static class PreviousWordAction extends LocalBaseAction {
2440:
2441: boolean select;
2442:
2443: static final long serialVersionUID = -5465143382669785799L;
2444:
2445: public PreviousWordAction(String nm, boolean select) {
2446: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2447: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2448: | CLEAR_STATUS_TEXT);
2449: this .select = select;
2450: }
2451:
2452: public void actionPerformed(ActionEvent evt,
2453: JTextComponent target) {
2454: if (target != null) {
2455: Caret caret = target.getCaret();
2456: try {
2457: int dot = Utilities.getPreviousWord(target, caret
2458: .getDot());
2459: if (caret instanceof BaseCaret) {
2460: BaseCaret bCaret = (BaseCaret) caret;
2461: if (select) {
2462: bCaret.moveDot(dot);
2463: } else {
2464: bCaret.setDot(dot, false);
2465: }
2466: } else {
2467: if (select) {
2468: caret.moveDot(dot);
2469: } else {
2470: caret.setDot(dot);
2471: }
2472: }
2473: } catch (BadLocationException ex) {
2474: target.getToolkit().beep();
2475: }
2476: }
2477: }
2478: }
2479:
2480: public static class BeginWordAction extends LocalBaseAction {
2481:
2482: boolean select;
2483:
2484: static final long serialVersionUID = 3991338381212491110L;
2485:
2486: public BeginWordAction(String nm, boolean select) {
2487: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2488: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2489: | CLEAR_STATUS_TEXT);
2490: this .select = select;
2491: }
2492:
2493: public void actionPerformed(ActionEvent evt,
2494: JTextComponent target) {
2495: if (target != null) {
2496: Caret caret = target.getCaret();
2497: try {
2498: int dot = Utilities.getWordStart(target, caret
2499: .getDot());
2500: if (select) {
2501: caret.moveDot(dot);
2502: } else {
2503: caret.setDot(dot);
2504: }
2505: } catch (BadLocationException ex) {
2506: target.getToolkit().beep();
2507: }
2508: }
2509: }
2510: }
2511:
2512: public static class EndWordAction extends LocalBaseAction {
2513:
2514: boolean select;
2515:
2516: static final long serialVersionUID = 3812523676620144633L;
2517:
2518: public EndWordAction(String nm, boolean select) {
2519: super (nm, MAGIC_POSITION_RESET | ABBREV_RESET
2520: | UNDO_MERGE_RESET | WORD_MATCH_RESET
2521: | CLEAR_STATUS_TEXT);
2522: this .select = select;
2523: }
2524:
2525: public void actionPerformed(ActionEvent evt,
2526: JTextComponent target) {
2527: if (target != null) {
2528: Caret caret = target.getCaret();
2529: try {
2530: int dot = Utilities.getWordEnd(target, caret
2531: .getDot());
2532: if (select) {
2533: caret.moveDot(dot);
2534: } else {
2535: caret.setDot(dot);
2536: }
2537: } catch (BadLocationException ex) {
2538: target.getToolkit().beep();
2539: }
2540: }
2541: }
2542: }
2543:
2544: /** Select word around caret */
2545: public static class SelectWordAction extends KitCompoundAction {
2546:
2547: static final long serialVersionUID = 7678848538073016357L;
2548:
2549: public SelectWordAction() {
2550: super (selectWordAction, new String[] { beginWordAction,
2551: selectionEndWordAction });
2552: }
2553:
2554: }
2555:
2556: /** Select line around caret */
2557: public static class SelectLineAction extends LocalBaseAction {
2558:
2559: static final long serialVersionUID = -7407681863035740281L;
2560:
2561: public SelectLineAction() {
2562: super (selectLineAction);
2563: }
2564:
2565: public void actionPerformed(ActionEvent evt,
2566: JTextComponent target) {
2567: if (target != null) {
2568: Caret caret = target.getCaret();
2569: BaseDocument doc = (BaseDocument) target.getDocument();
2570: doc.atomicLock();
2571: DocumentUtilities.setTypingModification(doc, true);
2572: try {
2573: int dotPos = caret.getDot();
2574: int bolPos = Utilities.getRowStart(target, dotPos);
2575: int eolPos = Utilities.getRowEnd(target, dotPos);
2576: eolPos = Math.min(eolPos + 1, doc.getLength()); // include '\n'
2577: caret.setDot(bolPos);
2578: caret.moveDot(eolPos);
2579: } catch (BadLocationException e) {
2580: target.getToolkit().beep();
2581: } finally {
2582: DocumentUtilities.setTypingModification(doc, false);
2583: doc.atomicUnlock();
2584: }
2585: }
2586: }
2587: }
2588:
2589: /** Select text of whole document */
2590: public static class SelectAllAction extends KitCompoundAction {
2591:
2592: static final long serialVersionUID = -3502499718130556524L;
2593:
2594: public SelectAllAction() {
2595: super (selectAllAction, new String[] { beginAction,
2596: selectionEndAction });
2597: }
2598:
2599: }
2600:
2601: public static class RemoveTrailingSpacesAction extends
2602: LocalBaseAction {
2603:
2604: public RemoveTrailingSpacesAction() {
2605: super (removeTrailingSpacesAction);
2606: }
2607:
2608: protected boolean asynchonous() {
2609: return true;
2610: }
2611:
2612: public void actionPerformed(ActionEvent evt,
2613: final JTextComponent target) {
2614: if (target != null) {
2615: final BaseDocument doc = (BaseDocument) target
2616: .getDocument();
2617: doc.runAtomic(new Runnable() {
2618: public void run() {
2619: try {
2620: Element lineRootElem = doc
2621: .getDefaultRootElement();
2622: int count = lineRootElem.getElementCount();
2623: for (int x = 0; x < count; x++) {
2624: Element elem = lineRootElem
2625: .getElement(x);
2626: int start = elem.getStartOffset();
2627: int end = elem.getEndOffset();
2628: CharSequence line = DocumentUtilities
2629: .getText(doc, start, end
2630: - start);
2631: int endIndex = line.length() - 1;
2632: if (endIndex >= 0
2633: && line.charAt(endIndex) == '\n') {
2634: endIndex--;
2635: if (endIndex >= 0
2636: && line.charAt(endIndex) == '\r') {
2637: endIndex--;
2638: }
2639: }
2640: int index = endIndex;
2641: while (index >= 0
2642: && Character.isWhitespace(line
2643: .charAt(index))
2644: && line.charAt(index) != '\n') {
2645: index--;
2646: }
2647: if (index < endIndex) {
2648: doc.remove(start + index + 1,
2649: endIndex - index);
2650: }
2651: } // for
2652: StatusDisplayer
2653: .getDefault()
2654: .setStatusText(
2655: NbBundle
2656: .getMessage(
2657: BaseKit.class,
2658: "TrailingSpacesWereRemoved_Lbl")); // NOI18N
2659: } catch (BadLocationException e) {
2660: e.printStackTrace();
2661: target.getToolkit().beep();
2662: }
2663: }
2664: });
2665: }
2666: }
2667: }
2668:
2669: private static final class DefaultSyntax extends Syntax {
2670:
2671: private static final int ISI_TEXT = 0;
2672:
2673: public DefaultSyntax() {
2674: tokenContextPath = DefaultSyntaxTokenContext.CONTEXT
2675: .getContextPath();
2676: }
2677:
2678: protected TokenID parseToken() {
2679: // The main loop that reads characters one by one follows
2680: while (offset < stopOffset) {
2681: char ch = buffer[offset]; // get the current character
2682:
2683: switch (state) { // switch by the current internal state
2684: case INIT:
2685: switch (ch) {
2686: case '\n':
2687: offset++;
2688: return DefaultSyntaxTokenContext.EOL;
2689: default:
2690: state = ISI_TEXT;
2691: break;
2692: }
2693: break;
2694:
2695: case ISI_TEXT:
2696: switch (ch) {
2697: case '\n':
2698: state = INIT;
2699: return DefaultSyntaxTokenContext.TEXT;
2700: }
2701: break;
2702:
2703: } // end of switch(state)
2704:
2705: offset++; // move to the next char
2706: }
2707:
2708: switch (state) {
2709: case ISI_TEXT:
2710: state = INIT;
2711: return DefaultSyntaxTokenContext.TEXT;
2712: }
2713:
2714: // need to continue on another buffer
2715: return null;
2716: }
2717: } // End of DefaultSyntax class
2718:
2719: private static final class DefaultSyntaxTokenContext extends
2720: TokenContext {
2721:
2722: // Numeric-ids for token-ids
2723: public static final int TEXT_ID = 1;
2724: public static final int EOL_ID = 2;
2725:
2726: public static final BaseTokenID TEXT = new BaseTokenID("text",
2727: TEXT_ID); // NOI18N
2728: public static final BaseImageTokenID EOL = new BaseImageTokenID(
2729: "EOL", EOL_ID, "\n"); // NOI18N
2730:
2731: // Context declaration
2732: public static final DefaultSyntaxTokenContext CONTEXT = new DefaultSyntaxTokenContext();
2733:
2734: private DefaultSyntaxTokenContext() {
2735: super ("defaultSyntax-token-"); // NOI18N
2736:
2737: try {
2738: addDeclaredTokenIDs();
2739: } catch (Exception e) {
2740: LOG.log(Level.WARNING, "Can't load token IDs", e); //NOI18N
2741: }
2742: }
2743: } // End of DefaultSyntaxTokenContext class
2744: }
|