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

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

Replaced all "Gatherer.config" with "Configuration".

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