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

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

Removed all occurrences of classes explicitly importing other classes in the same package.

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