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