001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.support.xml;
014:
015: import java.awt.Color;
016: import java.awt.Dimension;
017: import java.awt.Font;
018: import java.awt.Toolkit;
019: import java.awt.event.ActionEvent;
020: import java.awt.event.ActionListener;
021: import java.awt.event.FocusEvent;
022: import java.awt.event.FocusListener;
023: import java.lang.ref.Reference;
024: import java.lang.ref.ReferenceQueue;
025: import java.lang.ref.WeakReference;
026: import java.util.HashMap;
027: import java.util.Map;
028:
029: import javax.swing.AbstractAction;
030: import javax.swing.Action;
031: import javax.swing.BorderFactory;
032: import javax.swing.event.UndoableEditEvent;
033: import javax.swing.event.UndoableEditListener;
034: import javax.swing.text.BadLocationException;
035: import javax.swing.undo.CannotRedoException;
036: import javax.swing.undo.CannotUndoException;
037: import javax.swing.undo.UndoManager;
038:
039: import org.syntax.jedit.InputHandler;
040: import org.syntax.jedit.JEditTextArea;
041: import org.syntax.jedit.SyntaxStyle;
042: import org.syntax.jedit.tokenmarker.GroovyTokenMarker;
043: import org.syntax.jedit.tokenmarker.Token;
044: import org.syntax.jedit.tokenmarker.TokenMarker;
045: import org.syntax.jedit.tokenmarker.XMLTokenMarker;
046:
047: import com.eviware.soapui.SoapUI;
048: import com.eviware.soapui.model.settings.SettingsListener;
049: import com.eviware.soapui.settings.UISettings;
050: import com.eviware.soapui.support.UISupport;
051: import com.eviware.soapui.support.actions.FindAndReplaceDialog;
052: import com.eviware.soapui.support.actions.FindAndReplaceable;
053: import com.eviware.soapui.support.components.JEditorStatusBar.JEditorStatusBarTarget;
054:
055: /**
056: * JEditTextArea extension targeted specifically at XML-editing.
057: *
058: * //@todo move font handling to subclass
059: *
060: * @author Ole.Matzura
061: */
062:
063: public class JXEditTextArea extends JEditTextArea implements
064: UndoableEditListener, FocusListener, FindAndReplaceable,
065: JEditorStatusBarTarget {
066: public static final int UNDO_LIMIT = 1500;
067: private UndoManager undoManager;
068: private UndoAction undoAction;
069: private RedoAction redoAction;
070: private FindAndReplaceDialog findAndReplaceAction;
071: private boolean discardEditsOnSet = true;
072:
073: public static JXEditTextArea createXmlEditor() {
074: return new JXEditTextArea(new XMLTokenMarker());
075: }
076:
077: public static JXEditTextArea createGroovyEditor() {
078: return new JXEditTextArea(new GroovyTokenMarker());
079: }
080:
081: public JXEditTextArea(TokenMarker tokenMarker) {
082: String editorFont = SoapUI.getSettings().getString(
083: UISettings.EDITOR_FONT, UISettings.DEFAULT_EDITOR_FONT);
084: if (editorFont != null && editorFont.length() > 0)
085: getPainter().setFont(Font.decode(editorFont));
086: else
087: getPainter().setFont(
088: Font.decode(UISettings.DEFAULT_EDITOR_FONT));
089:
090: getPainter().setLineHighlightColor(new Color(240, 240, 180));
091: getPainter().setStyles(createXmlStyles());
092: setTokenMarker(tokenMarker);
093: setBorder(BorderFactory.createEtchedBorder());
094: addFocusListener(this );
095:
096: undoAction = new UndoAction();
097: getInputHandler().addKeyBinding("C+Z", undoAction);
098: redoAction = new RedoAction();
099: getInputHandler().addKeyBinding("C+Y", redoAction);
100: findAndReplaceAction = new FindAndReplaceDialog(this );
101: getInputHandler().addKeyBinding("C+F", findAndReplaceAction);
102: getInputHandler().addKeyBinding("F3", findAndReplaceAction);
103: getInputHandler().addKeyBinding("A+RIGHT",
104: new NextElementValueAction());
105: getInputHandler().addKeyBinding("A+LEFT",
106: new PreviousElementValueAction());
107: getInputHandler().addKeyBinding("C+D", new DeleteLineAction());
108: getInputHandler()
109: .addKeyBinding("S+INSERT", createPasteAction());
110: getInputHandler().addKeyBinding("S+DELETE", createCutAction());
111:
112: setMinimumSize(new Dimension(50, 50));
113: new InternalSettingsListener(this );
114: }
115:
116: @Override
117: public void addNotify() {
118: super .addNotify();
119: getDocument().addUndoableEditListener(this );
120: }
121:
122: @Override
123: public void removeNotify() {
124: super .removeNotify();
125: getDocument().removeUndoableEditListener(this );
126: }
127:
128: public Action getFindAndReplaceAction() {
129: return findAndReplaceAction;
130: }
131:
132: public Action getRedoAction() {
133: return redoAction;
134: }
135:
136: public Action getUndoAction() {
137: return undoAction;
138: }
139:
140: public void setText(String text) {
141: if (text != null && text.equals(getText()))
142: return;
143:
144: super .setText(text == null ? "" : text);
145:
146: if (discardEditsOnSet && undoManager != null)
147: undoManager.discardAllEdits();
148: }
149:
150: public boolean isDiscardEditsOnSet() {
151: return discardEditsOnSet;
152: }
153:
154: public void setDiscardEditsOnSet(boolean discardEditsOnSet) {
155: this .discardEditsOnSet = discardEditsOnSet;
156: }
157:
158: public UndoManager getUndoManager() {
159: return undoManager;
160: }
161:
162: public SyntaxStyle[] createXmlStyles() {
163: SyntaxStyle[] styles = new SyntaxStyle[Token.ID_COUNT];
164:
165: styles[Token.COMMENT1] = new SyntaxStyle(Color.black, true,
166: false);
167: styles[Token.COMMENT2] = new SyntaxStyle(new Color(0x990033),
168: true, false);
169: styles[Token.KEYWORD1] = new SyntaxStyle(Color.blue, false,
170: false);
171: styles[Token.KEYWORD2] = new SyntaxStyle(Color.magenta, false,
172: false);
173: styles[Token.KEYWORD3] = new SyntaxStyle(new Color(0x009600),
174: false, false);
175: styles[Token.LITERAL1] = new SyntaxStyle(new Color(0x650099),
176: false, false);
177: styles[Token.LITERAL2] = new SyntaxStyle(new Color(0x650099),
178: false, true);
179: styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033),
180: false, true);
181: styles[Token.OPERATOR] = new SyntaxStyle(Color.black, false,
182: true);
183: styles[Token.INVALID] = new SyntaxStyle(Color.red, false, true);
184:
185: return styles;
186: }
187:
188: private void createUndoMananger() {
189: undoManager = new UndoManager();
190: undoManager.setLimit(UNDO_LIMIT);
191: }
192:
193: /*
194:
195: private void removeUndoMananger()
196: {
197: if (undoManager == null)
198: return;
199: undoManager.end();
200: undoManager = null;
201: }
202:
203: */
204:
205: public void focusGained(FocusEvent fe) {
206: if (isEditable() && undoManager == null)
207: createUndoMananger();
208: }
209:
210: public void setEnabled(boolean flag) {
211: super .setEnabled(flag);
212: setEditable(flag);
213: }
214:
215: public void setEditable(boolean enabled) {
216: super .setEditable(enabled);
217: setCaretVisible(enabled);
218: getPainter().setLineHighlightEnabled(enabled);
219:
220: //getPainter().setBackground( enabled ? Color.WHITE : new Color(238, 238, 238) );
221: repaint();
222: }
223:
224: public void focusLost(FocusEvent fe) {
225: //removeUndoMananger();
226: }
227:
228: public void undoableEditHappened(UndoableEditEvent e) {
229: if (undoManager != null)
230: undoManager.addEdit(e.getEdit());
231: }
232:
233: private static ReferenceQueue<JXEditTextArea> testQueue = new ReferenceQueue<JXEditTextArea>();
234: private static Map<WeakReference<JXEditTextArea>, InternalSettingsListener> testMap = new HashMap<WeakReference<JXEditTextArea>, InternalSettingsListener>();
235:
236: static {
237: new Thread(new Runnable() {
238:
239: public void run() {
240: while (true) {
241: // System.out.println( "Waiting for weak references to be released.." );
242:
243: try {
244: Reference<? extends JXEditTextArea> ref = testQueue
245: .remove();
246: // System.out.println( "Got ref to clear" );
247: InternalSettingsListener listener = testMap
248: .remove(ref);
249: if (listener != null) {
250: // System.out.println( "Releasing listener" );
251: listener.release();
252: } else {
253: // System.out.println( "Listener not found" );
254: }
255: } catch (InterruptedException e) {
256: SoapUI.logError(e);
257: }
258: }
259:
260: }
261: }, "ReferenceQueueMonitor").start();
262: }
263:
264: private static final class InternalSettingsListener implements
265: SettingsListener {
266: private WeakReference<JXEditTextArea> textArea;
267:
268: public InternalSettingsListener(JXEditTextArea area) {
269: // System.out.println( "Creating weakreference for textarea" );
270: textArea = new WeakReference<JXEditTextArea>(area,
271: testQueue);
272: testMap.put(textArea, this );
273: SoapUI.getSettings().addSettingsListener(this );
274: }
275:
276: public void release() {
277: if (textArea.get() == null) {
278: SoapUI.getSettings().removeSettingsListener(this );
279: } else {
280: System.err.println("Error, cannot release listener");
281: }
282: }
283:
284: public void settingChanged(String name, String newValue,
285: String oldValue) {
286: if (name.equals(UISettings.EDITOR_FONT)
287: && textArea.get() != null) {
288: textArea.get().getPainter().setFont(
289: Font.decode(newValue));
290: textArea.get().invalidate();
291: }
292: }
293:
294: }
295:
296: private class UndoAction extends AbstractAction {
297: public UndoAction() {
298: super ("Undo");
299: putValue(Action.ACCELERATOR_KEY, UISupport
300: .getKeyStroke("menu Z"));
301: }
302:
303: public void actionPerformed(ActionEvent e) {
304: if (!isEditable()) {
305: getToolkit().beep();
306: return;
307: }
308:
309: try {
310: if (undoManager != null)
311: undoManager.undo();
312: } catch (CannotUndoException cue) {
313: Toolkit.getDefaultToolkit().beep();
314: }
315: }
316: }
317:
318: private class RedoAction extends AbstractAction {
319: public RedoAction() {
320: super ("Redo");
321: putValue(Action.ACCELERATOR_KEY, UISupport
322: .getKeyStroke("menu Y"));
323: }
324:
325: public void actionPerformed(ActionEvent e) {
326: if (!isEditable()) {
327: getToolkit().beep();
328: return;
329: }
330:
331: try {
332: if (undoManager != null)
333: undoManager.redo();
334: } catch (CannotRedoException cue) {
335: Toolkit.getDefaultToolkit().beep();
336: }
337: }
338: }
339:
340: public class NextElementValueAction implements ActionListener {
341: public void actionPerformed(ActionEvent e) {
342: toNextElement();
343: }
344: }
345:
346: public class PreviousElementValueAction implements ActionListener {
347: public void actionPerformed(ActionEvent e) {
348: toPreviousElement();
349: }
350: }
351:
352: public class DeleteLineAction implements ActionListener {
353: public void actionPerformed(ActionEvent e) {
354: if (!isEditable()) {
355: getToolkit().beep();
356: return;
357: }
358:
359: int caretLine = getCaretLine();
360: if (caretLine == -1)
361: return;
362: int lineStartOffset = getLineStartOffset(caretLine);
363: int lineEndOffset = getLineEndOffset(caretLine);
364:
365: try {
366: int len = lineEndOffset - lineStartOffset;
367: if (lineStartOffset + len >= getDocumentLength())
368: len = getDocumentLength() - lineStartOffset;
369:
370: getDocument().remove(lineStartOffset, len);
371: } catch (BadLocationException e1) {
372: SoapUI.logError(e1);
373: }
374: }
375: }
376:
377: public Action createCopyAction() {
378: return new InputHandler.clip_copy();
379: }
380:
381: public Action createCutAction() {
382: return new InputHandler.clip_cut();
383: }
384:
385: public Action createPasteAction() {
386: return new InputHandler.clip_paste();
387: }
388:
389: public int getCaretColumn() {
390: int pos = getCaretPosition();
391: int line = getLineOfOffset(pos);
392:
393: return pos - getLineStartOffset(line);
394: }
395:
396: public void toNextElement() {
397: int pos = getCaretPosition();
398: String text = getText();
399:
400: while (pos < text.length()) {
401: // find ending >
402: if (text.charAt(pos) == '>'
403: && pos < text.length() - 1
404: && (pos > 2 && !text.substring(pos - 2, pos)
405: .equals("--"))
406: && (pos > 1 && text.charAt(pos - 1) != '/')
407: && text.indexOf('/', pos) == text.indexOf('<', pos) + 1
408: && text.lastIndexOf('/', pos) != text.lastIndexOf(
409: '<', pos) + 1) {
410: setCaretPosition(pos + 1);
411: return;
412: }
413:
414: pos++;
415: }
416:
417: getToolkit().beep();
418: }
419:
420: public void toPreviousElement() {
421: int pos = getCaretPosition() - 2;
422: String text = getText();
423:
424: while (pos > 0) {
425: // find ending >
426: if (text.charAt(pos) == '>'
427: && pos < text.length() - 1
428: && (pos > 2 && !text.substring(pos - 2, pos)
429: .equals("--"))
430: && (pos > 1 && text.charAt(pos - 1) != '/')
431: && text.indexOf('/', pos) == text.indexOf('<', pos) + 1
432: && text.lastIndexOf('/', pos) != text.lastIndexOf(
433: '<', pos) + 1) {
434: setCaretPosition(pos + 1);
435: return;
436: }
437:
438: pos--;
439: }
440:
441: getToolkit().beep();
442: }
443: }
|