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

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

Removed a couple of debug statements.

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