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
73 public static JXEditTextArea createXmlEditor()
74 {
75 return new JXEditTextArea( new XMLTokenMarker() );
76 }
77
78 public static JXEditTextArea createGroovyEditor()
79 {
80 return new JXEditTextArea( new GroovyTokenMarker() );
81 }
82
83 public JXEditTextArea( TokenMarker tokenMarker )
84 {
85 String editorFont = SoapUI.getSettings().getString( UISettings.EDITOR_FONT, UISettings.DEFAULT_EDITOR_FONT );
86 if( editorFont != null && editorFont.length() > 0 )
87 getPainter().setFont(Font.decode(editorFont));
88 else
89 getPainter().setFont(Font.decode(UISettings.DEFAULT_EDITOR_FONT));
90
91 getPainter().setLineHighlightColor( new Color( 240, 240, 180 ) );
92 getPainter().setStyles(createXmlStyles());
93 setTokenMarker(tokenMarker);
94 setBorder(BorderFactory.createEtchedBorder());
95 addFocusListener(this);
96
97 undoAction = new UndoAction();
98 getInputHandler().addKeyBinding("C+Z", undoAction);
99 redoAction = new RedoAction();
100 getInputHandler().addKeyBinding("C+Y", redoAction);
101 findAndReplaceAction = new FindAndReplaceDialog( this );
102 getInputHandler().addKeyBinding( "C+F", findAndReplaceAction );
103 getInputHandler().addKeyBinding( "F3", findAndReplaceAction );
104 getInputHandler().addKeyBinding("A+RIGHT", new NextElementValueAction() );
105 getInputHandler().addKeyBinding("A+LEFT", new PreviousElementValueAction() );
106 getInputHandler().addKeyBinding("C+D", new DeleteLineAction() );
107 getInputHandler().addKeyBinding( "S+INSERT", createPasteAction() );
108 getInputHandler().addKeyBinding( "S+DELETE", createCutAction() );
109
110 setMinimumSize( new Dimension( 50, 50 ));
111 new InternalSettingsListener( this );
112 }
113
114 @Override
115 public void addNotify()
116 {
117 super.addNotify();
118 getDocument().addUndoableEditListener(this);
119 }
120
121 @Override
122 public void removeNotify()
123 {
124 super.removeNotify();
125 getDocument().removeUndoableEditListener(this);
126 }
127
128 public Action getFindAndReplaceAction()
129 {
130 return findAndReplaceAction;
131 }
132
133 public Action getRedoAction()
134 {
135 return redoAction;
136 }
137
138 public Action getUndoAction()
139 {
140 return undoAction;
141 }
142
143 public void setText(String text)
144 {
145 if( text != null && text.equals( getText() ))
146 return;
147
148 super.setText( text == null ? "" : text);
149
150 if( discardEditsOnSet && undoManager != null )
151 undoManager.discardAllEdits();
152 }
153
154 public boolean isDiscardEditsOnSet()
155 {
156 return discardEditsOnSet;
157 }
158
159 public void setDiscardEditsOnSet(boolean discardEditsOnSet)
160 {
161 this.discardEditsOnSet = discardEditsOnSet;
162 }
163
164 public UndoManager getUndoManager()
165 {
166 return undoManager;
167 }
168
169 public SyntaxStyle[] createXmlStyles()
170 {
171 SyntaxStyle[] styles = new SyntaxStyle[Token.ID_COUNT];
172
173 styles[Token.COMMENT1] = new SyntaxStyle(Color.black, true, false);
174 styles[Token.COMMENT2] = new SyntaxStyle(new Color(0x990033), true, false);
175 styles[Token.KEYWORD1] = new SyntaxStyle(Color.blue, false, false);
176 styles[Token.KEYWORD2] = new SyntaxStyle(Color.magenta, false, false);
177 styles[Token.KEYWORD3] = new SyntaxStyle(new Color(0x009600), false,
178 false);
179 styles[Token.LITERAL1] = new SyntaxStyle(new Color(0x650099), false,
180 false);
181 styles[Token.LITERAL2] = new SyntaxStyle(new Color(0x650099), false, true);
182 styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033), false, true);
183 styles[Token.OPERATOR] = new SyntaxStyle(Color.black, false, true);
184 styles[Token.INVALID] = new SyntaxStyle(Color.red, false, true);
185
186 return styles;
187 }
188
189 private void createUndoMananger()
190 {
191 undoManager = new UndoManager();
192 undoManager.setLimit(UNDO_LIMIT);
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207 public void focusGained(FocusEvent fe)
208 {
209 if (isEditable() && undoManager == null )
210 createUndoMananger();
211 }
212
213 public void setEnabled( boolean flag )
214 {
215 super.setEnabled( flag );
216 setEditable( flag );
217 }
218
219 public void setEditable(boolean enabled)
220 {
221 super.setEditable(enabled);
222 setCaretVisible( enabled );
223 getPainter().setLineHighlightEnabled( enabled );
224
225
226 repaint();
227 }
228
229 public void focusLost(FocusEvent fe)
230 {
231
232 }
233
234 public void undoableEditHappened(UndoableEditEvent e)
235 {
236 if (undoManager != null)
237 undoManager.addEdit(e.getEdit());
238 }
239
240 private static ReferenceQueue<JXEditTextArea> testQueue = new ReferenceQueue<JXEditTextArea>();
241 private static Map<WeakReference<JXEditTextArea>, InternalSettingsListener> testMap =
242 new HashMap<WeakReference<JXEditTextArea>, InternalSettingsListener>();
243
244 static
245 {
246 new Thread( new Runnable() {
247
248 public void run()
249 {
250 while( true )
251 {
252
253
254 try
255 {
256 Reference<? extends JXEditTextArea> ref = testQueue.remove();
257
258 InternalSettingsListener listener = testMap.remove( ref );
259 if( listener != null )
260 {
261
262 listener.release();
263 }
264 else
265 {
266
267 }
268 }
269 catch( InterruptedException e )
270 {
271 SoapUI.logError( e );
272 }
273 }
274
275 }}, "ReferenceQueueMonitor" ).start();
276 }
277
278 private static final class InternalSettingsListener implements SettingsListener
279 {
280 private WeakReference<JXEditTextArea> textArea;
281
282 public InternalSettingsListener( JXEditTextArea area )
283 {
284
285 textArea = new WeakReference<JXEditTextArea>(area,testQueue);
286 testMap.put( textArea, this );
287 SoapUI.getSettings().addSettingsListener(this);
288 }
289
290 public void release()
291 {
292 if( textArea.get() == null )
293 {
294 SoapUI.getSettings().removeSettingsListener(this);
295 }
296 else
297 {
298 System.err.println( "Error, cannot release listener" );
299 }
300 }
301
302 public void settingChanged(String name, String newValue, String oldValue)
303 {
304 if( name.equals( UISettings.EDITOR_FONT ) && textArea.get() != null)
305 {
306 textArea.get().getPainter().setFont( Font.decode( newValue ));
307 textArea.get().invalidate();
308 }
309 }
310
311
312 }
313
314 private class UndoAction extends AbstractAction
315 {
316 public UndoAction()
317 {
318 super( "Undo");
319 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Z" ));
320 }
321
322 public void actionPerformed(ActionEvent e)
323 {
324 if( !isEditable() )
325 {
326 getToolkit().beep();
327 return;
328 }
329
330 try
331 {
332 if( undoManager != null )
333 undoManager.undo();
334 }
335 catch (CannotUndoException cue)
336 {
337 Toolkit.getDefaultToolkit().beep();
338 }
339 }
340 }
341
342 private class RedoAction extends AbstractAction
343 {
344 public RedoAction()
345 {
346 super( "Redo");
347 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Y" ));
348 }
349
350 public void actionPerformed(ActionEvent e)
351 {
352 if( !isEditable() )
353 {
354 getToolkit().beep();
355 return;
356 }
357
358 try
359 {
360 if( undoManager != null )
361 undoManager.redo();
362 }
363 catch (CannotRedoException cue)
364 {
365 Toolkit.getDefaultToolkit().beep();
366 }
367 }
368 }
369
370 public class NextElementValueAction implements ActionListener
371 {
372 public void actionPerformed(ActionEvent e)
373 {
374 toNextElement();
375 }
376 }
377
378 public class PreviousElementValueAction implements ActionListener
379 {
380 public void actionPerformed(ActionEvent e)
381 {
382 toPreviousElement();
383 }
384 }
385
386
387 public class DeleteLineAction implements ActionListener
388 {
389 public void actionPerformed(ActionEvent e)
390 {
391 if( !isEditable() )
392 {
393 getToolkit().beep();
394 return;
395 }
396
397 int caretLine = getCaretLine();
398 if( caretLine == -1 ) return;
399 int lineStartOffset = getLineStartOffset( caretLine);
400 int lineEndOffset = getLineEndOffset( caretLine);
401
402 try
403 {
404 int len = lineEndOffset-lineStartOffset;
405 if( lineStartOffset+len >= getDocumentLength() ) len = getDocumentLength()-lineStartOffset;
406
407 getDocument().remove( lineStartOffset, len );
408 }
409 catch (BadLocationException e1)
410 {
411 SoapUI.logError( e1 );
412 }
413 }
414 }
415
416 public Action createCopyAction()
417 {
418 return new InputHandler.clip_copy();
419 }
420
421 public Action createCutAction()
422 {
423 return new InputHandler.clip_cut();
424 }
425
426 public Action createPasteAction()
427 {
428 return new InputHandler.clip_paste();
429 }
430
431 public int getCaretColumn()
432 {
433 int pos = getCaretPosition();
434 int line = getLineOfOffset( pos );
435
436 return pos - getLineStartOffset( line );
437 }
438
439 public void toNextElement()
440 {
441 int pos = getCaretPosition();
442 String text = getText();
443
444 while( pos < text.length() )
445 {
446
447 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
448 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
449 (pos > 1 && text.charAt(pos-1) != '/' ) &&
450 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
451 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
452 {
453 setCaretPosition( pos+1 );
454 return;
455 }
456
457 pos++;
458 }
459
460 getToolkit().beep();
461 }
462
463 public void toPreviousElement()
464 {
465 int pos = getCaretPosition()-2;
466 String text = getText();
467
468 while( pos > 0 )
469 {
470
471 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
472 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
473 (pos > 1 && text.charAt(pos-1) != '/' ) &&
474 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
475 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
476 {
477 setCaretPosition( pos+1 );
478 return;
479 }
480
481 pos--;
482 }
483
484 getToolkit().beep();
485 }
486 }