/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 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.cdm; /************************************************************************************** * Written: 06/05/02 * Revised: 04/10/02 - Commented * 14/07/03 - DOM support **************************************************************************************/ import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.cdm.Classifier; import org.greenstone.gatherer.cdm.ClassifierManager; import org.greenstone.gatherer.cdm.CollectionConfiguration; import org.greenstone.gatherer.cdm.CollectionDesignManager; import org.greenstone.gatherer.cdm.CommandTokenizer; import org.greenstone.gatherer.cdm.Control; import org.greenstone.gatherer.cdm.DOMProxyListModel; import org.greenstone.gatherer.cdm.Format; import org.greenstone.gatherer.msm.ElementWrapper; import org.greenstone.gatherer.util.GSDLSiteConfig; import org.greenstone.gatherer.util.Utility; import org.w3c.dom.*; /** This class maintains a list of format statements, and allows the addition and removal of these statements. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3 */ public class FormatManager extends DOMProxyListModel { static final private String BLANK = "blank"; static final private String FLAG = "flag"; static final private String VALUE = "value"; /** This flag is set if some change has occured to the format commands. When a collection has been built for previewing, and the greenstone local library server is used, then we have to send commands to remove then add the new collection. */ private boolean formats_changed = false; /** The controls used to edit the format commands. */ private Control controls = null; /** A reference to ourselves so inner classes can get at the model. */ private DOMProxyListModel model = null; /** Constructor. */ public FormatManager() { super(CollectionDesignManager.collect_config.getDocumentElement(), CollectionConfiguration.FORMAT_ELEMENT, new Format()); this.model = this; Gatherer.println("FormatManager: parsed " + getSize() + " format statements."); // Establish all of the format objects, so that classifier indexes are initially correct (subsequent refreshes of the model will be sufficient to keep these up to date, as long as we start with a live reference to a classifier. int size = getSize(); for(int i = 0; i < size; i++) { getElementAt(i); } } /** Method to add a new format to this manager. * @param format The Format to add. */ public void addFormat(Format format) { if(!contains(format)) { Element element = format.getElement(); // Locate where we should insert this new classifier. Node target_node = CollectionConfiguration.findInsertionPoint(element); add(root, format, target_node); Gatherer.c_man.configurationChanged(); formats_changed = true; } } public void destroy() { if(controls != null) { controls.destroy(); controls = null; } } /** Gets the format indicated by the index. * @param index The location of the desired format, as an int. */ public Format getFormat(int index) { Format result = null; if(0 < index && index < getSize()) { result = (Format) getElementAt(index); } return result; } public Format getFormat(String name) { int model_size = getSize(); for(int index = 0; index < model_size; index++) { Format format = (Format) getElementAt(index); if(format.getName().equals(name)) { return format; } } return null; } /** Method to retrieve this managers controls. * @return the Control for this collection. */ public Control getControls() { if(controls == null) { controls = new FormatControl(); } return controls; } /** Method to remove a format. * @param format The Format to remove. */ public void removeFormat(Format format) { remove(format); Gatherer.c_man.configurationChanged(); formats_changed = true; } private HashMap buildDefaultMappings(ArrayList features_model, ArrayList parts_model) { System.err.println("buildDefaultMappings(): replace me with something that reads in a data xml file."); return new HashMap(); } private ArrayList buildFeatureModel() { // Rebuild feature model. ArrayList feature_model = new ArrayList(); // Add the set options for(int i = 0; i < Format.DEFAULT_FEATURES.length; i++) { feature_model.add(new Entry(Format.DEFAULT_FEATURES[i])); } // Now the classifiers. for(int j = 0; j < CollectionDesignManager.classifier_manager.getSize(); j++) { feature_model.add(new Entry(CollectionDesignManager.classifier_manager.getClassifier(j))); } Collections.sort(feature_model); return feature_model; } private ArrayList buildPartModel() { System.err.println("buildPartModel(): replace me with something that reads in a data xml file."); ArrayList part_model = new ArrayList(); part_model.add(""); part_model.add("DateList"); part_model.add("HList"); part_model.add("Invisible"); part_model.add("VList"); return part_model; } private ArrayList buildVariableModel() { System.err.println("buildVariableModel(): replace me with something that reads in a data xml file."); ArrayList variable_model = new ArrayList(); variable_model.add("[Text]"); variable_model.add("[link]"); variable_model.add("[/link]"); variable_model.add("[icon]"); variable_model.add("[num]"); variable_model.add("[parent():_]"); variable_model.add("[parent(Top):_]"); variable_model.add("[parent(All'_'):_]"); Vector elements = Gatherer.c_man.getCollection().msm.getAssignedElements(); for(int i = 0; i < elements.size(); i++) { variable_model.add("[" + ((ElementWrapper)elements.get(i)).getName() + "]"); } Collections.sort(variable_model); return variable_model; } private class FormatControl extends JPanel implements Control { private ArrayList feature_model; private ArrayList part_model; private ArrayList variable_model; private boolean ignore_event = false; private boolean ready = false; // Are these controls available to be refreshed private CardLayout card_layout; private HashMap default_mappings; private JButton add_button; private JButton insert_button; private JButton remove_button; private JButton replace_button; private JCheckBox enabled_checkbox; private JComboBox feature_combobox; private JComboBox part_combobox; private JComboBox variable_combobox; private JList format_list; private JTextArea instructions_textarea; private JTextArea editor_textarea; private JPanel control_pane; private JPanel part_pane; private JPanel selection_pane; private String view_type; public FormatControl() { feature_model = buildFeatureModel(); part_model = buildPartModel(); variable_model = buildVariableModel(); default_mappings = buildDefaultMappings(feature_model, part_model); // Create JPanel instructions_pane = new JPanel(); JLabel title_label = new JLabel(); title_label.setHorizontalAlignment(JLabel.CENTER); title_label.setOpaque(true); Dictionary.registerText(title_label, "CDM.FormatManager.Title"); instructions_textarea = new JTextArea(); instructions_textarea.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false)); instructions_textarea.setEditable(false); instructions_textarea.setLineWrap(true); instructions_textarea.setRows(6); instructions_textarea.setWrapStyleWord(true); Dictionary.registerText(instructions_textarea, "CDM.FormatManager.Instructions"); JLabel format_label = new JLabel(); Dictionary.registerText(format_label, "CDM.FormatManager.Assigned_Formats"); format_list = new JList(model); selection_pane = new JPanel(); JPanel feature_pane = new JPanel(); JLabel feature_label = new JLabel(); feature_label.setPreferredSize(Utility.LABEL_SIZE); Dictionary.registerText(feature_label, "CDM.FormatManager.Feature"); feature_combobox = new JComboBox(feature_model.toArray()); feature_combobox.setEditable(false); Dictionary.registerTooltip(feature_combobox, "CDM.FormatManager.Feature_Tooltip"); part_pane = new JPanel(); JLabel part_label = new JLabel(); part_label.setPreferredSize(Utility.LABEL_SIZE); Dictionary.registerText(part_label, "CDM.FormatManager.Part"); part_combobox = new JComboBox(part_model.toArray()); part_combobox.setEditable(false); Dictionary.registerTooltip(part_combobox, "CDM.FormatManager.Part_Tooltip"); JPanel center_pane = new JPanel(); card_layout = new CardLayout(); control_pane = new JPanel(); JPanel blank_pane = new JPanel(); JPanel editor_pane = new JPanel(); JPanel editor_header_pane = new JPanel(); JLabel editor_label = new JLabel(); Dictionary.registerText(editor_label, "CDM.FormatManager.Editor"); editor_textarea = new JTextArea(); editor_textarea.setBackground(Gatherer.config.getColor("coloring.editable_background", false)); editor_textarea.setCaretPosition(0); editor_textarea.setLineWrap(true); editor_textarea.setRows(6); editor_textarea.setWrapStyleWord(false); Dictionary.registerTooltip(editor_textarea, "CDM.FormatManager.Editor_Tooltip"); JPanel variable_pane = new JPanel(); JLabel variable_label = new JLabel(); Dictionary.registerText(variable_label, "CDM.FormatManager.Variable"); variable_combobox = new JComboBox(variable_model.toArray()); Dictionary.registerTooltip(variable_combobox, "CDM.FormatManager.Variable_Tooltip"); insert_button = new JButton(); insert_button.setMnemonic(KeyEvent.VK_I); Dictionary.registerBoth(insert_button, "CDM.FormatManager.Insert", "CDM.FormatManager.Insert_Tooltip"); JPanel flag_pane = new JPanel(); enabled_checkbox = new JCheckBox(); Dictionary.registerText(enabled_checkbox, "CDM.FormatManager.Enabled"); JPanel button_pane = new JPanel(); add_button = new JButton(); add_button.setEnabled(false); add_button.setMnemonic(KeyEvent.VK_A); Dictionary.registerBoth(add_button, "CDM.FormatManager.Add", "CDM.FormatManager.Add_Tooltip"); replace_button = new JButton(); replace_button.setEnabled(false); replace_button.setMnemonic(KeyEvent.VK_C); Dictionary.registerBoth(replace_button, "CDM.FormatManager.Replace", "CDM.FormatManager.Replace_Tooltip"); remove_button = new JButton(); remove_button.setEnabled(false); remove_button.setMnemonic(KeyEvent.VK_R); Dictionary.registerBoth(remove_button, "CDM.FormatManager.Remove", "CDM.FormatManager.Remove_Tooltip"); // Connect add_button.addActionListener(new AddListener()); insert_button.addActionListener(new InsertListener()); remove_button.addActionListener(new RemoveListener()); replace_button.addActionListener(new ReplaceListener()); enabled_checkbox.addActionListener(new EnabledListener()); feature_combobox.addActionListener(new FeatureListener()); part_combobox.addActionListener(new PartListener()); editor_textarea.getDocument().addDocumentListener(new EditorListener()); format_list.addListSelectionListener(new FormatListListener()); // Layout instructions_pane.setLayout(new BorderLayout()); instructions_pane.add(title_label, BorderLayout.NORTH); instructions_pane.add(new JScrollPane(instructions_textarea), BorderLayout.CENTER); instructions_pane.add(format_label, BorderLayout.SOUTH); feature_pane.setLayout(new BorderLayout()); feature_pane.add(feature_label, BorderLayout.WEST); feature_pane.add(feature_combobox, BorderLayout.CENTER); part_pane.setLayout(new BorderLayout()); part_pane.add(part_label, BorderLayout.WEST); part_pane.add(part_combobox, BorderLayout.CENTER); selection_pane.setLayout(new GridLayout(2,1,0,2)); selection_pane.add(feature_pane); selection_pane.add(part_pane); flag_pane.add(enabled_checkbox); editor_header_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0)); editor_header_pane.setLayout(new GridLayout(1,3)); editor_header_pane.add(editor_label); editor_header_pane.add(new JPanel()); variable_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0)); variable_pane.setLayout(new GridLayout(1,3)); variable_pane.add(variable_label); variable_pane.add(variable_combobox); variable_pane.add(insert_button); editor_pane.setLayout(new BorderLayout()); editor_pane.add(editor_header_pane, BorderLayout.NORTH); editor_pane.add(new JScrollPane(editor_textarea), BorderLayout.CENTER); editor_pane.add(variable_pane, BorderLayout.SOUTH); control_pane.setLayout(card_layout); control_pane.add(flag_pane, FLAG); control_pane.add(editor_pane, VALUE); control_pane.add(blank_pane, BLANK); button_pane.setLayout(new GridLayout(1,3)); button_pane.add(add_button); button_pane.add(replace_button); button_pane.add(remove_button); center_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(Dictionary.get("CDM.FormatManager.Editing_Controls")), BorderFactory.createEmptyBorder(2,2,2,2))); center_pane.setLayout(new BorderLayout()); center_pane.add(selection_pane, BorderLayout.NORTH); center_pane.add(control_pane, BorderLayout.CENTER); center_pane.add(button_pane, BorderLayout.SOUTH); setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); setLayout(new BorderLayout()); add(instructions_pane, BorderLayout.NORTH); add(new JScrollPane(format_list), BorderLayout.CENTER); add(center_pane, BorderLayout.SOUTH); ready = true; } public void destroy() { } /** Overriden to ensure that the instructions pane is scrolled to the top. */ public void gainFocus() { // This is only necessary if the components have been realized if(ready) { formats_changed = false; model.refresh(); feature_model = buildFeatureModel(); // Remember the current selection Object selected_object = feature_combobox.getSelectedItem(); feature_combobox.setModel(new DefaultComboBoxModel(feature_model.toArray())); // Now restore the selected object as best as possible feature_combobox.setSelectedItem(selected_object); selected_object = null; if(instructions_textarea != null) { instructions_textarea.setCaretPosition(0); } } } public void loseFocus() { // Force all of the Formats to update their names with the correct values. int size = model.getSize(); for(int i = 0; i < size; i++) { Format format = (Format) model.getElementAt(i); format.update(); format = null; } // Now if the formats have changed, and the greenstone local library is being used and the collection has been built for previewing, then remove the add the collection to Greenstone to ensure the formatting updates correctly. if(formats_changed && Gatherer.c_man.built() && Gatherer.config.exec_file != null) { // Release the collection Gatherer.g_man.preview_pane.configServer(GSDLSiteConfig.RELEASE_COMMAND + Gatherer.c_man.getCollection().getName()); // Then re-add it to force format commands to be processed Gatherer.g_man.preview_pane.configServer(GSDLSiteConfig.ADD_COMMAND + Gatherer.c_man.getCollection().getName()); } formats_changed = false; } /** Listens for clicks on the add button, and if the relevant details are provided adds a new format. Note that formats are responsible for codecing the values into something that can be a) stored in a DOM and b) written to file */ private class AddListener implements ActionListener { public void actionPerformed(ActionEvent event) { ignore_event = true; // Prevent format_list excetera propagating events Entry entry = (Entry)feature_combobox.getSelectedItem(); Object f = entry.getFeature(); String p = (String)part_combobox.getSelectedItem(); Format format = null; if(view_type.equals(FLAG)) { format = new Format(f, p, enabled_checkbox.isSelected()); } else { format = new Format(f, p, editor_textarea.getText()); } addFormat(format); add_button.setEnabled(false); replace_button.setEnabled(false); remove_button.setEnabled(true); // Update list selection format_list.setSelectedValue(format, true); format = null; p = null; f = null; entry = null; ignore_event = false; } } private class EditorListener implements DocumentListener { public void changedUpdate(DocumentEvent e) { update(); } public void insertUpdate(DocumentEvent e) { update(); } public void removeUpdate(DocumentEvent e) { update(); } public void update() { // Determine if replace should be enabled if(!format_list.isSelectionEmpty()) { Format format = (Format)format_list.getSelectedValue(); replace_button.setEnabled(!format.isParamType() && editor_textarea.getText() != format.getValue()); } else { replace_button.setEnabled(false); } } } private class EnabledListener implements ActionListener { public void actionPerformed(ActionEvent event) { // If there is a current format selected, and the value of enable_checkbox is now different than to value in it, then enable to replace button. if(!format_list.isSelectionEmpty()) { Format format = (Format)format_list.getSelectedValue(); replace_button.setEnabled(format.isParamType() && enabled_checkbox.isSelected() != format.getState()); } // Thats it. Add would have been enabled upon feature/part selection depending if no existing format, um, existed. } } private class FeatureListener implements ActionListener { public void actionPerformed(ActionEvent event) { if(!ignore_event) { ignore_event = true; // Step one: reset part part_combobox.setSelectedIndex(0); // Step two: the rest Entry entry = (Entry) feature_combobox.getSelectedItem(); String name = entry.toString(); if(Format.isParamType(name)) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; } control_pane.updateUI(); // Add is only enabled if there isn't already a format for the choosen feature and part. Create a dummy format and test if itsa already in the model Object f = entry.getFeature(); String p = (String)part_combobox.getSelectedItem(); // You can never add anything to blank-blank if(f.toString().length() == 0 && p.length() == 0) { add_button.setEnabled(false); replace_button.setEnabled(false); remove_button.setEnabled(false); } else { name = Format.generateName(f, p); Format format = getFormat(name); // If there is an existing feature, select it. if(format != null) { format_list.setSelectedValue(format, true); // Now use type to determine what controls are visible, and what have initial values. if(format.isParamType()) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; // Initial value enabled_checkbox.setSelected(format.getState()); } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; // Initial value editor_textarea.setText(format.getValue()); } control_pane.updateUI(); remove_button.setEnabled(true); } else { format_list.clearSelection(); if(Format.isParamType(name)) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; // Initial value enabled_checkbox.setSelected(false); } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; // Initial value editor_textarea.setText(""); } add_button.setEnabled(true); } format = null; name = null; } p = null; f = null; replace_button.setEnabled(false); name = null; entry = null; ignore_event = false; } } } private class FormatListListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent event) { if(!ignore_event) { if(!format_list.isSelectionEmpty()) { ignore_event = true; Format format = (Format)format_list.getSelectedValue(); // Try to match the target, remembering the entries within are Entry's Entry an_entry = new Entry(format.getFeature()); feature_combobox.setSelectedItem(an_entry); // If we didn't match anything, add it and select it again Entry result_entry = (Entry) feature_combobox.getSelectedItem(); if(!an_entry.equals(result_entry)) { feature_combobox.insertItemAt(an_entry, feature_combobox.getItemCount()); feature_combobox.setSelectedItem(an_entry); } if(format.canHavePart()) { part_combobox.setEnabled(true); // Try to match the part. String part_entry = format.getPart(); part_combobox.setSelectedItem(part_entry); // If we didn't match anything, add it and select it again String selected_part = (String)part_combobox.getSelectedItem(); if(!part_entry.equals(selected_part)) { part_combobox.insertItemAt(part_entry, part_combobox.getItemCount()); part_combobox.setSelectedItem(part_entry); } } else { part_combobox.setEnabled(false); } // Now use type to determine what controls are visible, and what have initial values. if(format.isParamType()) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; // Initial value enabled_checkbox.setSelected(format.getState()); } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; // Initial value editor_textarea.setText(format.getValue()); } control_pane.updateUI(); remove_button.setEnabled(true); ignore_event = false; } else { remove_button.setEnabled(false); } add_button.setEnabled(false); replace_button.setEnabled(false); } } } private class InsertListener implements ActionListener { public void actionPerformed(ActionEvent event) { editor_textarea.insert((String)variable_combobox.getSelectedItem(), editor_textarea.getCaretPosition()); } } private class PartListener implements ActionListener { public void actionPerformed(ActionEvent event) { if(!ignore_event) { // Add is only enabled if there isn't already a format for the choosen feature and part. Create a dummy format and test if itsa already in the model Entry entry = (Entry) feature_combobox.getSelectedItem(); Object f = entry.getFeature(); String p = (String)part_combobox.getSelectedItem(); // You can never add anything to blank-blank if(f.toString().length() == 0 && p.length() == 0) { add_button.setEnabled(false); replace_button.setEnabled(false); remove_button.setEnabled(false); } else { String name = Format.generateName(f, p); Format format = getFormat(name); // If there is an existing feature, select it. if(format != null) { format_list.setSelectedValue(format, true); // Now use type to determine what controls are visible, and what have initial values. if(format.isParamType()) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; // Initial value enabled_checkbox.setSelected(format.getState()); } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; // Initial value editor_textarea.setText(format.getValue()); } control_pane.updateUI(); remove_button.setEnabled(true); } else { format_list.clearSelection(); if(Format.isParamType(name)) { // Flags first. selection_pane.remove(part_pane); card_layout.show(control_pane, FLAG); view_type = FLAG; // Initial value enabled_checkbox.setSelected(false); } else { selection_pane.add(part_pane); card_layout.show(control_pane, VALUE); view_type = VALUE; // Initial value editor_textarea.setText(""); } add_button.setEnabled(true); } format = null; name = null; } p = null; f = null; entry = null; replace_button.setEnabled(false); } } } private class RemoveListener implements ActionListener { public void actionPerformed(ActionEvent event) { if(!format_list.isSelectionEmpty()) { removeFormat((Format)format_list.getSelectedValue()); // Change buttons add_button.setEnabled(true); replace_button.setEnabled(false); } remove_button.setEnabled(false); } } private class ReplaceListener implements ActionListener { public void actionPerformed(ActionEvent event) { /** @todo **/ } } } /** This object provides a wrapping around an entry in the format target control, which is tranparent as to whether it is backed by a String or a Classifier. */ private class Entry implements Comparable { private Classifier classifier = null; private String text = null; public Entry(Object object) { if(object instanceof Classifier) { classifier = (Classifier)object; } else if(object instanceof String) { text = (String)object; } else { text = ""; } } public Entry(String text) { this.text = text; } public int compareTo(Object object) { if(object == null) { return 1; } if(toString() == null) { return -1; } else { String object_str = object.toString(); if(object_str == null) { return 1; } return toString().compareTo(object_str); } } public boolean equals(Object object) { if(compareTo(object) == 0) { return true; } return false; } public Classifier getClassifier() { return classifier; } public Object getFeature() { if(classifier != null) { return classifier; } return text; } public String toString() { if(classifier != null) { // Return the classifier - less the 'classify ' prefix return classifier.toString().substring(9); } return text; } } }