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
24 import javax.swing.AbstractAction;
25 import javax.swing.Action;
26 import javax.swing.BorderFactory;
27 import javax.swing.event.UndoableEditEvent;
28 import javax.swing.event.UndoableEditListener;
29 import javax.swing.text.BadLocationException;
30 import javax.swing.undo.CannotRedoException;
31 import javax.swing.undo.CannotUndoException;
32 import javax.swing.undo.UndoManager;
33
34 import org.syntax.jedit.DefaultInputHandler;
35 import org.syntax.jedit.JEditTextArea;
36 import org.syntax.jedit.SyntaxStyle;
37 import org.syntax.jedit.tokenmarker.GroovyTokenMarker;
38 import org.syntax.jedit.tokenmarker.Token;
39 import org.syntax.jedit.tokenmarker.TokenMarker;
40 import org.syntax.jedit.tokenmarker.XMLTokenMarker;
41
42 import com.eviware.soapui.SoapUI;
43 import com.eviware.soapui.model.settings.SettingsListener;
44 import com.eviware.soapui.settings.UISettings;
45 import com.eviware.soapui.support.UISupport;
46 import com.eviware.soapui.support.actions.FindAndReplaceDialog;
47 import com.eviware.soapui.support.actions.FindAndReplaceable;
48 import com.eviware.soapui.support.components.JEditorStatusBar.JEditorStatusBarTarget;
49
50 /***
51 * JEditTextArea extension targeted specifically at XML-editing.
52 *
53 * //@todo move font handling to subclass
54 *
55 * @author Ole.Matzura
56 */
57
58 public class JXEditTextArea extends JEditTextArea implements
59 UndoableEditListener, FocusListener, FindAndReplaceable, JEditorStatusBarTarget
60 {
61 public static final int UNDO_LIMIT = 1500;
62 private UndoManager undoManager;
63 private UndoAction undoAction;
64 private RedoAction redoAction;
65 private FindAndReplaceDialog findAndReplaceAction;
66 private boolean discardEditsOnSet = true;
67 private InternalSettingsListener internalSettingsListener;
68
69 public static JXEditTextArea createXmlEditor()
70 {
71 return new JXEditTextArea( new XMLTokenMarker() );
72 }
73
74 public static JXEditTextArea createGroovyEditor()
75 {
76 return new JXEditTextArea( new GroovyTokenMarker() );
77 }
78
79 public JXEditTextArea( TokenMarker tokenMarker )
80 {
81 String editorFont = SoapUI.getSettings().getString( UISettings.EDITOR_FONT, UISettings.DEFAULT_EDITOR_FONT );
82 if( editorFont != null && editorFont.length() > 0 )
83 getPainter().setFont(Font.decode(editorFont));
84 else
85 getPainter().setFont(Font.decode(UISettings.DEFAULT_EDITOR_FONT));
86
87 getPainter().setLineHighlightColor( new Color( 240, 240, 180 ) );
88 getPainter().setStyles(createXmlStyles());
89 setTokenMarker(tokenMarker);
90 setBorder(BorderFactory.createEtchedBorder());
91 getDocument().addUndoableEditListener(this);
92 addFocusListener(this);
93
94 undoAction = new UndoAction();
95 getInputHandler().addKeyBinding("C+Z", undoAction);
96 redoAction = new RedoAction();
97 getInputHandler().addKeyBinding("C+Y", redoAction);
98 findAndReplaceAction = new FindAndReplaceDialog( this );
99 getInputHandler().addKeyBinding( "C+F", findAndReplaceAction );
100 getInputHandler().addKeyBinding( "F3", findAndReplaceAction );
101 getInputHandler().addKeyBinding("A+RIGHT", new NextElementValueAction() );
102 getInputHandler().addKeyBinding("A+LEFT", new PreviousElementValueAction() );
103 getInputHandler().addKeyBinding("C+D", new DeleteLineAction() );
104 getInputHandler().addKeyBinding( "S+INSERT", getPasteAction() );
105 getInputHandler().addKeyBinding( "S+DELETE", getCutAction() );
106
107 setMinimumSize( new Dimension( 50, 50 ));
108 internalSettingsListener = new InternalSettingsListener();
109 SoapUI.getSettings().addSettingsListener( internalSettingsListener );
110 }
111
112 public Action getFindAndReplaceAction()
113 {
114 return findAndReplaceAction;
115 }
116
117 public Action getRedoAction()
118 {
119 return redoAction;
120 }
121
122 public Action getUndoAction()
123 {
124 return undoAction;
125 }
126
127 public void setText(String text)
128 {
129 if( text != null && text.equals( getText() ))
130 return;
131
132 super.setText( text == null ? "" : text);
133
134 if( discardEditsOnSet && undoManager != null )
135 undoManager.discardAllEdits();
136 }
137
138 public boolean isDiscardEditsOnSet()
139 {
140 return discardEditsOnSet;
141 }
142
143 public void setDiscardEditsOnSet(boolean discardEditsOnSet)
144 {
145 this.discardEditsOnSet = discardEditsOnSet;
146 }
147
148 public UndoManager getUndoManager()
149 {
150 return undoManager;
151 }
152
153 public SyntaxStyle[] createXmlStyles()
154 {
155 SyntaxStyle[] styles = new SyntaxStyle[Token.ID_COUNT];
156
157 styles[Token.COMMENT1] = new SyntaxStyle(Color.black, true, false);
158 styles[Token.COMMENT2] = new SyntaxStyle(new Color(0x990033), true, false);
159 styles[Token.KEYWORD1] = new SyntaxStyle(Color.blue, false, false);
160 styles[Token.KEYWORD2] = new SyntaxStyle(Color.magenta, false, false);
161 styles[Token.KEYWORD3] = new SyntaxStyle(new Color(0x009600), false,
162 false);
163 styles[Token.LITERAL1] = new SyntaxStyle(new Color(0x650099), false,
164 false);
165 styles[Token.LITERAL2] = new SyntaxStyle(new Color(0x650099), false, true);
166 styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033), false, true);
167 styles[Token.OPERATOR] = new SyntaxStyle(Color.black, false, true);
168 styles[Token.INVALID] = new SyntaxStyle(Color.red, false, true);
169
170 return styles;
171 }
172
173 private void createUndoMananger()
174 {
175 undoManager = new UndoManager();
176 undoManager.setLimit(UNDO_LIMIT);
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191 public void focusGained(FocusEvent fe)
192 {
193 if (isEditable() && undoManager == null )
194 createUndoMananger();
195 }
196
197 public void setEnabled( boolean flag )
198 {
199 super.setEnabled( flag );
200 setEditable( flag );
201 }
202
203 public void setEditable(boolean enabled)
204 {
205 super.setEditable(enabled);
206 setCaretVisible( enabled );
207 getPainter().setLineHighlightEnabled( enabled );
208
209
210 repaint();
211 }
212
213 public void focusLost(FocusEvent fe)
214 {
215
216 }
217
218 public void undoableEditHappened(UndoableEditEvent e)
219 {
220 if (undoManager != null)
221 undoManager.addEdit(e.getEdit());
222 }
223
224 private final class InternalSettingsListener implements SettingsListener
225 {
226 public void settingChanged(String name, String newValue, String oldValue)
227 {
228 if( name.equals( UISettings.EDITOR_FONT ))
229 {
230 getPainter().setFont( Font.decode( newValue ));
231 invalidate();
232 }
233 }
234 }
235
236 private class UndoAction extends AbstractAction
237 {
238 public UndoAction()
239 {
240 super( "Undo");
241 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Z" ));
242 }
243
244 public void actionPerformed(ActionEvent e)
245 {
246 if( !isEditable() )
247 {
248 getToolkit().beep();
249 return;
250 }
251
252 try
253 {
254 if( undoManager != null )
255 undoManager.undo();
256 }
257 catch (CannotUndoException cue)
258 {
259 Toolkit.getDefaultToolkit().beep();
260 }
261 }
262 }
263
264 private class RedoAction extends AbstractAction
265 {
266 public RedoAction()
267 {
268 super( "Redo");
269 putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "menu Y" ));
270 }
271
272 public void actionPerformed(ActionEvent e)
273 {
274 if( !isEditable() )
275 {
276 getToolkit().beep();
277 return;
278 }
279
280 try
281 {
282 if( undoManager != null )
283 undoManager.redo();
284 }
285 catch (CannotRedoException cue)
286 {
287 Toolkit.getDefaultToolkit().beep();
288 }
289 }
290 }
291
292 public class NextElementValueAction implements ActionListener
293 {
294 public void actionPerformed(ActionEvent e)
295 {
296 toNextElement();
297 }
298 }
299
300 public class PreviousElementValueAction implements ActionListener
301 {
302 public void actionPerformed(ActionEvent e)
303 {
304 toPreviousElement();
305 }
306 }
307
308
309 public class DeleteLineAction implements ActionListener
310 {
311 public void actionPerformed(ActionEvent e)
312 {
313 if( !isEditable() )
314 {
315 getToolkit().beep();
316 return;
317 }
318
319 int caretLine = getCaretLine();
320 if( caretLine == -1 ) return;
321 int lineStartOffset = getLineStartOffset( caretLine);
322 int lineEndOffset = getLineEndOffset( caretLine);
323
324 try
325 {
326 int len = lineEndOffset-lineStartOffset;
327 if( lineStartOffset+len >= getDocumentLength() ) len = getDocumentLength()-lineStartOffset;
328
329 getDocument().remove( lineStartOffset, len );
330 }
331 catch (BadLocationException e1)
332 {
333 e1.printStackTrace();
334 }
335 }
336 }
337
338 public Action getCopyAction()
339 {
340 return DefaultInputHandler.CLIP_COPY;
341 }
342
343 public Action getCutAction()
344 {
345 return DefaultInputHandler.CLIP_CUT;
346 }
347
348 public Action getPasteAction()
349 {
350 return DefaultInputHandler.CLIP_PASTE;
351 }
352
353 public int getCaretColumn()
354 {
355 int pos = getCaretPosition();
356 int line = getLineOfOffset( pos );
357
358 return pos - getLineStartOffset( line );
359 }
360
361 public void toNextElement()
362 {
363 int pos = getCaretPosition();
364 String text = getText();
365
366 while( pos < text.length() )
367 {
368
369 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
370 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
371 (pos > 1 && text.charAt(pos-1) != '/' ) &&
372 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
373 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
374 {
375 setCaretPosition( pos+1 );
376 return;
377 }
378
379 pos++;
380 }
381
382 getToolkit().beep();
383 }
384
385 public void toPreviousElement()
386 {
387 int pos = getCaretPosition()-2;
388 String text = getText();
389
390 while( pos > 0 )
391 {
392
393 if( text.charAt( pos ) == '>' && pos < text.length()-1 &&
394 (pos > 2 && !text.substring(pos-2,pos).equals( "--" )) &&
395 (pos > 1 && text.charAt(pos-1) != '/' ) &&
396 text.indexOf( '/', pos ) == text.indexOf( '<', pos) +1 &&
397 text.lastIndexOf( '/', pos ) != text.lastIndexOf( '<', pos) +1 )
398 {
399 setCaretPosition( pos+1 );
400 return;
401 }
402
403 pos--;
404 }
405
406 getToolkit().beep();
407 }
408 }