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

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

Avoided an extra valueChanged(TreeSelectionEvent) call when the pane is first entered.

  • Property svn:keywords set to Author Date Id Revision
File size: 53.9 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 metadata_value_table.rebuild();
503
504// // Update the meta-audit view to show the current selection, if any.
505// Gatherer.g_man.meta_audit.setRecords(file_nodes);
506 }
507
508
509 private class AppendMetadataTask
510 extends Thread
511 {
512 private MetadataValue metadata_value = null;
513
514 private AppendMetadataTask(MetadataValue metadata_value)
515 {
516 this.metadata_value = metadata_value;
517 }
518
519 public void run()
520 {
521 if (file_nodes == null || metadata_value == null) {
522 return;
523 }
524
525 // If we're adding metadata to folders display the warning
526 if (!file_nodes[0].isLeaf()) {
527 WarningDialog dialog = new WarningDialog("warning.DirectoryLevelMetadata", true);
528 int dialog_result = dialog.display();
529 dialog.dispose();
530
531 if (dialog_result != JOptionPane.OK_OPTION) {
532 return;
533 }
534 }
535
536 // Edit metadata.xml files to add the metadata
537 MetadataXMLFileManager.addMetadata(file_nodes, metadata_value);
538
539 // Update the metadata value table and tree
540 metadata_edit_event = true;
541 metadata_value_table.rebuild();
542 metadata_edit_event = false;
543 }
544 }
545
546
547 private class UpdateMetadataTask
548 extends Thread
549 {
550 private MetadataValue metadata_value = null;
551
552 private UpdateMetadataTask(MetadataValue metadata_value)
553 {
554 this.metadata_value = metadata_value;
555 }
556
557 public void run()
558 {
559 MetadataValueTableEntry selected_metadata_value_table_entry = metadata_value_table.getSelectedMetadataValueTableEntry();
560 if (selected_metadata_value_table_entry == null || file_nodes == null || metadata_value == null) {
561 return;
562 }
563
564 // Edit metadata.xml files to replace the metadata
565 MetadataXMLFileManager.removeMetadata(file_nodes, selected_metadata_value_table_entry);
566 MetadataXMLFileManager.addMetadata(file_nodes, metadata_value);
567
568 // Update the metadata value table and tree
569 metadata_edit_event = true;
570 metadata_value_table.rebuild();
571 metadata_edit_event = false;
572 }
573 }
574
575
576 private class RemoveMetadataTask
577 extends Thread
578 {
579 public void run()
580 {
581 MetadataValueTableEntry selected_metadata_value_table_entry = metadata_value_table.getSelectedMetadataValueTableEntry();
582 if (selected_metadata_value_table_entry == null || file_nodes == null) {
583 return;
584 }
585
586 // Edit metadata.xml files to remove the metadata
587 MetadataXMLFileManager.removeMetadata(file_nodes, selected_metadata_value_table_entry);
588
589 // Update the metadata value table and tree
590 metadata_edit_event = true;
591 metadata_value_table.rebuild();
592 metadata_edit_event = false;
593 }
594 }
595
596
597 private class MetadataValueTable
598 extends JTable
599 {
600 private MetadataValueTableModel metadata_value_table_model = null;
601 /** The currently selected metadata value table entry */
602 private MetadataValueTableEntry selected_metadata_value_table_entry = null;
603
604
605 public MetadataValueTable()
606 {
607 metadata_value_table_model = new MetadataValueTableModel();
608 setModel(metadata_value_table_model);
609
610 // We allow only one row to be selected at a time
611 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
612 getSelectionModel().addListSelectionListener(new MetadataValueTableListSelectionListener());
613
614 // The default JTable doesn't quite have the desired properties - when a
615 // table cell is clicked on, the table is scrolled so the cell is as
616 // visible as possible. This means that the left-most cells can become
617 // hidden. To fix this, the MouseInputHandler in BasicTableUI is removed
618 // as a MouseListener on the table, and a very slightly altered copy is
619 // added in its place (TableMouseInputHandler).
620 MouseListener[] mls = getMouseListeners();
621 for (int i = 0; i < mls.length; i++) {
622 // Remove the instances of MouseInputHandler
623 if (mls[i].toString().startsWith("javax.swing.plaf.basic.BasicTableUI$MouseInputHandler@")) {
624 removeMouseListener(mls[i]);
625 }
626 }
627 // Add the slightly-altered mouse listener
628 addMouseListener(new TableMouseInputHandler());
629
630 // 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
631 JTableHeader table_header = getTableHeader();
632 Dimension table_header_preferred_size = table_header.getPreferredSize();
633 if (table_header_preferred_size.height < MINIMUM_TABLE_HEADER_SIZE) {
634 table_header_preferred_size.setSize(table_header_preferred_size.width, MINIMUM_TABLE_HEADER_SIZE);
635 table_header.setPreferredSize(table_header_preferred_size);
636 }
637
638 // Set the table columns as we want them
639 setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
640 Dimension table_size = getPreferredSize();
641 TableColumnModel column_model = getColumnModel();
642
643 TableColumn inherited_column = column_model.getColumn(0);
644 inherited_column.setPreferredWidth(20);
645 inherited_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
646
647 TableColumn element_column = column_model.getColumn(1);
648 element_column.setPreferredWidth((TABLE_SIZE.width / 4) - 15);
649 element_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
650
651 TableColumn value_column = column_model.getColumn(2);
652 value_column.setPreferredWidth(((3 * TABLE_SIZE.width) / 4) - 30);
653 value_column.setCellRenderer(new MetadataValueTableCellRenderer(metadata_value_table_model));
654 }
655
656
657 public MetadataElement getSelectedMetadataElement()
658 {
659 return selected_metadata_value_table_entry.getMetadataElement();
660 }
661
662
663 public MetadataValueTableEntry getSelectedMetadataValueTableEntry()
664 {
665 return selected_metadata_value_table_entry;
666 }
667
668
669 public boolean isSelectedMetadataValueTableEntryCommon()
670 {
671 return metadata_value_table_model.isCommon(selected_metadata_value_table_entry);
672 }
673
674
675 public void rebuild()
676 {
677 // Remember the current metadata element selection so we can select it again later
678 MetadataValueTableEntry previously_selected_metadata_value_table_entry = selected_metadata_value_table_entry;
679
680 // We don't want a lot of ListSelectionEvents while the table is rebuilding
681 clearSelection();
682
683 // Rebuild the metadata value table model
684 metadata_value_table_model.rebuild(file_nodes);
685
686 // If the metadata value table is empty there isn't much to do
687 if (metadata_value_table_model.getRowCount() == 0) {
688 // ...except display the "no metadata available" card, unless no files are selected
689 if (file_nodes != null) {
690 metadata_value_table_card_layout.show(table_card_pane, TABLE_CARD_NO_METADATA_AVAILABLE);
691 }
692 return;
693 }
694
695 // Display the card with the metadata value table
696 metadata_value_table_card_layout.show(table_card_pane, TABLE_CARD);
697
698 // If there was no previous selection then we're done
699 if (previously_selected_metadata_value_table_entry == null) {
700 return;
701 }
702
703 // Look for an exact match
704 for (int i = 0; i < metadata_value_table_model.getRowCount(); i++) {
705 MetadataValueTableEntry metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(i);
706 int c = metadata_value_table_entry.compareTo(previously_selected_metadata_value_table_entry);
707
708 if (c == 0) {
709 // Select this row and we're done
710 addRowSelectionInterval(i, i);
711 return;
712 }
713
714 if (c < 0) {
715 // Not in the list
716 break;
717 }
718 }
719
720 // Select the first entry with the same metadata element
721 MetadataElement previously_selected_metadata_element = previously_selected_metadata_value_table_entry.getMetadataElement();
722 for (int i = 0; i < metadata_value_table_model.getRowCount(); i++) {
723 MetadataElement metadata_element = metadata_value_table_model.getMetadataValueTableEntry(i).getMetadataElement();
724 if (metadata_element.equals(previously_selected_metadata_element)) {
725 // Select this row and we're done
726 addRowSelectionInterval(i, i);
727 return;
728 }
729 }
730
731 // All else has failed, so just select the first row in the table
732 addRowSelectionInterval(0, 0);
733 }
734
735
736 private class MetadataValueTableListSelectionListener
737 implements ListSelectionListener
738 {
739 public void valueChanged(ListSelectionEvent event)
740 {
741 // We only want to handle one event per selection, so wait for the value to stabilise
742 if (getSelectionModel().getValueIsAdjusting() == true) {
743 return;
744 }
745
746 // System.err.println("In MetadataValueTableListSelectionListener.valueChanged()...");
747
748 selected_metadata_value_table_entry = null;
749 MetadataElement selected_metadata_element = null;
750 String selected_metadata_value = "";
751
752 // We have a SINGLE_SELECTION model set so there is at most one selection
753 int selected_row = getSelectedRow();
754 if (selected_row >= 0) {
755 selected_metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(selected_row);
756 selected_metadata_element = selected_metadata_value_table_entry.getMetadataElement();
757 MetadataValueTreeNode metadata_value_tree_node = selected_metadata_value_table_entry.getMetadataValueTreeNode();
758 if (metadata_value_tree_node != null) {
759 selected_metadata_value = metadata_value_tree_node.getFullValue();
760 }
761 }
762
763 // Update the metadata value tree
764 metadata_value_tree.rebuild(selected_metadata_element, selected_metadata_value);
765 }
766 }
767
768
769 /** 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. */
770 private class TableMouseInputHandler implements MouseInputListener
771 {
772 // Component receiving mouse events during editing.
773 // May not be editorComponent.
774 private Component dispatchComponent;
775 private boolean selectedOnPress;
776
777 // The Table's mouse listener methods.
778
779 public void mouseClicked(MouseEvent e) {}
780
781 private void setDispatchComponent(MouseEvent e) {
782 Component editorComponent = getEditorComponent();
783 Point p = e.getPoint();
784 Point p2 = SwingUtilities.convertPoint(metadata_value_table, p, editorComponent);
785 dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent,
786 p2.x, p2.y);
787 }
788
789 private boolean repostEvent(MouseEvent e) {
790 // Check for isEditing() in case another event has
791 // caused the editor to be removed. See bug #4306499.
792 if (dispatchComponent == null || !isEditing()) {
793 return false;
794 }
795 MouseEvent e2 = SwingUtilities.convertMouseEvent(metadata_value_table, e, dispatchComponent);
796 dispatchComponent.dispatchEvent(e2);
797 return true;
798 }
799
800 private void setValueIsAdjusting(boolean flag) {
801 getSelectionModel().setValueIsAdjusting(flag);
802 getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
803 }
804
805 private boolean shouldIgnore(MouseEvent e) {
806 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && isEnabled()));
807 }
808
809 public void mousePressed(MouseEvent e) {
810 if (e.isConsumed()) {
811 selectedOnPress = false;
812 return;
813 }
814 selectedOnPress = true;
815 adjustFocusAndSelection(e);
816 }
817
818 void adjustFocusAndSelection(MouseEvent e) {
819 if (shouldIgnore(e)) {
820 return;
821 }
822
823 Point p = e.getPoint();
824 int row = rowAtPoint(p);
825 int column = columnAtPoint(p);
826 // The autoscroller can generate drag events outside the Table's range.
827 if ((column == -1) || (row == -1)) {
828 return;
829 }
830
831 if (editCellAt(row, column, e)) {
832 setDispatchComponent(e);
833 repostEvent(e);
834 }
835 else if (isRequestFocusEnabled()) {
836 requestFocus();
837 }
838
839 CellEditor editor = getCellEditor();
840 if (editor == null || editor.shouldSelectCell(e)) {
841 boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
842 setValueIsAdjusting(adjusting);
843
844 // Special code for clicking the first column (folder-level metadata)
845 if (column == 0) {
846 selected_metadata_value_table_entry = metadata_value_table_model.getMetadataValueTableEntry(row);
847
848 // If this metadata is inherited, switch to the folder it came from
849 if (selected_metadata_value_table_entry.isInheritedMetadata()) {
850 File folder_metadata_inherited_from = selected_metadata_value_table_entry.getFolderMetadataInheritedFrom();
851 if (folder_metadata_inherited_from != null) {
852 collection_tree.setSelection(folder_metadata_inherited_from);
853 }
854 }
855
856 changeSelection(row, 0, e.isControlDown(), e.isShiftDown());
857 }
858 else {
859 changeSelection(row, 1, e.isControlDown(), e.isShiftDown());
860 }
861 }
862 }
863
864 public void mouseReleased(MouseEvent e) {
865 if (selectedOnPress) {
866 if (shouldIgnore(e)) {
867 return;
868 }
869
870 repostEvent(e);
871 dispatchComponent = null;
872 setValueIsAdjusting(false);
873 } else {
874 adjustFocusAndSelection(e);
875 }
876 }
877
878
879 public void mouseEntered(MouseEvent e) {}
880
881 public void mouseExited(MouseEvent e) {}
882
883 // The Table's mouse motion listener methods.
884
885 public void mouseMoved(MouseEvent e) {}
886
887 public void mouseDragged(MouseEvent e) {
888 if (shouldIgnore(e)) {
889 return;
890 }
891
892 repostEvent(e);
893
894 CellEditor editor = getCellEditor();
895 if (editor == null || editor.shouldSelectCell(e)) {
896 Point p = e.getPoint();
897 int row = rowAtPoint(p);
898 int column = columnAtPoint(p);
899 // The autoscroller can generate drag events outside the Table's range.
900 if ((column == -1) || (row == -1)) {
901 return;
902 }
903 changeSelection(row, column, false, true);
904 }
905 }
906 }
907 }
908
909
910 /**
911 * This class is a little bit complex, a little bit subtle, and a little bit odd.
912 * The strange interaction model is due to the fact that it is very tightly
913 * coupled to the EnrichPane.
914 *
915 * The interaction is complex because there are three separate controls that the
916 * user may interact with, each of which can affect the other two. The three
917 * controls are:
918 * - The "assigned metadata" table, at the top right of the meta edit pane.
919 * - The "metadata value" text field, where users can type in new values.
920 * - The "metadata value tree", which shows other values that have been
921 * assigned to the selected metadata element.
922 *
923 * The interactions are:
924 * - The "assigned metadata" table
925 * Users may select one (and only one) row in this table. Selecting a row
926 * chooses one metadata element. The text field will be set to the current
927 * value of the metadata element. This value will also be selected in the
928 * metadata value tree.
929 *
930 * - The "metadata value" text field
931 * If the value the user has typed in this is a prefix of an entry in the
932 * value tree, this value will be selected in the value tree. In this case,
933 * pressing "Tab" will complete the value (ie. make the value in the text
934 * field the same as the value in the tree). This is to allow users to
935 * quickly and easily assign existing metadata values to new documents.
936 *
937 * - The "metadata value tree"
938 * Selecting a value in the tree will set the text field to this value.
939 */
940 private class MetadataValueTree
941 extends JPanel
942 implements TreeSelectionListener
943 {
944 private boolean ignore_tree_selection_event = false;
945 private boolean manual_text_edit_event = false;
946 private CardLayout card_layout;
947 private String card_showing = null;
948 /** The metadata element that is currently selected. */
949 private MetadataElement selected_metadata_element = null;
950 private JTextArea extracted_message;
951 private JTextField value;
952 private JTree tree;
953
954 static final private String NONE = "None";
955 static final private String TREE = "Tree";
956
957
958 public MetadataValueTree(int width, int height)
959 {
960 super();
961
962 setFont(Configuration.getFont("general.font", false));
963 setPreferredSize(new Dimension(width, height));
964
965 JPanel metadata_pane = new JPanel();
966
967 JPanel value_pane = new JPanel();
968 JLabel value_label = new JLabel();
969 Dictionary.registerText(value_label, "MetaEdit.Value");
970
971 JPanel value_field_pane = new JPanel();
972 value = new JTextField();
973 value.setBackground(Configuration.getColor("coloring.editable_background", false));
974 value.setForeground(Configuration.getColor("coloring.editable_foreground", false));
975 value.setPreferredSize(new Dimension(413, 24));
976 value.getDocument().addDocumentListener(new DocumentListenerImpl());
977 value.addKeyListener(new MetadataFieldListener());
978 value.setFocusTraversalKeysEnabled(false);
979 Dictionary.setTooltip(value, "MetaEdit.Value_Field_Tooltip");
980
981 JPanel button_pane = new JPanel();
982
983 JPanel tree_pane = new JPanel();
984 JLabel tree_label = new JLabel();
985 Dictionary.registerText(tree_label, "MetaEdit.Tree");
986
987 tree = new JTree();
988 tree.addTreeSelectionListener(this);
989 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
990 tree.setRootVisible(false);
991 tree.putClientProperty("JTree.lineStyle", "Angled");
992
993 JPanel extracted_pane = new JPanel();
994 JPanel extracted_header_pane = new JPanel();
995 extracted_message = new JTextArea("");
996 extracted_message.setEditable(false);
997 extracted_message.setLineWrap(true);
998 extracted_message.setOpaque(false);
999 extracted_message.setWrapStyleWord(true);
1000
1001 card_layout = new CardLayout();
1002
1003 // Layout
1004 value_field_pane.setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
1005 value_field_pane.setLayout(new BorderLayout(0, 0));
1006 value_field_pane.add(value, BorderLayout.CENTER);
1007
1008 button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
1009 button_pane.setLayout(new GridLayout());
1010 button_pane.add(add);
1011 button_pane.add(update);
1012 button_pane.add(remove);
1013
1014 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
1015 value_pane.setLayout(new BorderLayout());
1016 value_pane.add(value_label, BorderLayout.WEST);
1017 value_pane.add(value_field_pane, BorderLayout.CENTER);
1018 value_pane.add(expand, BorderLayout.EAST);
1019 value_pane.add(button_pane, BorderLayout.SOUTH);
1020
1021 tree_pane.setLayout(new BorderLayout());
1022 tree_pane.add(tree_label, BorderLayout.NORTH);
1023 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
1024
1025 metadata_pane.setLayout(new BorderLayout());
1026 metadata_pane.add(value_pane, BorderLayout.NORTH);
1027 metadata_pane.add(tree_pane, BorderLayout.CENTER);
1028
1029 extracted_header_pane.setLayout(new BorderLayout());
1030 extracted_header_pane.add(expand_for_extracted, BorderLayout.EAST);
1031
1032 extracted_pane.setBorder(BorderFactory.createEmptyBorder(0,10,25,0));
1033 extracted_pane.setLayout(new BorderLayout());
1034 extracted_pane.add(extracted_header_pane, BorderLayout.NORTH);
1035 extracted_pane.add(extracted_message, BorderLayout.CENTER);
1036
1037 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1038 this.setLayout(card_layout);
1039 this.add(metadata_pane, TREE);
1040 this.add(extracted_pane, NONE);
1041 card_showing = TREE;
1042 }
1043
1044
1045 /** Returns a TreePath for the node most closely matching the metadata value. */
1046 private TreePath getClosestPath(String val)
1047 {
1048 // Start at the root of the tree
1049 MetadataValueTreeNode tree_node = (MetadataValueTreeNode) tree.getModel().getRoot();
1050
1051 // Separate hierarchical values
1052 PatternTokenizer tokenizer = new PatternTokenizer(val, MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN);
1053 while (tokenizer.hasMoreTokens()) {
1054 String token = tokenizer.nextToken();
1055
1056 // All components except the last must match exactly
1057 if (tokenizer.hasMoreTokens()) {
1058 for (int i = 0; i < tree_node.getChildCount(); i++) {
1059 MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
1060 if (child_node.getValue().equals(token)) {
1061 // The matching node has been found, so move onto the next token
1062 tree_node = child_node;
1063 break;
1064 }
1065 }
1066 }
1067
1068 // The last component may match partially
1069 else {
1070 for (int i = 0; i < tree_node.getChildCount(); i++) {
1071 MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
1072 if (child_node.getValue().startsWith(token)) {
1073 // The closest node has been found, so return its path
1074 return new TreePath(child_node.getPath());
1075 }
1076 }
1077
1078 // Not even a partial match
1079 return null;
1080 }
1081 }
1082
1083 // If nothing else, return the path of the root node
1084 return new TreePath(tree_node.getPath());
1085 }
1086
1087
1088 public String getSelectedValue()
1089 {
1090 return value.getText().replaceAll("\\\\", MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN);
1091 }
1092
1093
1094 public void rebuild(MetadataElement metadata_element, String metadata_value)
1095 {
1096 // System.err.println("In MetadataValueTree.rebuild()...");
1097
1098 // Clear selection
1099 if (metadata_element == null) {
1100 if (metadata_edit_event == false) {
1101 System.err.println("Clearing selection...");
1102 selected_metadata_element = null;
1103 validateDisplay();
1104 }
1105 return;
1106 }
1107
1108 // Reload the value tree model if the selected metadata element has changed
1109 if (selected_metadata_element != metadata_element) {
1110 selected_metadata_element = metadata_element;
1111 tree.setModel(selected_metadata_element.getMetadataValueTreeModel());
1112 }
1113
1114 validateDisplay();
1115
1116 setSelectedValue(metadata_value);
1117 }
1118
1119
1120 /** This function is copied from JTree::setPathToVisible(), and modified so tree rows
1121 are always shown flushed left. Note: doesn't do accessibleContext stuff. */
1122 private void scrollTreePathToVisible(TreePath path)
1123 {
1124 if (path != null) {
1125 tree.makeVisible(path);
1126
1127 Rectangle bounds = tree.getPathBounds(path);
1128 if (bounds != null) {
1129 bounds.width += bounds.x;
1130 bounds.x = 0;
1131 tree.scrollRectToVisible(bounds);
1132 }
1133 }
1134 }
1135
1136
1137 /** Sets the value in the text field. */
1138 private void setSelectedValue(String metadata_value)
1139 {
1140 // Setting the text of the field causes the DocumentListener to be notified, and
1141 // updating the tree is handled there (DocumentListenerImpl::validate())
1142 if (!card_showing.equals(NONE)) {
1143 manual_text_edit_event = metadata_value.equals(""); // Set to false unless val == ""
1144 value.setText(metadata_value);
1145 manual_text_edit_event = true;
1146 }
1147 }
1148
1149
1150 private void validateDisplay()
1151 {
1152 // If no metadata is selected in the metadata value table, display "no metadata element selected" card
1153 if (file_nodes == null || selected_metadata_element == null) {
1154 metadata_value_tree_card_layout.show(control_pane, TREE_CARD_NO_METADATA_ELEMENT_SELECTED);
1155 }
1156
1157 // Otherwise, display the card with the metadata value tree
1158 else {
1159 metadata_value_tree_card_layout.show(control_pane, TREE_CARD);
1160
1161 // Special case the extracted metadata set
1162 if (selected_metadata_element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
1163 // Display the panel showing the "you cannot edit this metadata" message
1164 String[] args = new String[1];
1165 args[0] = selected_metadata_element.getFullName();
1166 Dictionary.registerText(extracted_message, "MetaEdit.AutoMessage", args);
1167 card_layout.show(this, NONE);
1168 card_showing = NONE;
1169 }
1170 else {
1171 // Display the panel for viewing and assigning metadata
1172 card_layout.show(this, TREE);
1173 card_showing = TREE;
1174 }
1175
1176 // Validate the buttons above the value tree
1177 MetadataValueTableEntry metadata_value_table_entry = metadata_value_table.getSelectedMetadataValueTableEntry();
1178 if (metadata_value_table_entry.getValue().equals("")) {
1179 // Can only append if something has been entered
1180 add.setEnabled((getSelectedValue().length() > 0));
1181
1182 // Nothing to replace or remove
1183 update.setEnabled(false);
1184 remove.setEnabled(false);
1185 return;
1186 }
1187
1188 // Check if the text in the value field is the same as the metadata value
1189 if (getSelectedValue().equals(metadata_value_table_entry.getFullValue())) {
1190 // Can't replace
1191 update.setEnabled(false);
1192
1193 // 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.
1194 add.setEnabled(!metadata_value_table.isSelectedMetadataValueTableEntryCommon());
1195 }
1196 else {
1197 // Can append or replace, if something has been entered
1198 add.setEnabled((getSelectedValue().length() > 0));
1199 update.setEnabled((getSelectedValue().length() > 0));
1200 }
1201
1202 // Can only remove if the metadata is file level
1203 if (metadata_value_table_entry != null) { // Shouldn't be necessary, but is
1204 remove.setEnabled((metadata_value_table_entry.isInheritedMetadata() == false));
1205 }
1206 }
1207 }
1208
1209
1210 /** Handles TreeSelectionEvents. */
1211 public void valueChanged(TreeSelectionEvent event)
1212 {
1213 if (tree.getSelectionCount() != 0 && !ignore_tree_selection_event) {
1214 TreePath path = tree.getSelectionPath();
1215 MetadataValueTreeNode node = (MetadataValueTreeNode) path.getLastPathComponent();
1216 setSelectedValue(node.getFullValue());
1217 }
1218 }
1219
1220
1221 private class MetadataFieldListener
1222 implements KeyListener {
1223
1224 /** Gives notification of key events on the text field */
1225 public void keyPressed(KeyEvent e) {
1226 if (e.getKeyCode() == KeyEvent.VK_TAB) {
1227 // Tab: Auto-complete
1228 if (tree.getSelectionCount() != 0 && !getSelectedValue().equals("")) {
1229 TreePath path = tree.getSelectionPath();
1230 MetadataValueTreeNode node = (MetadataValueTreeNode) path.getLastPathComponent();
1231 setSelectedValue(node.getFullValue());
1232 }
1233 }
1234 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1235 // Enter: Append the metadata value, if we're allowed to
1236 if (add.isEnabled()) {
1237 add.doClick();
1238 }
1239 }
1240 }
1241
1242 public void keyReleased(KeyEvent e) { }
1243
1244 public void keyTyped(KeyEvent e) { }
1245 }
1246
1247
1248 private class DocumentListenerImpl
1249 implements DocumentListener
1250 {
1251 /** Gives notification that an attribute or set of attributes changed */
1252 public void changedUpdate(DocumentEvent e) {
1253 validate();
1254 }
1255
1256 /** Gives notification that there was an insert into the document */
1257 public void insertUpdate(DocumentEvent e) {
1258 validate();
1259 }
1260
1261 /** Gives notification that a portion of the document has been removed */
1262 public void removeUpdate(DocumentEvent e) {
1263 validate();
1264 }
1265
1266
1267 /** Ensures that the value text field and value tree are synchronized */
1268 private void validate()
1269 {
1270 String value_text = getSelectedValue();
1271
1272 // Ignore the validate() with empty text that occurs when value.setText() is used
1273 if (!value_text.equals("") || manual_text_edit_event == true) {
1274 TreePath closest_path = getClosestPath(value_text);
1275
1276 // Select the new path in the tree
1277 // The tree selection event this causes must be ignored, since it alters value
1278 ignore_tree_selection_event = true;
1279 tree.setSelectionPath(closest_path);
1280 ignore_tree_selection_event = false;
1281
1282 // If a folder has been specified, make sure it is expanded
1283 if (value_text.endsWith(MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN) && !tree.isExpanded(closest_path)) {
1284 tree.expandPath(closest_path);
1285 }
1286
1287 // Make sure the path is visible on the screen
1288 scrollTreePathToVisible(closest_path);
1289 // One call should be enough, but it isn't in some cases (for some reason)
1290 scrollTreePathToVisible(closest_path);
1291
1292 // Update the status of the buttons
1293 validateDisplay();
1294 }
1295 }
1296 }
1297 }
1298
1299
1300 /** This class listens for certain key presses, such as [Up] or [Down], -copied from Gather Pane */
1301 private class KeyListenerImpl
1302 extends KeyAdapter {
1303 private boolean vk_left_pressed = false;
1304 /** 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). */
1305 public void keyReleased(KeyEvent event) {
1306 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
1307 if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
1308 DragTree tree = (DragTree)event.getSource();
1309 // we need to manually do the up and down selections
1310 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
1311 int current_row = tree.getLeadSelectionRow();
1312 tree.setImmediate(true);
1313 if (up) {
1314 if (current_row > 0) {
1315 tree.clearSelection();
1316 tree.setSelectionRow(current_row-1);
1317 }
1318 } else {
1319 if (current_row < tree.getRowCount()-1){
1320 tree.clearSelection();
1321 tree.setSelectionRow(current_row+1);
1322 }
1323 }
1324 tree.setImmediate(false);
1325 } else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1326 // left key on a file shifts the selection to the parent folder
1327 DragTree tree = (DragTree)event.getSource();
1328 TreePath path = tree.getLeadSelectionPath();
1329 if(path != null) {
1330 File file = ((FileNode)path.getLastPathComponent()).getFile();
1331 if(file != null) {
1332 if (file.isFile() || vk_left_pressed) {
1333 vk_left_pressed = false;
1334 TreePath parent_path = path.getParentPath();
1335 if (parent_path != null && parent_path.getParentPath() != null) {
1336 // if this file is in the top level folder, don't move the focus
1337 tree.setImmediate(true);
1338 tree.clearSelection();
1339 tree.setSelectionPath(parent_path);
1340 tree.setImmediate(false);
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347
1348 // 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.
1349 public void keyPressed(KeyEvent event) {
1350 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1351 // left key on closed directory shifts the selection to the parent folder
1352 DragTree tree = (DragTree)event.getSource();
1353 TreePath path = tree.getLeadSelectionPath();
1354 if(path == null) return;
1355 File file = ((FileNode)path.getLastPathComponent()).getFile();
1356 if(file == null) return;
1357
1358 if (file.isDirectory() && tree.isCollapsed(path)) {
1359 vk_left_pressed = true;
1360 }
1361 }
1362 }
1363 }
1364
1365
1366 /** A listener for right mouse button clicks over the collection tree. */
1367 private class RightButtonListener
1368 extends MouseAdapter {
1369 /** Called whenever a mouse click occurs (right or left) over a target component.
1370 * @param event A <strong>MouseEvent</strong> containing further information about the mouse click action.
1371 * @see org.greenstone.gatherer.gui.EnrichPane.RightButtonMenu
1372 */
1373 public void mouseClicked(MouseEvent event) {
1374 if (SwingUtilities.isRightMouseButton(event)) {
1375 new RightButtonMenu(event);
1376 }
1377 }
1378 }
1379
1380
1381 /** 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.
1382 */
1383 private class RightButtonMenu
1384 extends JPopupMenu
1385 implements ActionListener {
1386
1387 /** The tree over which the right click action occurred. */
1388 private DragTree tree = null;
1389 /** The tree nodes selected when the right click action occurred. */
1390 private TreePath[] selection_paths = null;
1391 /** The file record over which the right click action occurred. */
1392 private FileNode node = null;
1393
1394 private JMenuItem collapse_folder = null;
1395 private JMenuItem expand_folder = null;
1396 private JMenuItem metaaudit = null;
1397 private JMenuItem open_externally = null;
1398
1399
1400 private RightButtonMenu(MouseEvent event)
1401 {
1402 super();
1403 this.tree = collection_tree;
1404
1405 // Get the paths currently selected in the tree
1406 selection_paths = tree.getSelectionPaths();
1407
1408 // Create an appropriate context menu, based on what is selected
1409 buildContextMenu(selection_paths);
1410
1411 // Show the popup menu on screen
1412 show(tree, event.getX(), event.getY());
1413 }
1414
1415
1416 private void buildContextMenu(TreePath[] selection_paths)
1417 {
1418 // If nothing is selected, no options are available
1419 if (selection_paths == null) {
1420 return;
1421 }
1422
1423 DebugStream.println("Number of files/folders selected: " + selection_paths.length);
1424
1425 // Always have meta-audit option
1426 metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", collection_tree.getSelectionDetails()), KeyEvent.VK_A);
1427 metaaudit.addActionListener(this);
1428 add(metaaudit);
1429
1430 // Only meta-audit is available if multiple items are selected...
1431 if (selection_paths.length > 1) {
1432 return;
1433 }
1434
1435 TreePath path = selection_paths[0];
1436 node = (FileNode) path.getLastPathComponent();
1437
1438 // ---- Options for file nodes ----
1439 if (node.isLeaf()) {
1440 // Open the file in an external program
1441 open_externally = new JMenuItem(Dictionary.get("Menu.Open_Externally"), KeyEvent.VK_O);
1442 open_externally.addActionListener(this);
1443 add(open_externally);
1444 return;
1445 }
1446
1447 // ---- Options for folder nodes ----
1448 // Collapse or expand, depending on current status
1449 if (tree.isExpanded(path)) {
1450 collapse_folder = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
1451 collapse_folder.addActionListener(this);
1452 add(collapse_folder);
1453 }
1454 else {
1455 expand_folder = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
1456 expand_folder.addActionListener(this);
1457 add(expand_folder);
1458 }
1459 }
1460
1461
1462 /** Called whenever one of the menu items is clicked, this method then causes the appropriate effect. */
1463 public void actionPerformed(ActionEvent event)
1464 {
1465 Object source = event.getSource();
1466
1467 // Collapse folder
1468 if (source == collapse_folder) {
1469 tree.collapsePath(selection_paths[0]);
1470 }
1471
1472 // Expand folder
1473 else if (source == expand_folder) {
1474 tree.expandPath(selection_paths[0]);
1475 }
1476
1477 // Meta-audit
1478 else if (source == metaaudit) {
1479 Gatherer.g_man.showMetaAuditBox();
1480 }
1481
1482 // Open in external program
1483 else if (source == open_externally) {
1484 Gatherer.self.spawnApplication(node.getFile());
1485 }
1486 }
1487 }
1488}
Note: See TracBrowser for help on using the repository browser.