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