001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.awt.event.ActionEvent;
017:
018: import javax.swing.Action;
019: import javax.swing.JMenuItem;
020: import javax.swing.text.BadLocationException;
021: import javax.swing.text.Caret;
022: import javax.swing.text.JTextComponent;
023: import javax.swing.text.TextAction;
024:
025: /**
026: * This is the parent of majority of the actions. It implements the necessary
027: * resetting depending of what is required by constructor of target action. The
028: * other thing implemented here is macro recording.
029: *
030: * @author Miloslav Metelka
031: * @version 1.00
032: */
033:
034: public abstract class BaseAction extends TextAction {
035:
036: /** Text of the menu item in popup menu for this action */
037: public static final String POPUP_MENU_TEXT = "PopupMenuText"; // NOI18N
038:
039: /** Prefix for the name of the key for description in locale support */
040: public static final String LOCALE_DESC_PREFIX = "desc-"; // NOI18N
041:
042: /** Prefix for the name of the key for popup description in locale support */
043: public static final String LOCALE_POPUP_PREFIX = "popup-"; // NOI18N
044:
045: /** Remove the selected text at the action begining */
046: public static final int SELECTION_REMOVE = 1;
047:
048: /** Reset magic caret position */
049: public static final int MAGIC_POSITION_RESET = 2;
050:
051: /** Reset abbreviation accounting to empty string */
052: public static final int ABBREV_RESET = 4;
053:
054: /**
055: * Prevents adding the new undoable edit to the old one when the next
056: * document change occurs.
057: */
058: public static final int UNDO_MERGE_RESET = 8;
059:
060: /** Reset word-match table */
061: public static final int WORD_MATCH_RESET = 16;
062:
063: /** Clear status bar text */
064: public static final int CLEAR_STATUS_TEXT = 32;
065:
066: /** The action will not be recorded if in macro recording */
067: public static final int NO_RECORDING = 64;
068:
069: /** Save current position in the jump list */
070: public static final int SAVE_POSITION = 128;
071:
072: /**
073: * Bit mask of what should be updated when the action is performed before
074: * the action's real task is invoked.
075: */
076: protected int updateMask;
077:
078: private static boolean recording;
079: private static StringBuffer macroBuffer = new StringBuffer();
080: private static StringBuffer textBuffer = new StringBuffer();
081:
082: static final long serialVersionUID = -4255521122272110786L;
083:
084: public BaseAction(String name) {
085: this (name, 0);
086: }
087:
088: public BaseAction(String name, int updateMask) {
089: super (name);
090: this .updateMask = updateMask;
091: // Initialize short description
092: String key = LOCALE_DESC_PREFIX + name;
093: String desc = LocaleSupport.getString(key);
094: if (desc == null) {
095: desc = LocaleSupport.getString(name, name);
096: }
097: putValue(SHORT_DESCRIPTION, desc);
098: // Initialize menu description
099: key = LOCALE_POPUP_PREFIX + name;
100: String popupMenuText = LocaleSupport.getString(key, desc);
101: putValue(POPUP_MENU_TEXT, popupMenuText);
102: }
103:
104: /**
105: * This method is called once after the action is constructed and then each
106: * time the settings are changed.
107: *
108: * @param evt
109: * event describing the changed setting name. It's null if it's
110: * called after the action construction.
111: * @param kitClass
112: * class of the kit that created the actions
113: */
114: protected void settingsChange(SettingsChangeEvent evt,
115: Class kitClass) {
116: }
117:
118: /**
119: * This method is made final here as there's an important processing that
120: * must be done before the real action functionality is performed. It can
121: * include the following: 1. Updating of the target component depending on
122: * the update mask given in action constructor. 2. Possible macro recoding
123: * when the macro recording is turned on. The real action functionality
124: * should be done in the method actionPerformed(ActionEvent evt,
125: * JTextComponent target) which must be redefined by the target action.
126: */
127: public final void actionPerformed(ActionEvent evt) {
128:
129: JTextComponent target = getTextComponent(evt);
130:
131: if (recording && 0 == (updateMask & NO_RECORDING)) {
132: recordAction(target, evt);
133: }
134:
135: updateComponent(target);
136:
137: actionPerformed(evt, target);
138: }
139:
140: private void recordAction(JTextComponent target, ActionEvent evt) {
141: if (this == target.getKeymap().getDefaultAction()) { // defaultKeyTyped
142: textBuffer.append(getFilteredActionCommand(evt
143: .getActionCommand()));
144: } else { // regular action
145: if (textBuffer.length() > 0) {
146: if (macroBuffer.length() > 0)
147: macroBuffer.append(' ');
148: macroBuffer.append(encodeText(textBuffer.toString()));
149: textBuffer.setLength(0);
150: }
151: if (macroBuffer.length() > 0)
152: macroBuffer.append(' ');
153: String name = (String) getValue(Action.NAME);
154: macroBuffer.append(encodeActionName(name));
155: }
156: }
157:
158: private String getFilteredActionCommand(String cmd) {
159: if (cmd == null || cmd.length() == 0)
160: return "";
161: char ch = cmd.charAt(0);
162: if ((ch >= 0x20) && (ch != 0x7F))
163: return cmd;
164: else
165: return "";
166: }
167:
168: boolean startRecording(JTextComponent target) {
169: if (recording)
170: return false;
171: recording = true;
172: macroBuffer.setLength(0);
173: textBuffer.setLength(0);
174: Utilities.setStatusText(target, LocaleSupport
175: .getString("macro-recording"));
176: return true;
177: }
178:
179: String stopRecording(JTextComponent target) {
180: if (!recording)
181: return null;
182:
183: if (textBuffer.length() > 0) {
184: if (macroBuffer.length() > 0)
185: macroBuffer.append(' ');
186: macroBuffer.append(encodeText(textBuffer.toString()));
187: }
188: String retVal = macroBuffer.toString();
189: recording = false;
190: Utilities.setStatusText(target, ""); // NOI18N
191: return retVal;
192: }
193:
194: private String encodeText(String s) {
195: char[] text = s.toCharArray();
196: StringBuffer encoded = new StringBuffer("\""); // NOI18N
197: for (int i = 0; i < text.length; i++) {
198: char c = text[i];
199: if (c == '"' || c == '\\')
200: encoded.append('\\');
201: encoded.append(c);
202: }
203: return encoded.append('"').toString();
204: }
205:
206: private String encodeActionName(String s) {
207: char[] actionName = s.toCharArray();
208: StringBuffer encoded = new StringBuffer();
209: for (int i = 0; i < actionName.length; i++) {
210: char c = actionName[i];
211: if (Character.isWhitespace(c) || c == '\\')
212: encoded.append('\\');
213: encoded.append(c);
214: }
215: return encoded.toString();
216: }
217:
218: /**
219: * The target method that performs the real action functionality.
220: *
221: * @param evt
222: * action event describing the action that occured
223: * @param target
224: * target component where the action occured. It's retrieved by
225: * the TextAction.getTextComponent(evt).
226: */
227: public abstract void actionPerformed(ActionEvent evt,
228: JTextComponent target);
229:
230: public JMenuItem getPopupMenuItem(JTextComponent target) {
231: return null;
232: }
233:
234: public String getPopupMenuText(JTextComponent target) {
235: String txt = (String) getValue(POPUP_MENU_TEXT);
236: if (txt == null) {
237: txt = (String) getValue(NAME);
238: }
239: return txt;
240: }
241:
242: /**
243: * Update the component according to the update mask specified in the
244: * constructor of the action.
245: *
246: * @param target
247: * target component to be updated.
248: */
249: public void updateComponent(JTextComponent target) {
250: updateComponent(target, this .updateMask);
251: }
252:
253: /**
254: * Update the component according to the given update mask
255: *
256: * @param target
257: * target component to be updated.
258: * @param updateMask
259: * mask that specifies what will be updated
260: */
261: public void updateComponent(JTextComponent target, int updateMask) {
262: if (target != null
263: && target.getDocument() instanceof BaseDocument) {
264: BaseDocument doc = (BaseDocument) target.getDocument();
265: boolean writeLocked = false;
266:
267: try {
268: // remove selected text
269: if ((updateMask & SELECTION_REMOVE) != 0) {
270: writeLocked = true;
271: doc.extWriteLock();
272: Caret caret = target.getCaret();
273: if (caret != null && caret.isSelectionVisible()) {
274: int dot = caret.getDot();
275: int markPos = caret.getMark();
276: if (dot < markPos) { // swap positions
277: int tmpPos = dot;
278: dot = markPos;
279: markPos = tmpPos;
280: }
281: try {
282: target.getDocument().remove(markPos,
283: dot - markPos);
284: } catch (BadLocationException e) {
285: if (Boolean
286: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
287: e.printStackTrace();
288: }
289: }
290: }
291: }
292:
293: // reset magic caret position
294: if ((updateMask & MAGIC_POSITION_RESET) != 0) {
295: if (target.getCaret() != null)
296: target.getCaret().setMagicCaretPosition(null);
297: }
298:
299: // reset abbreviation accounting
300: if ((updateMask & ABBREV_RESET) != 0) {
301: ((BaseTextUI) target.getUI()).getEditorUI()
302: .getAbbrev().reset();
303: }
304:
305: // reset merging of undoable edits
306: if ((updateMask & UNDO_MERGE_RESET) != 0) {
307: doc.resetUndoMerge();
308: }
309:
310: // reset word matching
311: if ((updateMask & WORD_MATCH_RESET) != 0) {
312: ((BaseTextUI) target.getUI()).getEditorUI()
313: .getWordMatch().clear();
314: }
315:
316: // Clear status bar text
317: if ((updateMask & CLEAR_STATUS_TEXT) != 0) {
318: Utilities.clearStatusText(target);
319: }
320:
321: // Save current caret position in the jump-list
322: if ((updateMask & SAVE_POSITION) != 0) {
323: JumpList.checkAddEntry(target);
324: }
325:
326: } finally {
327: if (writeLocked) {
328: doc.extWriteUnlock();
329: }
330: }
331: }
332: }
333:
334: }
|