source: trunk/gli/src/org/greenstone/gatherer/gui/EnrichPane.java@ 9334

Last change on this file since 9334 was 9334, checked in by mdewsnip, 19 years ago

Improved previous change so the GLI won't completely crap out if an exception happens while adding/replacing/removing metadata.

  • Property svn:keywords set to Author Date Id Revision
File size: 59.6 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.gui;
38
39
40import java.awt.*;
41import java.awt.event.*;
42import java.io.*;
43import java.util.*;
44import javax.swing.*;
45import javax.swing.event.*;
46import javax.swing.table.*;
47import javax.swing.tree.*;
48import org.greenstone.gatherer.Configuration;
49import org.greenstone.gatherer.DebugStream;
50import org.greenstone.gatherer.Dictionary;
51import org.greenstone.gatherer.Gatherer;
52import org.greenstone.gatherer.collection.CollectionTree;
53import org.greenstone.gatherer.collection.CollectionTreeNode;
54import org.greenstone.gatherer.gui.tree.DragTree;
55import org.greenstone.gatherer.metadata.MetadataElement;
56import org.greenstone.gatherer.metadata.MetadataValue;
57import org.greenstone.gatherer.metadata.MetadataValueTableEntry;
58import org.greenstone.gatherer.metadata.MetadataValueTableModel;
59import org.greenstone.gatherer.metadata.MetadataValueTreeModel;
60import org.greenstone.gatherer.metadata.MetadataValueTreeNode;
61import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
62import org.greenstone.gatherer.util.DragGroup;
63import org.greenstone.gatherer.util.PatternTokenizer;
64import org.greenstone.gatherer.util.TreeSynchronizer;
65import org.greenstone.gatherer.util.Utility;
66import org.greenstone.gatherer.util.XMLTools;
67
68
69/** Provides a view of controls for the editing of metadata.
70 */
71public class EnrichPane
72 extends JPanel
73 implements ActionListener, TreeSelectionListener
74{
75 static private Dimension CONTROL_SIZE = new Dimension(560, 240);
76 static private Dimension MINIMUM_SIZE = new Dimension(100, 100);
77 static private Dimension TABLE_SIZE = new Dimension(560, 25);
78 static private Dimension TREE_SIZE = new Dimension(250, 500);
79 static private int MINIMUM_TABLE_HEADER_SIZE = 15;
80
81 private MetadataValueTable metadata_value_table = null;
82 /** The layout manager used to display either the MetadataValueTable (when files are selected) or a placeholder panel */
83 private CardLayout metadata_value_table_card_layout = null;
84 /** The name of the panel containing the metadata table */
85 static final private String TABLE_CARD = "";
86 /** The name of the panel containing the "no files selected" placeholder for the metadata table */
87 static final private String TABLE_CARD_NO_FILES_SELECTED = "No files selected";
88 /** The name of the panel containing the "no metadata available" placeholder for the metadata table */
89 static final private String TABLE_CARD_NO_METADATA_AVAILABLE = "No metadata available";
90
91 private JTextField metadata_value_text_field = null;
92 /** The MetadataValueTree graphically shows the available metadata that has been previously assigned, and provides controls for adding, updating and removing metadata, depending on the user's selections in the other important components. */
93 private MetadataValueTree metadata_value_tree = null;
94 /** The layout manager used to display either the MetadataValueTree (when metadata is selected) or a placeholder panel */
95 private CardLayout metadata_value_tree_card_layout = null;
96 /** The name of the panel containing the metadata value tree */
97 static final private String TREE_CARD = "";
98 /** The name of the panel containing the "no metadata element selected" placeholder for the metadata table */
99 static final private String TREE_CARD_NO_METADATA_ELEMENT_SELECTED = "No metadata element selected";
100
101 /** Used to dynamically filter the collection tree at user request. Note that this is synchronized with the collection tree filter in the Gather view. */
102 private Filter collection_filter = null;
103 /** The currently reported selection. */
104 private CollectionTreeNode[] file_nodes = null;
105 /** The button, which when clicked, adds metadata to the selected records. */
106 private JButton add;
107 /** The button, which when clicked, removes the selected metadata from the selected records. */
108 private JButton remove;
109 /** The button, which when clicked, updates the selected metadata from the selected records. */
110 private JButton replace;
111 /** This button creates an editor dialog to allow for an expanded area to type in text. */
112 private JButton expand;
113 private JButton expand_for_extracted;
114 /** The label at the top of the collection tree, which shows the collection name. */
115 private JLabel collection_label;
116 /** The panel in which the metadata value card layout resides. */
117 private JPanel control_pane;
118 /** The panel in which the metadata table card layout resides. */
119 private JPanel table_card_pane;
120 /** The splitpane dividing the collection tree and the metadata based controls. */
121 private JSplitPane external_split;
122 /** The label shown at the top of the metadata table detailing the current selection statistics. */
123 private JTextField table_label;
124 /** A reference to the collection tree. */
125 private CollectionTree collection_tree;
126 /** Provide synchronization between the collection trees in this view and the collection pane view. */
127 private TreeSynchronizer collection_tree_sync = null;
128 private boolean metadata_edit_event = false;
129
130
131 /** Constructor.
132 * @param tree_sync The <strong>TreeSynchronizer</strong> to be used on the collection tree
133 */
134 public EnrichPane(TreeSynchronizer collection_tree_sync)
135 {
136 this.collection_tree_sync = collection_tree_sync;
137
138 add = new GLIButton();
139 add.addActionListener(this);
140 add.setEnabled(false);
141 add.setMnemonic(KeyEvent.VK_A);
142 add.setPreferredSize(Utility.MINIMUM_BUTTON_SIZE);
143 Dictionary.registerBoth(add, "EnrichPane.Accumulate", "EnrichPane.Accumulate_Tooltip");
144
145 replace = new GLIButton();
146 replace.addActionListener(this);
147 replace.setEnabled(false);
148 replace.setMnemonic(KeyEvent.VK_P);
149 replace.setPreferredSize(Utility.MINIMUM_BUTTON_SIZE);
150 Dictionary.registerBoth(replace, "EnrichPane.Overwrite", "EnrichPane.Overwrite_Tooltip");
151
152 remove = new GLIButton();
153 remove.addActionListener(this);
154 remove.setEnabled(false);
155 remove.setMnemonic(KeyEvent.VK_R);
156 remove.setPreferredSize(Utility.MINIMUM_BUTTON_SIZE);
157 Dictionary.registerBoth(remove, "EnrichPane.Remove", "EnrichPane.Remove_Tooltip");
158
159 expand = new GLIButton();
160 expand.addActionListener(this);
161 expand.setEnabled(true);
162 expand.setMnemonic(KeyEvent.VK_E);
163 expand.setPreferredSize(new Dimension(25, 25));
164 Dictionary.registerBoth(expand, "EnrichPane.Expand", "EnrichPane.Expand_Tooltip");
165
166 expand_for_extracted = new GLIButton();
167 expand_for_extracted.addActionListener(this);
168 expand_for_extracted.setEnabled(true);
169 expand_for_extracted.setMnemonic(KeyEvent.VK_E);
170 expand_for_extracted.setPreferredSize(new Dimension(25, 25));
171 Dictionary.registerBoth(expand_for_extracted, "EnrichPane.Expand", "EnrichPane.Expand_Tooltip");
172
173 metadata_value_text_field = new JTextField();
174 metadata_value_table = new MetadataValueTable();
175 metadata_value_tree = new MetadataValueTree(CONTROL_SIZE.width, CONTROL_SIZE.height);
176 }
177
178
179 /** Called whenever an action occurs on one of our registered buttons.
180 * @param event An <strong>ActionEvent</strong> containing information about the event.
181 */
182 public void actionPerformed(ActionEvent event)
183 {
184 Object esrc = event.getSource();
185
186 // One of the metadata editing buttons was pressed
187 if (esrc == add || esrc == replace || esrc == remove) {
188 // Double-check that some files and a metadata element have been selected
189 MetadataValueTableEntry selected_metadata_value_table_entry = metadata_value_table.getSelectedMetadataValueTableEntry();
190 if (file_nodes == null || selected_metadata_value_table_entry == null) {
191 return;
192 }
193
194 // If we're adding metadata to folders display the warning
195 if (esrc == add && !file_nodes[0].isLeaf()) {
196 WarningDialog dialog = new WarningDialog("warning.DirectoryLevelMetadata", null, true);
197 int dialog_result = dialog.display();
198 dialog.dispose();
199
200 if (dialog_result != JOptionPane.OK_OPTION) {
201 return;
202 }
203 }
204
205 // Lock the interface so nothing can be changed while the metadata edit is being processed
206 Gatherer.g_man.wait(true);
207
208 // Add button pressed
209 if (esrc == add) {
210 MetadataElement metadata_element = metadata_value_table.getSelectedMetadataElement();
211 MetadataValueTreeNode metadata_value_tree_node = metadata_element.addMetadataValue(metadata_value_tree.getSelectedValue());
212 MetadataValue metadata_value = new MetadataValue(metadata_element, metadata_value_tree_node);
213 metadata_value.setIsAccumulatingMetadata(true);
214 (new AppendMetadataTask(metadata_value)).run();
215 }
216
217 // Replace button pressed
218 else if (esrc == replace) {
219 MetadataElement metadata_element = metadata_value_table.getSelectedMetadataElement();
220 MetadataValueTreeNode metadata_value_tree_node = metadata_element.addMetadataValue(metadata_value_tree.getSelectedValue());
221 MetadataValue metadata_value = new MetadataValue(metadata_element, metadata_value_tree_node);
222 metadata_value.setIsAccumulatingMetadata(!metadata_value_table.getSelectedMetadataValueTableEntry().isInheritedMetadata());
223 (new ReplaceMetadataTask(metadata_value, selected_metadata_value_table_entry)).run();
224 }
225
226 // Remove button pressed
227 else if (esrc == remove) {
228 (new RemoveMetadataTask(selected_metadata_value_table_entry)).run();
229 }
230
231 // Note that the collection's metadata has been changed
232 Gatherer.c_man.getCollection().setMetadataChanged(true);
233 }
234
235 // Expand button pressed
236 else if (esrc == expand) {
237 EditorDialog ed = new EditorDialog();
238 String new_metadata_value = ed.display(metadata_value_tree.getSelectedValue());
239 if (new_metadata_value != null) {
240 metadata_value_tree.setSelectedValue(new_metadata_value);
241 }
242 }
243
244 // Expand button for extracted metadata pressed
245 else if (esrc == expand_for_extracted) {
246 EditorDialog ed = new EditorDialog();
247 ed.setEditable(false);
248 ed.display(metadata_value_table.getSelectedMetadataValueTableEntry().getFullValue());
249 }
250 }
251
252
253 /** Some actions can only occur after this panel has been displayed on-screen, so this method is provided to do exactly that. Such actions include the proportioning of the split panes and the setting of table column widths.
254 */
255 public void afterDisplay()
256 {
257 external_split.setDividerLocation(0.3);
258 }
259
260
261 /** Used to create, connect and layout the components to be shown on this control panel.
262 * @see org.greenstone.gatherer.Gatherer
263 * @see org.greenstone.gatherer.file.FileOpenActionListener
264 * @see org.greenstone.gatherer.gui.Filter
265 */
266 public void display()
267 {
268 // Creation
269 JPanel collection_pane = new JPanel(new BorderLayout());
270 collection_pane.setMinimumSize(MINIMUM_SIZE);
271 collection_pane.setPreferredSize(TREE_SIZE);
272
273 external_split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
274
275 collection_label = new JLabel();
276 Dictionary.registerText(collection_label, "Collection.No_Collection");
277 collection_label.setOpaque(true);
278
279 DragGroup group = new DragGroup();
280 collection_tree = new CollectionTree("Enrich", Gatherer.c_man.getCollectionTreeModel(), false);
281 group.add(collection_tree);
282 collection_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
283 collection_tree.putClientProperty("JTree.lineStyle", "Angled");
284 collection_tree.addMouseListener(Gatherer.g_man.foa_listener);
285 collection_tree.addMouseListener(new RightButtonListener());
286 collection_tree.addTreeSelectionListener(this);
287 collection_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
288 collection_tree.setBackgroundNonSelectionColor(Configuration.getColor("coloring.collection_tree_background", false));
289 collection_tree.setTextNonSelectionColor(Configuration.getColor("coloring.collection_tree_foreground", false));
290 collection_tree.setBackgroundSelectionColor(Configuration.getColor("coloring.collection_selection_background", false));
291 collection_tree.setTextSelectionColor(Configuration.getColor("coloring.collection_selection_foreground", false));
292 collection_tree.setRootVisible(false);
293
294 KeyListenerImpl key_listener = new KeyListenerImpl();
295 collection_tree.addKeyListener(key_listener);
296 JScrollPane collection_scroll = new JScrollPane(collection_tree);
297
298 collection_filter = Gatherer.g_man.getFilter(collection_tree);
299 collection_filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
300 collection_filter.setEditable(Configuration.getMode() > Configuration.LIBRARIAN_MODE);
301 Dictionary.registerTooltip(collection_filter.getComboBox(), "Collection.Filter_Tooltip");
302
303 // Layout
304 collection_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createLoweredBevelBorder()));
305 collection_pane.setMinimumSize(MINIMUM_SIZE);
306 collection_pane.setPreferredSize(new Dimension(Gatherer.g_man.getSize().width / 3, Gatherer.g_man.getSize().height));
307
308 // Collection Pane
309 collection_pane.add(collection_label, BorderLayout.NORTH);
310 collection_pane.add(collection_scroll, BorderLayout.CENTER);
311 collection_pane.add(collection_filter, BorderLayout.SOUTH);
312
313 JSplitPane main_split_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
314 main_split_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
315 main_split_pane.setDividerSize(8);
316
317 // Metadata Table. In order to achieve what Ian envisions I'm going
318 // to have to do a tricky. The table itself will sit in a stacked
319 // CardLayout. Card zero will be a see-through JPanel with a message
320 // smack bang in the center, something like "No Metadata". If any
321 // page of the table would appear to have no metadata this card will
322 // be swapped to the front.
323
324 JPanel table_pane = new JPanel();
325 table_pane.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
326
327 JPanel table_title_pane = new JPanel();
328
329 table_label = new JTextField();
330 table_label.setBackground(Configuration.getColor("coloring.collection_tree_background", false));
331 table_label.setEditable(false);
332 Dictionary.registerText(table_label, "EnrichPane.No_File");
333
334 table_card_pane = new JPanel();
335
336 JPanel table_pane_zero = new JPanel();
337 table_pane_zero.setOpaque(false);
338
339 JPanel table_pane_two = new JPanel();
340 table_pane_two.setOpaque(false);
341
342 JLabel no_file_message = new JLabel();
343 no_file_message.setHorizontalAlignment(JLabel.CENTER);
344 no_file_message.setOpaque(false);
345 no_file_message.setVerticalAlignment(JLabel.CENTER);
346 Dictionary.registerText(no_file_message, "EnrichPane.No_File");
347
348 JLabel no_metadata_message = new JLabel();
349 no_metadata_message.setHorizontalAlignment(JLabel.CENTER);
350 no_metadata_message.setOpaque(false);
351 no_metadata_message.setVerticalAlignment(JLabel.CENTER);
352 Dictionary.registerText(no_metadata_message, "EnrichPane.No_Metadata");
353
354 JPanel table_pane_one = new JPanel();
355
356 JScrollPane table_scroll = new JScrollPane(metadata_value_table);
357 table_scroll.getViewport().setBackground(Configuration.getColor("coloring.collection_tree_background", false));
358 table_scroll.setOpaque(true);
359
360 // Create the cards for the metadata value table
361 metadata_value_table_card_layout = new CardLayout();
362
363 table_pane_zero.setLayout(new BorderLayout());
364 table_pane_zero.add(no_file_message, BorderLayout.CENTER);
365
366 table_pane_one.setLayout(new BorderLayout());
367 table_pane_one.add(table_scroll, BorderLayout.CENTER);
368
369 table_pane_two.setLayout(new BorderLayout());
370 table_pane_two.add(no_metadata_message, BorderLayout.CENTER);
371
372 table_card_pane.setLayout(metadata_value_table_card_layout);
373 table_card_pane.add(table_pane_zero, TABLE_CARD_NO_FILES_SELECTED);
374 table_card_pane.add(table_pane_one, TABLE_CARD);
375 table_card_pane.add(table_pane_two, TABLE_CARD_NO_METADATA_AVAILABLE);
376
377 // Create the cards for the metadata value tree
378 metadata_value_tree_card_layout = new CardLayout();
379
380 // Control pane
381 control_pane = new JPanel();
382 control_pane.setBorder(BorderFactory.createLoweredBevelBorder());
383 control_pane.setMinimumSize(MINIMUM_SIZE);
384 control_pane.setSize(CONTROL_SIZE);
385 control_pane.setPreferredSize(CONTROL_SIZE);
386
387 JPanel tools_off_pane = new JPanel();
388
389 JLabel tools_off_label = new JLabel();
390 tools_off_label.setHorizontalAlignment(JLabel.CENTER);
391 tools_off_label.setOpaque(false);
392 tools_off_label.setVerticalAlignment(JLabel.CENTER);
393 Dictionary.registerText(tools_off_label, "EnrichPane.No_Metadata_Element");
394
395 JPanel tools_on_pane = new JPanel();
396 tools_on_pane.setMinimumSize(MINIMUM_SIZE);
397 tools_on_pane.setSize(TABLE_SIZE);
398 tools_on_pane.setPreferredSize(TABLE_SIZE);
399
400 // Layout.
401
402 // Metaedit controls
403 tools_off_pane.setLayout(new BorderLayout());
404 tools_off_pane.add(tools_off_label, BorderLayout.CENTER);
405
406 tools_on_pane.setLayout(new BorderLayout());
407 tools_on_pane.add(metadata_value_tree, BorderLayout.CENTER);
408
409 control_pane.setLayout(metadata_value_tree_card_layout);
410 control_pane.add(tools_off_pane, TREE_CARD_NO_METADATA_ELEMENT_SELECTED);
411 control_pane.add(tools_on_pane, TREE_CARD);
412
413 // Table components
414 table_title_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
415 table_title_pane.setLayout(new BorderLayout());
416 table_title_pane.add(table_label, BorderLayout.CENTER);
417
418 table_pane.setLayout(new BorderLayout());
419 table_pane.add(table_title_pane, BorderLayout.NORTH);
420 table_pane.add(table_card_pane, BorderLayout.CENTER);
421
422 main_split_pane.add(table_pane, JSplitPane.TOP);
423 main_split_pane.add(control_pane, JSplitPane.BOTTOM);
424 main_split_pane.setDividerLocation(250);
425
426 external_split.add(collection_pane, JSplitPane.LEFT);
427 external_split.add(main_split_pane, JSplitPane.RIGHT);
428
429 this.setLayout(new BorderLayout());
430 this.add(external_split, BorderLayout.CENTER);
431 }
432
433
434 /** Called to inform this control panel that it has just gained focus as an effect of the user clicking on its tab.
435 */
436 public void gainFocus()
437 {
438 // If there is nothing in the collection tree there is nothing to do
439 if (collection_tree == null || collection_tree.getRowCount() == 0) {
440 return;
441 }
442
443 // Select the first node in the tree if nothing is already selected
444 if (collection_tree.getSelectionPaths() == null) {
445 collection_tree.setImmediate(true);
446 collection_tree.setSelectionRow(0);
447 collection_tree.setImmediate(false);
448 return;
449 }
450
451 // Force all of the controls to be updated
452 valueChanged(new TreeSelectionEvent(this, null, false, null, null));
453 }
454
455
456 /** Called whenever the detail mode changes to ensure the filters are at an appropriate level (ie only editable by those that understand regular expression matching)
457 * @param mode the mode level as an int
458 */
459 public void modeChanged(int mode)
460 {
461 collection_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
462 }
463
464
465 /** Refresh this pane, depending on what has just happened (refresh_reason). */
466 public void refresh(int refresh_reason, boolean collection_loaded)
467 {
468 if (collection_loaded) {
469 // Update collection label
470 Dictionary.registerText(collection_label, "Collection.Collection");
471 collection_label.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
472 collection_label.setForeground(Configuration.getColor("coloring.collection_heading_foreground", false));
473
474 // Update collection tree
475 if (refresh_reason == Gatherer.COLLECTION_OPENED) {
476 collection_tree.setModel(Gatherer.c_man.getCollectionTreeModel());
477 }
478
479 // Update collection filter
480 collection_filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
481 }
482 else {
483 // Update collection label
484 Dictionary.registerText(collection_label, "Collection.No_Collection");
485 collection_label.setBackground(Color.lightGray);
486 collection_label.setForeground(Color.black);
487
488 // Update collection tree
489 collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("Error")));
490
491 // Update collection filter
492 collection_filter.setBackground(Color.lightGray);
493 }
494
495 // Enable or disable the controls
496 collection_tree.setEnabled(collection_loaded);
497 collection_filter.setEnabled(collection_loaded);
498
499 // Ensure that this collection tree view is synchronized with all others
500 collection_tree_sync.add(collection_tree);
501
502 // Force the metadata table to be rebuilt (for switching extracted metadata on or off)
503 if (refresh_reason == Gatherer.PREFERENCES_CHANGED) {
504 valueChanged(null);
505 }
506 }
507
508
509 /** This method is called whenever the selection within the collection
510 * tree changes. This causes the table to be rebuilt with a new model.
511 */
512 public void valueChanged(TreeSelectionEvent event)
513 {
514 // Nothing selected in the collection tree
515 if (collection_tree.getSelectionCount() == 0) {
516 file_nodes = null;
517
518 // Update the label to show nothing is selected in the collection tree
519 Dictionary.registerText(table_label, "EnrichPane.No_File");
520
521 // Display the "no file selected" card
522 metadata_value_table_card_layout.show(table_card_pane, TABLE_CARD_NO_FILES_SELECTED);
523 }
524
525 // Some files selected in the collection tree
526 else {
527 TreePath paths[] = collection_tree.getSelectionPaths();
528 file_nodes = new CollectionTreeNode[paths.length];
529 for (int i = 0; i < paths.length; i++) {
530 file_nodes[i] = (CollectionTreeNode) paths[i].getLastPathComponent();
531 }
532
533 // Update the label to show what is selected in the collection tree
534 table_label.setText(collection_tree.getSelectionDetails());
535 }
536
537 // Update the metadata value table (and consequently, the metadata value tree)
538 MetadataValue previously_selected_metadata_value = metadata_value_table.getSelectedMetadataValueTableEntry();
539 metadata_value_table.rebuildAndSelectRowWithValueClosestTo(previously_selected_metadata_value);
540 }
541
542
543 private class AppendMetadataTask
544 extends Thread
545 {
546 private MetadataValue metadata_value = null;
547
548 private AppendMetadataTask(MetadataValue metadata_value)
549 {
550 this.metadata_value = metadata_value;
551 }
552
553 public void run()
554 {
555 try {
556 // Edit metadata.xml files to add the metadata
557 MetadataXMLFileManager.addMetadata(file_nodes, metadata_value);
558
559 // Update the metadata value table and tree
560 metadata_edit_event = true;
561 metadata_value_table.rebuildAndSelectRowWithValueClosestTo(metadata_value);
562 metadata_edit_event = false;
563 }
564 catch (Exception exception) {
565 // We need to catch any exceptions here so the interface is unlocked below
566 DebugStream.printStackTrace(exception);
567 }
568
569 // Operation finished, so turn busy cursor off and unlock interface
570 Gatherer.g_man.wait(false);
571 }
572 }
573
574
575 private class ReplaceMetadataTask
576 extends Thread
577 {
578 private MetadataValue metadata_value = null;
579 private MetadataValueTableEntry selected_metadata_value_table_entry = null;
580
581 private ReplaceMetadataTask(MetadataValue metadata_value, MetadataValueTableEntry selected_metadata_value_table_entry)
582 {
583 this.metadata_value = metadata_value;
584 this.selected_metadata_value_table_entry = selected_metadata_value_table_entry;
585 }
586
587 public void run()
588 {
589 try {
590 // Edit metadata.xml files to replace the metadata
591 MetadataXMLFileManager.removeMetadata(file_nodes, selected_metadata_value_table_entry);
592 MetadataXMLFileManager.addMetadata(file_nodes, metadata_value);
593
594 // Update the metadata value table and tree
595 metadata_edit_event = true;
596 metadata_value_table.rebuildAndSelectRowWithValueClosestTo(metadata_value);
597 metadata_edit_event = false;
598 }
599 catch (Exception exception) {
600 // We need to catch any exceptions here so the interface is unlocked below
601 DebugStream.printStackTrace(exception);
602 }
603
604 // Operation finished, so turn busy cursor off and unlock interface
605 Gatherer.g_man.wait(false);
606 }
607 }
608
609
610 private class RemoveMetadataTask
611 extends Thread
612 {
613 private MetadataValueTableEntry selected_metadata_value_table_entry = null;
614
615 private RemoveMetadataTask(MetadataValueTableEntry selected_metadata_value_table_entry)
616 {
617 this.selected_metadata_value_table_entry = selected_metadata_value_table_entry;
618 }
619
620 public void run()
621 {
622 try {
623 // Edit metadata.xml files to remove the metadata
624 MetadataXMLFileManager.removeMetadata(file_nodes, selected_metadata_value_table_entry);
625
626 // Update the metadata value table and tree
627 metadata_edit_event = true;
628 metadata_value_table.rebuildAndSelectRowWithValueClosestTo(selected_metadata_value_table_entry);
629 metadata_edit_event = false;
630 }
631 catch (Exception exception) {
632 // We need to catch any exceptions here so the interface is unlocked below
633 DebugStream.printStackTrace(exception);
634 }
635
636 // Operation finished, so turn busy cursor off and unlock interface
637 Gatherer.g_man.wait(false);
638 }
639 }
640
641
642 private class MetadataValueTable
643 extends JTable
644 {
645 private MetadataValueTableModel metadata_value_table_model = null;
646 /** The currently selected metadata value table entry */
647 private MetadataValueTableEntry selected_metadata_value_table_entry = null;
648
649
650 public MetadataValueTable()
651 {
652 metadata_value_table_model = new MetadataValueTableModel();
653 setModel(metadata_value_table_model);
654
655 // We allow only one row to be selected at a time
656 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
657 getSelectionModel().addListSelectionListener(new MetadataValueTableListSelectionListener());
658
659 // The default JTable doesn't quite have the desired properties - when a
660 // table cell is clicked on, the table is scrolled so the cell is as
661 // visible as possible. This means that the left-most cells can become
662 // hidden. To fix this, the MouseInputHandler in BasicTableUI is removed
663 // as a MouseListener on the table, and a very slightly altered copy is
664 // added in its place (TableMouseInputHandler).
665 MouseListener[] mls = getMouseListeners();
666 for (int i = 0; i < mls.length; i++) {
667 // Remove the instances of MouseInputHandler
668 if (mls[i].toString().startsWith("javax.swing.plaf.basic.BasicTableUI$MouseInputHandler@")) {
669 removeMouseListener(mls[i]);
670 }
671 }
672 // Add the slightly-altered mouse listener
673 addMouseListener(new TableMouseInputHandler());
674
675 // We also have to ensure that the table column header hasn't gone on a severe Jenny Craig binge and has somehow lost 7/8th of its component size
676 JTableHeader table_header = getTableHeader();
677 Dimension table_header_preferred_size = table_header.getPreferredSize();
678 if (table_header_preferred_size.height < MINIMUM_TABLE_HEADER_SIZE) {
679 table_header_preferred_size.setSize(table_header_preferred_size.width, MINIMUM_TABLE_HEADER_SIZE);
680 table_header.setPreferredSize(table_header_preferred_size);
681 }
682
683 // Set the table columns as we want them
684 setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
685 Dimension table_size = getPreferredSize();
686 TableColumnModel column_model = getColumnModel();
687
688 TableColumn inherited_column = column_model.getColumn(0);
689 inherited_column.setPreferredWidth(20);
690 inherited_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
691
692 TableColumn element_column = column_model.getColumn(1);
693 element_column.setPreferredWidth((TABLE_SIZE.width / 4) - 15);
694 element_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
695
696 TableColumn value_column = column_model.getColumn(2);
697 value_column.setPreferredWidth(((3 * TABLE_SIZE.width) / 4) - 30);
698 value_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
699 }
700
701
702 public MetadataElement getSelectedMetadataElement()
703 {
704 return selected_metadata_value_table_entry.getMetadataElement();
705 }
706
707
708 public MetadataValueTableEntry getSelectedMetadataValueTableEntry()
709 {
710 return selected_metadata_value_table_entry;
711 }
712
713
714 public boolean isSelectedMetadataValueTableEntryCommon()
715 {
716 return metadata_value_table_model.isCommon(selected_metadata_value_table_entry);
717 }
718
719
720 public void moveSelectionDown()
721 {
722 int new_row_to_select = getSelectedRow() + 1;
723 if (new_row_to_select < metadata_value_table_model.getRowCount()) {
724 changeSelection(new_row_to_select, 1, false, false);
725 }
726 }
727
728
729 public void moveSelectionUp()
730 {
731 int new_row_to_select = getSelectedRow() - 1;
732 if (new_row_to_select >= 0) {
733 changeSelection(new_row_to_select, 1, false, false);
734 }
735 }
736
737
738 public void rebuildAndSelectRowWithValueClosestTo(MetadataValue optimal_metadata_value_to_select_when_building_complete)
739 {
740 // We don't want a lot of ListSelectionEvents while the table is rebuilding
741 clearSelection();
742
743 // Rebuild the metadata value table model
744 metadata_value_table_model.rebuild(file_nodes);
745
746 // If the metadata value table is empty there isn't much to do
747 if (metadata_value_table_model.getRowCount() == 0) {
748 // ...except display the "no metadata available" card, unless no files are selected
749 if (file_nodes != null) {
750 metadata_value_table_card_layout.show(table_card_pane, TABLE_CARD_NO_METADATA_AVAILABLE);
751 }
752 return;
753 }
754
755 // Display the card with the metadata value table
756 metadata_value_table_card_layout.show(table_card_pane, TABLE_CARD);
757
758 // If we don't need to select a row in the table after rebuilding, we're done
759 if (optimal_metadata_value_to_select_when_building_complete == null) {
760 return;
761 }
762
763 MetadataElement optimal_metadata_element = optimal_metadata_value_to_select_when_building_complete.getMetadataElement();
764
765 // Find the row containing the closest value to the optimal value
766 int optimal_row_to_select = 0;
767 for (int i = 0; i < metadata_value_table_model.getRowCount(); i++) {
768 MetadataValueTableEntry metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(i);
769 MetadataElement metadata_element = metadata_value_table_entry.getMetadataElement();
770 if (metadata_element.equals(optimal_metadata_element)) {
771 // This row will be the optimal row, unless the next row matches better
772 optimal_row_to_select = i;
773 }
774
775 int c = metadata_value_table_entry.compareTo(optimal_metadata_value_to_select_when_building_complete);
776 if (c >= 0) {
777 // We've either found an exact match, or there is no exact match, so stop here
778 break;
779 }
780 }
781
782 changeSelection(optimal_row_to_select, 1, false, false);
783 metadata_value_text_field.requestFocus();
784 }
785
786
787 private class MetadataValueTableListSelectionListener
788 implements ListSelectionListener
789 {
790 public void valueChanged(ListSelectionEvent event)
791 {
792 // We only want to handle one event per selection, so wait for the value to stabilise
793 if (getSelectionModel().getValueIsAdjusting() == true) {
794 return;
795 }
796
797 selected_metadata_value_table_entry = null;
798 MetadataElement selected_metadata_element = null;
799 String selected_metadata_value = "";
800
801 // We have a SINGLE_SELECTION model set so there is at most one selection
802 int selected_row = getSelectedRow();
803 if (selected_row >= 0) {
804 selected_metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(selected_row);
805 selected_metadata_element = selected_metadata_value_table_entry.getMetadataElement();
806 MetadataValueTreeNode metadata_value_tree_node = selected_metadata_value_table_entry.getMetadataValueTreeNode();
807 if (metadata_value_tree_node != null) {
808 selected_metadata_value = metadata_value_tree_node.getFullValue();
809 }
810 }
811
812 // Update the metadata value tree
813 metadata_value_tree.rebuild(selected_metadata_element, selected_metadata_value);
814 }
815 }
816
817
818 /** A direct copy of javax.swing.plaf.basic.BasicTableUI$MouseInputHandler, except for one change in adjustFocusAndSelection(). The purpose of this change is to always keep the left-most cells of the table row selected visible. */
819 private class TableMouseInputHandler implements MouseInputListener
820 {
821 // Component receiving mouse events during editing.
822 // May not be editorComponent.
823 private Component dispatchComponent;
824 private boolean selectedOnPress;
825
826 // The Table's mouse listener methods.
827
828 public void mouseClicked(MouseEvent e) {}
829
830 private void setDispatchComponent(MouseEvent e) {
831 Component editorComponent = getEditorComponent();
832 Point p = e.getPoint();
833 Point p2 = SwingUtilities.convertPoint(metadata_value_table, p, editorComponent);
834 dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent,
835 p2.x, p2.y);
836 }
837
838 private boolean repostEvent(MouseEvent e) {
839 // Check for isEditing() in case another event has
840 // caused the editor to be removed. See bug #4306499.
841 if (dispatchComponent == null || !isEditing()) {
842 return false;
843 }
844 MouseEvent e2 = SwingUtilities.convertMouseEvent(metadata_value_table, e, dispatchComponent);
845 dispatchComponent.dispatchEvent(e2);
846 return true;
847 }
848
849 private void setValueIsAdjusting(boolean flag) {
850 getSelectionModel().setValueIsAdjusting(flag);
851 getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
852 }
853
854 private boolean shouldIgnore(MouseEvent e) {
855 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && isEnabled()));
856 }
857
858 public void mousePressed(MouseEvent e) {
859 if (e.isConsumed()) {
860 selectedOnPress = false;
861 return;
862 }
863 selectedOnPress = true;
864 adjustFocusAndSelection(e);
865 }
866
867 void adjustFocusAndSelection(MouseEvent e) {
868 if (shouldIgnore(e)) {
869 return;
870 }
871
872 Point p = e.getPoint();
873 int row = rowAtPoint(p);
874 int column = columnAtPoint(p);
875 // The autoscroller can generate drag events outside the Table's range.
876 if ((column == -1) || (row == -1)) {
877 return;
878 }
879
880 if (editCellAt(row, column, e)) {
881 setDispatchComponent(e);
882 repostEvent(e);
883 }
884 // Commented this out and added the line below it to keep the value text field in focus
885 // else if (isRequestFocusEnabled()) {
886 // requestFocus();
887 // }
888 metadata_value_text_field.requestFocus();
889
890 CellEditor editor = getCellEditor();
891 if (editor == null || editor.shouldSelectCell(e)) {
892 boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
893 setValueIsAdjusting(adjusting);
894
895 // Special code for clicking the first column (folder-level metadata)
896 if (column == 0) {
897 selected_metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(row);
898
899 // If this metadata is inherited, switch to the folder it came from
900 if (selected_metadata_value_table_entry.isInheritedMetadata()) {
901 File folder_metadata_inherited_from = selected_metadata_value_table_entry.getFolderMetadataInheritedFrom();
902 if (folder_metadata_inherited_from != null) {
903 collection_tree.setSelection(folder_metadata_inherited_from);
904 }
905 }
906
907 changeSelection(row, 0, e.isControlDown(), e.isShiftDown());
908 }
909 else {
910 changeSelection(row, 1, e.isControlDown(), e.isShiftDown());
911 }
912 }
913 }
914
915 public void mouseReleased(MouseEvent e) {
916 if (selectedOnPress) {
917 if (shouldIgnore(e)) {
918 return;
919 }
920
921 repostEvent(e);
922 dispatchComponent = null;
923 setValueIsAdjusting(false);
924 } else {
925 adjustFocusAndSelection(e);
926 }
927 }
928
929
930 public void mouseEntered(MouseEvent e) {}
931
932 public void mouseExited(MouseEvent e) {}
933
934 // The Table's mouse motion listener methods.
935
936 public void mouseMoved(MouseEvent e) {}
937
938 public void mouseDragged(MouseEvent e) {
939 if (shouldIgnore(e)) {
940 return;
941 }
942
943 repostEvent(e);
944
945 CellEditor editor = getCellEditor();
946 if (editor == null || editor.shouldSelectCell(e)) {
947 Point p = e.getPoint();
948 int row = rowAtPoint(p);
949 int column = columnAtPoint(p);
950 // The autoscroller can generate drag events outside the Table's range.
951 if ((column == -1) || (row == -1)) {
952 return;
953 }
954 changeSelection(row, column, false, true);
955 }
956 }
957 }
958 }
959
960
961 /**
962 * This class is a little bit complex, a little bit subtle, and a little bit odd.
963 * The strange interaction model is due to the fact that it is very tightly
964 * coupled to the EnrichPane.
965 *
966 * The interaction is complex because there are three separate controls that the
967 * user may interact with, each of which can affect the other two. The three
968 * controls are:
969 * - The "assigned metadata" table, at the top right of the meta edit pane.
970 * - The "metadata value" text field, where users can type in new values.
971 * - The "metadata value tree", which shows other values that have been
972 * assigned to the selected metadata element.
973 *
974 * The interactions are:
975 * - The "assigned metadata" table
976 * Users may select one (and only one) row in this table. Selecting a row
977 * chooses one metadata element. The text field will be set to the current
978 * value of the metadata element. This value will also be selected in the
979 * metadata value tree.
980 *
981 * - The "metadata value" text field
982 * If the value the user has typed in this is a prefix of an entry in the
983 * value tree, this value will be selected in the value tree. In this case,
984 * pressing "Tab" will complete the value (ie. make the value in the text
985 * field the same as the value in the tree). This is to allow users to
986 * quickly and easily assign existing metadata values to new documents.
987 *
988 * - The "metadata value tree"
989 * Selecting a value in the tree will set the text field to this value.
990 */
991 private class MetadataValueTree
992 extends JPanel
993 {
994 private boolean ignore_tree_selection_event = false;
995 private boolean manual_text_edit_event = false;
996 private CardLayout card_layout;
997 private String card_showing = null;
998 /** The metadata element that is currently selected. */
999 private MetadataElement selected_metadata_element = null;
1000 private JTextArea extracted_message;
1001 private JTree tree;
1002
1003 static final private String NONE = "None";
1004 static final private String TREE = "Tree";
1005
1006
1007 public MetadataValueTree(int width, int height)
1008 {
1009 super();
1010
1011 setFont(Configuration.getFont("general.font", false));
1012 setPreferredSize(new Dimension(width, height));
1013
1014 JPanel metadata_pane = new JPanel();
1015
1016 JPanel value_pane = new JPanel();
1017 JLabel value_label = new JLabel();
1018 Dictionary.registerText(value_label, "EnrichPane.Value");
1019
1020 JPanel value_field_pane = new JPanel();
1021 metadata_value_text_field.setBackground(Configuration.getColor("coloring.editable_background", false));
1022 metadata_value_text_field.setForeground(Configuration.getColor("coloring.editable_foreground", false));
1023 metadata_value_text_field.setPreferredSize(new Dimension(413, 24));
1024 metadata_value_text_field.getDocument().addDocumentListener(new DocumentListenerImpl());
1025 metadata_value_text_field.addKeyListener(new MetadataValueTextFieldKeyListener());
1026 metadata_value_text_field.setFocusTraversalKeysEnabled(false);
1027 Dictionary.setTooltip(metadata_value_text_field, "EnrichPane.Value_Field_Tooltip");
1028
1029 JPanel button_pane = new JPanel();
1030
1031 JPanel tree_pane = new JPanel();
1032 JLabel tree_label = new JLabel();
1033 Dictionary.registerText(tree_label, "EnrichPane.Tree");
1034
1035 tree = new JTree();
1036 tree.addMouseListener(new MetadataValueTreeMouseListener());
1037 tree.addTreeSelectionListener(new MetadataValueTreeSelectionListener());
1038 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
1039 tree.setRootVisible(false);
1040 tree.putClientProperty("JTree.lineStyle", "Angled");
1041
1042 JPanel extracted_pane = new JPanel();
1043 JPanel extracted_header_pane = new JPanel();
1044 extracted_message = new JTextArea("");
1045 extracted_message.setEditable(false);
1046 extracted_message.setLineWrap(true);
1047 extracted_message.setOpaque(false);
1048 extracted_message.setWrapStyleWord(true);
1049
1050 card_layout = new CardLayout();
1051
1052 // Layout
1053 value_field_pane.setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
1054 value_field_pane.setLayout(new BorderLayout(0, 0));
1055 value_field_pane.add(metadata_value_text_field, BorderLayout.CENTER);
1056
1057 button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
1058 button_pane.setLayout(new GridLayout());
1059 button_pane.add(add);
1060 button_pane.add(replace);
1061 button_pane.add(remove);
1062
1063 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
1064 value_pane.setLayout(new BorderLayout());
1065 value_pane.add(value_label, BorderLayout.WEST);
1066 value_pane.add(value_field_pane, BorderLayout.CENTER);
1067 value_pane.add(expand, BorderLayout.EAST);
1068 value_pane.add(button_pane, BorderLayout.SOUTH);
1069
1070 tree_pane.setLayout(new BorderLayout());
1071 tree_pane.add(tree_label, BorderLayout.NORTH);
1072 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
1073
1074 metadata_pane.setLayout(new BorderLayout());
1075 metadata_pane.add(value_pane, BorderLayout.NORTH);
1076 metadata_pane.add(tree_pane, BorderLayout.CENTER);
1077
1078 extracted_header_pane.setLayout(new BorderLayout());
1079 extracted_header_pane.add(expand_for_extracted, BorderLayout.EAST);
1080
1081 extracted_pane.setBorder(BorderFactory.createEmptyBorder(0,10,25,0));
1082 extracted_pane.setLayout(new BorderLayout());
1083 extracted_pane.add(extracted_header_pane, BorderLayout.NORTH);
1084 extracted_pane.add(extracted_message, BorderLayout.CENTER);
1085
1086 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1087 this.setLayout(card_layout);
1088 this.add(metadata_pane, TREE);
1089 this.add(extracted_pane, NONE);
1090 card_showing = TREE;
1091 }
1092
1093
1094 /** Returns a TreePath for the node most closely matching the metadata value. */
1095 private TreePath getClosestPath(String val)
1096 {
1097 // Start at the root of the tree
1098 MetadataValueTreeNode tree_node = (MetadataValueTreeNode) tree.getModel().getRoot();
1099
1100 // Separate hierarchical values
1101 PatternTokenizer tokenizer = new PatternTokenizer(val, MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN);
1102 while (tokenizer.hasMoreTokens()) {
1103 String token = tokenizer.nextToken();
1104
1105 // All components except the last must match exactly
1106 if (tokenizer.hasMoreTokens()) {
1107 for (int i = 0; i < tree_node.getChildCount(); i++) {
1108 MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
1109 if (child_node.getValue().equals(token)) {
1110 // The matching node has been found, so move onto the next token
1111 tree_node = child_node;
1112 break;
1113 }
1114 }
1115 }
1116
1117 // The last component may match partially
1118 else {
1119 for (int i = 0; i < tree_node.getChildCount(); i++) {
1120 MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
1121 if (child_node.getFullValue().startsWith(val)) {
1122 // The closest node has been found, so return its path
1123 return new TreePath(child_node.getPath());
1124 }
1125 }
1126
1127 // Not even a partial match
1128 return null;
1129 }
1130 }
1131
1132 // If nothing else, return the path of the root node
1133 return new TreePath(tree_node.getPath());
1134 }
1135
1136
1137 public String getSelectedValue()
1138 {
1139 String metadata_value_raw = metadata_value_text_field.getText();
1140 String metadata_value = XMLTools.removeInvalidCharacters(metadata_value_raw);
1141 return metadata_value.replaceAll("\\\\", MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN);
1142 }
1143
1144
1145 public void rebuild(MetadataElement metadata_element, String metadata_value)
1146 {
1147 // System.err.println("In MetadataValueTree.rebuild()...");
1148
1149 // Clear selection
1150 if (metadata_element == null) {
1151 if (metadata_edit_event == false) {
1152 // System.err.println("Clearing selection...");
1153 selected_metadata_element = null;
1154 validateDisplay();
1155 }
1156 return;
1157 }
1158
1159 // Reload the value tree model if the selected metadata element has changed
1160 if (selected_metadata_element != metadata_element) {
1161 selected_metadata_element = metadata_element;
1162 tree.setModel(selected_metadata_element.getMetadataValueTreeModel());
1163 }
1164
1165 validateDisplay();
1166
1167 setSelectedValue(metadata_value);
1168 }
1169
1170
1171 /** This function is copied from JTree::setPathToVisible(), and modified so tree rows
1172 are always shown flushed left. Note: doesn't do accessibleContext stuff. */
1173 private void scrollTreePathToVisible(TreePath path)
1174 {
1175 if (path != null) {
1176 tree.makeVisible(path);
1177
1178 Rectangle bounds = tree.getPathBounds(path);
1179 if (bounds != null) {
1180 bounds.width += bounds.x;
1181 bounds.x = 0;
1182 tree.scrollRectToVisible(bounds);
1183 }
1184 }
1185 }
1186
1187
1188 /** Sets the value in the text field. */
1189 private void setSelectedValue(String metadata_value)
1190 {
1191 // Setting the text of the field causes the DocumentListener to be notified, and
1192 // updating the tree is handled there (DocumentListenerImpl::validate())
1193 if (!card_showing.equals(NONE)) {
1194 manual_text_edit_event = metadata_value.equals(""); // Set to false unless val == ""
1195 metadata_value_text_field.setText(metadata_value);
1196 manual_text_edit_event = true;
1197 }
1198 }
1199
1200
1201 private void validateDisplay()
1202 {
1203 // If no metadata is selected in the metadata value table, display "no metadata element selected" card
1204 if (file_nodes == null || selected_metadata_element == null) {
1205 metadata_value_tree_card_layout.show(control_pane, TREE_CARD_NO_METADATA_ELEMENT_SELECTED);
1206 }
1207
1208 // Otherwise, display the card with the metadata value tree
1209 else {
1210 metadata_value_tree_card_layout.show(control_pane, TREE_CARD);
1211
1212 // Special case the extracted metadata set
1213 if (selected_metadata_element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
1214 // Display the panel showing the "you cannot edit this metadata" message
1215 String[] args = new String[1];
1216 args[0] = selected_metadata_element.getFullName();
1217 Dictionary.registerText(extracted_message, "EnrichPane.AutoMessage", args);
1218 card_layout.show(this, NONE);
1219 card_showing = NONE;
1220 }
1221 else {
1222 // Display the panel for viewing and assigning metadata
1223 card_layout.show(this, TREE);
1224 card_showing = TREE;
1225 }
1226
1227 // Validate the buttons above the value tree
1228 MetadataValueTableEntry metadata_value_table_entry = metadata_value_table.getSelectedMetadataValueTableEntry();
1229 if (metadata_value_table_entry.getValue().equals("")) {
1230 // Can only append if something has been entered
1231 add.setEnabled((getSelectedValue().length() > 0));
1232
1233 // Nothing to replace or remove
1234 replace.setEnabled(false);
1235 remove.setEnabled(false);
1236 return;
1237 }
1238
1239 // Check if the text in the value field is the same as the metadata value
1240 if (getSelectedValue().equals(metadata_value_table_entry.getFullValue())) {
1241 // Can't replace
1242 replace.setEnabled(false);
1243
1244 // Adding, however, is dependant on whether the selected metadata is common or uncommon. If the later then you can append so as to make it common.
1245 add.setEnabled(!metadata_value_table.isSelectedMetadataValueTableEntryCommon());
1246 }
1247 else {
1248 // Can append or replace, if something has been entered
1249 add.setEnabled((getSelectedValue().length() > 0));
1250 replace.setEnabled((getSelectedValue().length() > 0));
1251 }
1252
1253 // Can only remove if the metadata is file level
1254 if (metadata_value_table_entry != null) { // Shouldn't be necessary, but is
1255 remove.setEnabled((metadata_value_table_entry.isInheritedMetadata() == false));
1256 }
1257 }
1258 }
1259
1260
1261 private class MetadataValueTextFieldKeyListener
1262 extends KeyAdapter
1263 {
1264 /** Gives notification of key events on the text field */
1265 public void keyPressed(KeyEvent e)
1266 {
1267 // Tab: Auto-complete
1268 if (e.getKeyCode() == KeyEvent.VK_TAB) {
1269 if (tree.getSelectionCount() != 0 && !getSelectedValue().equals("")) {
1270 TreePath path = tree.getSelectionPath();
1271 MetadataValueTreeNode node = (MetadataValueTreeNode) path.getLastPathComponent();
1272 setSelectedValue(node.getFullValue());
1273 }
1274 }
1275
1276 // Enter: Append the metadata value, if we're allowed to
1277 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1278 if (add.isEnabled()) {
1279 add.doClick();
1280 }
1281 }
1282
1283 // Down: Change the metadata value table selection
1284 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
1285 metadata_value_table.moveSelectionDown();
1286 }
1287
1288 // Up: Change the metadata value table selection
1289 if (e.getKeyCode() == KeyEvent.VK_UP) {
1290 metadata_value_table.moveSelectionUp();
1291 }
1292 }
1293 }
1294
1295
1296 private class MetadataValueTreeMouseListener
1297 extends MouseAdapter
1298 {
1299 public void mouseClicked(MouseEvent e)
1300 {
1301 // Double click: assign the selected value tree node immediately (if possible)
1302 if (e.getClickCount() == 2 && tree.getSelectionCount() != 0) {
1303 TreePath path = tree.getSelectionPath();
1304 MetadataValueTreeNode node = (MetadataValueTreeNode) path.getLastPathComponent();
1305
1306 // Leaf nodes only (folder nodes will expand/contract)
1307 if (node.getChildCount() == 0 && add.isEnabled()) {
1308 add.doClick();
1309 }
1310 }
1311 }
1312 }
1313
1314
1315 private class MetadataValueTreeSelectionListener
1316 implements TreeSelectionListener
1317 {
1318 public void valueChanged(TreeSelectionEvent event)
1319 {
1320 // Select a node in the tree: fill the metadata value text field with the selected node's value
1321 if (tree.getSelectionCount() != 0 && !ignore_tree_selection_event) {
1322 TreePath path = tree.getSelectionPath();
1323 MetadataValueTreeNode node = (MetadataValueTreeNode) path.getLastPathComponent();
1324 setSelectedValue(node.getFullValue());
1325 }
1326 }
1327 }
1328
1329
1330 private class DocumentListenerImpl
1331 implements DocumentListener
1332 {
1333 /** Gives notification that an attribute or set of attributes changed */
1334 public void changedUpdate(DocumentEvent e) {
1335 validate();
1336 }
1337
1338 /** Gives notification that there was an insert into the document */
1339 public void insertUpdate(DocumentEvent e) {
1340 validate();
1341 }
1342
1343 /** Gives notification that a portion of the document has been removed */
1344 public void removeUpdate(DocumentEvent e) {
1345 validate();
1346 }
1347
1348
1349 /** Ensures that the value text field and value tree are synchronized */
1350 private void validate()
1351 {
1352 String value_text = getSelectedValue();
1353
1354 // Ignore the validate() with empty text that occurs when value.setText() is used
1355 if (!value_text.equals("") || manual_text_edit_event == true) {
1356 TreePath closest_path = getClosestPath(value_text);
1357
1358 // Select the new path in the tree
1359 // The tree selection event this causes must be ignored, since it alters value
1360 ignore_tree_selection_event = true;
1361 tree.setSelectionPath(closest_path);
1362 ignore_tree_selection_event = false;
1363
1364 // If a folder has been specified, make sure it is expanded
1365 if (value_text.endsWith(MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN) && !tree.isExpanded(closest_path)) {
1366 tree.expandPath(closest_path);
1367 }
1368
1369 // Make sure the path is visible on the screen
1370 scrollTreePathToVisible(closest_path);
1371 // One call should be enough, but it isn't in some cases (for some reason)
1372 scrollTreePathToVisible(closest_path);
1373
1374 // Update the status of the buttons
1375 validateDisplay();
1376 }
1377 }
1378 }
1379 }
1380
1381
1382 /** This class listens for certain key presses, such as [Up] or [Down], -copied from Gather Pane */
1383 private class KeyListenerImpl
1384 extends KeyAdapter {
1385 private boolean vk_left_pressed = false;
1386 /** Called whenever a key that was pressed is released, it is this action that will cause the desired effects (this allows for the key event itself to be processed prior to this listener dealing with it). */
1387 public void keyReleased(KeyEvent event) {
1388 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
1389 if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
1390 DragTree tree = (DragTree)event.getSource();
1391 // we need to manually do the up and down selections
1392 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
1393 int current_row = tree.getLeadSelectionRow();
1394 tree.setImmediate(true);
1395 if (up) {
1396 if (current_row > 0) {
1397 tree.clearSelection();
1398 tree.setSelectionRow(current_row-1);
1399 }
1400 } else {
1401 if (current_row < tree.getRowCount()-1){
1402 tree.clearSelection();
1403 tree.setSelectionRow(current_row+1);
1404 }
1405 }
1406 tree.setImmediate(false);
1407 }
1408 else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1409 // left key on a file shifts the selection to the parent folder
1410 DragTree tree = (DragTree)event.getSource();
1411 TreePath path = tree.getLeadSelectionPath();
1412 if(path != null) {
1413 File file = ((CollectionTreeNode) path.getLastPathComponent()).getFile();
1414 if(file != null) {
1415 if (file.isFile() || vk_left_pressed) {
1416 vk_left_pressed = false;
1417 TreePath parent_path = path.getParentPath();
1418 if (parent_path != null && parent_path.getParentPath() != null) {
1419 // if this file is in the top level folder, don't move the focus
1420 tree.setImmediate(true);
1421 tree.clearSelection();
1422 tree.setSelectionPath(parent_path);
1423 tree.setImmediate(false);
1424 }
1425 }
1426 }
1427 }
1428 }
1429 }
1430
1431 // we need to watch for left clicks on an unopened folder - should shift the focus to teh parent folder. But because there is some other mysterious key listener that does opening and closing folders on right and left clicks, we must detect the situation before the other handler has done its job, and process it after.
1432 public void keyPressed(KeyEvent event) {
1433 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1434 // left key on closed directory shifts the selection to the parent folder
1435 DragTree tree = (DragTree)event.getSource();
1436 TreePath path = tree.getLeadSelectionPath();
1437 if(path == null) return;
1438 File file = ((CollectionTreeNode) path.getLastPathComponent()).getFile();
1439 if(file == null) return;
1440
1441 if (file.isDirectory() && tree.isCollapsed(path)) {
1442 vk_left_pressed = true;
1443 }
1444 }
1445 }
1446 }
1447
1448
1449 /** A listener for right mouse button clicks over the collection tree. */
1450 private class RightButtonListener
1451 extends MouseAdapter {
1452 /** Called whenever a mouse click occurs (right or left) over a target component.
1453 * @param event A <strong>MouseEvent</strong> containing further information about the mouse click action.
1454 * @see org.greenstone.gatherer.gui.EnrichPane.RightButtonMenu
1455 */
1456 public void mouseClicked(MouseEvent event) {
1457 if (SwingUtilities.isRightMouseButton(event)) {
1458 new RightButtonMenu(event);
1459 }
1460 }
1461 }
1462
1463
1464 /** When a user right-clicks within the collection tree they are presented with a small popup menu of context based options. This class provides such functionality.
1465 */
1466 private class RightButtonMenu
1467 extends JPopupMenu
1468 implements ActionListener {
1469
1470 /** The tree over which the right click action occurred. */
1471 private DragTree tree = null;
1472 /** The tree nodes selected when the right click action occurred. */
1473 private TreePath[] selection_paths = null;
1474 /** The file record over which the right click action occurred. */
1475 private CollectionTreeNode node = null;
1476
1477 private JMenuItem collapse_folder = null;
1478 private JMenuItem expand_folder = null;
1479 private JMenuItem metaaudit = null;
1480 private JMenuItem open_externally = null;
1481
1482
1483 private RightButtonMenu(MouseEvent event)
1484 {
1485 super();
1486 this.tree = collection_tree;
1487
1488 // Note we have to use setImmediate() with the set selction paths
1489 // otherwise the selection doesn't get updated until after the
1490 // popup comes up.
1491
1492 // the right click position
1493 TreePath right_click_path = tree.getPathForLocation(event.getX(), event.getY());
1494 if (right_click_path == null) {
1495 // user has clicked outside of the tree, clear the selection
1496 selection_paths = null;
1497 tree.setImmediate(true);
1498 tree.clearSelection();
1499 tree.setImmediate(false);
1500 } else {
1501 // Get the paths currently selected in the tree
1502 selection_paths = tree.getSelectionPaths();
1503 if (selection_paths == null) {
1504 // nothing currently selected - we shift the selection to
1505 // the node that was right clicked on
1506 selection_paths = new TreePath[1];
1507 selection_paths[0] = right_click_path;
1508 tree.setImmediate(true);
1509 tree.setSelectionPath(right_click_path);
1510 tree.setImmediate(false);
1511 } else if (selection_paths.length == 1 && ! selection_paths[0].equals( right_click_path)) {
1512 tree.setImmediate(true);
1513 tree.clearSelection();
1514 tree.setSelectionPath(right_click_path);
1515 tree.setImmediate(false);
1516 selection_paths[0] = right_click_path;
1517 } else {
1518 // we had multiply selected paths in the tree.
1519 // if we clicked on one of those paths, then use all the
1520 // current selection, otherwise clear the selection and
1521 // select the one we right clicked on
1522 boolean clicked_in_selection = false;
1523 for (int i=0; i<selection_paths.length; i++) {
1524 if (selection_paths[i].equals(right_click_path)) {
1525 clicked_in_selection = true;
1526 break;
1527 }
1528 }
1529 if (!clicked_in_selection) {
1530 // want the tree to update right away
1531 tree.setImmediate(true);
1532 tree.clearSelection();
1533 tree.setSelectionPath(right_click_path);
1534 tree.setImmediate(false);
1535 selection_paths = new TreePath[1];
1536 selection_paths[0] = right_click_path;
1537 }
1538 }
1539 }
1540 // finally we have the correct selection paths!!
1541
1542 // Create an appropriate context menu, based on what is selected
1543 buildContextMenu(selection_paths);
1544
1545 // Show the popup menu on screen
1546 show(tree, event.getX(), event.getY());
1547 }
1548
1549
1550 private void buildContextMenu(TreePath[] selection_paths)
1551 {
1552 // If nothing is selected, no options are available
1553 if (selection_paths == null) {
1554 return;
1555 }
1556
1557 DebugStream.println("Number of files/folders selected: " + selection_paths.length);
1558
1559 // Always have meta-audit option
1560 metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", collection_tree.getSelectionDetails()), KeyEvent.VK_A);
1561 metaaudit.addActionListener(this);
1562 add(metaaudit);
1563
1564 // Only meta-audit is available if multiple items are selected...
1565 if (selection_paths.length > 1) {
1566 return;
1567 }
1568
1569 TreePath path = selection_paths[0];
1570 node = (CollectionTreeNode) path.getLastPathComponent();
1571
1572 // ---- Options for file nodes ----
1573 if (node.isLeaf()) {
1574 // Open the file in an external program
1575 open_externally = new JMenuItem(Dictionary.get("Menu.Open_Externally"), KeyEvent.VK_O);
1576 open_externally.addActionListener(this);
1577 add(open_externally);
1578 return;
1579 }
1580
1581 // ---- Options for folder nodes ----
1582 // Collapse or expand, depending on current status
1583 if (tree.isExpanded(path)) {
1584 collapse_folder = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
1585 collapse_folder.addActionListener(this);
1586 add(collapse_folder);
1587 }
1588 else {
1589 expand_folder = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
1590 expand_folder.addActionListener(this);
1591 add(expand_folder);
1592 }
1593 }
1594
1595
1596 /** Called whenever one of the menu items is clicked, this method then causes the appropriate effect. */
1597 public void actionPerformed(ActionEvent event)
1598 {
1599 Object source = event.getSource();
1600
1601 // Collapse folder
1602 if (source == collapse_folder) {
1603 tree.collapsePath(selection_paths[0]);
1604 }
1605
1606 // Expand folder
1607 else if (source == expand_folder) {
1608 tree.expandPath(selection_paths[0]);
1609 }
1610
1611 // Meta-audit
1612 else if (source == metaaudit) {
1613 Gatherer.g_man.showMetaAuditBox();
1614 }
1615
1616 // Open in external program
1617 else if (source == open_externally) {
1618 Gatherer.self.spawnApplication(node.getFile());
1619 }
1620 }
1621 }
1622}
Note: See TracBrowser for help on using the repository browser.