View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 eviware.com
    3    *
    4    *  soapUI is free software; you can redistribute it and/or modify it under the
    5    *  terms of version 2.1 of the GNU Lesser General Public License as published by
    6    *  the Free Software Foundation.
    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  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 	private void removeUndoMananger()
    198 	{
    199 		if (undoManager == null)
    200 			return;
    201 		undoManager.end();
    202 		undoManager = null;
    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 		//getPainter().setBackground( enabled ? Color.WHITE : new Color(238, 238, 238) );
    226 		repaint();
    227 	}
    228
    229 	public void focusLost(FocusEvent fe)
    230 	{
    231 		//removeUndoMananger();
    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 //					System.out.println( "Waiting for weak references to be released.." );
    253
    254 					try
    255 					{
    256 						Reference<? extends JXEditTextArea> ref = testQueue.remove();
    257 //						System.out.println( "Got ref to clear" );
    258 						InternalSettingsListener listener = testMap.remove( ref );
    259 						if( listener != null )
    260 						{
    261 //							System.out.println( "Releasing listener" );
    262 							listener.release();
    263 						}
    264 						else
    265 						{
    266 //							System.out.println( "Listener not found" );
    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 //			System.out.println( "Creating weakreference for textarea" );
    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 			// find ending >
    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 			// find ending >
    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 }