source: trunk/gli/src/org/greenstone/gatherer/sarm/SearchAndReplace.java@ 4360

Last change on this file since 4360 was 4360, checked in by jmt12, 21 years ago

Added extra argument to msm.updateMetadata() so we can determine if its file or folder level - John

  • Property svn:keywords set to Author Date Id Revision
File size: 37.9 KB
Line 
1package org.greenstone.gatherer.sarm;
2/**
3 *#########################################################################
4 *
5 * A component of the Gatherer application, part of the Greenstone digital
6 * library suite from the New Zealand Digital Library Project at the
7 * University of Waikato, New Zealand.
8 *
9 * <BR><BR>
10 *
11 * Author: John Thompson, Greenstone Digital Library, University of Waikato
12 *
13 * <BR><BR>
14 *
15 * Copyright (C) 1999 New Zealand Digital Library Project
16 *
17 * <BR><BR>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * <BR><BR>
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * <BR><BR>
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
36 *########################################################################
37 */
38import java.awt.*;
39import java.awt.event.*;
40import java.util.*;
41import java.util.regex.*;
42import javax.swing.*;
43import javax.swing.tree.*;
44import org.greenstone.gatherer.Gatherer;
45import org.greenstone.gatherer.file.FileNode;
46import org.greenstone.gatherer.gui.CollectionPane;
47import org.greenstone.gatherer.gui.MetaEditPane;
48import org.greenstone.gatherer.gui.MetaEditPrompt;
49import org.greenstone.gatherer.gui.table.GTableModel;
50import org.greenstone.gatherer.msm.ElementWrapper;
51import org.greenstone.gatherer.msm.Metadata;
52import org.greenstone.gatherer.util.EnumeratedVector;
53import org.greenstone.gatherer.util.Utility;
54import org.greenstone.gatherer.util.XORToggleButtonGroup;
55import org.greenstone.gatherer.valuetree.GValueModel;
56import org.greenstone.gatherer.valuetree.GValueNode;
57/** Provides a convenient class which searches through all of the various data attached to a collection, globally searching and replacing values as necessary, and firing the relevant events to have the gui update properly.
58 * @author John Thompson, Greenstone Digital Library, University of Waikato
59 * @version 2.3
60 */
61public class SearchAndReplace
62 extends JDialog {
63 /** <i>true</i> if this dialog should allow for the replace and replace all buttons to be validated, <i>false</i> otherwise. */
64 private boolean enable_replace = false;
65 /** <i>true</i> if the last call to find() found something, <i>false</i> otherwise. */
66 private boolean found = false;
67 /** The size of the current screen real-estate. */
68 private Dimension screen_size = Toolkit.getDefaultToolkit().getScreenSize();
69 /** The metadata element that we are currently inspecting. If we are anywhere but within the find() function, then this is the last element found that matched the search string. */
70 private ElementWrapper current_element = null;
71 /** A vector containing the result of a depth first enumeration of the records in the collection record set tree. */
72 private EnumeratedVector records = null;
73 /** The file record that we are currently inspecting. If we are anywhere but within the find() function, and the target type is a FileNode, then this is the last record that matched our search string. Otherwise this is the record that the last metadata value that matched resides in. */
74 private FileNode current_record = null;
75 /** The button used to close the dialog. Has the same action as the destroy button. */
76 private JButton close_button = null;
77 /** The button used to begin a find() function call. Something must be typed in the search field before this button becomes active. */
78 private JButton find_button = null;
79 /** The button used to repeat the find() call in order to find the next matching object. Note this only becomes active after a successful find button press. */
80 private JButton find_next_button = null;
81 /** Redoes the action the was last undoes. Doesn't become active until there are job that are undone to be redone. :P */
82 private JButton redo_button = null;
83 /** Replaces the currently selected object with the given replacement text. The selection was determined by find() either as part of a previous action, or immediately before the replace() is called. This button only become active if there is something typed in the search and replace fields and if enable_replace is set. */
84 private JButton replace_button = null;
85 /** Replace all occurances of the search string with the replace string. This returns to the start of the collection and replaced every single matching reference. It only becomes enabled if something is typed in the search and replace fields and enable_replace is set. */
86 private JButton replace_all_button = null;
87 /** When pressed this button returns the 'searching pointer' back to the start of the collection. This is used when there are no further matches for your query, or at anytime you wish to start a new query. This button only becomes active after a success find(). */
88 private JButton reset_button = null;
89 /** This button undoes any changes made by the previous action, such as restoring all objects affected by a replace all. It only makes sense for this button to be active after a successful action. */
90 private JButton undo_button = null;
91 /** When checked this control indicates the search comparisons should be made taking into account case. Otherwise the case becomes irrelevant to matching. */
92 private JCheckBox case_sensitive = null;
93 /** When checked the SARM treats both search and replace string as regular expression when matching and replacing. */
94 private JCheckBox regular_expression = null;
95 /** When checked the SARM will search the metadata element identifiers for values that match your search string. Note that at least one of the search domains is selected at all times. */
96 private JCheckBox search_elements = null;
97 /** When checked the SARM will search the file records in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */
98 private JCheckBox search_records = null;
99 /** When checked the SARM will search the metadata values associated with each file record in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */
100 private JCheckBox search_values = null;
101 /** The status label shows the result of the last action. */
102 private JLabel status = null;
103 /** This field is used to enter a string which is in turn used to replace any text value that matches the search string. If enable_replace is not set, this field remains grayed out. If enabled, and you type within this field the replace buttons will become active. */
104 private JTextField replace = null;
105 /** The value entered in the search field is used to determine what object should be selected and brought to your attention. Note that this field can contain plain text or a regular expression, and that any typing in this field automatically resets the find() functions state to the start of the collection. */
106 private JTextField search = null;
107 /** Whenever a string is tested against the compiled regular Pattern, a Matcher is created. Not only does this matcher allow us to determine if the test string matches the pattern, but is can be subsequently used during a replace action to evaluate special 'capture group flags' within a regular expression based replace string.<BR><BR>For instance:Seach - '(.*)fug(.*)' will match 'Refugee'<BR>and<BR>Replace - '\1\1\1. \2' will replace the match with 'ReReRe. ee'. */
108 private Matcher matcher = null;
109 /** This is the current metadata value that we are inspecting. If non-null anywhere other then in the find() method, then this is the last metadata value that matched on search string. However it makes no sense by itself, and current_record is needed to actually reference this piece of metadata. */
110 private Metadata current_value = null;
111 /** This is used to determine the target class of any object whose text component we are trying to replace. It is actually set to the last object that matched the search string. */
112 private Object target = null;
113 /** This object is created by compiling a regular expression string retrieved from the search field, and in only non-null when regualr_expressions is checked. By compiling it once at the very beginning of the find() method, we lower processing time. Note that pattern is set to null whenever the find() state is reset. */
114 private Pattern pattern = null;
115 /** A reference to ourselves so that our inner classes can dispose of us for the insurance money. */
116 private SearchAndReplace self = null;
117 /** A stack of actions that have been undone, wait to be done again. */
118 private Stack redo = new Stack();
119 /** A stack of actions that have been done, waiting to undo. */
120 private Stack undo = new Stack();
121 /** A vector of the metadata elements attached to this collection, in the form of ElementWrappers for ease of data access. */
122 private Vector elements = null;
123 /** The default size for a label, used to produce purty column alignment. */
124 static final private Dimension LABEL_SIZE = new Dimension(120, 30);
125 /** The default size for the dialog. */
126 static final private Dimension SIZE = new Dimension(640,210);
127 /** Constructor.
128 * @param enable_replace <i>true</i> if this dialog can be used for replace actions, <i>false</i> otherwise.
129 */
130 public SearchAndReplace(boolean enable_replace) {
131 super();
132 ///ystem.err.println("Creating a new search and replace dialog.");
133 this.enable_replace = enable_replace;
134 this.self = this;
135 // Creation
136 setModal(false);
137 setSize(SIZE);
138 setTitle(get("Title"));
139 JPanel content_pane = (JPanel) getContentPane();
140
141 JPanel central_pane = new JPanel();
142
143 JPanel search_pane = new JPanel();
144 JLabel search_label = new JLabel(get("Search"));
145 search_label.setPreferredSize(LABEL_SIZE);
146 search = new JTextField("");
147
148 JPanel replace_pane = new JPanel();
149 JLabel replace_label = new JLabel(get("Replace"));
150 replace_label.setPreferredSize(LABEL_SIZE);
151 replace = new JTextField("");
152 replace.setEnabled(enable_replace);
153 if(!enable_replace) {
154 replace.setBackground(Color.lightGray);
155 }
156
157 JPanel options_pane = new JPanel();
158 JLabel options_label = new JLabel(get("Options"));
159 options_label.setPreferredSize(LABEL_SIZE);
160 JPanel inner_options_pane = new JPanel();
161 case_sensitive = new JCheckBox(get("Case_Sensitive"));
162 regular_expression = new JCheckBox(get("Regular_Expression"));
163
164 JPanel options2_pane = new JPanel();
165 JLabel options2_label = new JLabel(get("Search_In"));
166 options2_label.setPreferredSize(LABEL_SIZE);
167 JPanel inner_options2_pane = new JPanel();
168 XORToggleButtonGroup check_box_group = new XORToggleButtonGroup();
169 search_elements = new JCheckBox(get("Search_Elements"));
170 search_elements.setSelected(true);
171 check_box_group.add(search_elements);
172 search_records = new JCheckBox(get("Search_Records"));
173 search_records.setSelected(true);
174 check_box_group.add(search_records);
175 search_values = new JCheckBox(get("Search_Values"));
176 search_values.setSelected(true);
177 check_box_group.add(search_values);
178
179 JPanel results_pane = new JPanel();
180 status = new JLabel(get("Status"));
181
182 JPanel buttons_pane = new JPanel();
183 find_button = new JButton(get("Find_Button"));
184 find_button.setMnemonic(KeyEvent.VK_F);
185 find_next_button = new JButton(get("Find_Next_Button"));
186 find_next_button.setEnabled(false);
187 find_next_button.setMnemonic(KeyEvent.VK_N);
188 replace_button = new JButton(get("Replace_Button"));
189 replace_button.setEnabled(false);
190 replace_button.setMnemonic(KeyEvent.VK_R);
191 replace_all_button = new JButton(get("Replace_All_Button"));
192 replace_all_button.setEnabled(false);
193 replace_all_button.setMnemonic(KeyEvent.VK_N);
194 reset_button = new JButton(get("Reset"));
195 reset_button.setEnabled(false);
196 reset_button.setMnemonic(KeyEvent.VK_ESCAPE);
197 undo_button = new JButton(get("Undo"));
198 undo_button.setEnabled(false);
199 undo_button.setMnemonic(KeyEvent.VK_U);
200 redo_button = new JButton(get("Redo"));
201 redo_button.setEnabled(false);
202 redo_button.setMnemonic(KeyEvent.VK_D);
203 close_button = new JButton(get("General.Close"));
204 close_button.setMnemonic(KeyEvent.VK_C);
205 // Connection
206 close_button.addActionListener(new CloseActionListener());
207 find_button.addActionListener(new FindActionListener());
208 find_next_button.addActionListener(new FindNextActionListener());
209 redo_button.addActionListener(new RedoActionListener());
210 replace.addActionListener(new FindActionListener());
211 replace_button.addActionListener(new ReplaceActionListener());
212 replace_all_button.addActionListener(new ReplaceAllActionListener());
213 reset_button.addActionListener(new ResetActionListener());
214 undo_button.addActionListener(new UndoActionListener());
215 replace.addKeyListener(new ReplaceKeyListener());
216 search.addKeyListener(new SearchKeyListener());
217 // Layout
218 search_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
219
220 search_pane.setLayout(new BorderLayout());
221 search_pane.add(search_label, BorderLayout.WEST);
222 search_pane.add(search, BorderLayout.CENTER);
223
224 replace_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
225
226 replace_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
227 replace_pane.setLayout(new BorderLayout());
228 replace_pane.add(replace_label, BorderLayout.WEST);
229 replace_pane.add(replace, BorderLayout.CENTER);
230
231 inner_options_pane.setLayout(new GridLayout(1,2));
232 inner_options_pane.add(case_sensitive);
233 inner_options_pane.add(regular_expression);
234
235 options_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
236 options_pane.setLayout(new BorderLayout());
237 options_pane.add(options_label, BorderLayout.WEST);
238 options_pane.add(inner_options_pane, BorderLayout.CENTER);
239
240 inner_options2_pane.setLayout(new GridLayout(1,3));
241 inner_options2_pane.add(search_elements);
242 inner_options2_pane.add(search_records);
243 inner_options2_pane.add(search_values);
244
245 options2_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
246 options2_pane.setLayout(new BorderLayout());
247 options2_pane.add(options2_label, BorderLayout.WEST);
248 options2_pane.add(inner_options2_pane, BorderLayout.CENTER);
249
250 results_pane.setBorder(BorderFactory.createRaisedBevelBorder());
251 results_pane.setLayout(new BorderLayout());
252 results_pane.add(status, BorderLayout.CENTER);
253
254 central_pane.setLayout(new GridLayout(5,1));
255 central_pane.add(search_pane);
256 central_pane.add(replace_pane);
257 central_pane.add(options_pane);
258 central_pane.add(options2_pane);
259 central_pane.add(results_pane);
260
261 buttons_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
262 buttons_pane.setLayout(new GridLayout(2,4));
263 buttons_pane.add(find_button);
264 buttons_pane.add(find_next_button);
265 buttons_pane.add(replace_button);
266 buttons_pane.add(replace_all_button);
267 buttons_pane.add(reset_button);
268 buttons_pane.add(undo_button);
269 buttons_pane.add(redo_button);
270 buttons_pane.add(close_button);
271
272 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
273 content_pane.setLayout(new BorderLayout());
274 content_pane.add(central_pane, BorderLayout.CENTER);
275 content_pane.add(buttons_pane, BorderLayout.SOUTH);
276 // Reset data.
277 reset();
278 // Restore visual position.
279 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
280 show();
281 }
282 /** Updates the status bar to reflect the result of the latest action. Includes the ability to reset the status label whenever the find() state resets.
283 * @param key A <strong>String</strong> of text to use as the explaination of the actions result.
284 * @param optional_count An <i>int</i> which if any value, other than -1, shows the number of objects the action was performed on.
285 */
286 private void changeStatus(String key, int optional_count) {
287 String args[] = null;
288 if(optional_count >= 0) {
289 args = new String[2];
290 args[1] = "" + optional_count + "";
291 }
292 else {
293 args = new String[1];
294 }
295 if(key != null) {
296 args[0] = search.getText();
297 status.setText(get("Status") + ": " + get(key, args));
298 }
299 else {
300 status.setText(get("Status"));
301 }
302 }
303 /** The guts of this module, the find method, in conjunction with the matches method, searches through the several targetted domains looking for values which match the search string, returning true while leaving itself in a state that points to the matching object if one is found. What exact part of each object is matched against is decided by the objects type, and in one case this is made even more complex as one type of object exists inside the other so we need to carefully iterate through the inner before increamenting the outer.
304 * @param <i>true</i> if a match was found thus the current_<object> parameters are pointing to it, otherwise <i>false</i>.
305 */
306 private boolean find() {
307 boolean local_found = false;
308 String find_str = search.getText();
309 if(find_str.length() > 0) {
310 // If a pattern currently hasn't been compiled, compile one.
311 if(pattern == null) {
312 // If no actual regular expression script is present add .* to either end of the search string so we actually search for content of the target string not exact equality.
313 if(!regular_expression.isSelected()) {
314 find_str = "(.*)("+find_str+")(.*)";
315 }
316 // Compile pattern
317 try {
318 if(case_sensitive.isSelected()) {
319 pattern = Pattern.compile(find_str);
320 }
321 else {
322 pattern = Pattern.compile(find_str.toLowerCase());
323 }
324 }
325 catch(Exception error) {
326 }
327 }
328 // Search through the metadata elements if required for a matching entry.
329 if(search_elements.isSelected()) {
330 for(int i = (current_element != null ? elements.indexOf(current_element) + 1 : 0); !local_found && i < elements.size(); i++) {
331 current_element = (ElementWrapper) elements.get(i);
332 if(match(current_element.getIdentity())) {
333 local_found = true;
334 target = current_element;
335 fireSelect(current_element);
336 }
337 }
338 }
339 // Search through file names.
340 if(!local_found && (search_records.isSelected() || search_values.isSelected()) && (records.hasMoreElements() || current_record != null)) {
341 if(current_record == null) {
342 current_record = (FileNode)records.nextElement();
343 }
344 while(!local_found && (current_record != null || records.hasMoreElements())) {
345 // Try to match current record (if we haven't already).
346 if(match(current_record.toString()) && search_records.isSelected() && current_record != target) {
347 local_found = true;
348 target = current_record;
349 fireSelect(current_record);
350 }
351 else {
352 // Search through metadata values is required.
353 if(search_values.isSelected()) {
354 ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getMetadata(current_record.getFile());
355 for(int i = (current_value != null ? metadatum.indexOf(current_value) + 1 : 0); !local_found && i < metadatum.size(); i++) {
356 current_value = (Metadata)metadatum.get(i);
357 if(match(current_value.getValue())) {
358 local_found = true;
359 target = current_value;
360 fireSelect(current_record, current_value);
361 }
362 }
363 if(!local_found) {
364 current_value = null;
365 }
366 }
367 }
368 if(!local_found) {
369 current_record = (FileNode)records.nextElement();
370 }
371 }
372 }
373 }
374 return local_found;
375 }
376 /** When a metadata element that matches our search string is found, this method is called to provide a visual cue as to what element matched in which collection. This is done by changing the tab view of the main gui and selecting the correct line from the list of sets/elements on one screen of the CDM. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row).
377 * @param element The <strong>ElementWrapper</strong> that matches our search string.
378 */
379 private void fireSelect(ElementWrapper element) {
380 // Ensure the CDM view is visible.
381 Gatherer.g_man.setSelectedView(Gatherer.g_man.config_pane);
382 // Select the correct metadata element
383 Rectangle bounds = Gatherer.g_man.config_pane.setSelectedElement(element);
384 unhideSelection(bounds);
385 }
386 /** When a file record that matches our search string is found, this method is called to provide a visual cue as to what file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row).
387 * @param record The <strong>FileNode</strong> that matches our search string.
388 */
389 private void fireSelect(FileNode record) {
390 // Ensure that either the CollectionPane or the MetaEditPane is visible. If not make the CollectionPane visible.
391 Gatherer.g_man.setSelectedView(Gatherer.g_man.collection_pane);
392 // Select the appropriate record.
393 TreePath path = new TreePath(record.getPath());
394 Rectangle bounds = Gatherer.g_man.collection_pane.expandPath(path);
395 unhideSelection(bounds);
396 }
397 /** When a metadata value that matches our search string is found, this method is called to provide a visual cue as to what value from which file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records, and then highlighting the appropriate line of the metadata table. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row).
398 * @param record The <strong>FileNode</strong> that matches our search string.
399 */
400 private void fireSelect(FileNode record, Metadata metadata) {
401 // Ensure that either the MetaEditPane is visible.
402 Gatherer.g_man.setSelectedView(Gatherer.g_man.metaedit_pane);
403 // Select the appropriate record in the metaedit pane tree, which will cause the Metadata table to update with the appropriate bunch of metadata...
404 TreePath path = new TreePath(record.getPath());
405 Gatherer.g_man.metaedit_pane.expandPath(path);
406 // Now select the correct metadata.
407 Rectangle bounds = Gatherer.g_man.metaedit_pane.setSelectedMetadata(metadata);
408 unhideSelection(bounds);
409 }
410 /** Retrieve a pharse string from the Dictionary matching the given key.
411 * @param key The <strong>String</strong> which maps to a certain phrase.
412 * @return The required, locale specific, phrase as a <strong>String</strong>.
413 * @see org.greenstone.gatherer.Dictionary
414 */
415 private String get(String key) {
416 return get(key, null);
417 }
418 /** Retrieve a pharse string from the Dictionary matching the given key and based apon the given arguments.
419 * @param key The <strong>String</strong> which maps to a certain phrase.
420 * @param args The argument Strings to the Dictionary, as a <strong>String[]</strong>.
421 * @return The required, locale specific, phrase as a <strong>String</strong>.
422 * @see org.greenstone.gatherer.Dictionary
423 */
424 private String get(String key, String args[]) {
425 if(key.indexOf(".") == -1) {
426 key = "SearchAndReplace." + key;
427 }
428 return Gatherer.dictionary.get(key, args);
429 }
430 /** Used to determine is one string matches another tkaing into account the options of case sensitivity and regular expressions.
431 * @param find_str The search <strong>String</strong> we are trying to find. This may be a regular expression.
432 * @param target_str The <strong>String</strong> we are matching it against, as provided by some object.
433 * @return <i>true</i> if the Strings match given the option settings, <i>false</i> otherwise.
434 */
435 private boolean match(String target_str) {
436 ///ystem.err.println("Matching " + target_str + " against " + pattern.pattern());
437 boolean result = false;
438 if(!case_sensitive.isSelected()) {
439 target_str = target_str.toLowerCase();
440 }
441 try {
442 matcher = pattern.matcher(target_str);
443 result = matcher.matches();
444 }
445 catch(Exception error) {
446 }
447 return result;
448 }
449 /** Called once by the replace button, or repeatedly by replace all, in order to replace the text of the target object, as referenced by the current_<object>s, with the replace text string. This takes into account if regular expressions are expected, and modifies the replacement string with appropriate 'capture groups'.
450 * @return <i>true</i> if the replace action was successful, <i>false</i> otherwise.
451 */
452 private boolean replace() {
453 boolean result = false;
454 String replace_str = replace.getText();
455 // If regular expressions are allowed, we have to search for regular expression commands and deal with them.
456 if(matcher != null) {
457 StringBuffer temp = new StringBuffer("");
458 int index_ptr = 0;
459 while(index_ptr < replace_str.length()) {
460 char c = replace_str.charAt(index_ptr);
461 // First we check for escaped regualr expression commands which start '\\'
462 if(c == '\\') {
463 index_ptr++;
464 char s = replace_str.charAt(index_ptr);
465 if(s == '\\') {
466 index_ptr++;
467 char i = replace_str.charAt(index_ptr);
468 // I currently only support one regular expression command, 'capture groups'.
469 if(Character.isDigit(i)) {
470 try {
471 int group_num = Character.getNumericValue(i);
472 if(group_num <= matcher.groupCount()) {
473 temp.append(matcher.group(group_num));
474 }
475 }
476 catch (Exception error) {
477 }
478 }
479 }
480 // The \ had nothing to do with groups, so I guess its a path marker.
481 else {
482 temp.append(c);
483 temp.append(s);
484 }
485 }
486 else {
487 temp.append(c);
488 }
489 index_ptr++;
490 }
491 replace_str = temp.toString();
492 }
493 if(found && replace_str.length() > 0) {
494 if(target instanceof ElementWrapper) {
495 undo.push(new UndoItem(current_element, current_element.getIdentity(), replace_str));
496 updateElement(current_element, replace_str);
497 result = true;
498 }
499 if(target instanceof FileNode) {
500 undo.push(new UndoItem(current_record, current_record.toString(), replace_str));
501 updateRecord(current_record, replace_str);
502 result = true;
503 }
504 if(target instanceof Metadata) {
505 // If we are replacing metadata value there is a bit more messaging we need to do to any replacement string to ensure that any hierarchy context is transferred to the new value, so...
506 String absolute_value = current_value.getAbsoluteValue();
507 if(absolute_value.indexOf("\\") != -1) {
508 String absolute_path = absolute_value.substring(0, absolute_value.lastIndexOf("\\") + 1);
509 replace_str = absolute_path + replace_str;
510 ///ystem.err.println("I replaced a hierarchy value like so:\n"+absolute_value+"\nto\n"+replace_str);
511 }
512 undo.push(new UndoItem(current_value, current_record, current_value.getValue(), replace_str));
513 updateMetadata(current_value, current_record, replace_str);
514 result = true;
515 }
516 }
517 ///ystem.err.println("Undo queue length = " + undo.size());
518 return result;
519 }
520 /** Used to restore the search space within the collection to its initial state for find().
521 */
522 private void reset() {
523 current_element = null;
524 current_record = null;
525 current_value = null;
526 elements = Gatherer.c_man.getCollection().msm.getElements();
527 found = false;
528 matcher = null;
529 pattern = null;
530 records = Utility.depthFirstEnumeration((TreeNode)Gatherer.c_man.getRecordSet().getRoot(), new EnumeratedVector());
531 target = null;
532 }
533 /** Attempts to move the dialog so that it doesn't obscure the coordinates detailed. Can't always do so.
534 * @param bounds A <strong>Rectangle</strong> around the selected component, with co-ordinates corrected to be absolute to the screen rather than relative to whatever parent component the selection was in.
535 */
536 private void unhideSelection(Rectangle bounds) {
537 /** @TODO - Elsewhere generate absolute references. Here make use of them. */
538 ///ystem.err.println("Unhide selection with bounds " + bounds);
539 }
540 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of an element it uses this method. Hows that for code reuse.
541 * @param element The <strong>ElementWrapper</strong> containing the metadata element we want to change.
542 * @param value The new text value for the elements identifier as a <strong>String</strong>.
543 */
544 private void updateElement(ElementWrapper element, String value) {
545 Gatherer.c_man.getCollection().msm.renameElement(element, value);
546 }
547 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a metadata value it uses this method. Hows that for code reuse.
548 * @param metadata The <strong>Metadata</strong> we want to change.
549 * @param record The <strong>FileNode</strong> the target metadata will be found in.
550 * @param value The new text value for the metadata as a <strong>String</strong>.
551 */
552 private void updateMetadata(Metadata metadata, FileNode record, String value) {
553 FileNode array[] = new FileNode[1];
554 array[0] = record;
555 Gatherer.c_man.getCollection().msm.updateMetadata(0L, metadata, array, value, MetaEditPrompt.UPDATE_ONCE, metadata.isFileLevel());
556 }
557 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a file record it uses this method. Hows that for code reuse.
558 * @param record The <strong>FileNode</strong> we want to change.
559 * @param value The new text value for the files name as a <strong>String</strong>.
560 */
561 private void updateRecord(FileNode record, String value) {
562 TreeModel model = Gatherer.c_man.getRecordSet();
563 /* @todo
564 model.rename(record, value);
565 */
566 }
567 /** Several actions methods call this to check if the current state of the dialog allows for the replace butons to become active.
568 */
569 private void validateReplaceButtons() {
570 boolean value = (replace.getText().length() > 0) && enable_replace;
571 ///ystem.err.println("Validating replace buttons to " + value);
572 replace_button.setEnabled(value);
573 replace_all_button.setEnabled(value);
574 }
575
576 private class CloseActionListener
577 implements ActionListener {
578 public void actionPerformed(ActionEvent event) {
579 self.dispose();
580 }
581 }
582
583 private class FindActionListener
584 implements ActionListener {
585 public void actionPerformed(ActionEvent event) {
586 reset();
587 if(find()) {
588 find_button.setEnabled(false);
589 find_next_button.setEnabled(true);
590 if(enable_replace) {
591 replace_button.setEnabled(true);
592 replace_all_button.setEnabled(true);
593 }
594 reset_button.setEnabled(true);
595 changeStatus("Found", -1);
596 found = true;
597 }
598 else {
599 changeStatus("Not_Found", -1);
600 found = false;
601 }
602 }
603 }
604
605 private class FindNextActionListener
606 implements ActionListener {
607 public void actionPerformed(ActionEvent event) {
608 if(find()) {
609 changeStatus("Found", -1);
610 found = true;
611 }
612 else {
613 find_next_button.setEnabled(false);
614 changeStatus("Not_Found", -1);
615 }
616 }
617 }
618
619 private class RedoActionListener
620 implements ActionListener {
621 private boolean start = false;
622 public void actionPerformed(ActionEvent event) {
623 if(!redo.empty()) {
624 start = true;
625 UndoItem redo_item = (UndoItem) redo.pop();
626 while(redo_item != null && !redo_item.isDummy()) {
627 // Determine the type of action we have to undo, then undo it.
628 ElementWrapper element = redo_item.getElement();
629 if(element != null) {
630 updateElement(element, redo_item.getAfter());
631 addToUndo(redo_item);
632 }
633 FileNode record = redo_item.getRecord();
634 Metadata metadata = redo_item.getMetadata();
635 if(metadata != null && record != null) {
636 updateMetadata(metadata, record, redo_item.getAfter());
637 addToUndo(redo_item);
638 }
639 else if(record != null) {
640 updateRecord(record, redo_item.getAfter());
641 addToUndo(redo_item);
642 }
643 if(!redo.empty()) {
644 redo_item = (UndoItem) redo.pop();
645 }
646 else {
647 redo_item = null;
648 }
649 }
650 if(redo.empty()) {
651 redo_button.setEnabled(false);
652 }
653 }
654 }
655 private void addToUndo(UndoItem redo_item) {
656 if(start) {
657 undo.push(new UndoItem());
658 start = false;
659 }
660 undo.push(redo_item);
661 undo_button.setEnabled(true);
662 }
663 }
664
665 private class ReplaceActionListener
666 implements ActionListener {
667 public void actionPerformed(ActionEvent event) {
668 if(!found) {
669 found = find();
670 }
671 if(found) {
672 undo.push(new UndoItem());
673 replace();
674 replace_button.setEnabled(false);
675 reset_button.setEnabled(true);
676 undo_button.setEnabled(true);
677 changeStatus("Replaced", -1);
678 }
679 else {
680 find_next_button.setEnabled(false);
681 replace_button.setEnabled(false);
682 replace_all_button.setEnabled(false);
683 changeStatus("Not_Found", -1);
684 }
685 }
686 }
687
688 private class ReplaceAllActionListener
689 implements ActionListener {
690 public void actionPerformed(ActionEvent event) {
691 reset();
692 int count = 0;
693 boolean start = true;
694 while((found = find()) == true) {
695 if(start) {
696 undo.push(new UndoItem());
697 start = false;
698 }
699 count++;
700 replace();
701 }
702 find_button.setEnabled(false);
703 find_next_button.setEnabled(false);
704 replace_button.setEnabled(false);
705 replace_all_button.setEnabled(false);
706 reset_button.setEnabled(true);
707 if(!undo.empty()) {
708 undo_button.setEnabled(true);
709 }
710 changeStatus("Replaced_All", count);
711 }
712 }
713
714 private class ResetActionListener
715 implements ActionListener {
716 public void actionPerformed(ActionEvent event) {
717 reset();
718 find_button.setEnabled(true);
719 find_next_button.setEnabled(false);
720 replace_button.setEnabled(false);
721 replace_all_button.setEnabled(false);
722 reset_button.setEnabled(false);
723 changeStatus(null, -1);
724 }
725 }
726
727 private class UndoActionListener
728 implements ActionListener {
729 private boolean start = false;
730 public void actionPerformed(ActionEvent event) {
731 if(!undo.empty()) {
732 start = true;
733 UndoItem undo_item = (UndoItem) undo.pop();
734 while(undo_item != null && !undo_item.isDummy()) {
735 // Determine the type of action we have to undo, then undo it.
736 ElementWrapper element = undo_item.getElement();
737 if(element != null) {
738 updateElement(element, undo_item.getBefore());
739 addToRedo(undo_item);
740 }
741 else {
742 FileNode record = undo_item.getRecord();
743 Metadata metadata = undo_item.getMetadata();
744 if(metadata != null && record != null) {
745 // The old metadata doesn't actually exist anymore. We have to generate a new metadata object based on the old metadata objects element and the 'after' string.
746 GValueModel model = Gatherer.c_man.getCollection().msm.getValueTree(metadata.getElement());
747 GValueNode value = model.getValue(undo_item.getAfter());
748 Metadata new_metadata = new Metadata(metadata.getElement(), value);
749 updateMetadata(new_metadata, record, undo_item.getBefore());
750 addToRedo(undo_item);
751 }
752 else if(record != null) {
753 updateRecord(record, undo_item.getBefore());
754 addToRedo(undo_item);
755 }
756 else {
757 ///ystem.err.println("Unknown undo item.");
758 }
759 }
760 if(!undo.empty()) {
761 undo_item = (UndoItem) undo.pop();
762 }
763 else {
764 undo_item = null;
765 }
766 }
767 if(undo.empty()) {
768 undo_button.setEnabled(false);
769 }
770 }
771 }
772 private void addToRedo(UndoItem undo_item) {
773 if(start) {
774 redo.push(new UndoItem());
775 start = false;
776 }
777 redo.push(undo_item);
778 redo_button.setEnabled(true);
779 }
780 }
781
782 private class ReplaceKeyListener
783 extends KeyAdapter {
784 public void keyTyped(KeyEvent event) {
785 validateReplaceButtons();
786 }
787 }
788
789 private class SearchKeyListener
790 extends KeyAdapter {
791 public void keyTyped(KeyEvent event) {
792 if(current_element != null || current_record != null || current_value != null) {
793 reset();
794 find_button.setEnabled(true);
795 find_next_button.setEnabled(false);
796 replace_button.setEnabled(false);
797 replace_all_button.setEnabled(false);
798 reset_button.setEnabled(false);
799 changeStatus(null, -1);
800 }
801 }
802 }
803
804 private class UndoItem {
805 private ElementWrapper element = null;
806 private FileNode record = null;
807 private Metadata metadata = null;
808 private String after = null;
809 private String before = null;
810 /** Constructor for a dummy item, which separates different batches of changes. */
811 public UndoItem() {
812 }
813 public UndoItem(ElementWrapper element, String before, String after) {
814 this.after = after;
815 this.before = before;
816 this.element = element;
817 }
818 public UndoItem(FileNode record, String before, String after) {
819 this.after = after;
820 this.before = before;
821 this.record = record;
822 }
823 public UndoItem(Metadata metadata, FileNode record, String before, String after) {
824 this.after = after;
825 this.before = before;
826 this.metadata = metadata;
827 this.record = record;
828 }
829 public String getAfter() {
830 return after;
831 }
832 public String getBefore() {
833 return before;
834 }
835 public ElementWrapper getElement() {
836 return element;
837 }
838 public Metadata getMetadata() {
839 return metadata;
840 }
841 public FileNode getRecord() {
842 return record;
843 }
844 public boolean isDummy() {
845 return (element == null && record == null && metadata == null);
846 }
847 }
848}
849
Note: See TracBrowser for help on using the repository browser.