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

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

Heres a bunch of other changed files. If it wasn't a Friday afternoon I might be bothered finding out what I actually changed in them. Such changes include: a new option or three on preferences, a bug fix for the GDM classes, several changes to CDM to allow for G2.39 configuration files, a fix to Codec to allow for quotes in format strings and more work on CommandTokenizer to allow for stupid, stupid, stupid collectionextra's starting with speech marks then a new line. Plus other stuff. And things. Peace Out.

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