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

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

Addressed the bug with table columns having no height under MacOS and also removed one wastefull table resize

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