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