/** *######################################################################### * * 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. * * 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; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.undo.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.gui.DesignPaneHeader; import org.greenstone.gatherer.gui.GLIButton; import org.greenstone.gatherer.gui.FormatPane; import org.greenstone.gatherer.metadata.MetadataElement; import org.greenstone.gatherer.metadata.MetadataSetManager; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.Utility; import org.greenstone.gatherer.util.XMLTools; import org.w3c.dom.*; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; /** This class maintains a list of format statements, and allows the addition and removal of these statements. * This is the greenstone 3 equivalent class of FormatManager.java which is used by greenstone 2 */ public class Format4gs3Manager implements SharedByTwoFormatManager { static final private String SEARCH_FORMAT = ":"; static final private String SEARCH = "search"; static final private String DISPLAY_FORMAT = ""; static final private String DISPLAY = "display"; static final private String CLASSIFIER_DEFAULT_FORMAT ="Untitled
()
Untitled
()
"; static final private String CLASSIFIER_DEFAULT = "browse"; static final private String SEARCHTYPE_FORMAT = "plain,form"; static final private String SEARCHTYPE = "searchType"; static final private String[] FEATURE_NAME = {SEARCH, DISPLAY, CLASSIFIER_DEFAULT, SEARCHTYPE}; static final private String[] FEATURE_FORMAT = {SEARCH_FORMAT, DISPLAY_FORMAT, CLASSIFIER_DEFAULT_FORMAT, SEARCHTYPE_FORMAT}; static private HashMap default_format_map = null; static private HashMap default_format_formated_map = null; /** The controls used to edit the format commands. */ private Control controls = null;// an interface /** A reference */ private DOMProxyListModel format_list_model = null; //private DOMProxyListModel feature_list_model = null; /** Constructor. */ public Format4gs3Manager () {//pass the internal structure Element root = CollectionDesignManager.collect_config.getDocumentElement (); format_list_model = new DOMProxyListModel(root, StaticStrings.FORMAT_STR, new Format4gs3 ()); initDefault(format_list_model, FEATURE_NAME, FEATURE_FORMAT); initFormatMap(FEATURE_NAME, FEATURE_FORMAT); } private void initFormatMap(String[] feature_name, String[] feature_format) { default_format_map = new HashMap(); default_format_formated_map = new HashMap(); for(int i=0; i < feature_name.length; i++) { default_format_map.put(feature_name[i], feature_format[i]); default_format_formated_map.put(feature_name[i], Format4gs3.toFormatedFormat (feature_format[i])); } } public void initDefault(DOMProxyListModel model, String[] feature_name, String[] feature_format) { // Establish all of the format objects. for(int i = 0; i < model.getSize (); i++) { model.getElementAt (i);//get those objects cached } for(int i=0; i < feature_name.length; i++) { if (getFormat(model, feature_name[i]) == null) { model.add(new Format4gs3(feature_name[i], feature_format[i])); } } } /** Method to remove a format. * @param format The Format to remove. */ private void removeFormat (DOMProxyListModel model, Format4gs3 format) { model.remove (format); } private Format4gs3 getFormat (DOMProxyListModel model, String name) { for(int index = 0; index < model.getSize(); index++) { Format4gs3 format = (Format4gs3) model.getElementAt(index); if(format.getFeatureName().equals(name)) { return format; } } return null; } private void addFormat (Element parent, Format4gs3 format) { if(!format_list_model.contains (format)) { format_list_model.add (parent, format, null); } } public void destroy () { if(controls != null) { controls.destroy (); controls = null; } } /** Method to retrieve this managers controls. * @return the Control for this collection. */ public Control getControls () { if(controls == null) { controls = new FormatControl (); } //controls.gainFocus(); return controls; } /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden * @param mode the new mode as an int */ public void modeChanged (int mode) { } /** updates the format and feature model */ public synchronized void refresh () { for(int i = 0; i < format_list_model.getSize (); i++) { Format4gs3 format = (Format4gs3) format_list_model.getElementAt (i); format.update (); format = null; } //call the gainFocus() and in turn the buildFeatureModel() method to get the feature combobox refreshed as well if(controls == null) { controls = new FormatControl (); } controls.gainFocus(); //format_list_model.refresh(); //this call is not necessary as it is included in gainFocus() } private ArrayList buildFeatureModel () { // Rebuild feature model. ArrayList feature_model = new ArrayList (); //This will display 'choose a feature' and is used when the format feature panel is first gained focus feature_model.add (new Entry("")); for(int i = 0; i < format_list_model.getSize(); i++) { Format4gs3 format = (Format4gs3)format_list_model.getElementAt (i); String feature_name = format.getFeatureName(); if(!feature_name.startsWith(Classifier.CLASSIFIER_PREFIX)) { feature_model.add (new Entry (format.getFeatureName())); } } // Now the classifiers. Element root = CollectionDesignManager.collect_config.getDocumentElement (); NodeList classifier_list = root.getElementsByTagName (StaticStrings.CLASSIFY_ELEMENT); for(int j = 0; j < classifier_list.getLength (); j++) { feature_model.add (new Entry (CollectionDesignManager.classifier_manager.getClassifier (j))); } //Collections.sort (feature_model); return feature_model; } public class FormatControl extends JPanel implements Control { private ArrayList feature_model; private boolean ignore_event = false; private boolean ready = false; // Are these controls available to be refreshed private JButton add_button; private JButton remove_button; private JButton default_button; private JButton undo_button; private JButton redo_button; private JComboBox feature_combobox; private JList format_list; private NumberedJTextArea editor_textarea; private JTextArea editor_msgarea; private JPanel validation_msg_panel; private JPanel selection_pane; private final Dimension FIELD_SIZE = new Dimension (200, 30); private final UndoManager undo = new UndoManager (); private boolean newtext = true; private Format4gs3 previousFormat = null; private Format4gs3 currentFormat = null; private boolean fresh = true; public FormatControl () { feature_model = buildFeatureModel (); // Create JPanel header_pane = new DesignPaneHeader ("CDM.GUI.Formats", "formatstatements"); format_list = new JList (format_list_model); selection_pane = new JPanel (); JPanel feature_pane = new JPanel (); JLabel feature_label = new JLabel (Dictionary.get ("CDM.FormatManager.Feature")); feature_combobox = new JComboBox (feature_model.toArray ()); feature_combobox.setOpaque (!Utility.isMac ()); feature_combobox.setPreferredSize (FIELD_SIZE); feature_combobox.setEditable (false); feature_combobox.setToolTipText (Dictionary.get ("CDM.FormatManager.Feature_Tooltip")); JPanel center_pane = new JPanel (); JPanel editor_pane = new JPanel (); editor_textarea = new NumberedJTextArea (); editor_textarea.setOpaque(false); editor_textarea.setBackground (Configuration.getColor ("coloring.editable_background", false)); editor_textarea.setCaretPosition (0); editor_textarea.setLineWrap (true); editor_textarea.setRows (11); editor_textarea.setWrapStyleWord (false); editor_textarea.setToolTipText (Dictionary.get ("CDM.FormatManager.Add_Tooltip")); default_button = new GLIButton (Dictionary.get ("CDM.FormatManager.Default"), Dictionary.get ("CDM.FormatManager.Default_Tooltip")); JPanel button_pane = new JPanel (); add_button = new GLIButton (Dictionary.get ("CDM.FormatManager.Add"), Dictionary.get ("CDM.FormatManager.Add_Tooltip")); add_button.setEnabled (false); remove_button = new GLIButton (Dictionary.get ("CDM.FormatManager.Remove"), Dictionary.get ("CDM.FormatManager.Remove_Tooltip")); remove_button.setEnabled (false); undo_button = new GLIButton (Dictionary.get ("General.Undo"), Dictionary.get ("General.Undo_Tooltip")); undo_button.setEnabled (false); redo_button = new GLIButton (Dictionary.get ("General.Redo"), Dictionary.get ("General.Redo_Tooltip")); redo_button.setEnabled (false); // Connect add_button.addActionListener (new AddListener ()); remove_button.addActionListener (new RemoveListener ()); default_button.addActionListener (new DefaultListener ()); undo_button.addActionListener (new UndoListener ()); redo_button.addActionListener (new RedoListener ()); feature_combobox.addActionListener (new FeatureListener ()); editor_textarea.getDocument ().addDocumentListener (new EditorListener ()); // Listen for undo and redo events editor_textarea.getDocument ().addUndoableEditListener (new UndoableEditListener () { public void undoableEditHappened (UndoableEditEvent evt) { undo.addEdit (evt.getEdit ()); } }); format_list.addListSelectionListener (new FormatListListener ()); // Layout JPanel format_list_pane = new JPanel (); format_list_pane.setBorder (BorderFactory.createEmptyBorder (5,0,0,0)); format_list_pane.setLayout (new BorderLayout ()); format_list_pane.add (new JScrollPane (format_list), BorderLayout.CENTER); feature_pane.setBorder (BorderFactory.createEmptyBorder (5,0,0,0)); feature_pane.setLayout (new BorderLayout (5,0)); feature_pane.add (feature_label, BorderLayout.WEST); feature_pane.add (feature_combobox, BorderLayout.CENTER); JPanel rupanel = new JPanel (); rupanel.setLayout (new GridLayout (1,2)); rupanel.add (undo_button); rupanel.add (redo_button); editor_pane.setLayout (new BorderLayout ()); editor_pane.add (new JScrollPane (editor_textarea), BorderLayout.CENTER); validation_msg_panel = new JPanel(); JLabel validation_label = new JLabel (Dictionary.get ("CDM.FormatManager.MessageBox")); editor_msgarea = new JTextArea (); editor_msgarea.setCaretPosition (0); editor_msgarea.setLineWrap (true); editor_msgarea.setRows (3); editor_msgarea.setWrapStyleWord (false); editor_msgarea.setEditable (false); editor_msgarea.setToolTipText (Dictionary.get ("CDM.FormatManager.MessageBox_Tooltip")); validation_msg_panel.setBorder (BorderFactory.createEmptyBorder (2,0,0,0)); validation_msg_panel.setLayout (new BorderLayout (5, 0)); validation_msg_panel.add (validation_label, BorderLayout.WEST); validation_msg_panel.add (new JScrollPane (editor_msgarea), BorderLayout.CENTER); selection_pane.setLayout (new BorderLayout ()); selection_pane.add (validation_msg_panel, BorderLayout.NORTH); selection_pane.add (rupanel, BorderLayout.SOUTH); selection_pane.add (editor_pane, BorderLayout.CENTER); button_pane.setLayout (new GridLayout (1,3)); button_pane.add (add_button); button_pane.add (remove_button); button_pane.add (default_button); center_pane.setLayout (new BorderLayout ()); center_pane.add (feature_pane, BorderLayout.NORTH); center_pane.add (selection_pane, BorderLayout.CENTER); center_pane.add (button_pane, BorderLayout.SOUTH); setBorder (BorderFactory.createEmptyBorder (0,5,0,0)); setLayout (new BorderLayout ()); add (header_pane, BorderLayout.NORTH); add (format_list_pane, 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 () { if(ready) { format_list_model.refresh(); // Update the feature model, trying to maintain the same selected object Object selected_feature = feature_combobox.getSelectedItem (); feature_combobox.setSelectedItem (selected_feature); feature_model = buildFeatureModel (); feature_combobox.setModel (new DefaultComboBoxModel (feature_model.toArray ())); } } public void loseFocus () { //validate the templates. If not wellformed, pop up an alert; otherwise, do nothing. String msg = XMLTools.parse(editor_textarea.getText ()); if(msg.startsWith(XMLTools.NOTWELLFORMED)) { JOptionPane.showMessageDialog(null, msg, XMLTools.NOTWELLFORMED, JOptionPane.ERROR_MESSAGE); } format_list_model.refresh(); } public Format4gs3 getCurrentFormat () { return (Format4gs3)format_list.getSelectedValue (); } /** 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 String format_str = editor_textarea.getText (); Entry entry = (Entry) feature_combobox.getSelectedItem (); String feature_name = entry.getClassifier().getPositionString(); Format4gs3 format = new Format4gs3 (feature_name, format_str); Element e = format.getClassifyElement(); addFormat(e, format); existingFormat (format.getFeatureName().startsWith (Classifier.CLASSIFIER_PREFIX)); // set the appropriate enable/disable on the interface // Update list selection (make the new added format highlighted on the format list panel) format_list.setSelectedValue (format, true); format = null; ignore_event = false; } } private class EditorListener implements DocumentListener { public void changedUpdate (DocumentEvent e) { update (); } public void insertUpdate (DocumentEvent e) { update (); updateUndo ("insert"); } public void removeUpdate (DocumentEvent e) { update (); updateUndo ("remove"); } private void updateUndo (String from) { if (!newtext) { undo_button.setEnabled (true); } if (editor_textarea.getText ().length ()!=0 && newtext) { newtext = false; } } public void update () { if(!format_list.isSelectionEmpty ()) { Format4gs3 format = (Format4gs3)format_list.getSelectedValue (); String format_str = editor_textarea.getText (); String msg = XMLTools.parse(format_str); editor_msgarea.setText(msg); if(msg.startsWith(XMLTools.WELLFORMED)) { format.setPureFormat (Format4gs3.toOneLineFormat (format_str)); format.update(); format_list_model.refresh (format); editor_msgarea.setBackground (Color.white); FormatPane.setPreviewButton(true); } else { editor_msgarea.setBackground (Color.red); FormatPane.setPreviewButton(false); } } else { add_button.setEnabled (false); } } } private class FeatureListener implements ActionListener { public void actionPerformed (ActionEvent event) { undo_button.setEnabled (false); redo_button.setEnabled (false); default_button.setEnabled (true); newtext = true; if (ignore_event == true) { undo.discardAllEdits (); return; } ignore_event = true; // 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 (); String feature_name = entry.getFeatureName(); Format4gs3 format = getFormat(format_list_model, feature_name); if(format != null) { ///ystem.err.println("There is an existing format!"); format_list.setSelectedValue (format, true); editor_textarea.setText (format.getPureFormat()); editor_textarea.setCaretPosition (0); existingFormat (feature_name.startsWith (Classifier.CLASSIFIER_PREFIX)); } // Otherwise there is no existing format, then this feature must be a classifier (CL1, 2, ...) // we display the ClassifierDefault for this format else { //Fist reset the format list panel format_list.clearSelection (); if (feature_name.equals("")) { editor_textarea.setText (""); } else { //Only for debugging purposes if (entry.getClassifier () == null) { DebugStream.println ("It should be a classifier or choose a feature. What is it? " + entry.toString ()); } editor_textarea.setText (Format4gs3.toFormatedFormat (CLASSIFIER_DEFAULT_FORMAT)); editor_textarea.setCaretPosition (0); newFormat (); } } ignore_event = false; undo.discardAllEdits (); } } private class FormatListListener implements ListSelectionListener { public void valueChanged (ListSelectionEvent event) { undo_button.setEnabled (false); redo_button.setEnabled (false); default_button.setEnabled (true); newtext = true; if(!ignore_event && !event.getValueIsAdjusting ()) { if(!format_list.isSelectionEmpty ()) { ignore_event = true; Format4gs3 format = (Format4gs3)format_list.getSelectedValue (); String feature_name = format.getFeatureName(); Entry entry = null; if(feature_name.startsWith(Classifier.CLASSIFIER_PREFIX)) { entry = new Entry(format.getClassifier ()); } else { entry = new Entry(feature_name); } feature_combobox.setSelectedItem (entry); existingFormat (format.getFeatureName().startsWith (Classifier.CLASSIFIER_PREFIX)); editor_textarea.setText (format.getPureFormat()); editor_textarea.setCaretPosition (0); ignore_event = false; } } undo.discardAllEdits (); } } private class RemoveListener implements ActionListener { public void actionPerformed (ActionEvent event) { if (!format_list.isSelectionEmpty ()) { // Remove the current format Format4gs3 format = (Format4gs3)format_list.getSelectedValue (); removeFormat (format_list_model, format); // Change buttons add_button.setEnabled (true); feature_combobox.setSelectedItem (new Entry("")); newFormat (); } } } private class DefaultListener implements ActionListener { public void actionPerformed (ActionEvent event) { newtext = false; if(!ignore_event) { Entry entry = (Entry) feature_combobox.getSelectedItem (); String feature_name = entry.getFeatureName (); Format4gs3 format = getFormat (format_list_model, feature_name); if(format != null) { if (format.isClassifier () == true) { editor_textarea.setText ((String) default_format_formated_map.get (CLASSIFIER_DEFAULT)); editor_textarea.setCaretPosition (0); remove_button.setEnabled (true); } else { editor_textarea.setText ((String) default_format_formated_map.get (format.getFeatureName ())); editor_textarea.setCaretPosition (0); remove_button.setEnabled (false); } } else { editor_textarea.setText ((String) default_format_formated_map.get (CLASSIFIER_DEFAULT)); editor_textarea.setCaretPosition (0); remove_button.setEnabled (false); add_button.setEnabled (true); } } } } private class UndoListener implements ActionListener { public void actionPerformed (ActionEvent event) { try { if (undo.canUndo ()) { int pos = editor_textarea.getCaretPosition (); redo_button.setEnabled (true); undo.undo (); if (pos > 0) editor_textarea.setCaretPosition (pos-1); else editor_textarea.setCaretPosition (pos); } if (!undo.canUndo ()) { undo_button.setEnabled (false); } else { undo_button.setEnabled (true); } } catch (Exception e) { } } } private class RedoListener implements ActionListener { public void actionPerformed (ActionEvent evt) { try { if (undo.canRedo ()) { int pos = editor_textarea.getCaretPosition (); undo.redo (); editor_textarea.setCaretPosition (pos); } if (!undo.canRedo ()) { redo_button.setEnabled (false); } else { redo_button.setEnabled (true); } } catch (Exception e) {} } } private void newFormat () { editor_textarea.setEditable (false); editor_textarea.setBackground (Color.lightGray); editor_textarea.setToolTipText (Dictionary.get ("CDM.FormatManager.Editor_Disabled_Tooltip")); undo_button.setEnabled (false); redo_button.setEnabled (false); add_button.setEnabled (true); remove_button.setEnabled (false); default_button.setEnabled (false); FormatPane.setPreviewButton(true); } private void existingFormat (boolean enableRemoveButton) { editor_textarea.setEditable (true); editor_textarea.setBackground (Color.white); editor_textarea.setToolTipText (Dictionary.get ("CDM.FormatManager.Editor_Tooltip")); add_button.setEnabled (false); remove_button.setEnabled (enableRemoveButton); default_button.setEnabled (true); FormatPane.setPreviewButton(true); } /** * A textarea with the line number next to each line of the text */ public class NumberedJTextArea extends JTextArea { public void paintComponent (Graphics g) { Insets insets = getInsets (); Rectangle rectangle = g.getClipBounds (); g.setColor (Color.white); g.fillRect (rectangle.x, rectangle.y, rectangle.width, rectangle.height); if (rectangle.x < insets.left) { FontMetrics font_metrics = g.getFontMetrics (); int font_height = font_metrics.getHeight (); int y = font_metrics.getAscent () + insets.top; int line_number_start_point = ((rectangle.y + insets.top) / font_height) + 1; if (y < rectangle.y) { y = line_number_start_point * font_height - (font_height - font_metrics.getAscent ()); } int y_axis_end_point = y + rectangle.height + font_height; int x_axis_start_point = insets.left; x_axis_start_point -= getFontMetrics (getFont ()).stringWidth (Math.max (getRows (), getLineCount () + 1) + " "); if (!this.getText().trim().equals("") ) { g.setColor (Color.DARK_GRAY); } else { g.setColor (Color.white); } int length = ("" + Math.max (getRows (), getLineCount () + 1)).length (); while (y < y_axis_end_point) { g.drawString (line_number_start_point + " ", x_axis_start_point, y); y += font_height; line_number_start_point++; } } super.paintComponent (g); } public Insets getInsets () { Insets insets = super.getInsets (new Insets (0,0,0,0)); insets.left += getFontMetrics (getFont ()).stringWidth (Math.max (getRows (), getLineCount () + 1) + " "); return insets; } } } /** This object provides a wrapping around an entry in Format4gs3, which is tranparent. */ // This class is used for display in the feature combobox private class Entry implements Comparable { private Classifier classifier = null; private String feature_name = null; public Entry (Object object) { if(object instanceof Classifier) { classifier = (Classifier)object; feature_name = classifier.getPositionString (); } else if(object instanceof String) { feature_name = (String)object; } else { feature_name = ""; } } public Entry (String text) { this.feature_name = 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 String toString () { if(classifier != null) { // Return the classifier name - with its CL index shown, and all its metadata options as well return classifier.getPositionString () + StaticStrings.SPACE_CHARACTER + classifier.toString (); } if (feature_name.equals ("")) { return ""+"Choose a feature"+""; } return feature_name; } public String getFeatureName () { return feature_name; } } }