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

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

Fixed up a few more refreshing problems.

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