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

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

Added unicode character recognition to ValueTree

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