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

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

Big tidy up of the workspace and collection trees (Gather/Enrich panes). Still a few more changes and bug fixes to come.

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