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

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

Have rearranged where and how strings are feed through the Codec. After several hours work and a dozen paper trials I discovered the TEXT_TO_DOM conversion was completely pointless (DOM does it itself). Also the quotes only need to be dealt to if they are being sent to the collect.cfg file. Hopefully I've got it all going now - including using that pesky pipe character that I would rather not have to deal with. And everything seems to be ok - I tested all the dangerous characters including square brackets and amperstamp. I also tried hierarchies, and then as the piece'd'resistance I tried a hierarchies with dangerous characters. All good. I'm all about the working metadata.

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