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

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

Major CDM rewrite so it uses DOM.

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