source: trunk/gli/src/org/greenstone/gatherer/cdm/custom/CustomAZList.java@ 4466

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

2030093: CustomAZList now has valueIsAdjusting() to stop inifinite loops, and only adds hidden range metadata to files/folders which have the ranged metadata set. Having tested this works if the user appends new metadata.

  • Property svn:keywords set to Author Date Id Revision
File size: 33.5 KB
Line 
1package org.greenstone.gatherer.cdm.custom;
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 javax.swing.*;
42import javax.swing.tree.*;
43import org.greenstone.gatherer.Gatherer;
44import org.greenstone.gatherer.cdm.Classifier;
45import org.greenstone.gatherer.cdm.ClassifierManager;
46import org.greenstone.gatherer.cdm.CustomClassifier;
47import org.greenstone.gatherer.file.FileNode;
48import org.greenstone.gatherer.msm.ElementWrapper;
49import org.greenstone.gatherer.msm.Metadata;
50import org.greenstone.gatherer.msm.MetadataSet;
51import org.greenstone.gatherer.msm.MetadataSetManager;
52import org.greenstone.gatherer.msm.MSMUtils;
53import org.greenstone.gatherer.util.ArrayTools;
54import org.greenstone.gatherer.util.Utility;
55import org.greenstone.gatherer.valuetree.GValueModel;
56import org.greenstone.gatherer.valuetree.GValueNode;
57import org.greenstone.gatherer.gui.SimpleMenuBar;
58import org.greenstone.gatherer.gui.ModalDialog;
59import org.w3c.dom.*;
60/** Provides the functionality for a custom AZList, including a GUI for configuration of this classifier.
61 * @author John Thompson, Greenstone Digital Library, University of Waikato
62 * @version 2.3
63 */
64final public class CustomAZList
65 implements CustomClassifier {
66 /** When this the control dialog is disposed, should we go ahead a add this custom AZ list (by processing all file records and building a new hierarchy)? */
67 private boolean process = false;
68 /** A reference to the Gatherer for access to the Collection and MetadataSetManager. */
69 private Gatherer gatherer = null;
70 /** A mapping from a String pattern to a specific GValueNode within the hidden hierarchy. */
71 private Hashtable mappings = null;
72 /** The button used to cancel the dialog. */
73 private JButton cancel = null;
74 /** The button used to confirm and close the dialog. */
75 private JButton ok = null;
76 /** The dialog which contains the controls for configuring this custom classifier. */
77 private JDialog controls = null;
78 /** The checkbox used to indicate whether there is a name given for the classifier button (in the Greenstone collections menubar). */
79 private JCheckBox buttonname_label = null;
80 /** The checkbox used to indicate that the final classifier should be sorted in some way. */
81 private JCheckBox sort_label = null;
82 /** The control allowing you to choose the assigned metadata this classifier should be based on. */
83 private JComboBox metadata = null;
84 /** The metadata the final classifier should be sorted by. */
85 private JComboBox sort = null;
86 /** A field for entering the button name. */
87 private JTextField buttonname = null;
88 /** A field which provides a clear indication of the current ranges selected for your custom classifier. */
89 private JTextField separators_preview = null;
90 /** An array of togglebuttons showing what separations are available. */
91 private JToggleButton separators[] = null;
92 /** A reference to the progress dialog if it exists. */
93 private ProgressDialog pd = null;
94 /** A private dictionary for this custom classifier. */
95 private ResourceBundle dictionary = null;
96 /** The name of the hidden metadata. We at least luck out in that we don't need a live reference to the metadata, because the user can never change it (or if they do its their own stinking fault). */
97 private String hidden_mde_name = null;
98 /** The name of this pseudo-classifier. */
99 final static public String NAME = "CustomAZList";
100 /** The size of a label. */
101 final static private Dimension LABEL_SIZE = new Dimension(200,25);
102 /** The size of the controls for this pseudo-classifier. */
103 final static private Dimension SIZE = new Dimension(550,425);
104 /** An array of values for the separators. */
105 final static private String VALUES[] = {"Numbers", "A", "B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
106 /** The same array as above, except as integer values (note that "Numbers" becomes "#"). */
107 static public int INT_VALUES[] = {'#','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
108 /** A third array which is almost the same as the others (can anyone else say poor code reuse) but subsitutes "#" for "Numbers". */
109 static public String STRING_VALUES[] = {"#","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
110 /** Default Constructor. Needed to load this class dynamically. */
111 public CustomAZList() {}
112 /** Constructor.
113 * @param gatherer A reference to the <strong>Gatherer</strong>.
114 */
115 public CustomAZList(Gatherer gatherer) {
116 this.gatherer = gatherer;
117 }
118 /** Used to compare this classifier to another classifier for the purposes of ordering.
119 * @param object The other classifier as an <strong>Object</strong>.
120 * @see org.greenstone.gatherer.cdm.Classifier
121 * @see org.greenstone.gatherer.cdm.CustomClassifier
122 */
123 public int compareTo(Object object) {
124 if(object instanceof Classifier) {
125 Classifier classifier = (Classifier) object;
126 return NAME.compareTo(classifier.getName());
127 }
128 else if(object instanceof CustomClassifier) {
129 CustomClassifier classifier = (CustomClassifier) object;
130 return NAME.compareTo(classifier.getName());
131 }
132 return NAME.compareTo(object.toString());
133 }
134 /** Ensure that the dialog can be correctly garbage collected. */
135 public void destroy() {
136 controls = null;
137 }
138 /** Produce a new copy of this custom classifier. Remember that what the classifier manager does is create instances of all possible classifiers, then as they are assigned creates and assigns copies of the original reserve. This way we only have to parse arguments once.
139 * @return A new <strong>CustomClassifier</strong> which is a copy of this one.
140 * @see org.greenstone.gatherer.Gatherer
141 */
142 public CustomClassifier copy() {
143 return new CustomAZList(gatherer);
144 }
145 /** Show the controls for configuring this pseudo-classifier.
146 * @param show <i>true</i> to actually display the configuration dialog on screen, <i>false</i> to do everything except show the control and process the data (useful for setting the control up and/or reloading custom classifiers from collect.cfg).
147 * @see org.greenstone.gatherer.Configuration
148 * @see org.greenstone.gatherer.Gatherer
149 * @see org.greenstone.gatherer.cdm.custom.CustomAZList.ButtonNameListener
150 * @see org.greenstone.gatherer.cdm.custom.CustomAZList.CancelListener
151 * @see org.greenstone.gatherer.cdm.custom.CustomAZList.OKListener
152 * @see org.greenstone.gatherer.cdm.custom.CustomAZList.SeparatorListener
153 * @see org.greenstone.gatherer.cdm.custom.CustomAZList.SortListener
154 * @see org.greenstone.gatherer.collection.CollectionManager
155 * @see org.greenstone.gatherer.msm.ElementWrapper
156 * @see org.greenstone.gatherer.msm.MetadataSetManager
157 */
158 public boolean display(boolean show) {
159 if(controls == null) {
160 Vector elements = gatherer.c_man.getCollection().msm.getAssignedElements();
161 // Creation
162 controls = new ModalDialog(gatherer.g_man);
163 controls.setModal(true);
164 controls.setSize(SIZE);
165 controls.setTitle(get("Title_JDialog"));
166 controls.setJMenuBar(new SimpleMenuBar("7.7"));
167 JPanel content_pane = (JPanel) controls.getContentPane();
168 JPanel upper_pane = new JPanel();
169 JPanel metadata_pane = new JPanel();
170 JLabel metadata_label = new JLabel(get("Metadata_JLabel"));
171 metadata_label.setPreferredSize(LABEL_SIZE);
172 metadata = new JComboBox(elements);
173 JPanel buttonname_pane = new JPanel();
174 buttonname_label = new JCheckBox(get("Buttonname_JCheckBox"));
175 buttonname_label.setPreferredSize(LABEL_SIZE);
176 buttonname = new JTextField();
177 buttonname.setBackground(Color.lightGray);
178 buttonname.setEnabled(false);
179 JPanel sort_pane = new JPanel();
180 sort_label = new JCheckBox(get("Sort_JLabel"));
181 sort = new JComboBox(elements);
182 JPanel separations_pane = new JPanel();
183 JLabel separations_label = new JLabel(get("Separations_JLabel"));
184 separations_label.setPreferredSize(LABEL_SIZE);
185 separators_preview = new JTextField();
186 separators_preview.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
187 separators_preview.setEnabled(false); // View only.
188 separators_preview.setForeground(Color.black);
189 JPanel lower_pane = new JPanel();
190 JPanel button_pane = new JPanel();
191 ok = new JButton(get("OK_JButton"));
192 cancel = new JButton(get("Cancel_JButton"));
193 // Connection
194 buttonname_label.addActionListener(new ButtonNameListener());
195 cancel.addActionListener(new CancelListener());
196 ok.addActionListener(new OKListener());
197 sort_label.addActionListener(new SortListener());
198 // The Toggle Button block. Done all at once so we don't end up with three for loops.
199 JPanel center_pane = new JPanel();
200 center_pane.setLayout(new GridLayout(3, 9));
201 separators = new JToggleButton[VALUES.length];
202 SeparatorListener sl = new SeparatorListener();
203 for(int i = 0; i < VALUES.length; i++) {
204 separators[i] = new JToggleButton(get(VALUES[i]));
205 separators[i].addActionListener(sl);
206 if(i != 0) {
207 center_pane.add(separators[i]);
208 }
209 else {
210 JLabel temp = new JLabel(get(VALUES[0]));
211 temp.setHorizontalAlignment(JLabel.CENTER);
212 center_pane.add(temp);
213 }
214 }
215 separators_preview.setText(getPreview(true));
216 // Layout
217 metadata_pane.setBorder(BorderFactory.createEmptyBorder(1,0,1,0));
218 metadata_pane.setLayout(new BorderLayout());
219 metadata_pane.add(metadata_label, BorderLayout.WEST);
220 metadata_pane.add(metadata, BorderLayout.CENTER);
221
222 buttonname_pane.setBorder(BorderFactory.createEmptyBorder(1,0,1,0));
223 buttonname_pane.setLayout(new BorderLayout());
224 buttonname_pane.add(buttonname_label, BorderLayout.WEST);
225 buttonname_pane.add(buttonname, BorderLayout.CENTER);
226
227 sort_pane.setBorder(BorderFactory.createEmptyBorder(1,0,1,0));
228 sort_pane.setLayout(new BorderLayout());
229 sort_pane.add(sort_label, BorderLayout.WEST);
230 sort_pane.add(sort, BorderLayout.CENTER);
231
232 upper_pane.setBorder(BorderFactory.createEmptyBorder(0,0,4,0));
233 upper_pane.setLayout(new GridLayout(3, 1));
234 upper_pane.add(metadata_pane);
235 upper_pane.add(buttonname_pane);
236 upper_pane.add(separations_label);
237
238 separations_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,0,5,0), BorderFactory.createRaisedBevelBorder()));
239 separations_pane.setLayout(new BorderLayout());
240 separations_pane.add(separators_preview, BorderLayout.CENTER);
241
242 lower_pane.setLayout(new GridLayout(2,1));
243 lower_pane.add(separations_pane);
244 lower_pane.add(button_pane);
245
246 button_pane.setLayout(new GridLayout(1,2));
247 button_pane.add(ok);
248 button_pane.add(cancel);
249
250 content_pane.setBorder(BorderFactory.createEmptyBorder(4,5,5,5));
251 content_pane.setLayout(new BorderLayout());
252 content_pane.add(upper_pane, BorderLayout.NORTH);
253 content_pane.add(center_pane, BorderLayout.CENTER);
254 content_pane.add(lower_pane, BorderLayout.SOUTH);
255 // Display
256 Dimension screen_size = Gatherer.config.screen_size;
257 controls.setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
258 screen_size = null;
259 if(show) {
260 controls.setVisible(true);
261 // Create the new metadata, hierarchy etc.
262 if(process) {
263 process((ElementWrapper)metadata.getSelectedItem(), getPreview(false));
264 return true;
265 }
266 return false;
267 }
268 else {
269 return true;
270 }
271 // We do everything -except- display the control.
272 }
273 else {
274 process = false;
275 controls.setVisible(true);
276 if(process) {
277 process((ElementWrapper)metadata.getSelectedItem(), getPreview(false));
278 return true;
279 }
280 return false;
281 }
282 }
283 /** Determine if this classifier and another given classifier are equal.
284 * @param object The classifier to check against for equality, as an <strong>Object</strong>.
285 * @see org.greenstone.gatherer.cdm.CustomClassifier
286 */
287 public boolean equals(Object object) {
288 if(object instanceof CustomClassifier) {
289 CustomClassifier classifier = (CustomClassifier) object;
290 if(getCommand().equalsIgnoreCase(classifier.getCommand())) {
291 return true;
292 }
293 return false;
294 }
295 else {
296 if(getCommand().equalsIgnoreCase(object.toString())) {
297 return true;
298 }
299 return false;
300 }
301 }
302 /** Method to return this pseudo-classifier represented as a String.
303 * @return A <strong>String</strong>.
304 */
305 public String getCommand() {
306 StringBuffer text = new StringBuffer("classify ");
307 text.append("Hierarchy");
308 text.append(" -metadata ");
309 text.append(hidden_mde_name); // @TODO
310 if(buttonname_label.isSelected()) {
311 text.append(" -buttonname \"");
312 text.append(buttonname.getText());
313 text.append("\"");
314 }
315 text.append(" -hfile ");
316 text.append(hidden_mde_name + ".txt");
317 text.append(" -hlist_at_top");
318 text.append("\n");
319 return text.toString();
320 }
321 /** Retrieve the custom command, a command line that overrides and replaces some other 'actual' classifier.
322 * @param index The number of the classifer this one is replacing.
323 */
324 public String getCustomCommand(int index) {
325 StringBuffer text = new StringBuffer("customclassifier ");
326 text.append(NAME);
327 text.append(" -replaces CL");
328 text.append(index);
329 text.append(" -separations ");
330 text.append(getPreview(false));
331 return text.toString();
332 }
333 /** Get the name of this custom classifier.
334 * @return The name as a <strong>String</strong>.
335 */
336 public String getName() {
337 return NAME;
338 }
339 /** Process a record by adding hidden metadata as necessary.
340 * @param record The <strong>FileNode</strong> to be edited.
341 * @see org.greenstone.gatherer.msm.ElementWrapper
342 */
343 public void process(FileNode record) {
344 ElementWrapper element = (ElementWrapper) metadata.getSelectedItem();
345 if(element != null) {
346 addMetadata(0L, record, element);
347 }
348 }
349 /** Recreate a CustomAZList given several parameters including the real classifier created during custom design.
350 * @param classifier The real <strong>Classifier</strong>.
351 * @param separations A <strong>String</strong> representing the choosen separations.
352 * @see org.greenstone.gatherer.Gatherer
353 * @see org.greenstone.gatherer.cdm.Argument
354 * @see org.greenstone.gatherer.collection.CollectionManager
355 * @see org.greenstone.gatherer.msm.ElementWrapper
356 * @see org.greenstone.gatherer.msm.MetadataSetManager
357 * @see org.greenstone.gatherer.valuetree.GValueModel
358 * @see org.greenstone.gatherer.valuetree.GValueNode
359 */
360 public void recreate(Classifier classifier, String separations) {
361 // Rebuild controls
362 display(false);
363 // Calculate original element.
364 hidden_mde_name = classifier.getArgument("metadata").getValue();
365 ElementWrapper hidden_mde = gatherer.c_man.getCollection().msm.getElement(hidden_mde_name);
366 String mde_name = (hidden_mde_name.substring(MetadataSetManager.HIDDEN.length() + 1)).replace('_','.');
367 ElementWrapper mde = gatherer.c_man.getCollection().msm.getElement(mde_name);
368 // Set metadata element combobox.
369 metadata.setSelectedItem(mde);
370 // Set button name.
371 String bn = classifier.getArgument("buttonname").getValue();
372 if(bn != null) {
373 buttonname_label.setSelected(true);
374 buttonname.setBackground(Color.white);
375 buttonname.setEnabled(true);
376 buttonname.setText(bn);
377 }
378 // Set sort element.
379 String sort_mde_name = classifier.getArgument("sort").getValue();
380 ElementWrapper sort_mde = gatherer.c_man.getCollection().msm.getElement(sort_mde_name);
381 if(sort_mde != null) {
382 sort_label.setSelected(true);
383 sort.setBackground(Color.white);
384 sort.setEnabled(true);
385 sort.setSelectedItem(sort_mde);
386 }
387 // Recover value tree.
388 GValueModel model = gatherer.c_man.getCollection().msm.getValueTree(hidden_mde);
389 // For each token in the tokenizer, set toggle buttons and recover node.
390 StringTokenizer tokenizer = new StringTokenizer(separations, ",");
391 mappings = new Hashtable();
392 while(tokenizer.hasMoreTokens()) {
393 String key = tokenizer.nextToken();
394 for(int j = 1; j < STRING_VALUES.length; j++) {
395 if(key.startsWith(STRING_VALUES[0])) {
396 j = STRING_VALUES.length;
397 }
398 else if(key.startsWith(STRING_VALUES[j])) {
399 separators[j].setSelected(true);
400 j = STRING_VALUES.length;
401 }
402 }
403 // Rebuild mappings hashtable for this pattern. Add it if not present.
404 GValueNode node = model.addValue(key);
405 // If the key is simple, ie "C", then add the mapping "C"->node
406 if(node == null) {
407 // Do nothing.
408 }
409 else if(key.indexOf("-") == -1) {
410 mappings.put(key, node);
411 }
412 // If the key is more complex, say "D-L", then we add mappings for all string values so "D"->node, "E"->node, ... "L"->node. This is miles eaiser to do here, than when you are trying to match a records metadata ie matchin "D-L" to "Igloos are cool". Note that it is guarantee that the keys are all of the same length, ie "A-L", "MA-ML", "MMA-STF", "STFA-ZZZZ".
413 else {
414 String start = key.substring(0, key.indexOf("-"));
415 String end = key.substring(key.indexOf("-") + 1);
416 for(key = start; key != null && key.compareTo(end) <= 0; key = increment(key)) {
417 ///ystem.err.println("Adding " + key + " -> " + node);
418 mappings.put(key, node);
419 }
420 }
421 }
422 separators_preview.setText(getPreview(true));
423 }
424 /** Sets the value of Gatherer, for those classes loaded dynamically.
425 * @param gatherer A reference to the <strong>Gatherer</strong>.
426 */
427 public void setGatherer(Gatherer gatherer) {
428 this.gatherer = gatherer;
429 }
430
431 public void setManager(ClassifierManager manager) {
432 }
433
434 /** Translate this object into a string such as you would find in the collection configuration file.
435 * @return A <strong>String</strong> representation.
436 */
437 public String toString() {
438 StringBuffer text = new StringBuffer("classify ");
439 text.append(NAME);
440 text.append(" -metadata ");
441 text.append(metadata.getSelectedItem().toString());
442 if(buttonname_label.isSelected()) {
443 text.append(" -buttonname \"");
444 text.append(buttonname.getText());
445 text.append("\"");
446 }
447 text.append(" -separations ");
448 text.append(getPreview(false));
449 return text.toString();
450 }
451 /** Add a pertinant piece of metadata to the given record. */
452 private void addMetadata(long id, FileNode record, ElementWrapper element) {
453 // Add custom metadata based on target metadata. No recursion.
454 ArrayList temp_value = Gatherer.c_man.getCollection().gdm.getMetadata(record.getFile(), element);
455 boolean found = false; // Only want to add the custom classifier metadata once.
456 for(int i = 0; !found && i < temp_value.size(); i++) {
457 String metadata_value = (String)temp_value.get(i);
458 for(Enumeration keys = mappings.keys(); metadata_value != null && keys.hasMoreElements(); ) {
459 String key = (String)keys.nextElement();
460 // Try to match the value and the pattern. Remember to check for any case, ie key is '#' and metadata_value starts with anything other than a character.
461 if(metadata_value.toUpperCase().startsWith(key) || (key.equals("#") && !Character.isLetter(metadata_value.charAt(0)))) {
462 GValueNode node = (GValueNode) mappings.get(key);
463 if(node != null) { // And it shouldn't.
464 Metadata metadata = new Metadata(node);
465 //record.addMetadata(id, metadata, MetaEditPromptBox.ACCUMULATE_ALL, false, 1);
466 Gatherer.c_man.getCollection().msm.fireMetadataChanged(0, record, null, metadata);
467 found = true;
468 }
469 }
470 }
471 }
472 }
473 /** Create a separation preview String from the currently selected toggle buttons.
474 * @param spaces <i>true</i> if the returned String should contain spaces for readability, <i>false</i> if we mean to process it and spaces would just make that more difficult.
475 * @return A <strong>String</strong> representing the current separation state, such as "#, A-L, M-Z".
476 */
477 private String getPreview(boolean spaces) {
478 String sep_sep = ",";
479 if(spaces) {
480 sep_sep = ", ";
481 }
482 StringBuffer preview = new StringBuffer(get(VALUES[0]));
483 for(int i = 1; i < separators.length - 1; i++) {
484 if(separators[i].isSelected()) {
485 // Special case, where we specifically avoid #-#, A...
486 if(i == 1) {
487 preview.append(sep_sep);
488 preview.append(get(VALUES[i]));
489 }
490 // Check previous value. We don't want ...A-L, M-M, N...
491 else if(separators[i - 1].isSelected()) {
492 preview.append(sep_sep);
493 preview.append(get(VALUES[i]));
494 }
495 // Otherwise ..-VALUE[i-1], VALUE[i]...
496 else {
497 preview.append("-");
498 preview.append(get(VALUES[i-1]));
499 preview.append(sep_sep);
500 preview.append(get(VALUES[i]));
501 }
502 }
503 }
504 // Special case of "...-Y,Z"
505 if(separators[separators.length - 1].isSelected()) {
506 preview.append("-");
507 preview.append(get(VALUES[VALUES.length - 2]));
508 preview.append(sep_sep);
509 preview.append(get(VALUES[VALUES.length - 1]));
510 }
511 // Otherwise "...-Z"
512 else {
513 preview.append("-");
514 preview.append(get(VALUES[VALUES.length - 1]));
515 }
516 return preview.toString();
517 }
518 /** Load the custom argument dictionary if necessary, then retrieve the requested phrase.
519 * @param key A key <strong>String</strong> which maps to the phrase to retrieve.
520 * @return A phrase as a <strong>String</strong>.
521 * @see org.greenstone.gatherer.Dictionary
522 * @see org.greenstone.gatherer.Gatherer
523 */
524 private String get(String key) {
525 if(dictionary == null) {
526 dictionary = ResourceBundle.getBundle("org.greenstone.gatherer.cdm.custom." + NAME, Gatherer.config.getLocale("general.locale", false));
527 }
528 return dictionary.getString(key);
529 }
530 /** Listens to the buttonname checkbox, and enable buttonname appropriately when action detected. */
531 private class ButtonNameListener
532 implements ActionListener {
533 /** When an action is performed on a registered control, this method is called which either enables or disables the buttonname field depending on the buttonname checkbox.
534 * @param event An <strong>ActionEvent</strong> containing information about the action performed.
535 */
536 public void actionPerformed(ActionEvent event) {
537 if(buttonname_label.isSelected()) {
538 buttonname.setBackground(Color.white);
539 buttonname.setEnabled(true);
540 }
541 else {
542 buttonname.setBackground(Color.lightGray);
543 buttonname.setEnabled(false);
544 }
545 }
546 }
547 /** Listens to the cancel button, and disposes appropriately when action detected. */
548 private class CancelListener
549 implements ActionListener {
550 /** If the cancel button is pressed then we should dispose of the dialog without processing any records.
551 * @param event An <strong>ActionEvent</strong> containing information about the action performed.
552 */
553 public void actionPerformed(ActionEvent event) {
554 process = false;
555 controls.dispose();
556 }
557 }
558 /** Listens to the ok button, and sets process to true then disposes appropriately when action detected. */
559 private class OKListener
560 implements ActionListener {
561 /** If the ok button is pressed then we should dispose of the dialog after processing all records.
562 * @param event An <strong>ActionEvent</strong> containing information about the action performed.
563 */
564 public void actionPerformed(ActionEvent event) {
565 process = true;
566 controls.dispose();
567 }
568 }
569 /** Listens for the toggling of a separator value, and updates the separator preview field. */
570 private class SeparatorListener
571 implements ActionListener {
572 /** When one of the separator buttons is toggled we update the separator preview field.
573 * @param event An <strong>ActionEvent</strong> containing information about the action performed.
574 */
575 public void actionPerformed(ActionEvent event) {
576 separators_preview.setText(getPreview(true));
577 }
578 }
579 /** Listens to the sort checkbox, and enables/disables sort appropriately when action detected. */
580 private class SortListener
581 implements ActionListener {
582 /** If this checkbox is actioned, enabled or disable the sort control based on whether we are checked (selected) or not.
583 * @param event An <strong>ActionEvent</strong> containing information about the action performed.
584 */
585 public void actionPerformed(ActionEvent event) {
586 if(sort_label.isSelected()) {
587 sort.setBackground(Color.white);
588 sort.setEnabled(true);
589 }
590 else {
591 sort.setBackground(Color.lightGray);
592 sort.setEnabled(false);
593 }
594 }
595 }
596 /** Display a progress bar while creating the required hidden system hierarchy.
597 * @param element An <strong>ElementWrapper</strong> containing the metadata element we want to build a custom classifier on.
598 * @param state A <strong>String</strong> representing the currently selected separators, of the form "#,A-L,MA-Ml,MM-MZ,N-Z". */
599 private void process(ElementWrapper element, String state) {
600 // Create a new progress bar dialog, using these divisions:
601 // 33% - Creation and removal of old metadata tree.
602 // 33% - Creation of new metadata tree, 33 / <number of separations>.
603 // 33% - Allocation of metadata to records, 33 / <number of files>.
604 // Hopefully the previous dialog should have already vanished (its dispose() caused by the process click).
605 pd = new ProgressDialog("Custom Classifier Creation", "Preparing for operation.");
606 // Step 1: Create the new dummy element and add it if necessary.
607 pd.setText("Creating dummy metadata element.");
608 MetadataSet hidden_mds = gatherer.c_man.getCollection().msm.getSet(MetadataSetManager.HIDDEN);
609 Element hidden_e = hidden_mds.getElement(element.toString().replace('.','_'));
610 boolean found = true;
611 ElementWrapper hidden_mde = null;
612 if(hidden_mde != null) {
613 hidden_mde = new ElementWrapper(hidden_e);
614 }
615 else {
616 hidden_mde = hidden_mds.addElement(element.toString().replace('.','_'));
617 found = false;
618 }
619 hidden_mde_name = hidden_mde.toString();
620 pd.increase(1,2,20);
621 // Step 2: Remove any existing value tree for this element. Wastefull I know but I don't want to lag for too long, and this is the quickest way.
622 pd.setText("Removing any existing hierarchy.");
623 if(found) {
624 hidden_mds.removeValueTree(hidden_mde);
625 }
626 pd.increase(1,2,20);
627 // Step 3: Create a value tree for this element.
628 pd.setText("Creating new hierary root.");
629 GValueModel value_tree = gatherer.c_man.getCollection().msm.getValueTree(hidden_mde);
630 pd.increase(5, 5, 5);
631 // Step 4: Build the single level value hierarchy using state's value. Here a hashtable mapping string patterns to value nodes is built.
632 pd.setText("Creating separations.");
633 StringTokenizer tokenizer = new StringTokenizer(state, ",");
634 int separator_count = tokenizer.countTokens();
635 mappings = new Hashtable();
636 for(int i = 0; i < separator_count; i++) {
637 String key = tokenizer.nextToken();
638 GValueNode node = value_tree.addValue(key);
639 // If the key is simple, ie "C", then add the mapping "C"->node
640 if(key.indexOf("-") == -1) {
641 mappings.put(key, node);
642 }
643 // If the key is more complex, say "D-L", then we add mappings for all string values so "D"->node, "E"->node, ... "L"->node. This is miles eaiser to do here, than when you are trying to match a records metadata ie matchin "D-L" to "Igloos are cool". Note that it is guarantee that the keys are all of the same length, ie "A-L", "MA-ML", "MMA-STF", "STFA-ZZZZ".
644 else {
645 String start = key.substring(0, key.indexOf("-"));
646 String end = key.substring(key.indexOf("-") + 1);
647 for(key = start; key != null && key.compareTo(end) <= 0; key = increment(key)) {
648 mappings.put(key, node);
649 }
650 }
651 // Each iteration.
652 pd.increase(1, separator_count, 25);
653 }
654 // Step 5: Recurse the entire tree, adding the appropriate values where necessary (overwriting any existing value for this element).
655 pd.setText("Allocating metadata to records.");
656 int count = gatherer.c_man.getCollection().getCount();
657 TreeModel record_tree = gatherer.c_man.getRecordSet();
658 FileNode records[] = ArrayTools.add(null, (FileNode)record_tree.getRoot());
659 while(records != null) {
660 FileNode current_record = records[0];
661 // There is no point in assigning hidden metadata to a file node we doesn't have any of that metadata set...
662 ArrayList current_metadatum = Gatherer.c_man.getCollection().gdm.getMetadata(current_record.getFile());
663 for(int z = 0; z < current_metadatum.size(); z++) {
664 Metadata current_metadata = (Metadata) current_metadatum.get(z);
665 if(current_metadata.getElement().equals(element) && current_metadata.isFileLevel()) {
666 ///ystem.err.println("The file " + current_record + " has previous " + element + " metadata in: " + current_metadata);
667 addMetadata(0L, current_record, element);
668 }
669 }
670 if(!current_record.isLeaf()) {
671 // Add all children to records at once hopefully.
672 FileNode children[] = new FileNode[current_record.getChildCount()];
673 for(int i = 0; i < children.length; i++) {
674 children[i] = (FileNode) current_record.getChildAt(i);
675 }
676 records = ArrayTools.add(records, children);
677 }
678 records = ArrayTools.tail(records);
679 pd.increase(1, count, 50);
680 }
681 // Step 6: Done.
682 pd.dispose();
683 pd = null;
684 }
685
686 public boolean valueIsAdjusting() {
687 return (pd == null);
688 }
689
690 /** A dialog window used to show the progress of processing the file records in terms of adding hidden metadata. */
691 private class ProgressDialog
692 extends JDialog {
693 /** The current value represented by the progress bar (where value is double and the progress bar shows an int). */
694 private double value = 0.0;
695 /** The default size of this dialog. */
696 private Dimension dialog_size = new Dimension(500,100);
697 /** The label explaining what action is currently happening (1 of 5 steps). */
698 private JLabel label = new JLabel();
699 /** The progress bar itself. */
700 private JProgressBar bar = new JProgressBar();
701 /** Constructor.
702 * @param t The title for this dialog as a <strong>String</strong>.
703 * @param l The initial label for the progress bar also a <strong>String</strong>.
704 */
705 public ProgressDialog(String t, String l) {
706 super();
707 setModal(false);
708 setSize(dialog_size);
709 setTitle(t);
710 // Create
711 JPanel content_pane = (JPanel) getContentPane();
712 label.setText(l);
713 bar.setMaximum(100);
714 bar.setMinimum(0);
715 bar.setValue(0);
716 // Connect
717 // Layout
718 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
719 content_pane.setLayout(new BorderLayout());
720 content_pane.add(label, BorderLayout.NORTH);
721 content_pane.add(bar, BorderLayout.CENTER);
722 // Display
723 Dimension screen_size = Gatherer.config.screen_size;
724 setLocation((screen_size.width - dialog_size.width) / 2, (screen_size.height - dialog_size.height) / 2);
725 screen_size = null;
726 show();
727 }
728 /** Increase the progress bar by a certain ammount.
729 * @param item An <i>int</i> indicating what item number this is.
730 * @param out_of An <i>int</i> detailing the number of items expected in total for this phase.
731 * @param total The maximum value of the progress bar as an <i>int</i>.
732 */
733 public void increase(int item, int out_of, int total) {
734 value = value + (((double) item / (double) out_of) * (double)total);
735 int int_value = (int) value;
736 ///ystem.err.println("Value is " + value + " which rounds to " + int_value);
737 if(int_value != bar.getValue()) {
738 bar.setValue(int_value);
739 }
740 }
741 /** Set the message shown in the progress bar.
742 * @param l The new progress bar label as a <strong>String</strong>.
743 */
744 public void setText(String l) {
745 label.setText(l);
746 }
747 }
748 /** The String equivelent of int++, this method increments the value of a String by one letter at a time.
749 * @param str The <strong>String</strong> to be incremented.
750 * @return A <strong>String</strong> whose 'checksum' value (adding the INT_VALUE[] of the letters together) is one greater than the initial String, while still containing only those values in STRING_VALUE[].
751 */
752 public String increment(String str) {
753 ///ystem.err.print("Incrementing " + str + " -> ");
754 char chars[] = str.toCharArray();
755 boolean found = false;
756 int pos = str.length() - 1;
757 while(found == false && pos >= 0) {
758 if((int)chars[pos] + 1 <= INT_VALUES[26]) {
759 if((int)chars[pos] == INT_VALUES[0]) { // Anything else symbol
760 chars[pos] = (char) INT_VALUES[1];
761 }
762 else {
763 chars[pos] = (char)((int)chars[pos] + 1);
764 }
765 found = true;
766 }
767 else {
768 chars[pos] = (char)INT_VALUES[1];
769 pos--;
770 }
771 }
772 // If we haven't found it yet, return null.
773 if(!found) {
774 return null;
775 }
776 ///ystem.err.println(String.valueOf(chars));
777 return String.valueOf(chars);
778 }
779}
780
781
782
783
784
Note: See TracBrowser for help on using the repository browser.