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

Last change on this file since 8364 was 8364, checked in by mdewsnip, 20 years ago

Much improved algorithm for selecting the best row in the table after adding/replacing/removing metadata, and improved entry speed: can now use up/down keys for controlling table selection when the value field is in focus.

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