View Javadoc

1   /*
2    *  soapui, copyright (C) 2005 Ole Matzura / 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.actions;
14  
15  import java.awt.BorderLayout;
16  import java.awt.GridLayout;
17  import java.awt.Toolkit;
18  import java.awt.event.ActionEvent;
19  import java.awt.event.KeyEvent;
20  
21  import javax.swing.AbstractAction;
22  import javax.swing.Action;
23  import javax.swing.BorderFactory;
24  import javax.swing.ButtonGroup;
25  import javax.swing.JButton;
26  import javax.swing.JCheckBox;
27  import javax.swing.JComboBox;
28  import javax.swing.JDialog;
29  import javax.swing.JLabel;
30  import javax.swing.JPanel;
31  import javax.swing.JRadioButton;
32  import javax.swing.KeyStroke;
33  
34  import com.eviware.soapui.SoapUI;
35  import com.eviware.soapui.support.JXmlTextArea;
36  import com.eviware.soapui.support.UISupport;
37  import com.jgoodies.forms.builder.ButtonBarBuilder;
38  
39  /***
40   * Find-and-Replace dialog for a JXmlTextArea
41   * 
42   * @author Ole.Matzura
43   */
44  
45  public class FindAndReplaceAction extends AbstractAction
46  {
47  	private final JXmlTextArea area;
48  	private JDialog dialog;
49  	private JCheckBox caseCheck;
50  	private JRadioButton allButton;
51  	private JRadioButton selectedLinesButton;
52  	private JRadioButton forwardButton;
53  	private JRadioButton backwardButton;
54  	private JCheckBox wholeWordCheck;
55  	private JButton findButton;
56  	private JButton replaceButton;
57  	private JButton replaceAllButton;
58  	private JComboBox findCombo;
59  	private JComboBox replaceCombo;
60  	private JCheckBox wrapCheck;
61  
62  	public FindAndReplaceAction(JXmlTextArea area)
63  	{
64  		super( "Find / Replace" );
65  		putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_F, KeyEvent.CTRL_MASK ));
66  		this.area = area;
67  	}
68  
69  	public void actionPerformed(ActionEvent e)
70  	{
71  		if( dialog == null )
72  			buildDialog();
73  		
74  		replaceCombo.setEnabled( area.isEditable() );
75  		replaceAllButton.setEnabled( area.isEditable() );
76  		replaceButton.setEnabled( area.isEditable() );
77  
78  		UISupport.showDialog( dialog );
79  		findCombo.getEditor().selectAll();
80  		findCombo.requestFocus();
81  	}
82  
83  	private void buildDialog()
84  	{
85  		dialog = new JDialog( SoapUI.getInstance().getFrame(), "Find / Replace", false );
86  	
87  		JPanel panel = new JPanel( new BorderLayout() );
88  		findCombo = new JComboBox();
89  		findCombo.setEditable( true );
90  		replaceCombo = new JComboBox();
91  		replaceCombo.setEditable( true );
92  		
93  		// create inputs
94  		GridLayout gridLayout = new GridLayout( 2, 2 );
95  		gridLayout.setVgap( 5 );
96  		JPanel inputPanel = new JPanel( gridLayout);
97  		inputPanel.add( new JLabel( "Find:"));
98  		inputPanel.add( findCombo );
99  		inputPanel.add( new JLabel( "Replace with:"));
100 		inputPanel.add( replaceCombo );
101 		inputPanel.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ));
102 		
103 		// create direction panel
104 		ButtonGroup directionGroup = new ButtonGroup();
105 		forwardButton = new JRadioButton("Forward",true);
106 		forwardButton.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
107 		directionGroup.add( forwardButton);
108 		backwardButton = new JRadioButton("Backward");
109 		backwardButton.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
110 		directionGroup.add( backwardButton);
111 		
112 		JPanel directionPanel = new JPanel( new GridLayout( 2, 1 ));
113 		directionPanel.add( forwardButton );
114 		directionPanel.add( backwardButton );
115 		directionPanel.setBorder( BorderFactory.createTitledBorder( "Direction") );
116 
117 		// create scope panel
118 		ButtonGroup scopeGroup = new ButtonGroup();
119 	   allButton = new JRadioButton( "All", true );
120 	   allButton.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
121 		selectedLinesButton = new JRadioButton( "Selected Lines" );
122 		selectedLinesButton.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
123 		scopeGroup.add( allButton );
124 	   scopeGroup.add( selectedLinesButton );
125 		
126 		JPanel scopePanel = new JPanel( new GridLayout( 2, 1 ));
127 		scopePanel.add( allButton );
128 		scopePanel.add( selectedLinesButton );
129 		scopePanel.setBorder( BorderFactory.createTitledBorder( "Scope") );
130 		
131 		// create options
132 		caseCheck = new JCheckBox("Case Sensitive");
133 		caseCheck.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
134 		wholeWordCheck = new JCheckBox("Whole Word");
135 		wholeWordCheck.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
136 		wrapCheck = new JCheckBox("Wrap Search");
137 		wrapCheck.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
138 		JPanel optionsPanel = new JPanel( new GridLayout( 3, 1 ));
139 		optionsPanel.add( caseCheck );
140 		optionsPanel.add( wholeWordCheck );
141 		optionsPanel.add( wrapCheck );
142 		optionsPanel.setBorder( BorderFactory.createTitledBorder( "Options") );
143 		
144 		// create panel with options
145 		JPanel options = new JPanel( new GridLayout( 1, 2 ) );
146 		
147 		JPanel radios = new JPanel( new GridLayout( 2, 1 ));
148 		radios.add( directionPanel );
149 		radios.add( scopePanel );
150 		
151 		options.add( optionsPanel );
152 		options.add( radios );
153 		options.setBorder( BorderFactory.createEmptyBorder( 0, 8, 0, 8 ));
154 		
155 		// create buttons
156 		ButtonBarBuilder builder = new ButtonBarBuilder();
157 		findButton = new JButton( new FindAction() );
158 		builder.addFixed( findButton);
159 		builder.addRelatedGap();
160 		replaceButton = new JButton( new ReplaceAction() );
161 		builder.addFixed( replaceButton);
162 		builder.addRelatedGap();
163 		replaceAllButton = new JButton( new ReplaceAllAction() );
164 		builder.addFixed( replaceAllButton);
165 		builder.addUnrelatedGap();
166 		builder.addFixed( new JButton( new CloseAction() ));
167 		builder.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ));
168 
169 		// tie it up!
170 		panel.add( inputPanel, BorderLayout.NORTH );
171 		panel.add( options, BorderLayout.CENTER );
172 		panel.add( builder.getPanel(), BorderLayout.SOUTH );
173 		
174 		dialog.getContentPane().add( panel );
175 		dialog.pack();
176 		dialog.getRootPane().setDefaultButton( findButton );
177 	}
178 	
179 	private int findNext(int pos, String txt, String value)
180 	{
181 		int ix = forwardButton.isSelected() ? txt.indexOf( value, pos+1 ) :
182 				  txt.lastIndexOf( value, pos-1 );
183 		
184 		if( wholeWordCheck.isSelected() )
185 		{
186 			while( ix != -1 &&  
187 					(ix > 0 && Character.isLetterOrDigit( txt.charAt( ix-1 ))) ||
188 					(ix < txt.length()-value.length()-1 && Character.isLetterOrDigit( txt.charAt( ix+value.length() ))))
189 			{
190 				ix = findNext(ix, txt, value);
191 			}
192 		}
193 
194 		if( ix == -1 && wrapCheck.isSelected() ) 
195 		{
196 			if( forwardButton.isSelected() && pos > 0 )
197 				return findNext( 0, txt, value );
198 			else if( backwardButton.isSelected() && pos < txt.length()-1 )
199 				return findNext( txt.length()-1, txt, value );
200 		}
201 		
202 		if( selectedLinesButton.isSelected() && 
203 				(ix < area.getSelectionStart() || ix > area.getSelectionEnd() ) )
204 			ix = -1;
205 		
206 		return ix;
207 	}
208 	
209 	private class FindAction extends AbstractAction
210 	{
211 		public FindAction()
212 		{
213 			super( "Find" );
214 		}
215 		
216 		public void actionPerformed(ActionEvent e)
217 		{
218 			int pos = area.getCaretPosition();
219 			String txt = area.getText();
220 			
221 			String value = findCombo.getSelectedItem().toString();
222 			if( value.length() == 0 || pos == txt.length() ) return;
223 			
224 			if( !caseCheck.isSelected())
225 			{
226 				value = value.toUpperCase();
227 				txt = txt.toUpperCase();
228 			}
229 			
230 			int ix = findNext(pos, txt, value);
231 			
232 			if( ix != -1 )
233 			{
234 				area.select( ix + value.length(), ix );
235 				
236 				for( int c = 0; c < findCombo.getItemCount(); c++ )
237 				{
238 					if( findCombo.getItemAt( c ).equals( value ))
239 					{
240 						findCombo.removeItem( c );
241 						break;
242 					}
243 				}
244 				
245 				findCombo.insertItemAt( value, 0 );
246 			}
247 			else Toolkit.getDefaultToolkit().beep();
248 		}
249 	}	
250 	
251 	private class ReplaceAction extends AbstractAction
252 	{
253 		public ReplaceAction()
254 		{
255 			super( "Replace" );
256 		}
257 		
258 		public void actionPerformed(ActionEvent e)
259 		{
260 			if( area.getSelectedText() == null )
261 				return;
262 			
263 			String value = replaceCombo.getSelectedItem().toString();
264 			int ix = area.getSelectionStart();
265 			area.setSelectedText( value );
266 			area.select( ix+value.length(), ix );
267 			
268 			for( int c = 0; c < replaceCombo.getItemCount(); c++ )
269 			{
270 				if( replaceCombo.getItemAt( c ).equals( value ))
271 				{
272 					replaceCombo.removeItem( c );
273 					break;
274 				}
275 			}
276 			
277 			replaceCombo.insertItemAt( value, 0 );
278 		}
279 	}	
280 	
281 	private class ReplaceAllAction extends AbstractAction
282 	{
283 		public ReplaceAllAction()
284 		{
285 			super( "Replace All" );
286 		}
287 		
288 		public void actionPerformed(ActionEvent e)
289 		{
290 			int pos = area.getCaretPosition();
291 			String txt = area.getText();
292 			
293 			String value = findCombo.getSelectedItem().toString();
294 			if( value.length() == 0 || txt.length() == 0 ) return;
295 			String newValue = replaceCombo.getSelectedItem().toString();
296 			
297 			if( !caseCheck.isSelected() )
298 			{
299 				if( newValue.equalsIgnoreCase( value )) return;
300 				value = value.toUpperCase();
301 				txt = txt.toUpperCase();
302 			}
303 			else if( newValue.equals( value )) return;
304 			
305 			int ix = findNext(pos, txt, value);
306 			int firstIx = ix;
307 			int valueInNewValueIx = !caseCheck.isSelected() ? newValue.toUpperCase().indexOf( value ) : newValue.indexOf( value );
308 			
309 			while( ix != -1 )
310 			{
311 				System.out.println( "found match at " + ix+ ", " + firstIx + ", " + valueInNewValueIx);
312 				area.select( ix + value.length(), ix );
313 				
314 				area.setSelectedText( newValue );
315 				area.select( ix+newValue.length(), ix );
316 				
317 				// adjust firstix 
318 				if( ix < firstIx )
319 					firstIx += newValue.length()-value.length();
320 				
321 				txt = area.getText();
322 				if( !caseCheck.isSelected())
323 				{
324 					txt = txt.toUpperCase();
325 				}
326 				
327 				ix = findNext(ix+newValue.length(), txt, value);
328 				if( wrapCheck.isSelected() && valueInNewValueIx != -1 && ix == firstIx+valueInNewValueIx )
329 				{
330 					break;
331 				}					
332 			}
333 		}
334 	}	
335 	
336 	private class CloseAction extends AbstractAction
337 	{
338 		public CloseAction()
339 		{
340 			super( "Close" );
341 		}
342 		
343 		public void actionPerformed(ActionEvent e)
344 		{
345 			dialog.setVisible( false );
346 		}
347 	}	
348 
349 }