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