1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.support.xml;
14
15 import java.awt.Color;
16 import java.awt.Dimension;
17 import java.awt.Font;
18 import java.awt.Toolkit;
19 import java.awt.event.ActionEvent;
20 import java.awt.event.ActionListener;
21 import java.awt.event.FocusEvent;
22 import java.awt.event.FocusListener;
23 import java.lang.ref.Reference;
24 import java.lang.ref.ReferenceQueue;
25 import java.lang.ref.WeakReference;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import javax.swing.AbstractAction;
30 import javax.swing.Action;
31 import javax.swing.BorderFactory;
32 import javax.swing.event.UndoableEditEvent;
33 import javax.swing.event.UndoableEditListener;
34 import javax.swing.text.BadLocationException;
35 import javax.swing.undo.CannotRedoException;
36 import javax.swing.undo.CannotUndoException;
37 import javax.swing.undo.UndoManager;
38
39 import org.syntax.jedit.InputHandler;
40 import org.syntax.jedit.JEditTextArea;
41 import org.syntax.jedit.SyntaxStyle;
42 import org.syntax.jedit.tokenmarker.GroovyTokenMarker;
43 import org.syntax.jedit.tokenmarker.Token;
44 import org.syntax.jedit.tokenmarker.TokenMarker;
45 import org.syntax.jedit.tokenmarker.XMLTokenMarker;
46
47 import com.eviware.soapui.SoapUI;
48 import com.eviware.soapui.model.settings.SettingsListener;
49 import com.eviware.soapui.settings.UISettings;
50 import com.eviware.soapui.support.UISupport;
51 import com.eviware.soapui.support.actions.FindAndReplaceDialog;
52 import com.eviware.soapui.support.actions.FindAndReplaceable;
53 import com.eviware.soapui.support.components.JEditorStatusBar.JEditorStatusBarTarget;
54
55 /***
56 * JEditTextArea extension targeted specifically at XML-editing.
57 *
58 * //@todo move font handling to subclass
59 *
60 * @author Ole.Matzura
61 */
62
63 public class JXEditTextArea extends JEditTextArea implements
64 UndoableEditListener, FocusListener, FindAndReplaceable, JEditorStatusBarTarget
65 {
66 public static final int UNDO_LIMIT = 1500;
67 private UndoManager undoManager;
68 private UndoAction undoAction;
69 private RedoAction redoAction;
70 private FindAndReplaceDialog findAndReplaceAction;
71 private boolean discardEditsOnSet = true;
72 private GoToLineAction goToLineAction;
73
74 public static JXEditTextArea createXmlEditor()
75 {
76 return new JXEditTextArea( new XMLTokenMarker() );
77 }
78
79 public static JXEditTextArea createGroovyEditor()
80 {
81 return new JXEditTextArea( new GroovyTokenMarker() );
82 }
83
84 public JXEditTextArea( TokenMarker tokenMarker )
85 {
86 String editorFont = SoapUI.getSettings().getString( UISettings.EDITOR_FONT, UISettings.DEFAULT_EDITOR_FONT );
87 if( editorFont != null && editorFont.length() > 0 )
88 getPainter().setFont(Font.decode(editorFont));
89 else
90 getPainter().setFont(Font.decode(UISettings.DEFAULT_EDITOR_FONT));
91
92 getPainter().setLineHighlightColor( new Color( 240, 240, 180 ) );
93 getPainter().setStyles(createXmlStyles());
94 setTokenMarker(tokenMarker);
95 setBorder(BorderFactory.createEtchedBorder());
96 addFocusListener(this);
97
98 undoAction = new UndoAction();
99 getInputHandler().addKeyBinding("C+Z", undoAction);
100 redoAction = new RedoAction();
101 getInputHandler().addKeyBinding("C+Y", redoAction);
102 findAndReplaceAction = new FindAndReplaceDialog( this );
103 getInputHandler().addKeyBinding( "C+F", findAndReplaceAction );
104 getInputHandler().addKeyBinding( "F3", findAndReplaceAction );
105 getInputHandler().addKeyBinding("A+RIGHT", new NextElementValueAction() );
106 getInputHandler().addKeyBinding("A+LEFT", new PreviousElementValueAction() );
107 getInputHandler().addKeyBinding("C+D", new DeleteLineAction() );
108 getInputHandler().addKeyBinding( "S+INSERT", createPasteAction() );
109 getInputHandler().addKeyBinding( "S+DELETE", createCutAction() );
110
111 goToLineAction = new GoToLineAction();
112 getInputHandler().addKeyBinding( "CA+L", goToLineAction );
113
114 setMinimumSize( new Dimension( 50, 50 ));
115 new InternalSettingsListener( this );
116 }
117
118 @Override
119 public void addNotify()
120 {
121 super.addNotify();
122 getDocument().addUndoableEditListener(this);
123 }
124
125 @Override
126 public void removeNotify()
127 {
128 super.removeNotify();
129 getDocument().removeUndoableEditListener(this);
130 }
131
132 public Action getFindAndReplaceAction()
133 {
134 return findAndReplaceAction;
135 }
136
137 public Action getGoToLineAction()
138 {
139 return goToLineAction;
140 }
141
142 public Action getRedoAction()
143 {
144 return redoAction;
145 }
146
147 public Action getUndoAction()
148 {
149 return undoAction;
150 }
151
152 public void setText(String text)
153 {
154 if( text != null && text.equals( getText() ))
155 return;
156
157 super.setText( text == null ? "" : text);
158
159 if( discardEditsOnSet && undoManager != null )
160 undoManager.discardAllEdits();
161 }
162
163 public boolean isDiscardEditsOnSet()
164 {
165 return discardEditsOnSet;
166 }
167
168 public void setDiscardEditsOnSet(boolean discardEditsOnSet)
169 {
170 this.discardEditsOnSet = discardEditsOnSet;
171 }
172
173 public UndoManager getUndoManager()
174 {
175 return undoManager;
176 }
177
178 public SyntaxStyle[] createXmlStyles()
179 {
180 SyntaxStyle[] styles = new SyntaxStyle[Token.ID_COUNT];
181
182 styles[Token.COMMENT1] = new SyntaxStyle(Color.black, true, false);
183 styles[Token.COMMENT2] = new SyntaxStyle(new Color(0x990033), true, false);
184 styles[Token.KEYWORD1] = new SyntaxStyle(Color.blue, false, false);
185 styles[Token.KEYWORD2] = new SyntaxStyle(Color.magenta, false, false);
186 styles[Token.KEYWORD3] = new SyntaxStyle(new Color(0x009600), false,
187 false);
188 styles[Token.LITERAL1] = new SyntaxStyle(new Color(0x650099), false,
189 false);
190 styles[Token.LITERAL2] = new SyntaxStyle(new Color(0x650099), false, true);
191 styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033), false, true);
192 styles[Token.OPERATOR] = new SyntaxStyle(Color.black, false, true);
193 styles[Token.INVALID] = new SyntaxStyle(Color.red, false, true);
194
195 return styles;
196 }
197
198 private void createUndoMananger()
199 {
200 undoManager = new UndoManager();
201 undoManager.setLimit(UNDO_LIMIT);
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216 public void focusGained(FocusEvent fe)
217 {
218 if (isEditable() && undoManager == null )
219 createUndoMananger();
220 }
221
222 public void setEnabled( boolean flag )
223 {
224 super.setEnabled( flag );
225 setEditable( flag );
226 }
227
228 public void setEditable(boolean enabled)
229 {
230 super.setEditable(enabled);
231 setCaretVisible( enabled );
232 getPainter().setLineHighlightEnabled( enabled );
233
234
235 repaint();
236 }
237
238 public void focusLost(FocusEvent fe)
239 {
240
241 }
242
243 public void undoableEditHappened(UndoableEditEvent e)
244 {
245 if (undoManager != null)
246 undoManager.addEdit(e.getEdit());
247 }
248
249 private static ReferenceQueue<JXEditTextArea> testQueue = new ReferenceQueue<JXEditTextArea>();
250 private static Map<WeakReference<JXEditTextArea>, InternalSettingsListener> testMap =
251 new HashMap<WeakReference<JXEditTextArea>, InternalSettingsListener>();
252
253 static
254 {
255 new Thread( new Runnable() {
256
257 public void run()
258 {
259 while( true )
260 {
261
262
263 try
264 {
265 Reference<? extends JXEditTextArea> ref = testQueue.remove();
266
267 InternalSettingsListener listener = testMap.remove( ref );
268 if( listener != null )
269 {
270
271 listener.release();
272 }
273 else
274 {
275
276 }
277 }
278 catch( InterruptedException e )
279 {
280 SoapUI.logError( e );
281 }
282 }
283
284 }}, "ReferenceQueueMonitor" ).start();
285 }
286
287 private static final class InternalSettingsListener implements SettingsListener
288 {
289 private WeakReference<JXEditTextArea> textArea;
290
291 public InternalSettingsListener( JXEditTextArea area )
292 {
293
294 textArea = new WeakReference<JXEditTextArea>(area,testQueue);
295 testMap.put( textArea, this );
296 SoapUI.getSettings().addSettingsListener(this);
297 }
298
299 public void release()
300 {
301 if( textArea.get() == null )
302 {
303 SoapUI.getSettings().removeSettingsListener(this);
304 }
305 else
306 {
307 System.err.println( "Error, cannot release listener" );
308 }
309 }
310
311 public void settingChanged(String name, String newValue, String oldValue)
312 {
313 if( name.equals( UISettings.EDITOR_FONT ) && textArea.get() != null)
314 {
315 textArea.get().getPainter().setFont( Font.decode( newValue ));
316 textArea.get().invalidate();
317 }
318 }
319
320
321 }
322
323 private class UndoAction extends AbstractAction
324 {
325 public UndoAction()
326 {
327 super( "Undo");
328 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Z" ));
329 }
330
331 public void actionPerformed(ActionEvent e)
332 {
333 undo();
334 }
335 }
336
337 private class RedoAction extends AbstractAction
338 {
339 public RedoAction()
340 {
341 super( "Redo");
342 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Y" ));
343 }
344
345 public void actionPerformed(ActionEvent e)
346 {
347 redo();
348 }
349 }
350
351 private final class GoToLineAction extends AbstractAction
352 {
353 public GoToLineAction()
354 {
355 super( "Go To Line" );
356 putValue( Action.SHORT_DESCRIPTION, "Moves the caret to the specified line" );
357 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu alt L" ));
358 }
359
360 public void actionPerformed( ActionEvent e )
361 {
362 String line = UISupport.prompt( "Enter line-number to (1.." + (getLineCount()) + ")", "Go To Line",
363 String.valueOf( getCaretLine()+1 ));
364
365 if( line != null )
366 {
367 try
368 {
369 int ln = Integer.parseInt( line )-1;
370 if( ln >= 0 && ln < getLineCount() )
371 {
372 scrollTo( ln, 0 );
373 setCaretPosition( getLineStartOffset( ln ));
374 }
375 }
376 catch( NumberFormatException e1 )
377 {
378 }
379 }
380 }
381 }
382
383 public class NextElementValueAction implements ActionListener
384 {
385 public void actionPerformed(ActionEvent e)
386 {
387 toNextElement();
388 }
389 }
390
391 public class PreviousElementValueAction implements ActionListener
392 {
393 public void actionPerformed(ActionEvent e)
394 {
395 toPreviousElement();
396 }
397 }
398
399
400 public class DeleteLineAction implements ActionListener
401 {
402 public void actionPerformed(ActionEvent e)
403 {
404 if( !isEditable() )
405 {
406 getToolkit().beep();
407 return;
408 }
409
410 int caretLine = getCaretLine();
411 if( caretLine == -1 ) return;
412 int lineStartOffset = getLineStartOffset( caretLine);
413 int lineEndOffset = getLineEndOffset( caretLine);
414
415 try
416 {
417 int len = lineEndOffset-lineStartOffset;
418 if( lineStartOffset+len >= getDocumentLength() ) len = getDocumentLength()-lineStartOffset;
419
420 getDocument().remove( lineStartOffset, len );
421 }
422 catch (BadLocationException e1)
423 {
424 SoapUI.logError( e1 );
425 }
426 }
427 }
428
429 public Action createCopyAction()
430 {
431 return new InputHandler.clip_copy();
432 }
433
434 public void undo()
435 {
436 if( !isEditable() )
437 {
438 getToolkit().beep();
439 return;
440 }
441
442 try
443 {
444 if( undoManager != null )
445 undoManager.undo();
446 }
447 catch (CannotUndoException cue)
448 {
449 Toolkit.getDefaultToolkit().beep();
450 }
451 }
452
453 public Action createCutAction()
454 {
455 return new InputHandler.clip_cut();
456 }
457
458 public Action createPasteAction()
459 {
460 return new InputHandler.clip_paste();
461 }
462
463 public int getCaretColumn()
464 {
465 int pos = getCaretPosition();
466 int line = getLineOfOffset( pos );
467
468 return pos - getLineStartOffset( line );
469 }
470
471 public void toNextElement()
472 {
473 int pos = getCaretPosition();
474 String text = getText();
475
476 while( pos < text.length() )
477 {
478
479 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
480 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
481 (pos > 1 && text.charAt(pos-1) != '/' ) &&
482 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
483 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
484 {
485 setCaretPosition( pos+1 );
486 return;
487 }
488
489 pos++;
490 }
491
492 getToolkit().beep();
493 }
494
495 public void toPreviousElement()
496 {
497 int pos = getCaretPosition()-2;
498 String text = getText();
499
500 while( pos > 0 )
501 {
502
503 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
504 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
505 (pos > 1 && text.charAt(pos-1) != '/' ) &&
506 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
507 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
508 {
509 setCaretPosition( pos+1 );
510 return;
511 }
512
513 pos--;
514 }
515
516 getToolkit().beep();
517 }
518
519 public boolean canUndo()
520 {
521 return undoManager != null && undoManager.canUndo();
522 }
523
524 public boolean canRedo()
525 {
526 return undoManager != null && undoManager.canRedo();
527 }
528
529 public void redo()
530 {
531 if( !isEditable() )
532 {
533 getToolkit().beep();
534 return;
535 }
536
537 try
538 {
539 if( canRedo() )
540 undoManager.redo();
541 }
542 catch (CannotRedoException cue)
543 {
544 Toolkit.getDefaultToolkit().beep();
545 } }
546 }