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