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

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

Here is the result of sixteen hours work over the weekend. I'm too tired to comment them all separately, but here are some of the highlights:
Rewrote how the 'base on collection' method actually retrieves and updates the collection configuration - ensuring the CDM.CollectionConfiguration class is used instead of the retarded Collection.CollectionConfiguration (which coincidently has had a name change to BasicCollectionConfiguration). Went through code search for places where the two versions had been confused. Rewrote large swathes of GDMDocument so as to differentiate between normal and extracted metadata - an attempt to prevent the snowballing extracted metadata problem. Fixed problem where GLI was correctly recieving the last few lines of an external process. The collection shortname is no longer visible, nor is the confusing double name for metadata elements. Also coloured folders in the trees are kaput. The users email is now saved as part of the GLI configuration and is used as appropriate to fill out collection fields. There are new options on the right click menus over trees to allow the expansion and collapsing of folders. 'Show Files' now shows all types (or at least 6 types) of image properly (arg, the plagues of copy and paste). 'Based On' collections are public, plugin list automatically moves to next entry if plugin removed (I guess we should do the same in every other screen?) and metadata arguments in plugins/classifiers are no longer editable. There are about a dozen other small things, but I can't remember them. Hope I remembered to set all of the files to UNIX line-endings.

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