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