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

Last change on this file since 5350 was 5349, checked in by mdewsnip, 21 years ago

Changed dictionary get()s to have the whole key.

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