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