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

Last change on this file since 5877 was 5877, checked in by jmt12, 20 years ago

Introduced two new codec transforms to correctly handle path information

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