source: trunk/gli/src/org/greenstone/gatherer/gui/EnrichPane.java@ 8236

Last change on this file since 8236 was 8236, checked in by mdewsnip, 20 years ago

Replaced all Gatherer.print* with DebugStream.print*.

  • Property svn:keywords set to Author Date Id Revision
File size: 58.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.Configuration;
49import org.greenstone.gatherer.DebugStream;
50import org.greenstone.gatherer.Dictionary;
51import org.greenstone.gatherer.Gatherer;
52import org.greenstone.gatherer.file.FileNode;
53import org.greenstone.gatherer.gui.Filter;
54import org.greenstone.gatherer.gui.GComboBox;
55import org.greenstone.gatherer.gui.GLIButton;
56import org.greenstone.gatherer.gui.MetaEditPrompt;
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.TreeSynchronizer;
71import org.greenstone.gatherer.util.Utility;
72import org.greenstone.gatherer.valuetree.GValueModel;
73import org.greenstone.gatherer.valuetree.GValueNode;
74/** 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 EnrichPane 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).
75* @author John Thompson, Greenstone Digital Libraries, University of Waikato
76* @version 2.3b
77*/
78public class EnrichPane
79 extends JPanel
80 implements ActionListener, ListSelectionListener, TreeSelectionListener, MSMListener {
81
82 static private Dimension BUTTON_SIZE = new Dimension(190, 25);
83 static private Dimension CONTROL_SIZE = new Dimension(560, 240);
84 static private Dimension MINIMUM_SIZE = new Dimension(100, 100);
85 static private Dimension TABLE_SIZE = new Dimension(560, 25);
86 static private Dimension TREE_SIZE = new Dimension(250, 500);
87 static private int MINIMUM_TABLE_HEADER_SIZE = 15;
88 /** The name of the panel containing the metadata table. */
89 static final private String CARD_ONE = "Card One";
90 /** The name of the panel containing the 'no file' placeholder for the metadata table. */
91 static final private String CARD_TWO = "Card Two";
92 /** The name of the panel containing the 'no metadata' placeholder for the metadata table. */
93 static final private String CARD_ZERO = "Card Zero";
94 /** The name of the panel containing the placeholder for the value tree. */
95 static final private String TOOLS_OFF = "Tools Off";
96 /** The name of the panel containing the value tree. */
97 static final private String TOOLS_ON = "Tools On";
98
99 /** 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. */
100 private GValueTree tree = null;
101 /** The layout manager used to display either the GTable (when records are selected) or a placeholder panel with the immortal words 'No Record Selected'. */
102 private CardLayout card_layout = null;
103 /** The layout manager used to display either the GValueTree (when records are selected) or a placeholder panel with the immortal words 'No Record Selected'. */
104 private CardLayout card_layout2 = null;
105 /** Used to dynamically filter the collection tree at user request. Note that this is synchronized with the collection tree filter in the Gather view. */
106 private Filter filter = null;
107 /** The data model behind the GTable, to which we hold a reference for convenient access. */
108 private GTableModel model = null;
109 /** 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. */
110 private FileNode records[] = null;
111 /** The button, which when clicked, adds metadata to the selected records. */
112 private JButton add;
113 /** The button, which when clicked, removes the selected metadata from the selected records. */
114 private JButton remove;
115 /** The button, which when clicked, updates the selected metadata from the selected records. */
116 private JButton update;
117 /** This button creates an editor dialog to allow for an expanded area to type in text. */
118 private JButton expand;
119 private JButton expand_for_extracted;
120 /** The label at the top of the collection tree, which shows the collection name. */
121 private JLabel collection_label;
122 /** The panel in which the metadata value card layout resides. */
123 private JPanel control_pane;
124 /** The panel in which the metadata table card layout resides. */
125 private JPanel table_card_pane;
126 /** The splitpane dividing the collection tree and the metadata based controls. */
127 private JSplitPane external_split;
128 /** A reference to the metadata table, via its superclass. */
129 private JTable table;
130 /** The label shown at the top of the metadata table detailing the current selection statistics. */
131 private JTextField table_label;
132 /** A reference to the collection tree. */
133 private DragTree collection_tree;
134 /** The currently selected metadata determined by listening to every second list selection event from the metadata table. */
135 private Metadata selected_metadata;
136 /** Provide synchronization between the collection trees in this view and the collection pane view. */
137 private TreeSynchronizer tree_sync = null;
138
139 /** Constructor.
140 * @param tree_sync The <strong>TreeSynchronizer</strong> to be used on the collection tree
141 * @see org.greenstone.gatherer.Configuration
142 * @see org.greenstone.gatherer.gui.EnrichPane.GValueTree
143 */
144 public EnrichPane(TreeSynchronizer tree_sync)
145 {
146 this.tree = null;
147 this.tree_sync = tree_sync;
148
149 add = new GLIButton();
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 GLIButton();
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 GLIButton();
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 GLIButton();
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 GLIButton();
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.EnrichPane.GValueTree
192 * @see org.greenstone.gatherer.gui.table.GTableModel
193 * @see org.greenstone.gatherer.msm.ElementWrapper
194 * @see org.greenstone.gatherer.msm.MetadataSetManager
195 * @see org.greenstone.gatherer.msm.MSMEvent
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 // System.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 DebugStream.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 DebugStream.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.util.TreeSynchronizer
319 */
320 public void collectionChanged(boolean ready) {
321 if (ready) {
322 TreeModel collection_model = Gatherer.c_man.getRecordSet();
323 //String[] args = new String[1];
324 //args[0] = Gatherer.c_man.getCollection().getName();
325 Dictionary.registerText(collection_label, "Collection.Collection");
326 // Update label coloring.
327 collection_label.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
328 collection_label.setForeground(Configuration.getColor("coloring.collection_heading_foreground", false));
329 collection_tree.setModel(collection_model);
330 // Update tree coloring.
331 collection_tree.setBackground(Configuration.getColor("coloring.collection_tree_background", false));
332 collection_tree.setForeground(Configuration.getColor("coloring.collection_tree_foreground", false));
333 // Register our msm dependant components (or correctly their models) with msm.
334 if (model != null) {
335 // remove the listener first - in case its already present
336 Gatherer.c_man.getCollection().msm.removeMSMListener(model);
337 Gatherer.c_man.getCollection().msm.addMSMListener(model);
338 }
339 // 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
340 Gatherer.c_man.getCollection().msm.removeMSMListener(this);
341 Gatherer.c_man.getCollection().msm.addMSMListener(this);
342
343 // Clear the currently selected metadata element in the table
344 // This is important so the value tree is refreshed when the collection changes
345 tree.setSelectedMetadataElement(null);
346 }
347 else {
348 Dictionary.registerText(collection_label, "Collection.No_Collection");
349 collection_label.setBackground(Color.lightGray);
350 collection_label.setForeground(Color.black);
351 collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(Dictionary.get("Collection.No_Collection"))));
352 }
353
354 filter.setEnabled(ready);
355 // Ensure that this collection tree view is synchronized with any others.
356 tree_sync.add(collection_tree);
357 }
358 /** Used to create, connect and layout the components to be shown on this control panel.
359 * @see org.greenstone.gatherer.Gatherer
360 * @see org.greenstone.gatherer.collection.CollectionManager
361 * @see org.greenstone.gatherer.file.FileOpenActionListener
362 * @see org.greenstone.gatherer.gui.Filter
363 * @see org.greenstone.gatherer.gui.GComboBox
364 * @see org.greenstone.gatherer.gui.table.GTableModel
365 */
366 public void display() {
367 RightButtonListener right_button_listener = new RightButtonListener();
368 // Creation
369 JPanel collection_pane = new JPanel(new BorderLayout());
370 collection_pane.setMinimumSize(MINIMUM_SIZE);
371 collection_pane.setPreferredSize(TREE_SIZE);
372
373 external_split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
374
375 ///atherer.println("\tCreating collection_label");
376 collection_label = new JLabel();
377 Dictionary.registerText(collection_label, "Collection.No_Collection");
378 collection_label.setOpaque(true);
379
380 DragGroup group = new DragGroup();
381 TreeModel collection_model = Gatherer.c_man.getRecordSet();
382 if (collection_model != null) {
383 collection_tree = new DragTree("MetaEdit", collection_model, null, false);
384 }
385 else {
386 collection_tree = new DragTree("MetaEdit", null, false);
387 }
388 group.add(collection_tree);
389 collection_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
390 collection_tree.putClientProperty("JTree.lineStyle", "Angled");
391 collection_tree.addMouseListener(Gatherer.g_man.foa_listener);
392 collection_tree.addMouseListener(right_button_listener);
393 collection_tree.addTreeSelectionListener(this);
394 collection_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
395 collection_tree.setLargeModel(true);
396 collection_tree.setRootVisible(false);
397
398 KeyListenerImpl key_listener = new KeyListenerImpl();
399 collection_tree.addKeyListener(key_listener);
400 JScrollPane collection_scroll = new JScrollPane(collection_tree);
401
402 filter = Gatherer.g_man.getFilter(collection_tree);
403 filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
404 filter.setEditable(Configuration.getMode() > Configuration.LIBRARIAN_MODE);
405 Dictionary.registerTooltip(filter.getComboBox(), "Collection.Filter_Tooltip");
406
407 // Connection
408
409 // Layout
410 collection_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createLoweredBevelBorder()));
411 collection_pane.setMinimumSize(MINIMUM_SIZE);
412 collection_pane.setPreferredSize(new Dimension(Gatherer.g_man.getSize().width / 3, Gatherer.g_man.getSize().height));
413
414 // Collection Pane
415 collection_pane.add(collection_label, BorderLayout.NORTH);
416 collection_pane.add(collection_scroll, BorderLayout.CENTER);
417 collection_pane.add(filter, BorderLayout.SOUTH);
418
419 JSplitPane main_split_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
420 main_split_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
421 main_split_pane.setDividerSize(8);
422
423 // Metadata Table. In order to achieve what Ian envisions I'm going
424 // to have to do a tricky. The table itself will sit in a stacked
425 // CardLayout. Card zero will be a see-through JPanel with a message
426 // smack bang in the center, something like "No Metadata". If any
427 // page of the table would appear to have no metadata this card will
428 // be swapped to the front.
429
430 JPanel table_pane = new JPanel();
431 table_pane.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
432
433 JPanel table_title_pane = new JPanel();
434
435 table_label = new JTextField();
436 table_label.setBackground(Configuration.getColor("coloring.collection_tree_background", false));
437 table_label.setEditable(false);
438 Dictionary.registerText(table_label, "MetaEdit.No_File");
439
440 card_layout = new CardLayout();
441
442 table_card_pane = new JPanel();
443
444 JPanel table_pane_zero = new JPanel();
445 table_pane_zero.setOpaque(false);
446
447 JPanel table_pane_two = new JPanel();
448 table_pane_two.setOpaque(false);
449
450 JLabel no_file_message = new JLabel();
451 no_file_message.setHorizontalAlignment(JLabel.CENTER);
452 no_file_message.setOpaque(false);
453 no_file_message.setVerticalAlignment(JLabel.CENTER);
454 Dictionary.registerText(no_file_message, "MetaEdit.No_File");
455
456 JLabel no_metadata_message = new JLabel();
457 no_metadata_message.setHorizontalAlignment(JLabel.CENTER);
458 no_metadata_message.setOpaque(false);
459 no_metadata_message.setVerticalAlignment(JLabel.CENTER);
460 Dictionary.registerText(no_metadata_message, "MetaEdit.No_Metadata");
461
462 JPanel table_pane_one = new JPanel();
463
464 ///atherer.println("\tCreating metadata_table");
465 table = new JTable();
466 model = new GTableModel(table);
467 table.setModel(model);
468 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
469 table.getSelectionModel().addListSelectionListener(this);
470 table.setColumnSelectionAllowed(false);
471
472 // The default JTable doesn't quite have the desired properties - when a
473 // table cell is clicked on, the table is scrolled so the cell is as
474 // visible as possible. This means that the left-most cells can become
475 // hidden. To fix this, the MouseInputHandler in BasicTableUI is removed
476 // as a MouseListener on the table, and a very slightly altered copy is
477 // added in its place (TableMouseInputHandler).
478 MouseListener[] mls = table.getMouseListeners();
479 for (int i = 0; i < mls.length; i++) {
480 // Remove the instances of MouseInputHandler
481 if (mls[i].toString().startsWith("javax.swing.plaf.basic.BasicTableUI$MouseInputHandler@")) {
482 table.removeMouseListener(mls[i]);
483 }
484 }
485 // Add the slightly-altered mouse listener
486 table.addMouseListener(new TableMouseInputHandler());
487
488 // 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).
489 JTableHeader table_header = table.getTableHeader();
490 Dimension table_header_preferred_size = table_header.getPreferredSize();
491 if (table_header_preferred_size.height < MINIMUM_TABLE_HEADER_SIZE) {
492 table_header_preferred_size.setSize(table_header_preferred_size.width, MINIMUM_TABLE_HEADER_SIZE);
493 table_header.setPreferredSize(table_header_preferred_size);
494 }
495
496 JScrollPane table_scroll = new JScrollPane(table);
497 table_scroll.getViewport().setBackground(Configuration.getColor("coloring.collection_tree_background", false));
498 table_scroll.setOpaque(true);
499
500 // Control pane
501 ///atherer.println("\tCreating Metadata Controls");
502
503 control_pane = new JPanel();
504 control_pane.setBorder(BorderFactory.createLoweredBevelBorder());
505 control_pane.setMinimumSize(MINIMUM_SIZE);
506 control_pane.setSize(CONTROL_SIZE);
507 control_pane.setPreferredSize(CONTROL_SIZE);
508
509 card_layout2 = new CardLayout();
510
511 JPanel tools_off_pane = new JPanel();
512
513 JLabel tools_off_label = new JLabel();
514 tools_off_label.setHorizontalAlignment(JLabel.CENTER);
515 tools_off_label.setOpaque(false);
516 tools_off_label.setVerticalAlignment(JLabel.CENTER);
517 Dictionary.registerText(tools_off_label, "MetaEdit.No_Metadata_Element");
518
519 JPanel tools_on_pane = new JPanel();
520 tools_on_pane.setMinimumSize(MINIMUM_SIZE);
521 tools_on_pane.setSize(TABLE_SIZE);
522 tools_on_pane.setPreferredSize(TABLE_SIZE);
523
524 // Layout.
525
526 // Metaedit controls
527 tools_off_pane.setLayout(new BorderLayout());
528 tools_off_pane.add(tools_off_label, BorderLayout.CENTER);
529
530 tools_on_pane.setLayout(new BorderLayout());
531 tools_on_pane.add(tree, BorderLayout.CENTER);
532
533 control_pane.setLayout(card_layout2);
534 control_pane.add(tools_off_pane, TOOLS_OFF);
535 control_pane.add(tools_on_pane, TOOLS_ON);
536
537 // Table components
538 table_title_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
539 table_title_pane.setLayout(new BorderLayout());
540 table_title_pane.add(table_label, BorderLayout.CENTER);
541
542 table_pane_zero.setLayout(new BorderLayout());
543 table_pane_zero.add(no_file_message, BorderLayout.CENTER);
544
545 table_pane_one.setLayout(new BorderLayout());
546 table_pane_one.add(table_scroll, BorderLayout.CENTER);
547
548 table_pane_two.setLayout(new BorderLayout());
549 table_pane_two.add(no_metadata_message, BorderLayout.CENTER);
550
551 table_card_pane.setLayout(card_layout);
552 table_card_pane.add(table_pane_zero, CARD_ZERO);
553 table_card_pane.add(table_pane_one, CARD_ONE);
554 table_card_pane.add(table_pane_two, CARD_TWO);
555
556 table_pane.setLayout(new BorderLayout());
557 table_pane.add(table_title_pane, BorderLayout.NORTH);
558 table_pane.add(table_card_pane, BorderLayout.CENTER);
559
560 main_split_pane.add(table_pane, JSplitPane.TOP);
561 main_split_pane.add(control_pane, JSplitPane.BOTTOM);
562 main_split_pane.setDividerLocation(250);
563
564 external_split.add(collection_pane, JSplitPane.LEFT);
565 external_split.add(main_split_pane, JSplitPane.RIGHT);
566
567 this.setLayout(new BorderLayout());
568 this.add(external_split, BorderLayout.CENTER);
569 }
570
571 /** Method that is called whenever an element within a set is changed or modified. - needed for MSMListener
572 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
573 */
574 public void elementChanged(MSMEvent event) {}
575
576 /** Ensures a certain tree path is expanded, visible and selected within the collection tree.
577 * @param path The <strong>TreePath</strong> to make visible.
578 * @see org.greenstone.gatherer.gui.tree.DragTree
579 */
580 private void expandPath(TreePath path) {
581 collection_tree.expandPath(path);
582 collection_tree.setSelectionPath(path);
583 }
584 /** Called to inform this control panel that it has just gained focus as an effect of the user clicking on its tab.
585 * @see org.greenstone.gatherer.gui.tree.DragTree
586 */
587 public void gainFocus() {
588 // Use this opportunity to update the table model etc.
589 // Select the first file (if any)
590 if (collection_tree != null && collection_tree.getRowCount() > 0) {
591 collection_tree.setImmediate(true);
592 collection_tree.setSelectionRow(0);
593 collection_tree.setImmediate(false);
594 }
595 else {
596 // No selection. Reset other components
597 valueChanged(new TreeSelectionEvent(this, null, false, null, null));
598 }
599
600 // Update the menubars idea of whats been selected
601 if (collection_tree != null) {
602 if (collection_tree.isSelectionEmpty()) {
603 Gatherer.g_man.menu_bar.setMetaAuditSuffix(null);
604 }
605 else {
606 Gatherer.g_man.menu_bar.setMetaAuditSuffix(collection_tree.getSelectionDetails());
607 }
608 }
609 // Update the meta-audit view to show the current selection, if any.
610 Gatherer.g_man.meta_audit.setRecords(records);
611 }
612
613 /** Retrieve the currently selected records.
614 * @return A <strong>FileNode[]</strong> containing the current, and up to date, selection.
615 */
616 public FileNode[] getSelectedNode() {
617 return records;
618 }
619
620 /** Called whenever the metadata value changes in some way, such as the addition of a new value. - needed for MSMListener
621 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
622 */
623 public void metadataChanged(MSMEvent event) {}
624
625 /** 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)
626 * @param mode the mode level as an int
627 */
628 public void modeChanged(int mode) {
629 filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
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.EnrichPane.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 this metadata is inherited, switch to the folder it came from
900 if (!selected_metadata.isFileLevel()) {
901 File folder_inherited_from = selected_metadata.getFile();
902 if (folder_inherited_from != null) {
903 collection_tree.setSelection(folder_inherited_from);
904 }
905 }
906
907 table.changeSelection(row, 0, e.isControlDown(), e.isShiftDown());
908 }
909 else {
910 table.changeSelection(row, 1, e.isControlDown(), e.isShiftDown());
911 }
912 }
913 }
914
915 public void mouseReleased(MouseEvent e) {
916 if (selectedOnPress) {
917 if (shouldIgnore(e)) {
918 return;
919 }
920
921 repostEvent(e);
922 dispatchComponent = null;
923 setValueIsAdjusting(false);
924 } else {
925 adjustFocusAndSelection(e);
926 }
927 }
928
929
930 public void mouseEntered(MouseEvent e) {}
931
932 public void mouseExited(MouseEvent e) {}
933
934 // The Table's mouse motion listener methods.
935
936 public void mouseMoved(MouseEvent e) {}
937
938 public void mouseDragged(MouseEvent e) {
939 if (shouldIgnore(e)) {
940 return;
941 }
942
943 repostEvent(e);
944
945 CellEditor editor = table.getCellEditor();
946 if (editor == null || editor.shouldSelectCell(e)) {
947 Point p = e.getPoint();
948 int row = table.rowAtPoint(p);
949 int column = table.columnAtPoint(p);
950 // The autoscroller can generate drag events outside the Table's range.
951 if ((column == -1) || (row == -1)) {
952 return;
953 }
954 table.changeSelection(row, column, false, true);
955 }
956 }
957 }
958
959
960 /** A listener for right mouse button clicks over the collection tree. */
961 private class RightButtonListener
962 extends MouseAdapter {
963 /** Called whenever a mouse click occurs (right or left) over a target component.
964 * @param event A <strong>MouseEvent</strong> containing further information about the mouse click action.
965 * @see org.greenstone.gatherer.gui.EnrichPane.RightButtonMenu
966 */
967 public void mouseClicked(MouseEvent event) {
968 if (SwingUtilities.isRightMouseButton(event)) {
969 new RightButtonMenu(event);
970 }
971 }
972 }
973
974
975 /** When a user right-clicks within the collection tree they are presented with a small popup menu of context based options. This class provides such functionality.
976 */
977 private class RightButtonMenu
978 extends JPopupMenu
979 implements ActionListener {
980
981 /** The tree over which the right click action occurred. */
982 private DragTree tree = null;
983 /** The tree nodes selected when the right click action occurred. */
984 private TreePath[] selection_paths = null;
985 /** The file record over which the right click action occurred. */
986 private FileNode node = null;
987
988 private JMenuItem collapse_folder = null;
989 private JMenuItem expand_folder = null;
990 private JMenuItem metaaudit = null;
991 private JMenuItem open_externally = null;
992
993
994 private RightButtonMenu(MouseEvent event)
995 {
996 super();
997 this.tree = collection_tree;
998
999 // Get the paths currently selected in the tree
1000 selection_paths = tree.getSelectionPaths();
1001
1002 // Create an appropriate context menu, based on what is selected
1003 buildContextMenu(selection_paths);
1004
1005 // Show the popup menu on screen
1006 show(tree, event.getX(), event.getY());
1007 }
1008
1009
1010 private void buildContextMenu(TreePath[] selection_paths)
1011 {
1012 // If nothing is selected, no options are available
1013 if (selection_paths == null) {
1014 return;
1015 }
1016
1017 DebugStream.println("Number of files/folders selected: " + selection_paths.length);
1018
1019 // Always have meta-audit option
1020 String[] args = new String[1];
1021 args[0] = collection_tree.getSelectionDetails();
1022 metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", args), KeyEvent.VK_A);
1023 metaaudit.addActionListener(this);
1024 add(metaaudit);
1025
1026 // Only meta-audit is available if multiple items are selected...
1027 if (selection_paths.length > 1) {
1028 return;
1029 }
1030
1031 TreePath path = selection_paths[0];
1032 node = (FileNode) path.getLastPathComponent();
1033
1034 // ---- Options for file nodes ----
1035 if (node.isLeaf()) {
1036 // Open the file in an external program
1037 open_externally = new JMenuItem(Dictionary.get("Menu.Open_Externally"), KeyEvent.VK_O);
1038 open_externally.addActionListener(this);
1039 add(open_externally);
1040
1041 return;
1042 }
1043
1044 // ---- Options for folder nodes ----
1045 // Collapse or expand, depending on current status
1046 if (tree.isExpanded(path)) {
1047 collapse_folder = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
1048 collapse_folder.addActionListener(this);
1049 add(collapse_folder);
1050 }
1051 else {
1052 expand_folder = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
1053 expand_folder.addActionListener(this);
1054 add(expand_folder);
1055 }
1056 }
1057
1058
1059 /** Called whenever one of the menu items is clicked, this method then causes the appropriate effect. */
1060 public void actionPerformed(ActionEvent event)
1061 {
1062 Object source = event.getSource();
1063
1064 // Collapse folder
1065 if (source == collapse_folder) {
1066 tree.collapsePath(selection_paths[0]);
1067 }
1068
1069 // Expand folder
1070 else if (source == expand_folder) {
1071 tree.expandPath(selection_paths[0]);
1072 }
1073
1074 // Meta-audit
1075 else if (source == metaaudit) {
1076 Gatherer.g_man.showMetaAuditBox();
1077 }
1078
1079 // Open in external program
1080 else if (source == open_externally) {
1081 Gatherer.self.spawnApplication(node.getFile());
1082 }
1083 }
1084 }
1085
1086
1087 /** This class listens for certain key presses, such as [Up] or [Down], -copied from Gather Pane */
1088 private class KeyListenerImpl
1089 extends KeyAdapter {
1090 private boolean vk_left_pressed = false;
1091 /** Called whenever a key that was pressed is released, it is this action that will cause the desired effects (this allows for the key event itself to be processed prior to this listener dealing with it). */
1092 public void keyReleased(KeyEvent event) {
1093 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
1094 if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
1095 DragTree tree = (DragTree)event.getSource();
1096 // we need to manually do the up and down selections
1097 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
1098 int current_row = tree.getLeadSelectionRow();
1099 tree.setImmediate(true);
1100 if (up) {
1101 if (current_row > 0) {
1102 tree.clearSelection();
1103 tree.setSelectionRow(current_row-1);
1104 }
1105 } else {
1106 if (current_row < tree.getRowCount()-1){
1107 tree.clearSelection();
1108 tree.setSelectionRow(current_row+1);
1109 }
1110 }
1111 tree.setImmediate(false);
1112 } else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1113 // left key on a file shifts the selection to the parent folder
1114 DragTree tree = (DragTree)event.getSource();
1115 TreePath path = tree.getLeadSelectionPath();
1116 if(path != null) {
1117 File file = ((FileNode)path.getLastPathComponent()).getFile();
1118 if(file != null) {
1119 if (file.isFile() || vk_left_pressed) {
1120 vk_left_pressed = false;
1121 TreePath parent_path = path.getParentPath();
1122 if (parent_path != null && parent_path.getParentPath() != null) {
1123 // if this file is in the top level folder, don't move the focus
1124 tree.setImmediate(true);
1125 tree.clearSelection();
1126 tree.setSelectionPath(parent_path);
1127 tree.setImmediate(false);
1128 }
1129 }
1130 }
1131 }
1132 }
1133 }
1134
1135 // we need to watch for left clicks on an unopened folder - should shift the focus to teh parent folder. But because there is some other mysterious key listener that does opening and closing folders on right and left clicks, we must detect the situation before the other handler has done its job, and process it after.
1136 public void keyPressed(KeyEvent event) {
1137 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
1138 // left key on closed directory shifts the selection to the parent folder
1139 DragTree tree = (DragTree)event.getSource();
1140 TreePath path = tree.getLeadSelectionPath();
1141 if(path == null) return;
1142 File file = ((FileNode)path.getLastPathComponent()).getFile();
1143 if(file == null) return;
1144
1145 if (file.isDirectory() && tree.isCollapsed(path)) {
1146 vk_left_pressed = true;
1147 }
1148 }
1149 }
1150 }
1151
1152
1153 /**
1154 * This class is a little bit complex, a little bit subtle, and a little bit odd.
1155 * The strange interaction model is due to the fact that it is very tightly
1156 * coupled to the EnrichPane.
1157 *
1158 * The interaction is complex because there are three separate controls that the
1159 * user may interact with, each of which can affect the other two. The three
1160 * controls are:
1161 * - The "assigned metadata" table, at the top right of the meta edit pane.
1162 * - The "metadata value" text field, where users can type in new values.
1163 * - The "metadata value tree", which shows other values that have been
1164 * assigned to the selected metadata element.
1165 *
1166 * The interactions are:
1167 * - The "assigned metadata" table
1168 * Users may select one (and only one) row in this table. Selecting a row
1169 * chooses one metadata element. The text field will be set to the current
1170 * value of the metadata element. This value will also be selected in the
1171 * metadata value tree.
1172 *
1173 * - The "metadata value" text field
1174 * If the value the user has typed in this is a prefix of an entry in the
1175 * value tree, this value will be selected in the value tree. In this case,
1176 * pressing "Enter" will complete the value (ie. make the value in the text
1177 * field the same as the value in the tree). This is to allow users to
1178 * quickly and easily assign existing metadata values to new documents.
1179 *
1180 * - The "metadata value tree"
1181 * Selecting a value in the tree will set the text field to this value.
1182 */
1183 private class GValueTree
1184 extends JPanel
1185 implements TreeSelectionListener {
1186 private boolean ignore_tree_selection_event = false;
1187 private boolean manual_text_edit_event;
1188 private CardLayout card_layout;
1189 private String card_showing = null;
1190 /** The metadata element that is currently selected. */
1191 private ElementWrapper selected_metadata_element = null;
1192 private GValueModel vm;
1193 private JTextArea extracted_message;
1194 private JTextField value;
1195 private JTree tree;
1196
1197 static final private String NONE = "None";
1198 static final private String TREE = "Tree";
1199
1200
1201 public GValueTree(int width, int height)
1202 {
1203 super();
1204
1205 setFont(Configuration.getFont("general.font", false));
1206 setPreferredSize(new Dimension(width, height));
1207
1208 JPanel metadata_pane = new JPanel();
1209
1210 JPanel value_pane = new JPanel();
1211 JLabel value_label = new JLabel();
1212 Dictionary.registerText(value_label, "MetaEdit.Value");
1213
1214 JPanel value_field_pane = new JPanel();
1215 value = new JTextField();
1216 value.setBackground(Configuration.getColor("coloring.editable_background", false));
1217 value.setForeground(Configuration.getColor("coloring.editable_foreground", false));
1218 value.setPreferredSize(new Dimension(413, 24));
1219 value.getDocument().addDocumentListener(new DocumentListenerImpl());
1220 value.addKeyListener(new MetadataFieldListener());
1221 value.setFocusTraversalKeysEnabled(false);
1222 Dictionary.setTooltip(value, "MetaEdit.Value_Field_Tooltip");
1223
1224 JPanel button_pane = new JPanel();
1225
1226 JPanel tree_pane = new JPanel();
1227 JLabel tree_label = new JLabel();
1228 Dictionary.registerText(tree_label, "MetaEdit.Tree");
1229
1230 tree = new JTree(new GValueModel());
1231 tree.addTreeSelectionListener(this);
1232 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
1233 tree.setRootVisible(false);
1234 tree.putClientProperty("JTree.lineStyle", "Angled");
1235
1236 JPanel extracted_pane = new JPanel();
1237 JPanel extracted_header_pane = new JPanel();
1238 extracted_message = new JTextArea("");
1239 extracted_message.setEditable(false);
1240 extracted_message.setLineWrap(true);
1241 extracted_message.setOpaque(false);
1242 extracted_message.setWrapStyleWord(true);
1243
1244 card_layout = new CardLayout();
1245
1246 // Layout
1247 value_field_pane.setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
1248 value_field_pane.setLayout(new BorderLayout(0, 0));
1249 value_field_pane.add(value, BorderLayout.CENTER);
1250
1251 button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
1252 button_pane.setLayout(new GridLayout());
1253 button_pane.add(add);
1254 button_pane.add(update);
1255 button_pane.add(remove);
1256
1257 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
1258 value_pane.setLayout(new BorderLayout());
1259 value_pane.add(value_label, BorderLayout.WEST);
1260 value_pane.add(value_field_pane, BorderLayout.CENTER);
1261 value_pane.add(expand, BorderLayout.EAST);
1262 value_pane.add(button_pane, BorderLayout.SOUTH);
1263
1264 tree_pane.setLayout(new BorderLayout());
1265 tree_pane.add(tree_label, BorderLayout.NORTH);
1266 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
1267
1268 metadata_pane.setLayout(new BorderLayout());
1269 metadata_pane.add(value_pane, BorderLayout.NORTH);
1270 metadata_pane.add(tree_pane, BorderLayout.CENTER);
1271
1272 extracted_header_pane.setLayout(new BorderLayout());
1273 extracted_header_pane.add(expand_for_extracted, BorderLayout.EAST);
1274
1275 extracted_pane.setBorder(BorderFactory.createEmptyBorder(0,10,25,0));
1276 extracted_pane.setLayout(new BorderLayout());
1277 extracted_pane.add(extracted_header_pane, BorderLayout.NORTH);
1278 extracted_pane.add(extracted_message, BorderLayout.CENTER);
1279
1280 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1281 this.setLayout(card_layout);
1282 this.add(metadata_pane, TREE);
1283 this.add(extracted_pane, NONE);
1284 card_showing = TREE;
1285 }
1286
1287
1288 public GValueModel getModel() {
1289 return (GValueModel) tree.getModel();
1290 }
1291
1292
1293 public ElementWrapper getSelectedMetadataElement() {
1294 return selected_metadata_element;
1295 }
1296
1297
1298 public String getSelectedValue() {
1299 String raw_value = value.getText();
1300 raw_value = Codec.transformUnicode(raw_value);
1301 raw_value = Codec.transform(raw_value, Codec.ENCODE_PATH);
1302 raw_value = Codec.transform(raw_value, Codec.ENCODE_SQUARE_BRACKETS);
1303 return raw_value;
1304 }
1305
1306
1307 public void setSelectedMetadataElement(ElementWrapper element)
1308 {
1309 ///ystem.err.println("Setting selected metadata element to: " + element);
1310 if (element == null) {
1311 selected_metadata_element = null;
1312 return;
1313 }
1314
1315 // Don't bother if the element selected is the same as the current one
1316 if (selected_metadata_element != null && element.equals(selected_metadata_element)) {
1317 return;
1318 }
1319
1320 if (vm != null) {
1321 // Close any expanded paths in the tree
1322 GValueNode root_node = (GValueNode) vm.getRoot();
1323 for (int i = 0; i < root_node.getChildCount(); i++) {
1324 GValueNode child_node = (GValueNode) root_node.getChildAt(i);
1325 TreePath child_path = new TreePath(child_node.getPath());
1326 if (tree.isExpanded(child_path)) {
1327 tree.collapsePath(child_path);
1328 }
1329 }
1330 }
1331
1332 // Load the model for the newly selected metadata element
1333 selected_metadata_element = element;
1334 if (Gatherer.c_man != null && Gatherer.c_man.getCollection() != null && Gatherer.c_man.getCollection().msm != null && tree != null) {
1335 vm = Gatherer.c_man.getCollection().msm.getValueTree(element);
1336 tree.setModel(vm);
1337 }
1338
1339 // Special case the extracted metadata set
1340 if (selected_metadata_element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
1341 // Display the panel showing the "you cannot edit this metadata" message
1342 String[] args = new String[1];
1343 args[0] = selected_metadata_element.toString();
1344 Dictionary.registerText(extracted_message, "MetaEdit.AutoMessage", args);
1345 card_layout.show(this, NONE);
1346 card_showing = NONE;
1347 }
1348 else {
1349 // Display the panel for viewing and assigning metadata
1350 card_layout.show(this, TREE);
1351 card_showing = TREE;
1352 }
1353 }
1354
1355
1356 /** Handles TreeSelectionEvents. */
1357 public void valueChanged(TreeSelectionEvent event)
1358 {
1359 if (tree.getSelectionCount() != 0 && !ignore_tree_selection_event) {
1360 TreePath path = tree.getSelectionPath();
1361 GValueNode node = (GValueNode) path.getLastPathComponent();
1362 setSelectedValue(node.getFullPath(true));
1363 }
1364 }
1365
1366
1367 /** Sets the value in the text field. */
1368 public void setSelectedValue(String val)
1369 {
1370 // Setting the text of the field causes the DocumentListener to be notified, and
1371 // updating the tree is handled there (DocumentListenerImpl::validate())
1372 if (!card_showing.equals(NONE)) {
1373 // Decode val
1374 ///ystem.err.println("Before transforms: " + val);
1375 //val = Codec.transform(val, Codec.DECODE_SQUARE_BRACKETS);
1376 //val = Codec.transform(val, Codec.DECODE_PATH);
1377 ///ystem.err.println("Setting selected value to: " + val);
1378 manual_text_edit_event = val.equals(""); // Set to false unless val == ""
1379 value.setText(val); ///odec.transform(val, /odec.GREENSTONE_TO_TEXT));
1380 value.setCaretPosition(0);
1381 manual_text_edit_event = true;
1382 }
1383 }
1384
1385
1386 /** Returns a TreePath for the node most closely matching the metadata value. */
1387 private TreePath getClosestPath(String val)
1388 {
1389 ///ystem.err.println("Select closest path to: " + val);
1390 // Start at the root of the tree
1391 GValueNode tree_node = (GValueNode) tree.getModel().getRoot();
1392
1393 // Hierarchical values are separated using '|'
1394 PatternTokenizer tokenizer = new PatternTokenizer(val, StaticStrings.PIPE_STR);
1395 while (tokenizer.hasMoreTokens()) {
1396 String token = tokenizer.nextToken();
1397
1398 // All components except the last must match exactly
1399 if (tokenizer.hasMoreTokens()) {
1400 for (int i = 0; i < tree_node.getChildCount(); i++) {
1401 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1402 if (child_node.toString(GValueNode.DOM).equals(token)) {
1403 // The matching node has been found, so move onto the next token
1404 tree_node = child_node;
1405 break;
1406 }
1407 }
1408 }
1409
1410 // The last component may match partially
1411 else {
1412 for (int i = 0; i < tree_node.getChildCount(); i++) {
1413 GValueNode child_node = (GValueNode) tree_node.getChildAt(i);
1414 if (child_node.toString(GValueNode.DOM).startsWith(token)) {
1415 // The closest node has been found, so return its path
1416 return new TreePath(child_node.getPath());
1417 }
1418 }
1419
1420 // Not even a partial match
1421 return null;
1422 }
1423 }
1424
1425 // If nothing else, return the path of the root node
1426 return new TreePath(tree_node.getPath());
1427 }
1428
1429
1430 /** This function is copied from JTree::setPathToVisible(), and modified so tree rows
1431 are always shown flushed left. Note: doesn't do accessibleContext stuff. */
1432 private void scrollTreePathToVisible(TreePath path)
1433 {
1434 if (path != null) {
1435 tree.makeVisible(path);
1436
1437 Rectangle bounds = tree.getPathBounds(path);
1438 if (bounds != null) {
1439 bounds.width += bounds.x;
1440 bounds.x = 0;
1441 tree.scrollRectToVisible(bounds);
1442 }
1443 }
1444 }
1445
1446
1447 private class MetadataFieldListener
1448 implements KeyListener {
1449
1450 /** Gives notification of key events on the text field */
1451 public void keyPressed(KeyEvent e) {
1452 if (e.getKeyCode() == KeyEvent.VK_TAB) {
1453 // Tab: Auto-complete
1454 if (tree.getSelectionCount() != 0 && !getSelectedValue().equals("")) {
1455 TreePath path = tree.getSelectionPath();
1456 GValueNode node = (GValueNode) path.getLastPathComponent();
1457 String val = node.getFullPath(true);
1458 ///ystem.err.println("Setting value to: " + val);
1459 value.setText(val);
1460 val = null;
1461 }
1462 }
1463 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
1464 // Enter: Append the metadata value, if we're allowed to
1465 if (add.isEnabled()) {
1466 add.doClick();
1467 }
1468 }
1469 }
1470
1471 public void keyReleased(KeyEvent e) { }
1472
1473 public void keyTyped(KeyEvent e) { }
1474 }
1475
1476
1477 private class DocumentListenerImpl
1478 implements DocumentListener {
1479 /** Gives notification that an attribute or set of attributes changed */
1480 public void changedUpdate(DocumentEvent e) {
1481 validate();
1482 }
1483
1484 /** Gives notification that there was an insert into the document */
1485 public void insertUpdate(DocumentEvent e) {
1486 validate();
1487 }
1488
1489 /** Gives notification that a portion of the document has been removed */
1490 public void removeUpdate(DocumentEvent e) {
1491 validate();
1492 }
1493
1494
1495 /** Ensures that the value text field and value tree are synchronized */
1496 private void validate()
1497 {
1498 String value_text = getSelectedValue(); ///odec.transform(getSelectedValue(), /odec.TEXT_TO_DOM);
1499 ///ystem.err.println("\n(Validate) Value text: " + value_text);
1500
1501 // Ignore the validate() with empty text that occurs when value.setText() is used
1502 if (!value_text.equals("") || manual_text_edit_event == true) {
1503 TreePath closest_path = getClosestPath(value_text);
1504
1505 ///ystem.err.println("The closest path is: " + closest_path);
1506
1507 // Select the new path in the tree
1508 // The tree selection event this causes must be ignored, since it alters value
1509 ignore_tree_selection_event = true;
1510 tree.setSelectionPath(closest_path);
1511 ignore_tree_selection_event = false;
1512
1513 // If a folder has been specified, make sure it is expanded
1514 if (value_text.endsWith("\\") && !tree.isExpanded(closest_path)) {
1515 tree.expandPath(closest_path);
1516 }
1517
1518 // Make sure the path is visible on the screen
1519 scrollTreePathToVisible(closest_path);
1520 // One call should be enough, but it isn't in some cases (for some reason)
1521 scrollTreePathToVisible(closest_path);
1522
1523 // Update the status of the buttons
1524 validateControls(); // Don't update the tools view itself
1525 }
1526 }
1527 }
1528 }
1529}
Note: See TracBrowser for help on using the repository browser.