source: trunk/gli/src/org/greenstone/gatherer/gui/MetaEditPane.java@ 5204

Last change on this file since 5204 was 5204, checked in by jmt12, 21 years ago

Fixed conflicts

  • Property svn:keywords set to Author Date Id Revision
File size: 50.4 KB
Line 
1package org.greenstone.gatherer.gui;
2/**
3 *#########################################################################
4 *
5 * A component of the Gatherer application, part of the Greenstone digital
6 * library suite from the New Zealand Digital Library Project at the
7 * University of Waikato, New Zealand.
8 *
9 * <BR><BR>
10 *
11 * Author: John Thompson, Greenstone Digital Library, University of Waikato
12 *
13 * <BR><BR>
14 *
15 * Copyright (C) 1999 New Zealand Digital Library Project
16 *
17 * <BR><BR>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * <BR><BR>
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * <BR><BR>
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
36 *########################################################################
37 */
38import java.awt.*;
39import java.awt.event.*;
40import java.io.*;
41import java.util.*;
42import javax.swing.*;
43import javax.swing.border.*;
44import javax.swing.event.*;
45import javax.swing.table.*;
46import javax.swing.tree.*;
47import org.greenstone.gatherer.Gatherer;
48import org.greenstone.gatherer.file.FileNode;
49import org.greenstone.gatherer.gui.Filter;
50import org.greenstone.gatherer.gui.GComboBox;
51import org.greenstone.gatherer.gui.MetaEditPrompt;
52import org.greenstone.gatherer.gui.WarningDialog;
53import org.greenstone.gatherer.gui.table.GTableModel;
54import org.greenstone.gatherer.gui.table.TableCellRenderer;
55import org.greenstone.gatherer.gui.tree.DragTree;
56import org.greenstone.gatherer.msm.ElementWrapper;
57import org.greenstone.gatherer.msm.Metadata;
58import org.greenstone.gatherer.msm.MSMEvent;
59import org.greenstone.gatherer.msm.MSMListener;
60import org.greenstone.gatherer.util.ArrayTools;
61import org.greenstone.gatherer.util.DragGroup;
62import org.greenstone.gatherer.util.TreeSynchronizer;
63import org.greenstone.gatherer.util.Utility;
64import org.greenstone.gatherer.valuetree.GValueModel;
65import org.greenstone.gatherer.valuetree.GValueNode;
66/** Provides a view of controls for the editing of metadata. It makes use of several other important components such as a GTree, GTable and GValueTree. While much of the gui content is left to these components, the MetaEditPane is resposible for actioning metadata edit requests, listing for mouse clicks within its scope and other data functionality (such as providing a list of the selected files).
67* @author John Thompson, Greenstone Digital Libraries, University of Waikato
68* @version 2.3b
69*/
70public class MetaEditPane
71 extends JPanel
72 implements ActionListener, ListSelectionListener, TreeSelectionListener, MSMListener {
73 /** The GValueTree graphically shows the available metadata that has been previously assigned, and provides controls for adding, updating and removing metadata, depending on the users selections in the other important components. */
74 private GValueTree tree = null;
75 /** The layout manager used to display either the GTable (when records are selected) or a placeholder panel with the immortal words 'No Record Selected'. */
76 private CardLayout card_layout = null;
77 /** The layout manager used to display either the GValueTree (when records are selected) or a placeholder panel with the immortal words 'No Record Selected'. */
78 private CardLayout card_layout2 = null;
79 /** Used to dynamically filter the collection tree at user request. Note that this is synchronized with the collection tree filter in the Gather view. */
80 private Filter filter = null;
81 /** The data model behind the GTable, to which we hold a reference for convenient access. */
82 private GTableModel model = null;
83 /** The currently reported selection. Note that this may actually be incorrect, but if so the changed flag will be set, and the selection will be updated the next time it is needed. */
84 private FileNode records[] = null;
85 /** The button, which when clicked, adds metadata to the selected records. */
86 private JButton add;
87 /** The button, which when clicked, removes the selected metadata from the selected records. */
88 private JButton remove;
89 /** The button, which when clicked, updates the selected metadata from the selected records. */
90 private JButton update;
91 /** This button creates an editor dialog to allow for an expanded area to type in text. */
92 private JButton expand;
93 /** The label at the top of the collection tree, which shows the collection name. */
94 private JLabel collection_label;
95 /** The panel in which the metadata value card layout resides. */
96 private JPanel control_pane;
97 /** The panel in which the metadata table card layout resides. */
98 private JPanel table_card_pane;
99 /** The splitpane dividing the collection tree and the metadata based controls. */
100 private JSplitPane external_split;
101 /** A reference to the metadata table, via its superclass. */
102 private JTable table;
103 /** The label shown at the top of the metadata table detailing the current selection statistics. */
104 private JTextField table_label;
105 /** A reference to the collection tree. */
106 private DragTree collection_tree;
107 /** The currently selected metadata determined by listening to every second list selection event from the metadata table. */
108 private Metadata selected_metadata;
109 /** Provide synchronization between the collection trees in this view and the collection pane view. */
110 private TreeSynchronizer tree_sync = null;
111 static private Dimension BUTTON_SIZE = new Dimension(190, 25);
112 static private Dimension CONTROL_SIZE = new Dimension(560, 240);
113 static private Dimension MINIMUM_SIZE = new Dimension(100, 100);
114 static private Dimension TABLE_SIZE = new Dimension(560, 25);
115 static private Dimension TREE_SIZE = new Dimension(250, 500);
116 /** The name of the panel containing the metadata table. */
117 static final private String CARD_ONE = "Card One";
118 /** The name of the panel containing the 'no file' placeholder for the metadata table. */
119 static final private String CARD_TWO = "Card Two";
120 /** The name of the panel containing the 'no metadata' placeholder for the metadata table. */
121 static final private String CARD_ZERO = "Card Zero";
122 /** The name of the panel containing the placeholder for the value tree. */
123 static final private String TOOLS_OFF = "Tools Off";
124 /** The name of the panel containing the value tree. */
125 static final private String TOOLS_ON = "Tools On";
126
127 /** Constructor.
128 * @param tree_sync The <strong>TreeSynchronizer</strong> to be used on the collection tree.
129 * @see org.greenstone.gatherer.Configuration
130 * @see org.greenstone.gatherer.gui.table.GTable
131 * @see org.greenstone.gatherer.valuetree.GValueTree
132 */
133 public MetaEditPane(TreeSynchronizer tree_sync) {
134 this.tree = null;
135 this.tree_sync = tree_sync;
136
137 add = new JButton(get("MetaEditPrompt.Accumulate"));
138 add.addActionListener(this);
139 add.setEnabled(false);
140 add.setMnemonic(KeyEvent.VK_A);
141 add.setPreferredSize(BUTTON_SIZE);
142
143 update = new JButton(get("MetaEditPrompt.Overwrite"));
144 update.addActionListener(this);
145 update.setEnabled(false);
146 update.setMnemonic(KeyEvent.VK_P);
147 update.setPreferredSize(BUTTON_SIZE);
148
149 remove = new JButton(get("MetaEditPrompt.Remove"));
150 remove.addActionListener(this);
151 remove.setEnabled(false);
152 remove.setMnemonic(KeyEvent.VK_R);
153 remove.setPreferredSize(BUTTON_SIZE);
154
155 expand = new JButton(get("General.Expand"));
156 expand.addActionListener(this);
157 expand.setEnabled(true);
158 expand.setMnemonic(KeyEvent.VK_E);
159 expand.setPreferredSize(new Dimension(25, 25));
160
161 tree = new GValueTree(CONTROL_SIZE.width, CONTROL_SIZE.height);
162 }
163
164 /** Called whenever an action occurs on one of our registered buttons.
165 * @param event An <strong>ActionEvent</strong> containing information about the event.
166 * @see org.greenstone.gatherer.collection.CollectionManager
167 * @see org.greenstone.gatherer.gui.table.GTableModel
168 * @see org.greenstone.gatherer.msm.ElementWrapper
169 * @see org.greenstone.gatherer.msm.MetadataSetManager
170 * @see org.greenstone.gatherer.msm.MSMEvent
171 * @see org.greenstone.gatherer.valuetree.GValueTree
172 */
173 public void actionPerformed(ActionEvent event) {
174 Object esrc = event.getSource();
175 if(esrc == add) {
176 (new AppendMetadataTask()).start();
177 }
178 else if(esrc == update) {
179 (new UpdateMetadataTask()).start();
180 }
181 else if(esrc == remove) {
182 (new RemoveMetadataTask()).start();
183 }
184 else if(esrc == expand) {
185 EditorDialog ed = new EditorDialog();
186 String temp = ed.display(tree.getSelectedValue());
187 if(temp != null) {
188 tree.setSelectedValue(temp);
189 }
190 }
191 validateControls();
192 }
193
194 private class AppendMetadataTask
195 extends Thread {
196
197 private AppendMetadataTask() { }
198
199 public void run() {
200 // Check the new metadata is valid
201 ElementWrapper element = tree.getSelectedMetadataElement();
202 String value = tree.getSelectedValue();
203 if(records != null && element != null && value != null) {
204 // Check the records, and if they are folders then display the warning.
205 if(!records[0].isLeaf()) {
206 WarningDialog dialog = new WarningDialog("warning.DirectoryLevelMetadata", true);
207 if(dialog.display() == JOptionPane.OK_OPTION) {
208 Gatherer.c_man.getCollection().msm.addMetadata(System.currentTimeMillis(), records, element, value);
209 }
210 dialog.dispose();
211 dialog = null;
212 }
213 else {
214 Gatherer.c_man.getCollection().msm.addMetadata(System.currentTimeMillis(), records, element, value);
215 }
216 GValueNode value_node = ((GValueModel) tree.getModel()).addValue(value);
217 Metadata added_metadata = new Metadata(element, value_node);
218 model.selectMetadataWhenBuildingComplete(added_metadata);
219 }
220 }
221 }
222
223 private class UpdateMetadataTask
224 extends Thread {
225
226 private UpdateMetadataTask() { }
227
228 public void run() {
229 // You can only update if there is a selected_metadata and
230 // you have valid values in all fields.
231 ElementWrapper element = tree.getSelectedMetadataElement();
232 String value = tree.getSelectedValue();
233 if(selected_metadata != null && records != null && element != null && value != null) {
234 selected_metadata = Gatherer.c_man.getCollection().msm.updateMetadata(System.currentTimeMillis(), selected_metadata, records, value, MetaEditPrompt.CONFIRM, selected_metadata.isFileLevel());
235 }
236 model.selectMetadataWhenBuildingComplete(selected_metadata);
237 }
238 }
239
240 private class RemoveMetadataTask
241 extends Thread {
242
243 private RemoveMetadataTask() { }
244
245 public void run() {
246 if(selected_metadata != null && records != null) {
247 Gatherer.c_man.getCollection().msm.removeMetadata(System.currentTimeMillis(), selected_metadata, records);
248 }
249
250 // Select the closest piece of metadata with the same element name
251 model.selectClosestMetadataWhenBuildingComplete(selected_metadata);
252 }
253 }
254
255 /** 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.
256 * @see org.greenstone.gatherer.gui.table.GTableModel
257 */
258 public void afterDisplay() {
259 external_split.setDividerLocation(0.3);
260 if(table != null) {
261 Dimension table_size = table.getPreferredSize();
262 TableColumnModel column_model = table.getColumnModel();
263
264 TableColumn inherited_column = column_model.getColumn(0);
265 inherited_column.setPreferredWidth(20);
266 inherited_column.setCellRenderer(new TableCellRenderer(model));
267
268 TableColumn element_column = column_model.getColumn(1);
269 element_column.setPreferredWidth(table_size.width / 4);
270 element_column.setCellRenderer(new TableCellRenderer(model));
271
272 TableColumn value_column = column_model.getColumn(2);
273 value_column.setPreferredWidth(((3 * table_size.width) / 4) - 15);
274 value_column.setCellRenderer(new TableCellRenderer(model));
275 }
276 table.doLayout();
277 model.fireTableDataChanged();
278 }
279 /** Called whenever a significant change has occured in the state of the currently loaded collection.
280 * @param ready <i>true</i> if there is a collection currently ready to be editing, <i>false</i> otherwise.
281 * @see org.greenstone.gatherer.collection.CollectionManager
282 * @see org.greenstone.gatherer.collection.CollectionModel
283 * @see org.greenstone.gatherer.tree.GTree
284 * @see org.greenstone.gatherer.util.TreeSynchronizer
285 */
286 public void collectionChanged(boolean ready) {
287 if(ready) {
288 TreeModel collection_model = Gatherer.c_man.getRecordSet();
289 String[] args = new String[1];
290 args[0] = Gatherer.c_man.getCollection().getName();
291 collection_label.setText(get("Collection.Collection", args));
292 // Update label coloring.
293 collection_label.setBackground(Gatherer.config.getColor("coloring.collection_heading_background", false));
294 collection_label.setForeground(Gatherer.config.getColor("coloring.collection_heading_foreground", false));
295 collection_tree.setModel(collection_model);
296 // Update tree coloring.
297 collection_tree.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
298 collection_tree.setForeground(Gatherer.config.getColor("coloring.collection_tree_foreground", false));
299 // Register our msm dependant components (or correctly their models) with msm.
300 if(model != null) {
301 // remove the listener first - in case its already present
302 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
303 Gatherer.c_man.getCollection().msm.addMSMListener(model);
304 }
305 // register the pane itself as an MSMListener, so if we add a new metadata set, it will immediately show up. - remove itself first just in case
306 Gatherer.c_man.getCollection().msm.removeMSMListener(this);
307 Gatherer.c_man.getCollection().msm.addMSMListener(this);
308 }
309 else {
310 collection_label.setText(get("Collection.No_Collection"));
311 collection_label.setBackground(Color.lightGray);
312 collection_label.setForeground(Color.black);
313 collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(get("Collection.No_Collection"))));
314 }
315
316 filter.setEnabled(ready);
317 // Ensure that this collection tree view is synchronized with any others.
318 tree_sync.add(collection_tree);
319 }
320 /** Used to create, connect and layout the components to be shown on this control panel.
321 * @see org.greenstone.gatherer.Gatherer
322 * @see org.greenstone.gatherer.collection.CollectionManager
323 * @see org.greenstone.gatherer.collection.CollectionModel
324 * @see org.greenstone.gatherer.file.FileOpenActionListener
325 * @see org.greenstone.gatherer.gui.Filter
326 * @see org.greenstone.gatherer.gui.GComboBox
327 * @see org.greenstone.gatherer.gui.table.GTableModel
328 * @see org.greenstone.gatherer.tree.GTree
329 * @see org.greenstone.gatherer.tree.GTreeModel
330 * @see org.greenstone.gatherer.valuetree.GValueTree
331 */
332 public void display() {
333 RightButtonListener right_button_listener = new RightButtonListener();
334 // Creation
335 JPanel collection_pane = new JPanel(new BorderLayout());
336 collection_pane.setMinimumSize(MINIMUM_SIZE);
337 collection_pane.setPreferredSize(TREE_SIZE);
338
339 external_split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
340
341 ///atherer.println("\tCreating collection_label");
342 collection_label = new JLabel(get("Collection.Collection"));
343 collection_label.setOpaque(true);
344
345
346 DragGroup group = new DragGroup();
347 TreeModel collection_model = Gatherer.c_man.getRecordSet();
348 if(collection_model != null) {
349 collection_tree = new DragTree("MetaEdit", collection_model, null, false);
350 }
351 else {
352 collection_tree = new DragTree("MetaEdit", null, false);
353 }
354 group.add(collection_tree);
355 collection_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
356 collection_tree.putClientProperty("JTree.lineStyle", "Angled");
357 collection_tree.addMouseListener(Gatherer.g_man.foa_listener);
358 collection_tree.addMouseListener(right_button_listener);
359 collection_tree.addTreeSelectionListener(this);
360 collection_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
361 collection_tree.setLargeModel(true);
362 collection_tree.setRootVisible(false);
363
364 JScrollPane collection_scroll = new JScrollPane(collection_tree);
365
366 filter = Gatherer.g_man.getFilter(collection_tree);
367 filter.setBackground(Gatherer.config.getColor("coloring.collection_heading_background", false));
368 // Change the default colours of this filters combobox.
369 GComboBox fcb = filter.getComboBox();
370 fcb.setBackgroundNonSelectionColor(Color.white);
371 fcb.setTextNonSelectionColor(Gatherer.config.getColor("coloring.collection_tree_foreground", false));
372 fcb.setBackgroundSelectionColor(Gatherer.config.getColor("coloring.collection_selection_background", false));
373 fcb.setTextSelectionColor(Gatherer.config.getColor("coloring.collection_selection_foreground", false));
374 fcb = null;
375
376 // Connection
377
378 // Layout
379 collection_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createLoweredBevelBorder()));
380 collection_pane.setMinimumSize(MINIMUM_SIZE);
381 collection_pane.setPreferredSize(new Dimension(Gatherer.g_man.getSize().width / 3, Gatherer.g_man.getSize().height));
382
383 // Collection Pane
384
385 collection_pane.add(collection_label, BorderLayout.NORTH);
386 collection_pane.add(collection_scroll, BorderLayout.CENTER);
387 collection_pane.add(filter, BorderLayout.SOUTH);
388
389 JSplitPane main_split_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
390 main_split_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
391 main_split_pane.setDividerSize(8);
392
393 // Metadata Table. In order to achieve what Ian envisions I'm going
394 // to have to do a tricky. The table itself will sit in a stacked
395 // CardLayout. Card zero will be a see-through JPanel with a message
396 // smack bang in the center, something like "No Metadata". If any
397 // page of the table would appear to have no metadata this card will
398 // be swapped to the front.
399
400 JPanel table_pane = new JPanel();
401 table_pane.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
402
403 JPanel table_title_pane = new JPanel();
404
405 ///atherer.println("\tCreating metadata_label");
406 table_label = new JTextField(get("No_File"));
407 table_label.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
408 table_label.setEditable(false);
409
410 card_layout = new CardLayout();
411
412 table_card_pane = new JPanel();
413
414 JPanel table_pane_zero = new JPanel();
415 table_pane_zero.setOpaque(false);
416
417 JPanel table_pane_two = new JPanel();
418 table_pane_two.setOpaque(false);
419
420 JLabel no_file_message = new JLabel(get("No_File"));
421 no_file_message.setHorizontalAlignment(JLabel.CENTER);
422 no_file_message.setOpaque(false);
423 no_file_message.setVerticalAlignment(JLabel.CENTER);
424
425 JLabel no_metadata_message = new JLabel(get("No_Metadata"));
426 no_metadata_message.setHorizontalAlignment(JLabel.CENTER);
427 no_metadata_message.setOpaque(false);
428 no_metadata_message.setVerticalAlignment(JLabel.CENTER);
429
430 JPanel table_pane_one = new JPanel();
431
432 ///atherer.println("\tCreating metadata_table");
433 table = new JTable();
434 model = new GTableModel(table);
435 table.setModel(model);
436 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
437 table.getSelectionModel().addListSelectionListener(this);
438 table.setColumnSelectionAllowed(false);
439
440 // The default JTable doesn't quite have the desired properties - when a
441 // table cell is clicked on, the table is scrolled so the cell is as
442 // visible as possible. This means that the left-most cells can become
443 // hidden. To fix this, the MouseInputHandler in BasicTableUI is removed
444 // as a MouseListener on the table, and a very slightly altered copy is
445 // added in its place (TableMouseInputHandler).
446 MouseListener[] mls = table.getMouseListeners();
447 for (int i = 0; i < mls.length; i++) {
448 // Remove the instances of MouseInputHandler
449 if (mls[i].toString().startsWith("javax.swing.plaf.basic.BasicTableUI$MouseInputHandler@")) {
450 table.removeMouseListener(mls[i]);
451 }
452 }
453 // Add the slightly-altered mouse listener
454 table.addMouseListener(new TableMouseInputHandler());
455
456 JScrollPane table_scroll = new JScrollPane(table);
457 table_scroll.getViewport().setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
458 table_scroll.setOpaque(true);
459
460 // Control pane
461 ///atherer.println("\tCreating Metadata Controls");
462
463 control_pane = new JPanel();
464 control_pane.setBorder(BorderFactory.createLoweredBevelBorder());
465 control_pane.setMinimumSize(MINIMUM_SIZE);
466 control_pane.setSize(CONTROL_SIZE);
467 control_pane.setPreferredSize(CONTROL_SIZE);
468
469 card_layout2 = new CardLayout();
470
471 JPanel tools_off_pane = new JPanel();
472
473 JLabel tools_off_label = new JLabel(get("No_Metadata_Element"));
474 tools_off_label.setHorizontalAlignment(JLabel.CENTER);
475 tools_off_label.setOpaque(false);
476 tools_off_label.setVerticalAlignment(JLabel.CENTER);
477
478 JPanel tools_on_pane = new JPanel();
479 tools_on_pane.setMinimumSize(MINIMUM_SIZE);
480 tools_on_pane.setSize(TABLE_SIZE);
481 tools_on_pane.setPreferredSize(TABLE_SIZE);
482
483 // Layout.
484
485 // Metaedit controls
486 tools_off_pane.setLayout(new BorderLayout());
487 tools_off_pane.add(tools_off_label, BorderLayout.CENTER);
488
489 tools_on_pane.setLayout(new BorderLayout());
490 tools_on_pane.add(tree, BorderLayout.CENTER);
491
492 control_pane.setLayout(card_layout2);
493 control_pane.add(tools_off_pane, TOOLS_OFF);
494 control_pane.add(tools_on_pane, TOOLS_ON);
495
496 // Table components
497 table_title_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
498 table_title_pane.setLayout(new BorderLayout());
499 table_title_pane.add(table_label, BorderLayout.CENTER);
500
501 table_pane_zero.setLayout(new BorderLayout());
502 table_pane_zero.add(no_file_message, BorderLayout.CENTER);
503
504 table_pane_one.setLayout(new BorderLayout());
505 table_pane_one.add(table_scroll, BorderLayout.CENTER);
506
507 table_pane_two.setLayout(new BorderLayout());
508 table_pane_two.add(no_metadata_message, BorderLayout.CENTER);
509
510 table_card_pane.setLayout(card_layout);
511 table_card_pane.add(table_pane_zero, CARD_ZERO);
512 table_card_pane.add(table_pane_one, CARD_ONE);
513 table_card_pane.add(table_pane_two, CARD_TWO);
514
515 table_pane.setLayout(new BorderLayout());
516 table_pane.add(table_title_pane, BorderLayout.NORTH);
517 table_pane.add(table_card_pane, BorderLayout.CENTER);
518
519 main_split_pane.add(table_pane, JSplitPane.TOP);
520 main_split_pane.add(control_pane, JSplitPane.BOTTOM);
521 main_split_pane.setDividerLocation(250);
522
523 external_split.add(collection_pane, JSplitPane.LEFT);
524 external_split.add(main_split_pane, JSplitPane.RIGHT);
525
526 this.setLayout(new BorderLayout());
527 this.add(external_split, BorderLayout.CENTER);
528 }
529
530 /** Method that is called whenever an element within a set is changed or modified. - needed for MSMListener
531 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
532 */
533 public void elementChanged(MSMEvent event) {}
534
535 /** Ensures a certain tree path is expanded, visible and selected within the collection tree.
536 * @param path The <strong>TreePath</strong> to make visible.
537 * @see org.greenstone.gatherer.tree.GTree
538 */
539 private void expandPath(TreePath path) {
540 collection_tree.expandPath(path);
541 collection_tree.setSelectionPath(path);
542 }
543 /** Called to inform this control panel that it has just gained focus as an effect of the user clicking on its tab.
544 * @see org.greenstone.gatherer.tree.GTree
545 */
546 public void gainFocus() {
547 // Use this opportunity to update the table model etc.
548 valueChanged(new TreeSelectionEvent(this, null, false, null, null));
549 // tree.setElementModel(Gatherer.c_man.getCollection().msm.getElementModel());
550 // Update the menubars idea of whats been selected
551 if(collection_tree != null) {
552 if(collection_tree.isSelectionEmpty()) {
553 Gatherer.g_man.menu_bar.setMetaAuditSuffix(null);
554 }
555 else {
556 Gatherer.g_man.menu_bar.setMetaAuditSuffix(collection_tree.getSelectionDetails());
557 }
558 }
559 // Update the meta-audit view to show the current selection, if any.
560 Gatherer.g_man.meta_audit.setRecords(records);
561 }
562
563 /** Retrieve the currently selected records.
564 * @return A <strong>FileNode[]</strong> containing the current, and up to date, selection.
565 */
566 public FileNode[] getSelectedNode() {
567 return records;
568 }
569
570 /** Called whenever the metadata value changes in some way, such as the addition of a new value. - needed for MSMListener
571 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
572 */
573 public void metadataChanged(MSMEvent event) {}
574
575 public void refreshTrees() {
576 collection_tree.refresh(null); // Refresh entire tree.
577 }
578
579 /** Method that is called whenever the metadata set collection changes in some way, such as the addition of a new set or the merging of two sets. - needed for MSMListener
580 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
581 */
582 public void setChanged(MSMEvent event){
583 // call the collection tree value changed - we need to do the same stuff here
584 valueChanged((TreeSelectionEvent) null);
585 }
586
587 /** This method enables or diables the various controls based upon the state of the system. This method should be called after every change which could affect the GUI.
588 */
589 private void validateControls() {
590 validateMetadataTable();
591 // Validate card_layout_2
592 if (selected_metadata != null) {
593 card_layout2.show(control_pane, TOOLS_ON);
594 }
595 else {
596 card_layout2.show(control_pane, TOOLS_OFF);
597 }
598
599 // Validate the buttons in the lower pane
600 if (selected_metadata == null) {
601 // Nothing selected
602 return;
603 }
604
605 // Does the metadata element have no current value?
606 GValueNode value_node = selected_metadata.getValueNode();
607 if (value_node == null) {
608 // Can only append if something has been entered
609 add.setEnabled((tree.getSelectedValue().length() > 0));
610 // Nothing to replace or remove
611 update.setEnabled(false);
612 remove.setEnabled(false);
613 }
614
615 else {
616 // Check if the text in the value field is the same as the metadata value
617 if (tree.getSelectedValue().equals(value_node.getFullPath())) {
618 // Can't append or replace
619 add.setEnabled(false);
620 update.setEnabled(false);
621 }
622 else {
623 // Can append or replace, if something has been entered
624 add.setEnabled((tree.getSelectedValue().length() > 0));
625 update.setEnabled((tree.getSelectedValue().length() > 0));
626 }
627
628 // Can only remove if the metadata is file level
629 if (selected_metadata != null) { // Shouldn't be necessary, but is
630 remove.setEnabled(selected_metadata.isFileLevel());
631 }
632 }
633 }
634
635 /** Validate the metadata table area, and if no metadata is available display the no metadata panel. */
636 public void validateMetadataTable() {
637 // Validate card_layout
638 if (records == null) {
639 card_layout.show(table_card_pane, CARD_ZERO);
640 } else if (model.getRowCount() == 0) {
641 card_layout.show(table_card_pane, CARD_TWO);
642 } else {
643 card_layout.show(table_card_pane, CARD_ONE);
644 }
645 }
646
647 /** Another in a long list of listeners, this one is called whenever the
648 * selection within the JTable changes. When it does we load the new data
649 * and set the selected_metadata if applicable (ie the metadata is non-
650 * empty).
651 * @param event The <strong>ListSelectionEvent</strong> which contains all the information about the event that has been fired.
652 * @see org.greenstone.gatherer.gui.table.GTableModel
653 * @see org.greenstone.gatherer.valuetree.GValueTree
654 */
655 public void valueChanged(ListSelectionEvent event) {
656 // We only want to handle one event per selection, so wait for the value to stabilise
657 ListSelectionModel lsm = table.getSelectionModel();
658 if (lsm.getValueIsAdjusting() == false) {
659 // We have a SINGLE_SELECTION model set so there is at most one
660 // selection. Get the offending row.
661 int selected = table.getSelectedRow();
662 selected_metadata = model.getMetadataAtRow(selected);
663 if(selected_metadata != null) { // Check something is selected.
664 tree.setSelectedMetadataElement(selected_metadata.getElement());
665 GValueNode value_node = selected_metadata.getValueNode();
666 if(value_node != null) {
667 tree.setSelectedValue(value_node.getFullPath());
668 }
669 else {
670 tree.setSelectedValue("");
671 }
672 }
673 validateControls();
674 }
675 }
676
677 /** Called whenever the value tree of an metadata element changes in some way, such as the addition of a new value. - needed for MSMListener
678 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
679 */
680 public void valueChanged(MSMEvent event) {}
681
682 /** This method is called whenever the selection within the collection
683 * tree changes. This causes the table to be rebuilt with a new model
684 * @param event A <strong>TreeSelectionEvent</strong> containing information about the event.
685 * @see org.greenstone.gatherer.Gatherer
686 * @see org.greenstone.gatherer.collection.FileNode
687 * @see org.greenstone.gatherer.gui.GUIManager
688 * @see org.greenstone.gatherer.gui.MenuBar
689 * @see org.greenstone.gatherer.gui.table.GTableModel
690 * @see org.greenstone.gatherer.msm.MetadataSetManager
691 * @see org.greenstone.gatherer.tree.GTree
692 * @see org.greenstone.gatherer.util.ArrayTools
693 */
694 public void valueChanged(TreeSelectionEvent event) {
695 ///ystem.err.println("\n(MEP) valueChanged(TreeSelectionEvent)...");
696
697 // Don't bother if this isn't the selected view
698 if (Gatherer.g_man.getSelectedView() != this) {
699 return;
700 }
701
702 if(collection_tree.getSelectionCount() > 0) {
703 records = null;
704 TreePath paths[] = collection_tree.getSelectionPaths();
705 for(int i = 0; i < paths.length; i++) {
706 FileNode record = (FileNode) paths[i].getLastPathComponent();
707 records = ArrayTools.add(records, record);
708 }
709
710 // Remember the previous selection so we can select it again later
711 Metadata previous_selection = selected_metadata;
712
713 table_label.setText(collection_tree.getSelectionDetails());
714
715 // Remove old model from msm
716 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
717 // Create new model
718 model = new GTableModel(table, records);
719 table.setModel(model);
720
721 // Select the closest piece of metadata in the new file
722 model.selectClosestMetadataWhenBuildingComplete(previous_selection);
723 }
724 else {
725 records = null;
726 table_label.setText(get("No_File"));
727 // Remove old model from msm
728 if(Gatherer.c_man.ready()) {
729 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
730 }
731 // Create new model
732 model = new GTableModel(table);
733 table.setModel(model);
734 }
735
736 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
737 if(table != null) {
738 Dimension table_size = table.getPreferredSize();
739 TableColumnModel column_model = table.getColumnModel();
740
741 TableColumn inherited_column = column_model.getColumn(0);
742 inherited_column.setPreferredWidth(20);
743 inherited_column.setCellRenderer(new TableCellRenderer(model));
744
745 TableColumn element_column = column_model.getColumn(1);
746 element_column.setPreferredWidth(TABLE_SIZE.width / 4);
747 element_column.setCellRenderer(new TableCellRenderer(model));
748
749 TableColumn value_column = column_model.getColumn(2);
750 value_column.setPreferredWidth(((3 * TABLE_SIZE.width) / 4) - 15);
751 value_column.setCellRenderer(new TableCellRenderer(model));
752 }
753 table.doLayout();
754 validateControls();
755 }
756
757
758 /** Retrieve a phrase from the Dictionary based on the given key.
759 * @param key The <strong>String</strong> which maps to the phrase to retrieve.
760 * @return The desired phrase as a <strong>String</strong>, or possibly an error message if no such phrase exists.
761 */
762 private String get(String key) {
763 return get(key, null);
764 }
765 /** Retrieve a phrase from the Dictionary based on the given key and filled in with the given arguments.
766 * @param key The <strong>String</strong> which maps to the phrase to retrieve.
767 * @param args A <strong>String[]</strong> of arguments to be inserted in the phrase.
768 * @return The desired phrase as a <strong>String</strong>, or possibly an error message if no such phrase exists.
769 * @see org.greenstone.gatherer.Dictionary
770 * @see org.greenstone.gatherer.Gatherer
771 */
772 private String get(String key, String args[]) {
773 if(key.indexOf('.') == -1) {
774 key = "MetaEdit." + key;
775 }
776 return Gatherer.dictionary.get(key,args);
777 }
778
779
780 /** 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. */
781 private class TableMouseInputHandler implements MouseInputListener
782 {
783 // Component receiving mouse events during editing.
784 // May not be editorComponent.
785 private Component dispatchComponent;
786 private boolean selectedOnPress;
787
788 // The Table's mouse listener methods.
789
790 public void mouseClicked(MouseEvent e) {}
791
792 private void setDispatchComponent(MouseEvent e) {
793 Component editorComponent = table.getEditorComponent();
794 Point p = e.getPoint();
795 Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
796 dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent,
797 p2.x, p2.y);
798 }
799
800 private boolean repostEvent(MouseEvent e) {
801 // Check for isEditing() in case another event has
802 // caused the editor to be removed. See bug #4306499.
803 if (dispatchComponent == null || !table.isEditing()) {
804 return false;
805 }
806 MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent);
807 dispatchComponent.dispatchEvent(e2);
808 return true;
809 }
810
811 private void setValueIsAdjusting(boolean flag) {
812 table.getSelectionModel().setValueIsAdjusting(flag);
813 table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
814 }
815
816 private boolean shouldIgnore(MouseEvent e) {
817 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled()));
818 }
819
820 public void mousePressed(MouseEvent e) {
821 if (e.isConsumed()) {
822 selectedOnPress = false;
823 return;
824 }
825 selectedOnPress = true;
826 adjustFocusAndSelection(e);
827 }
828
829 void adjustFocusAndSelection(MouseEvent e) {
830 if (shouldIgnore(e)) {
831 return;
832 }
833
834 Point p = e.getPoint();
835 int row = table.rowAtPoint(p);
836 int column = table.columnAtPoint(p);
837 // The autoscroller can generate drag events outside the Table's range.
838 if ((column == -1) || (row == -1)) {
839 return;
840 }
841
842 if (table.editCellAt(row, column, e)) {
843 setDispatchComponent(e);
844 repostEvent(e);
845 }
846 else if (table.isRequestFocusEnabled()) {
847 table.requestFocus();
848 }
849
850 CellEditor editor = table.getCellEditor();
851 if (editor == null || editor.shouldSelectCell(e)) {
852 boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
853 setValueIsAdjusting(adjusting);
854 if (column == 0) {
855 table.changeSelection(row, 0, e.isControlDown(), e.isShiftDown());
856 }
857 else {
858 table.changeSelection(row, 1, e.isControlDown(), e.isShiftDown());
859 }
860 // table.changeSelection(row, column, e.isControlDown(), e.isShiftDown());
861 }
862 }
863
864 public void mouseReleased(MouseEvent e) {
865 if (selectedOnPress) {
866 if (shouldIgnore(e)) {
867 return;
868 }
869
870 repostEvent(e);
871 dispatchComponent = null;
872 setValueIsAdjusting(false);
873 } else {
874 adjustFocusAndSelection(e);
875 }
876 }
877
878
879 public void mouseEntered(MouseEvent e) {}
880
881 public void mouseExited(MouseEvent e) {}
882
883 // The Table's mouse motion listener methods.
884
885 public void mouseMoved(MouseEvent e) {}
886
887 public void mouseDragged(MouseEvent e) {
888 if (shouldIgnore(e)) {
889 return;
890 }
891
892 repostEvent(e);
893
894 CellEditor editor = table.getCellEditor();
895 if (editor == null || editor.shouldSelectCell(e)) {
896 Point p = e.getPoint();
897 int row = table.rowAtPoint(p);
898 int column = table.columnAtPoint(p);
899 // The autoscroller can generate drag events outside the Table's range.
900 if ((column == -1) || (row == -1)) {
901 return;
902 }
903 table.changeSelection(row, column, false, true);
904 }
905 }
906 }
907
908
909 /** A listener for right mouse button clicks over the collection tree. */
910 private class RightButtonListener
911 extends MouseAdapter {
912 /** Called whenever a mouse click occurs (right or left) over a target component.
913 * @param event A <strong>MouseEvent</strong> containing further information about the mouse click action.
914 * @see org.greenstone.gatherer.gui.MetaEditPane.RightButtonMenu
915 */
916 public void mouseClicked(MouseEvent event) {
917 if(SwingUtilities.isRightMouseButton(event)) {
918 new RightButtonMenu(event);
919 }
920 }
921 }
922
923
924 /** Provides a popup menu to display when a right mouse button click is detected over the collection tree. */
925 private class RightButtonMenu
926 extends JPopupMenu
927 implements ActionListener {
928 /** Constructor.
929 * @param event The <strong>MouseEvent</strong> that triggered the creation of this menu. Used to determine where the menu will be located.
930 */
931 private RightButtonMenu(MouseEvent event) {
932 super();
933 // Creation
934 JMenuItem show_metaaudit = new JMenuItem(get("Menu.Metadata_View") + " " + collection_tree.getSelectionDetails(), KeyEvent.VK_V);
935 show_metaaudit.addActionListener(this);
936 add(show_metaaudit);
937 // Display
938 show(collection_tree, event.getX(), event.getY());
939 show_metaaudit = null;
940 }
941
942 /** Called whenever a user clicks on the single menu item, view all assigned metadata.
943 * @param event An <strong>ActionEvent</strong> containing further information about the action.
944 * @see org.greenstone.gatherer.Gatherer
945 * @see org.greenstone.gatherer.gui.GUIManager
946 */
947 public void actionPerformed(ActionEvent event) {
948 Gatherer.g_man.showMetaAuditBox();
949 }
950 }
951
952
953 /**
954 * This class is a little bit complex, a little bit subtle, and a little bit odd.
955 * The strange interaction model is due to the fact that it is very tightly
956 * coupled to the MetaEditPane.
957 *
958 * The interaction is complex because there are three separate controls that the
959 * user may interact with, each of which can affect the other two. The three
960 * controls are:
961 * - The "assigned metadata" table, at the top right of the meta edit pane.
962 * - The "metadata value" text field, where users can type in new values.
963 * - The "metadata value tree", which shows other values that have been
964 * assigned to the selected metadata element.
965 *
966 * The interactions are:
967 * - The "assigned metadata" table
968 * Users may select one (and only one) row in this table. Selecting a row
969 * chooses one metadata element. The text field will be set to the current
970 * value of the metadata element. This value will also be selected in the
971 * metadata value tree.
972 *
973 * - The "metadata value" text field
974 * If the value the user has typed in this is a prefix of an entry in the
975 * value tree, this value will be selected in the value tree. In this case,
976 * pressing "Enter" will complete the value (ie. make the value in the text
977 * field the same as the value in the tree). This is to allow users to
978 * quickly and easily assign existing metadata values to new documents.
979 *
980 * - The "metadata value tree"
981 * Selecting a value in the tree will set the text field to this value.
982 */
983 private class GValueTree
984 extends JPanel
985 implements TreeSelectionListener {
986 private boolean ignore_tree_selection_event = false;
987 private boolean manual_text_edit_event;
988 private CardLayout card_layout;
989 private String card_showing = null;
990 /** The metadata element that is currently selected. */
991 private ElementWrapper selected_metadata_element = null;
992 private GValueModel vm;
993 private JTextArea extracted_message;
994 private JTextField value;
995 private JTree tree;
996 /** Stock standard size for labels. */
997 final private Dimension VALUE_LABEL_SIZE = new Dimension(66, 26);
998
999 static final private String NONE = "None";
1000 static final private String TREE = "Tree";
1001
1002
1003 public GValueTree(int width, int height) {
1004 super();
1005
1006 setFont(Gatherer.config.getFont("general.font", false));
1007 setPreferredSize(new Dimension(width, height));
1008
1009 JPanel metadata_pane = new JPanel();
1010
1011 JPanel value_pane = new JPanel();
1012 JLabel value_label = new JLabel(get("Value"));
1013 value_label.setPreferredSize(VALUE_LABEL_SIZE);
1014
1015 JPanel value_field_pane = new JPanel();
1016 value = new JTextField();
1017 value.setBackground(Gatherer.config.getColor("coloring.editable_background", false));
1018 value.setForeground(Gatherer.config.getColor("coloring.editable_foreground", false));
1019 value.setPreferredSize(new Dimension(413, 24));
1020 value.getDocument().addDocumentListener(new DocumentListenerImpl());
1021 value.addKeyListener(new MetadataFieldListener());
1022 value.setFocusTraversalKeysEnabled(false);
1023
1024 JPanel button_pane = new JPanel();
1025
1026 JPanel tree_pane = new JPanel();
1027 JLabel tree_label = new JLabel(get("Tree"));
1028
1029 tree = new JTree(new GValueModel());
1030 tree.addTreeSelectionListener(this);
1031 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
1032 tree.setRootVisible(false);
1033 tree.putClientProperty("JTree.lineStyle", "Angled");
1034
1035 JPanel extracted_pane = new JPanel();
1036 extracted_message = new JTextArea("");
1037 extracted_message.setEditable(false);
1038 extracted_message.setLineWrap(true);
1039 extracted_message.setOpaque(false);
1040 extracted_message.setWrapStyleWord(true);
1041
1042 card_layout = new CardLayout();
1043
1044 // Layout
1045
1046 value_field_pane.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
1047 value_field_pane.setLayout(new BorderLayout(0, 0));
1048 value_field_pane.add(value, BorderLayout.CENTER);
1049
1050 button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
1051 button_pane.setLayout(new GridLayout());
1052 button_pane.add(add);
1053 button_pane.add(update);
1054 button_pane.add(remove);
1055
1056 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
1057 value_pane.setLayout(new BorderLayout());
1058 value_pane.add(value_label, BorderLayout.WEST);
1059 value_pane.add(value_field_pane, BorderLayout.CENTER);
1060 value_pane.add(expand, BorderLayout.EAST);
1061 value_pane.add(button_pane, BorderLayout.SOUTH);
1062
1063 tree_pane.setLayout(new BorderLayout());
1064 tree_pane.add(tree_label, BorderLayout.NORTH);
1065 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
1066
1067 metadata_pane.setLayout(new BorderLayout());
1068 metadata_pane.add(value_pane, BorderLayout.NORTH);
1069 metadata_pane.add(tree_pane, BorderLayout.CENTER);
1070
1071 extracted_pane.setBorder(BorderFactory.createEmptyBorder(25,10,25,10));
1072 extracted_pane.setLayout(new BorderLayout());
1073 extracted_pane.add(extracted_message, BorderLayout.CENTER);
1074
1075 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1076 this.setLayout(card_layout);
1077 this.add(metadata_pane, TREE);
1078 this.add(extracted_pane, NONE);
1079 card_showing = TREE;
1080 }
1081
1082
1083 public GValueModel getModel() {
1084 return (GValueModel) tree.getModel();
1085 }
1086
1087
1088 public ElementWrapper getSelectedMetadataElement() {
1089 return selected_metadata_element;
1090 }
1091
1092
1093 public String getSelectedValue() {
1094 return value.getText();
1095 }
1096
1097
1098 public void setSelectedMetadataElement(ElementWrapper element)
1099 {
1100 ///ystem.err.println("Setting selected metadata element to: " + element);
1101 if (element == null) {
1102 selected_metadata_element = null;
1103 return;
1104 }
1105
1106 // Don't bother if the element selected is the same as the current one
1107 if (selected_metadata_element != null && element.equals(selected_metadata_element)) {
1108 return;
1109 }
1110
1111 if (vm != null) {
1112 // Close any expanded paths in the tree
1113 GValueNode root_node = (GValueNode) vm.getRoot();
1114 for (int i = 0; i < root_node.getChildCount(); i++) {
1115 GValueNode child_node = (GValueNode) root_node.getChildAt(i);
1116 TreePath child_path = new TreePath(child_node.getPath());
1117 if (tree.isExpanded(child_path)) {
1118 tree.collapsePath(child_path);
1119 }
1120 }
1121 }
1122
1123 // Load the model for the newly selected metadata element
1124 selected_metadata_element = element;
1125 vm = Gatherer.c_man.getCollection().msm.getValueTree(element);
1126 tree.setModel(vm);
1127
1128 // Special case the extracted metadata set
1129 if (selected_metadata_element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
1130 // Display the panel showing the "you cannot edit this metadata" message
1131 String[] args = new String[1];
1132 args[0] = selected_metadata_element.toString();
1133 extracted_message.setText(get("AutoMessage", args));
1134 card_layout.show(this, NONE);
1135 card_showing = NONE;
1136 }
1137 else {
1138 // Display the panel for viewing and assigning metadata
1139 card_layout.show(this, TREE);
1140 card_showing = TREE;
1141 }
1142 }
1143
1144
1145 /** Handles TreeSelectionEvents. */
1146 public void valueChanged(TreeSelectionEvent event)
1147 {
1148 if (tree.getSelectionCount() != 0 && !ignore_tree_selection_event) {
1149 TreePath path = tree.getSelectionPath();
1150 GValueNode node = (GValueNode) path.getLastPathComponent();
1151 setSelectedValue(node.getFullPath());
1152 }
1153 }
1154
1155
1156 /** Sets the value in the text field. */
1157 public void setSelectedValue(String val)
1158 {
1159 // Setting the text of the field causes the DocumentListener to be notified, and
1160 // updating the tree is handled there (DocumentListenerImpl::validate())
1161 ///ystem.err.println("Setting selected value to: " + val);
1162 if (!card_showing.equals(NONE)) {
1163 manual_text_edit_event = val.equals(""); // Set to false unless val == ""
1164 value.setText(Utility.stripNL(val));
1165 value.setCaretPosition(0);
1166 manual_text_edit_event = true;
1167 }
1168 }
1169
1170
1171 private String get(String key) {
1172 return get(key, null);
1173 }
1174
1175 private String get(String key, String args[]) {
1176 if (key.indexOf('.') == -1) {
1177 key = "MetaEdit." + key;
1178 }
1179 return Gatherer.dictionary.get(key, args);
1180 }
1181
1182
1183 /** Returns a TreePath for the node most closely matching the metadata value. */
1184 private TreePath getClosestPath(String val)
1185 {
1186 // Start at the root of the tree
1187 GValueNode tree_node = (GValueNode) tree.getModel().getRoot();
1188
1189 // Hierarchical values are separated using '\'
1190 StringTokenizer tokenizer = new StringTokenizer(val, "\\");
1191 while (tokenizer.hasMoreTokens()) {
1192 String token = tokenizer.nextToken();
1193
1194 // All components except the last must match exactly
1195 if (tokenizer.hasMoreTokens()) {
1196 for (int i = 0; i < tree_node.getChildCount(); i++) {
1197 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1198 if (child_node.toString().equals(token)) {
1199 // The matching node has been found, so move onto the next token
1200 tree_node = child_node;
1201 break;
1202 }
1203 }
1204 }
1205
1206 // The last component may match partially
1207 else {
1208 for (int i = 0; i < tree_node.getChildCount(); i++) {
1209 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1210 if (child_node.toString().startsWith(token)) {
1211 // The closest node has been found, so return its path
1212 return new TreePath(child_node.getPath());
1213 }
1214 }
1215
1216 // Not even a partial match
1217 return null;
1218 }
1219 }
1220
1221 // If nothing else, return the path of the root node
1222 return new TreePath(tree_node.getPath());
1223 }
1224
1225
1226 /** This function is copied from JTree::setPathToVisible(), and modified so tree rows
1227 are always shown flushed left. Note: doesn't do accessibleContext stuff. */
1228 private void scrollTreePathToVisible(TreePath path)
1229 {
1230 if (path != null) {
1231 tree.makeVisible(path);
1232
1233 Rectangle bounds = tree.getPathBounds(path);
1234 if (bounds != null) {
1235 bounds.width += bounds.x;
1236 bounds.x = 0;
1237 tree.scrollRectToVisible(bounds);
1238 }
1239 }
1240 }
1241
1242
1243 private class MetadataFieldListener
1244 implements KeyListener {
1245
1246 /** Gives notification of key events on the text field */
1247 public void keyPressed(KeyEvent e) {
1248 if (e.getKeyCode() == KeyEvent.VK_TAB) {
1249 // Tab: Auto-complete
1250 if (tree.getSelectionCount() != 0 && !value.getText().equals("")) {
1251 TreePath path = tree.getSelectionPath();
1252 GValueNode node = (GValueNode) path.getLastPathComponent();
1253 value.setText(Utility.stripNL(node.getFullPath()));
1254 }
1255 }
1256 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1257 // Enter: Append the metadata value, if we're allowed to
1258 if (add.isEnabled()) {
1259 add.doClick();
1260 }
1261 }
1262 }
1263
1264 public void keyReleased(KeyEvent e) { }
1265
1266 public void keyTyped(KeyEvent e) { }
1267 }
1268
1269
1270 private class DocumentListenerImpl
1271 implements DocumentListener {
1272 /** Gives notification that an attribute or set of attributes changed */
1273 public void changedUpdate(DocumentEvent e) {
1274 validate();
1275 }
1276
1277 /** Gives notification that there was an insert into the document */
1278 public void insertUpdate(DocumentEvent e) {
1279 validate();
1280 }
1281
1282 /** Gives notification that a portion of the document has been removed */
1283 public void removeUpdate(DocumentEvent e) {
1284 validate();
1285 }
1286
1287
1288 /** Ensures that the value text field and value tree are synchronized */
1289 private void validate()
1290 {
1291 String value_text = value.getText();
1292 ///ystem.err.println("\n(Validate) Value text: " + value_text);
1293
1294 // Ignore the validate() with empty text that occurs when value.setText() is used
1295 if (!value_text.equals("") || manual_text_edit_event == true) {
1296 TreePath closest_path = getClosestPath(value_text);
1297
1298 // Select the new path in the tree
1299 // The tree selection event this causes must be ignored, since it alters value
1300 ignore_tree_selection_event = true;
1301 tree.setSelectionPath(closest_path);
1302 ignore_tree_selection_event = false;
1303
1304 // If a folder has been specified, make sure it is expanded
1305 if (value_text.endsWith("\\") && !tree.isExpanded(closest_path)) {
1306 tree.expandPath(closest_path);
1307 }
1308
1309 // Make sure the path is visible on the screen
1310 scrollTreePathToVisible(closest_path);
1311 // One call should be enough, but it isn't in some cases (for some reason)
1312 scrollTreePathToVisible(closest_path);
1313
1314 // Update the status of the buttons
1315 validateControls();
1316 }
1317 }
1318 }
1319 }
1320}
Note: See TracBrowser for help on using the repository browser.