/** *############################################################################ * A component of the Greenstone Librarian Interface, part of the Greenstone * digital library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: Michael Dewsnip, NZDL Project, University of Waikato, NZ * * Copyright (C) 2005 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *############################################################################ */ package org.greenstone.gatherer.gui; import java.awt.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.metadata.MetadataElement; import org.greenstone.gatherer.metadata.MetadataValue; import org.greenstone.gatherer.metadata.MetadataValueTreeNode; import org.greenstone.gatherer.util.PatternTokenizer; /** * This class is a little bit complex, a little bit subtle, and a little bit odd. * The strange interaction model is due to the fact that it is very tightly * coupled to the EnrichPane. * * The interaction is complex because there are three separate controls that the * user may interact with, each of which can affect the other two. The three * controls are: * - The "assigned metadata" table, at the top right of the meta edit pane. * - The "metadata value" text field, where users can type in new values. * - The "metadata value tree", which shows other values that have been * assigned to the selected metadata element. * * The interactions are: * - The "assigned metadata" table * Users may select one (and only one) row in this table. Selecting a row * chooses one metadata element. The text field will be set to the current * value of the metadata element. This value will also be selected in the * metadata value tree. * * - The "metadata value" text field * If the value the user has typed in this is a prefix of an entry in the * value tree, this value will be selected in the value tree. In this case, * pressing "Tab" will complete the value (ie. make the value in the text * field the same as the value in the tree). This is to allow users to * quickly and easily assign existing metadata values to new documents. * * - The "metadata value tree" * Selecting a value in the tree will set the text field to this value. */ public class MetadataValueTreePane extends JPanel { private boolean ignore_tree_selection_event = false; /** The metadata value that the tree pane is built on */ private MetadataValue metadata_value = null; /** Used to display either the MetadataValueTree (when metadata is selected) or a placeholder panel */ private CardLayout card_layout = null; /** The name of the panel containing the metadata value tree */ private String METADATA_VALUE_TREE_CARD = ""; /** The name of the panel containing the "extracted metadata element selected" placeholder */ private String EXTRACTED_METADATA_ELEMENT_SELECTED_CARD = "Extracted metadata element selected"; /** The name of the panel containing the "inherited metadata selected" placeholder */ private String INHERITED_METADATA_SELECTED_CARD = "Inherited metadata selected"; /** The name of the panel containing the "not only file only metadata selected" placeholder */ private String NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD = "Not one file only metadata selected"; /** The name of the panel containing the "no metadata element selected" placeholder */ private String NO_METADATA_ELEMENT_SELECTED_CARD = "No metadata element selected"; private JLabel metadata_value_tree_label = new JLabel(); private JTextArea extracted_metadata_element_selected_message; private JTree metadata_value_tree; public MetadataValueTreePane() { super(); JScrollPane scrol_tmp; this.setComponentOrientation(Dictionary.getOrientation()); metadata_value_tree_label.setComponentOrientation(Dictionary.getOrientation()); // Card containing the metadata value tree metadata_value_tree = new JTree(); metadata_value_tree.setComponentOrientation(Dictionary.getOrientation()); metadata_value_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); metadata_value_tree.setModel(null); metadata_value_tree.setRootVisible(false); metadata_value_tree.putClientProperty("JTree.lineStyle", "Angled"); JPanel metadata_value_tree_pane = new JPanel(); metadata_value_tree_pane.setComponentOrientation(Dictionary.getOrientation()); metadata_value_tree_pane.setLayout(new BorderLayout()); metadata_value_tree_pane.add(metadata_value_tree_label, BorderLayout.NORTH); scrol_tmp = new JScrollPane(metadata_value_tree); scrol_tmp.setComponentOrientation(Dictionary.getOrientation()); metadata_value_tree_pane.add(scrol_tmp, BorderLayout.CENTER); // Card containing the "extracted metadata element selected" message extracted_metadata_element_selected_message = new JTextArea(""); extracted_metadata_element_selected_message.setComponentOrientation(Dictionary.getOrientation()); extracted_metadata_element_selected_message.setEditable(false); extracted_metadata_element_selected_message.setLineWrap(true); extracted_metadata_element_selected_message.setOpaque(false); extracted_metadata_element_selected_message.setWrapStyleWord(true); JPanel extracted_metadata_element_selected_pane = new JPanel(); extracted_metadata_element_selected_pane.setComponentOrientation(Dictionary.getOrientation()); extracted_metadata_element_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0)); extracted_metadata_element_selected_pane.setLayout(new BorderLayout()); extracted_metadata_element_selected_pane.add(extracted_metadata_element_selected_message, BorderLayout.CENTER); // Card containing the "inherited metadata selected" message JTextArea inherited_metadata_selected_message = new JTextArea(Dictionary.get("EnrichPane.InheritedMetadataSelected")); inherited_metadata_selected_message.setEditable(false); inherited_metadata_selected_message.setLineWrap(true); inherited_metadata_selected_message.setOpaque(false); inherited_metadata_selected_message.setWrapStyleWord(true); inherited_metadata_selected_message.setComponentOrientation(Dictionary.getOrientation()); JPanel inherited_metadata_selected_pane = new JPanel(); inherited_metadata_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0)); inherited_metadata_selected_pane.setLayout(new BorderLayout()); inherited_metadata_selected_pane.add(inherited_metadata_selected_message, BorderLayout.CENTER); inherited_metadata_selected_pane.setComponentOrientation(Dictionary.getOrientation()); // Card containing the "not one file only metadata selected" message JTextArea not_one_file_only_metadata_selected_message = new JTextArea(Dictionary.get("EnrichPane.NotOneFileOnlyMetadataSelected")); not_one_file_only_metadata_selected_message.setEditable(false); not_one_file_only_metadata_selected_message.setLineWrap(true); not_one_file_only_metadata_selected_message.setOpaque(false); not_one_file_only_metadata_selected_message.setWrapStyleWord(true); not_one_file_only_metadata_selected_message.setComponentOrientation(Dictionary.getOrientation()); JPanel not_one_file_only_metadata_selected_pane = new JPanel(); not_one_file_only_metadata_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0)); not_one_file_only_metadata_selected_pane.setLayout(new BorderLayout()); not_one_file_only_metadata_selected_pane.add(not_one_file_only_metadata_selected_message, BorderLayout.CENTER); not_one_file_only_metadata_selected_pane.setComponentOrientation(Dictionary.getOrientation()); // Card containing the "no metadata element selected" message JLabel no_metadata_element_selected_label = new JLabel(Dictionary.get("EnrichPane.No_Metadata_Element")); no_metadata_element_selected_label.setHorizontalAlignment(JLabel.CENTER); no_metadata_element_selected_label.setOpaque(false); no_metadata_element_selected_label.setVerticalAlignment(JLabel.CENTER); no_metadata_element_selected_label.setComponentOrientation(Dictionary.getOrientation()); JPanel no_metadata_element_selected_pane = new JPanel(); no_metadata_element_selected_pane.setLayout(new BorderLayout()); no_metadata_element_selected_pane.add(no_metadata_element_selected_label, BorderLayout.CENTER); no_metadata_element_selected_pane.setComponentOrientation(Dictionary.getOrientation()); card_layout = new CardLayout(); this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); this.setFont(Configuration.getFont("general.font", false)); this.setLayout(card_layout); this.add(no_metadata_element_selected_pane, NO_METADATA_ELEMENT_SELECTED_CARD); this.add(extracted_metadata_element_selected_pane, EXTRACTED_METADATA_ELEMENT_SELECTED_CARD); this.add(inherited_metadata_selected_pane, INHERITED_METADATA_SELECTED_CARD); this.add(not_one_file_only_metadata_selected_pane, NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD); this.add(metadata_value_tree_pane, METADATA_VALUE_TREE_CARD); } public void addMetadataValueTreeSelectionListener(TreeSelectionListener tree_selection_listener) { metadata_value_tree.addTreeSelectionListener(tree_selection_listener); } /** Returns a TreePath for the node most closely matching the metadata value. */ private TreePath getClosestPath(String metadata_value_string) { if (metadata_value_tree.getModel() == null) { return null; } // Start at the root of the tree MetadataValueTreeNode tree_node = (MetadataValueTreeNode) metadata_value_tree.getModel().getRoot(); // Separate hierarchical values PatternTokenizer tokenizer = new PatternTokenizer(metadata_value_string, MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); // All components except the last must match exactly if (tokenizer.hasMoreTokens()) { for (int i = 0; i < tree_node.getChildCount(); i++) { MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i); if (child_node.getValue().equals(token)) { // The matching node has been found, so move onto the next token tree_node = child_node; break; } } } // The last component may match partially else { for (int i = 0; i < tree_node.getChildCount(); i++) { MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i); if (child_node.getFullValue().startsWith(metadata_value_string)) { // The closest node has been found, so return its path return new TreePath(child_node.getPath()); } } // Not even a partial match return null; } } // If nothing else, return the path of the root node return new TreePath(tree_node.getPath()); } public MetadataValueTreeNode getSelectedMetadataValueTreeNode() { if (metadata_value_tree.getSelectionCount() == 0 || ignore_tree_selection_event) { return null; } return (MetadataValueTreeNode) metadata_value_tree.getSelectionPath().getLastPathComponent(); } public void rebuild(MetadataValue new_metadata_value) { // If the metadata value hasn't changed there is nothing to do if (new_metadata_value == metadata_value) { return; } MetadataElement metadata_element = ((metadata_value != null) ? metadata_value.getMetadataElement() : null); metadata_value = new_metadata_value; // Selection cleared, so display "no metadata element selected" card and we're done if (new_metadata_value == null) { metadata_value_tree.setModel(null); card_layout.show(this, NO_METADATA_ELEMENT_SELECTED_CARD); return; } // If a piece of inherited metadata is selected, display "inherited metadata selected" card and we're done if (new_metadata_value.isInheritedMetadata()) { card_layout.show(this, INHERITED_METADATA_SELECTED_CARD); return; } // If the metadata applies to multiple files, display "not one file only metadata selected" card and we're done if (new_metadata_value.isOneFileOnlyMetadata() == false) { card_layout.show(this, NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD); return; } // If an extracted metadata element is selected, display "extracted metadata element selected" card MetadataElement new_metadata_element = new_metadata_value.getMetadataElement(); if (new_metadata_element.isExtractedMetadataElement()) { String[] args = new String[1]; args[0] = new_metadata_element.getDisplayName(); extracted_metadata_element_selected_message.setText(Dictionary.get("EnrichPane.AutoMessage", args)); card_layout.show(this, EXTRACTED_METADATA_ELEMENT_SELECTED_CARD); return; } // Display the value tree for the selected metadata element String[] args = new String[1]; args[0] = new_metadata_element.getDisplayName(); metadata_value_tree_label.setText(Dictionary.get("EnrichPane.ExistingValues", args)); metadata_value_tree.setModel(new_metadata_element.getMetadataValueTreeModel()); card_layout.show(this, METADATA_VALUE_TREE_CARD); selectBestPathForMetadataValue(new_metadata_value.getFullValue()); } public void selectBestPathForMetadataValue(String metadata_value_string) { TreePath best_path = getClosestPath(metadata_value_string); // Select the new path in the tree // The tree selection event this causes must be ignored, since it alters the metadata text field value ignore_tree_selection_event = true; metadata_value_tree.setSelectionPath(best_path); ignore_tree_selection_event = false; // If a folder has been specified, make sure it is expanded if (metadata_value_string.endsWith(MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN) && !metadata_value_tree.isExpanded(best_path)) { metadata_value_tree.expandPath(best_path); } // Make sure the tree path is expanded metadata_value_tree.makeVisible(best_path); // Make sure the tree path is visible on screen final Rectangle bounds = metadata_value_tree.getPathBounds(best_path); if (bounds != null) { bounds.x = 0; // Want the tree to be always flushed left // Scrolling the path to be visible must be done on the GUI thread Runnable task = new Runnable() { public void run() { metadata_value_tree.scrollRectToVisible(bounds); } }; SwingUtilities.invokeLater(task); } } }