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

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

Finally committing the (many) changes to the GLI to use the new metadata code... I hope this doesn't have too many bugs in it and committing it now doesn't stuff anyone up! (Katherine said I could commit it, so blame her if anything goes wrong).

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