/**
*#########################################################################
*
* 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;
}
}
}