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

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

Tidied up the workspace and collection trees a bit more, in preparation for having special icons for some collection files.

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