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

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

Changed the test for refreshing the tool pane card layout so it is only validated when the model builder isn't running

  • Property svn:keywords set to Author Date Id Revision
File size: 55.4 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 ///ystem.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 validateControls(false);
654 }
655
656 private void validateControls(boolean update_card) {
657 validateMetadataTable();
658 // Validate card_layout_2
659 // We only ever change tools if the metadata table has finished building or is empty.
660 if(update_card) {
661 if (selected_metadata == null && model.isBuildingComplete() && model.getRowCount() == 0) {
662 card_layout2.show(control_pane, TOOLS_OFF);
663 }
664 else {
665 card_layout2.show(control_pane, TOOLS_ON);
666 }
667 }
668
669 // Validate the buttons in the lower pane
670 if (selected_metadata == null) {
671 // Nothing selected
672 return;
673 }
674
675 // Does the metadata element have no current value?
676 GValueNode value_node = selected_metadata.getValueNode();
677 if (value_node == null) {
678 // Can only append if something has been entered
679 add.setEnabled((tree.getSelectedValue().length() > 0));
680 // Nothing to replace or remove
681 update.setEnabled(false);
682 remove.setEnabled(false);
683 }
684
685 else {
686 // Check if the text in the value field is the same as the metadata value
687 if (tree.getSelectedValue().equals(value_node.getFullPath(false))) {
688 // Can't replace
689 update.setEnabled(false);
690 // 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.
691 add.setEnabled(!model.isCommon(selected_metadata));
692 }
693 else {
694 // Can append or replace, if something has been entered
695 add.setEnabled((tree.getSelectedValue().length() > 0));
696 update.setEnabled((tree.getSelectedValue().length() > 0));
697 }
698
699 // Can only remove if the metadata is file level
700 if (selected_metadata != null) { // Shouldn't be necessary, but is
701 remove.setEnabled(selected_metadata.isFileLevel());
702 }
703 }
704 }
705
706 /** Validate the metadata table area, and if no metadata is available display the no metadata panel. */
707 public void validateMetadataTable() {
708 // Validate card_layout
709 if (records == null) {
710 card_layout.show(table_card_pane, CARD_ZERO);
711 } else if (model.getRowCount() == 0) {
712 card_layout.show(table_card_pane, CARD_TWO);
713 } else {
714 card_layout.show(table_card_pane, CARD_ONE);
715 }
716 }
717
718 /** Another in a long list of listeners, this one is called whenever the
719 * selection within the JTable changes. When it does we load the new data
720 * and set the selected_metadata if applicable (ie the metadata is non-
721 * empty).
722 * @param event The <strong>ListSelectionEvent</strong> which contains all the information about the event that has been fired.
723 * @see org.greenstone.gatherer.gui.table.GTableModel
724 * @see org.greenstone.gatherer.valuetree.GValueTree
725 */
726 public void valueChanged(ListSelectionEvent event) {
727 // We only want to handle one event per selection, so wait for the value to stabilise
728 ListSelectionModel lsm = table.getSelectionModel();
729 if (lsm.getValueIsAdjusting() == false) {
730 // We have a SINGLE_SELECTION model set so there is at most one
731 // selection. Get the offending row.
732 int selected = table.getSelectedRow();
733 selected_metadata = model.getMetadataAtRow(selected);
734 if (selected_metadata != null) { // Check something is selected.
735 tree.setSelectedMetadataElement(selected_metadata.getElement());
736 GValueNode value_node = selected_metadata.getValueNode();
737 if(value_node != null) {
738 tree.setSelectedValue(value_node.getFullPath(true));
739 }
740 else {
741 tree.setSelectedValue("");
742 }
743 }
744 validateControls(true);
745 }
746 }
747
748 /** Called whenever the value tree of an metadata element changes in some way, such as the addition of a new value. - needed for MSMListener
749 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
750 */
751 public void valueChanged(MSMEvent event) {}
752
753 /** This method is called whenever the selection within the collection
754 * tree changes. This causes the table to be rebuilt with a new model
755 * @param event A <strong>TreeSelectionEvent</strong> containing information about the event.
756 * @see org.greenstone.gatherer.Gatherer
757 * @see org.greenstone.gatherer.collection.FileNode
758 * @see org.greenstone.gatherer.gui.GUIManager
759 * @see org.greenstone.gatherer.gui.MenuBar
760 * @see org.greenstone.gatherer.gui.table.GTableModel
761 * @see org.greenstone.gatherer.msm.MetadataSetManager
762 * @see org.greenstone.gatherer.tree.GTree
763 * @see org.greenstone.gatherer.util.ArrayTools
764 */
765 public void valueChanged(TreeSelectionEvent event) {
766 ///ystem.err.println("\n(MEP) valueChanged(TreeSelectionEvent)...");
767
768 // Don't bother if this isn't the selected view
769 if (Gatherer.g_man.getSelectedView() != this) {
770 return;
771 }
772
773 if(collection_tree.getSelectionCount() > 0) {
774 records = null;
775 TreePath paths[] = collection_tree.getSelectionPaths();
776 for(int i = 0; i < paths.length; i++) {
777 FileNode record = (FileNode) paths[i].getLastPathComponent();
778 records = ArrayTools.add(records, record);
779 }
780
781 // Remember the previous selection so we can select it again later
782 Metadata previous_selection = selected_metadata;
783
784 table_label.setText(collection_tree.getSelectionDetails());
785
786 // Remove old model from msm
787 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
788 // Create new model
789 model = new GTableModel(table, records);
790 table.setModel(model);
791
792 // Select the closest piece of metadata in the new file
793 model.selectClosestMetadataWhenBuildingComplete(previous_selection);
794 }
795 else {
796 records = null;
797 Dictionary.registerText(table_label, "MetaEdit.No_File");
798
799 // Remove old model from msm
800 if(Gatherer.c_man.ready()) {
801 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
802 }
803 // Create new model
804 model = new GTableModel(table);
805 table.setModel(model);
806 }
807
808 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
809 if(table != null) {
810 Dimension table_size = table.getPreferredSize();
811 TableColumnModel column_model = table.getColumnModel();
812
813 TableColumn inherited_column = column_model.getColumn(0);
814 inherited_column.setPreferredWidth(20);
815 inherited_column.setCellRenderer(new TableCellRenderer(model));
816
817 TableColumn element_column = column_model.getColumn(1);
818 element_column.setPreferredWidth(TABLE_SIZE.width / 4);
819 element_column.setCellRenderer(new TableCellRenderer(model));
820
821 TableColumn value_column = column_model.getColumn(2);
822 value_column.setPreferredWidth(((3 * TABLE_SIZE.width) / 4) - 15);
823 value_column.setCellRenderer(new TableCellRenderer(model));
824 }
825 table.doLayout();
826 validateControls();
827 }
828
829
830 /** 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. */
831 private class TableMouseInputHandler implements MouseInputListener
832 {
833 // Component receiving mouse events during editing.
834 // May not be editorComponent.
835 private Component dispatchComponent;
836 private boolean selectedOnPress;
837
838 // The Table's mouse listener methods.
839
840 public void mouseClicked(MouseEvent e) {}
841
842 private void setDispatchComponent(MouseEvent e) {
843 Component editorComponent = table.getEditorComponent();
844 Point p = e.getPoint();
845 Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
846 dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent,
847 p2.x, p2.y);
848 }
849
850 private boolean repostEvent(MouseEvent e) {
851 // Check for isEditing() in case another event has
852 // caused the editor to be removed. See bug #4306499.
853 if (dispatchComponent == null || !table.isEditing()) {
854 return false;
855 }
856 MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent);
857 dispatchComponent.dispatchEvent(e2);
858 return true;
859 }
860
861 private void setValueIsAdjusting(boolean flag) {
862 table.getSelectionModel().setValueIsAdjusting(flag);
863 table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
864 }
865
866 private boolean shouldIgnore(MouseEvent e) {
867 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled()));
868 }
869
870 public void mousePressed(MouseEvent e) {
871 if (e.isConsumed()) {
872 selectedOnPress = false;
873 return;
874 }
875 selectedOnPress = true;
876 adjustFocusAndSelection(e);
877 }
878
879 void adjustFocusAndSelection(MouseEvent e) {
880 if (shouldIgnore(e)) {
881 return;
882 }
883
884 Point p = e.getPoint();
885 int row = table.rowAtPoint(p);
886 int column = table.columnAtPoint(p);
887 // The autoscroller can generate drag events outside the Table's range.
888 if ((column == -1) || (row == -1)) {
889 return;
890 }
891
892 if (table.editCellAt(row, column, e)) {
893 setDispatchComponent(e);
894 repostEvent(e);
895 }
896 else if (table.isRequestFocusEnabled()) {
897 table.requestFocus();
898 }
899
900 CellEditor editor = table.getCellEditor();
901 if (editor == null || editor.shouldSelectCell(e)) {
902 boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
903 setValueIsAdjusting(adjusting);
904
905 // Special code for clicking the first column (folder-level metadata)
906 if (column == 0) {
907 selected_metadata = model.getMetadataAtRow(row);
908
909 // If there is folder-level metadata, switch to the folder it came from
910 File folder_inherited_from = selected_metadata.getFile();
911 if (folder_inherited_from != null) {
912 collection_tree.setSelection(folder_inherited_from);
913 }
914
915 table.changeSelection(row, 0, e.isControlDown(), e.isShiftDown());
916 }
917 else {
918 table.changeSelection(row, 1, e.isControlDown(), e.isShiftDown());
919 }
920 }
921 }
922
923 public void mouseReleased(MouseEvent e) {
924 if (selectedOnPress) {
925 if (shouldIgnore(e)) {
926 return;
927 }
928
929 repostEvent(e);
930 dispatchComponent = null;
931 setValueIsAdjusting(false);
932 } else {
933 adjustFocusAndSelection(e);
934 }
935 }
936
937
938 public void mouseEntered(MouseEvent e) {}
939
940 public void mouseExited(MouseEvent e) {}
941
942 // The Table's mouse motion listener methods.
943
944 public void mouseMoved(MouseEvent e) {}
945
946 public void mouseDragged(MouseEvent e) {
947 if (shouldIgnore(e)) {
948 return;
949 }
950
951 repostEvent(e);
952
953 CellEditor editor = table.getCellEditor();
954 if (editor == null || editor.shouldSelectCell(e)) {
955 Point p = e.getPoint();
956 int row = table.rowAtPoint(p);
957 int column = table.columnAtPoint(p);
958 // The autoscroller can generate drag events outside the Table's range.
959 if ((column == -1) || (row == -1)) {
960 return;
961 }
962 table.changeSelection(row, column, false, true);
963 }
964 }
965 }
966
967
968 /** A listener for right mouse button clicks over the collection tree. */
969 private class RightButtonListener
970 extends MouseAdapter {
971 /** Called whenever a mouse click occurs (right or left) over a target component.
972 * @param event A <strong>MouseEvent</strong> containing further information about the mouse click action.
973 * @see org.greenstone.gatherer.gui.MetaEditPane.RightButtonMenu
974 */
975 public void mouseClicked(MouseEvent event) {
976 if(SwingUtilities.isRightMouseButton(event)) {
977 new RightButtonMenu(event);
978 }
979 }
980 }
981
982
983 /** Provides a popup menu to display when a right mouse button click is detected over the collection tree. */
984 private class RightButtonMenu
985 extends JPopupMenu
986 implements ActionListener {
987
988 private FileNode node;
989 private JMenuItem collapse_expand_folder_menuitem;
990 private JMenuItem show_metaaudit;
991 private TreePath path;
992
993 /** Constructor.
994 * @param event The <strong>MouseEvent</strong> that triggered the creation of this menu. Used to determine where the menu will be located.
995 */
996 private RightButtonMenu(MouseEvent event) {
997 super();
998 this.path = collection_tree.getClosestPathForLocation(event.getX(), event.getY());
999 this.node = (FileNode)path.getLastPathComponent();
1000 // Creation
1001 // Any folder node gets a menu item allowing you to collapse or expand it depending on its current status
1002 if(!node.isLeaf()) {
1003 // Collapse
1004 if(collection_tree.isExpanded(path)) {
1005 collapse_expand_folder_menuitem = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
1006 }
1007 // Expand
1008 else {
1009 collapse_expand_folder_menuitem = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
1010 }
1011 collapse_expand_folder_menuitem.addActionListener(this);
1012 add(collapse_expand_folder_menuitem);
1013 }
1014 String[] args = new String[1];
1015 args[0] = collection_tree.getSelectionDetails();
1016 show_metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", args), KeyEvent.VK_V);
1017 show_metaaudit.addActionListener(this);
1018 add(show_metaaudit);
1019 // Display
1020 show(collection_tree, event.getX(), event.getY());
1021 show_metaaudit = null;
1022 }
1023
1024 /** Called whenever a user clicks on the single menu item, view all assigned metadata.
1025 * @param event An <strong>ActionEvent</strong> containing further information about the action.
1026 * @see org.greenstone.gatherer.Gatherer
1027 * @see org.greenstone.gatherer.gui.GUIManager
1028 */
1029 public void actionPerformed(ActionEvent event) {
1030 Object source = event.getSource();
1031 if(source == show_metaaudit) {
1032 Gatherer.g_man.showMetaAuditBox();
1033 }
1034 else if(source == collapse_expand_folder_menuitem && path != null && node != null && !node.isLeaf()) {
1035 // Collapse
1036 if(collection_tree.isExpanded(path)) {
1037 collection_tree.collapsePath(path);
1038 }
1039 // Expand
1040 else {
1041 collection_tree.expandPath(path);
1042 }
1043 }
1044 }
1045 }
1046
1047
1048 /**
1049 * This class is a little bit complex, a little bit subtle, and a little bit odd.
1050 * The strange interaction model is due to the fact that it is very tightly
1051 * coupled to the MetaEditPane.
1052 *
1053 * The interaction is complex because there are three separate controls that the
1054 * user may interact with, each of which can affect the other two. The three
1055 * controls are:
1056 * - The "assigned metadata" table, at the top right of the meta edit pane.
1057 * - The "metadata value" text field, where users can type in new values.
1058 * - The "metadata value tree", which shows other values that have been
1059 * assigned to the selected metadata element.
1060 *
1061 * The interactions are:
1062 * - The "assigned metadata" table
1063 * Users may select one (and only one) row in this table. Selecting a row
1064 * chooses one metadata element. The text field will be set to the current
1065 * value of the metadata element. This value will also be selected in the
1066 * metadata value tree.
1067 *
1068 * - The "metadata value" text field
1069 * If the value the user has typed in this is a prefix of an entry in the
1070 * value tree, this value will be selected in the value tree. In this case,
1071 * pressing "Enter" will complete the value (ie. make the value in the text
1072 * field the same as the value in the tree). This is to allow users to
1073 * quickly and easily assign existing metadata values to new documents.
1074 *
1075 * - The "metadata value tree"
1076 * Selecting a value in the tree will set the text field to this value.
1077 */
1078 private class GValueTree
1079 extends JPanel
1080 implements TreeSelectionListener {
1081 private boolean ignore_tree_selection_event = false;
1082 private boolean manual_text_edit_event;
1083 private CardLayout card_layout;
1084 private String card_showing = null;
1085 /** The metadata element that is currently selected. */
1086 private ElementWrapper selected_metadata_element = null;
1087 private GValueModel vm;
1088 private JTextArea extracted_message;
1089 private JTextField value;
1090 private JTree tree;
1091 /** Stock standard size for labels. */
1092 final private Dimension VALUE_LABEL_SIZE = new Dimension(66, 26);
1093
1094 static final private String NONE = "None";
1095 static final private String TREE = "Tree";
1096
1097
1098 public GValueTree(int width, int height)
1099 {
1100 super();
1101
1102 setFont(Gatherer.config.getFont("general.font", false));
1103 setPreferredSize(new Dimension(width, height));
1104
1105 JPanel metadata_pane = new JPanel();
1106
1107 JPanel value_pane = new JPanel();
1108 JLabel value_label = new JLabel();
1109 value_label.setPreferredSize(VALUE_LABEL_SIZE);
1110 Dictionary.registerText(value_label, "MetaEdit.Value");
1111
1112 JPanel value_field_pane = new JPanel();
1113 TransformCharacterTextField value_init = new TransformCharacterTextField();
1114 //value_init.replaceCharacter(StaticStrings.PIPE_CHAR, StaticStrings.FORWARDSLASH_CHAR);
1115 value = value_init;
1116 value_init = null;
1117 value.setBackground(Gatherer.config.getColor("coloring.editable_background", false));
1118 value.setForeground(Gatherer.config.getColor("coloring.editable_foreground", false));
1119 value.setPreferredSize(new Dimension(413, 24));
1120 value.getDocument().addDocumentListener(new DocumentListenerImpl());
1121 value.addKeyListener(new MetadataFieldListener());
1122 value.setFocusTraversalKeysEnabled(false);
1123 Dictionary.setTooltip(value, "MetaEdit.Value_Field_Tooltip");
1124
1125 JPanel button_pane = new JPanel();
1126
1127 JPanel tree_pane = new JPanel();
1128 JLabel tree_label = new JLabel();
1129 Dictionary.registerText(tree_label, "MetaEdit.Tree");
1130
1131 tree = new JTree(new GValueModel());
1132 tree.addTreeSelectionListener(this);
1133 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
1134 tree.setRootVisible(false);
1135 tree.putClientProperty("JTree.lineStyle", "Angled");
1136
1137 JPanel extracted_pane = new JPanel();
1138 JPanel extracted_header_pane = new JPanel();
1139 extracted_message = new JTextArea("");
1140 extracted_message.setEditable(false);
1141 extracted_message.setLineWrap(true);
1142 extracted_message.setOpaque(false);
1143 extracted_message.setWrapStyleWord(true);
1144
1145 card_layout = new CardLayout();
1146
1147 // Layout
1148 value_field_pane.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
1149 value_field_pane.setLayout(new BorderLayout(0, 0));
1150 value_field_pane.add(value, BorderLayout.CENTER);
1151
1152 button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
1153 button_pane.setLayout(new GridLayout());
1154 button_pane.add(add);
1155 button_pane.add(update);
1156 button_pane.add(remove);
1157
1158 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
1159 value_pane.setLayout(new BorderLayout());
1160 value_pane.add(value_label, BorderLayout.WEST);
1161 value_pane.add(value_field_pane, BorderLayout.CENTER);
1162 value_pane.add(expand, BorderLayout.EAST);
1163 value_pane.add(button_pane, BorderLayout.SOUTH);
1164
1165 tree_pane.setLayout(new BorderLayout());
1166 tree_pane.add(tree_label, BorderLayout.NORTH);
1167 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
1168
1169 metadata_pane.setLayout(new BorderLayout());
1170 metadata_pane.add(value_pane, BorderLayout.NORTH);
1171 metadata_pane.add(tree_pane, BorderLayout.CENTER);
1172
1173 extracted_header_pane.setLayout(new BorderLayout());
1174 extracted_header_pane.add(expand_for_extracted, BorderLayout.EAST);
1175
1176 extracted_pane.setBorder(BorderFactory.createEmptyBorder(0,10,25,0));
1177 extracted_pane.setLayout(new BorderLayout());
1178 extracted_pane.add(extracted_header_pane, BorderLayout.NORTH);
1179 extracted_pane.add(extracted_message, BorderLayout.CENTER);
1180
1181 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1182 this.setLayout(card_layout);
1183 this.add(metadata_pane, TREE);
1184 this.add(extracted_pane, NONE);
1185 card_showing = TREE;
1186 }
1187
1188
1189 public GValueModel getModel() {
1190 return (GValueModel) tree.getModel();
1191 }
1192
1193
1194 public ElementWrapper getSelectedMetadataElement() {
1195 return selected_metadata_element;
1196 }
1197
1198
1199 public String getSelectedValue() {
1200 //return /odec.transform(/odec.transformUnicode(value.getText()), /odec.TEXT_TO_GREENSTONE);
1201 String raw_value = value.getText();
1202 raw_value = Codec.transformUnicode(raw_value);
1203 raw_value = Codec.transform(raw_value, Codec.ENCODE_PATH);
1204 raw_value = Codec.transform(raw_value, Codec.ENCODE_SQUARE_BRACKETS);
1205 return raw_value;
1206 }
1207
1208
1209 public void setSelectedMetadataElement(ElementWrapper element)
1210 {
1211 ///ystem.err.println("Setting selected metadata element to: " + element);
1212 if (element == null) {
1213 selected_metadata_element = null;
1214 return;
1215 }
1216
1217 // Don't bother if the element selected is the same as the current one
1218 if (selected_metadata_element != null && element.equals(selected_metadata_element)) {
1219 return;
1220 }
1221
1222 if (vm != null) {
1223 // Close any expanded paths in the tree
1224 GValueNode root_node = (GValueNode) vm.getRoot();
1225 for (int i = 0; i < root_node.getChildCount(); i++) {
1226 GValueNode child_node = (GValueNode) root_node.getChildAt(i);
1227 TreePath child_path = new TreePath(child_node.getPath());
1228 if (tree.isExpanded(child_path)) {
1229 tree.collapsePath(child_path);
1230 }
1231 }
1232 }
1233
1234 // Load the model for the newly selected metadata element
1235 selected_metadata_element = element;
1236 if(Gatherer.c_man != null && Gatherer.c_man.getCollection() != null && Gatherer.c_man.getCollection().msm != null && tree != null) {
1237 vm = Gatherer.c_man.getCollection().msm.getValueTree(element);
1238 tree.setModel(vm);
1239 }
1240
1241 // Special case the extracted metadata set
1242 if (selected_metadata_element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
1243 // Display the panel showing the "you cannot edit this metadata" message
1244 String[] args = new String[1];
1245 args[0] = selected_metadata_element.toString();
1246 Dictionary.registerText(extracted_message, "MetaEdit.AutoMessage", args);
1247 card_layout.show(this, NONE);
1248 card_showing = NONE;
1249 }
1250 else {
1251 // Display the panel for viewing and assigning metadata
1252 card_layout.show(this, TREE);
1253 card_showing = TREE;
1254 }
1255 }
1256
1257
1258 /** Handles TreeSelectionEvents. */
1259 public void valueChanged(TreeSelectionEvent event)
1260 {
1261 if (tree.getSelectionCount() != 0 && !ignore_tree_selection_event) {
1262 TreePath path = tree.getSelectionPath();
1263 GValueNode node = (GValueNode) path.getLastPathComponent();
1264 setSelectedValue(node.getFullPath(true));
1265 }
1266 }
1267
1268
1269 /** Sets the value in the text field. */
1270 public void setSelectedValue(String val)
1271 {
1272 // Setting the text of the field causes the DocumentListener to be notified, and
1273 // updating the tree is handled there (DocumentListenerImpl::validate())
1274 if (!card_showing.equals(NONE)) {
1275 // Decode val
1276 ///ystem.err.println("Before transforms: " + val);
1277 //val = Codec.transform(val, Codec.DECODE_SQUARE_BRACKETS);
1278 //val = Codec.transform(val, Codec.DECODE_PATH);
1279 ///ystem.err.println("Setting selected value to: " + val);
1280 manual_text_edit_event = val.equals(""); // Set to false unless val == ""
1281 value.setText(val); ///odec.transform(val, /odec.GREENSTONE_TO_TEXT));
1282 value.setCaretPosition(0);
1283 manual_text_edit_event = true;
1284 }
1285 }
1286
1287
1288 /** Returns a TreePath for the node most closely matching the metadata value. */
1289 private TreePath getClosestPath(String val)
1290 {
1291 ///ystem.err.println("Select closest path to: " + val);
1292 // Start at the root of the tree
1293 GValueNode tree_node = (GValueNode) tree.getModel().getRoot();
1294
1295 // Hierarchical values are separated using '|'
1296 PatternTokenizer tokenizer = new PatternTokenizer(val, StaticStrings.PIPE_STR);
1297 while (tokenizer.hasMoreTokens()) {
1298 String token = tokenizer.nextToken();
1299
1300 // All components except the last must match exactly
1301 if (tokenizer.hasMoreTokens()) {
1302 for (int i = 0; i < tree_node.getChildCount(); i++) {
1303 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1304 if (child_node.toString(GValueNode.DOM).equals(token)) {
1305 // The matching node has been found, so move onto the next token
1306 tree_node = child_node;
1307 break;
1308 }
1309 }
1310 }
1311
1312 // The last component may match partially
1313 else {
1314 for (int i = 0; i < tree_node.getChildCount(); i++) {
1315 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1316 if (child_node.toString(GValueNode.DOM).startsWith(token)) {
1317 // The closest node has been found, so return its path
1318 return new TreePath(child_node.getPath());
1319 }
1320 }
1321
1322 // Not even a partial match
1323 return null;
1324 }
1325 }
1326
1327 // If nothing else, return the path of the root node
1328 return new TreePath(tree_node.getPath());
1329 }
1330
1331
1332 /** This function is copied from JTree::setPathToVisible(), and modified so tree rows
1333 are always shown flushed left. Note: doesn't do accessibleContext stuff. */
1334 private void scrollTreePathToVisible(TreePath path)
1335 {
1336 if (path != null) {
1337 tree.makeVisible(path);
1338
1339 Rectangle bounds = tree.getPathBounds(path);
1340 if (bounds != null) {
1341 bounds.width += bounds.x;
1342 bounds.x = 0;
1343 tree.scrollRectToVisible(bounds);
1344 }
1345 }
1346 }
1347
1348
1349 private class MetadataFieldListener
1350 implements KeyListener {
1351
1352 /** Gives notification of key events on the text field */
1353 public void keyPressed(KeyEvent e) {
1354 if (e.getKeyCode() == KeyEvent.VK_TAB) {
1355 // Tab: Auto-complete
1356 if (tree.getSelectionCount() != 0 && !getSelectedValue().equals("")) {
1357 TreePath path = tree.getSelectionPath();
1358 GValueNode node = (GValueNode) path.getLastPathComponent();
1359 String val = node.getFullPath(true);
1360 ///ystem.err.println("Setting value to: " + val);
1361 value.setText(val);
1362 val = null;
1363 }
1364 }
1365 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1366 // Enter: Append the metadata value, if we're allowed to
1367 if (add.isEnabled()) {
1368 add.doClick();
1369 }
1370 }
1371 }
1372
1373 public void keyReleased(KeyEvent e) { }
1374
1375 public void keyTyped(KeyEvent e) { }
1376 }
1377
1378
1379 private class DocumentListenerImpl
1380 implements DocumentListener {
1381 /** Gives notification that an attribute or set of attributes changed */
1382 public void changedUpdate(DocumentEvent e) {
1383 validate();
1384 }
1385
1386 /** Gives notification that there was an insert into the document */
1387 public void insertUpdate(DocumentEvent e) {
1388 validate();
1389 }
1390
1391 /** Gives notification that a portion of the document has been removed */
1392 public void removeUpdate(DocumentEvent e) {
1393 validate();
1394 }
1395
1396
1397 /** Ensures that the value text field and value tree are synchronized */
1398 private void validate()
1399 {
1400 String value_text = getSelectedValue(); ///odec.transform(getSelectedValue(), /odec.TEXT_TO_DOM);
1401 ///ystem.err.println("\n(Validate) Value text: " + value_text);
1402
1403 // Ignore the validate() with empty text that occurs when value.setText() is used
1404 if (!value_text.equals("") || manual_text_edit_event == true) {
1405 TreePath closest_path = getClosestPath(value_text);
1406
1407 ///ystem.err.println("The closest path is: " + closest_path);
1408
1409 // Select the new path in the tree
1410 // The tree selection event this causes must be ignored, since it alters value
1411 ignore_tree_selection_event = true;
1412 tree.setSelectionPath(closest_path);
1413 ignore_tree_selection_event = false;
1414
1415 // If a folder has been specified, make sure it is expanded
1416 if (value_text.endsWith("\\") && !tree.isExpanded(closest_path)) {
1417 tree.expandPath(closest_path);
1418 }
1419
1420 // Make sure the path is visible on the screen
1421 scrollTreePathToVisible(closest_path);
1422 // One call should be enough, but it isn't in some cases (for some reason)
1423 scrollTreePathToVisible(closest_path);
1424
1425 // Update the status of the buttons
1426 validateControls(); // Don't update the tools view itself
1427 }
1428 }
1429 }
1430 }
1431}
Note: See TracBrowser for help on using the repository browser.