View Javadoc

1   /*
2    *  soapUI, copyright (C) 2006 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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 	private void removeUndoMananger()
182 	{
183 		if (undoManager == null)
184 			return;
185 		undoManager.end();
186 		undoManager = null;
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 		//getPainter().setBackground( enabled ? Color.WHITE : new Color(238, 238, 238) );
210 		repaint();
211 	}
212 
213 	public void focusLost(FocusEvent fe)
214 	{
215 		//removeUndoMananger();
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 			// find ending >
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 			// find ending >
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 }