1 | package 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 | */
|
---|
38 | import java.awt.*;
|
---|
39 | import java.awt.event.*;
|
---|
40 | import java.util.*;
|
---|
41 | import java.util.regex.*;
|
---|
42 | import javax.swing.*;
|
---|
43 | import javax.swing.tree.*;
|
---|
44 | import org.greenstone.gatherer.Gatherer;
|
---|
45 | import org.greenstone.gatherer.file.FileNode;
|
---|
46 | import org.greenstone.gatherer.gui.CollectionPane;
|
---|
47 | import org.greenstone.gatherer.gui.MetaEditPane;
|
---|
48 | import org.greenstone.gatherer.gui.MetaEditPrompt;
|
---|
49 | import org.greenstone.gatherer.gui.table.GTableModel;
|
---|
50 | import org.greenstone.gatherer.msm.ElementWrapper;
|
---|
51 | import org.greenstone.gatherer.msm.Metadata;
|
---|
52 | import org.greenstone.gatherer.util.EnumeratedVector;
|
---|
53 | import org.greenstone.gatherer.util.Utility;
|
---|
54 | import org.greenstone.gatherer.util.XORToggleButtonGroup;
|
---|
55 | import org.greenstone.gatherer.valuetree.GValueModel;
|
---|
56 | import 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 | */
|
---|
61 | public 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 |
|
---|