Index: main/trunk/gli/classes/dictionary.properties
===================================================================
--- main/trunk/gli/classes/dictionary.properties	(revision 30700)
+++ main/trunk/gli/classes/dictionary.properties	(revision 30701)
@@ -648,4 +648,5 @@
 General.Redo:Redo
 General.Redo_Tooltip:Restore the last undo
+General.Save:Save
 General.Undo:Undo
 General.Undo_Tooltip:Undo the last edit
@@ -769,4 +770,5 @@
 Menu.Edit_Cut:Cut ({0}-x)
 Menu.Edit_Paste:Paste ({0}-v)
+Menu.Edit_Config:Edit collectionConfig.xml
 Menu.Expand:Open Folder
 Menu.Explode_Metadata_Database:Explode metadata database
@@ -962,4 +964,9 @@
 FormatConversionDialog.XHTML_Tidy_Tooltip:Will run HTML Tidy over the Greenstone3 format statement to try to fix up obvious XHTML errors.
 FormatConversionDialog.XML_Still_Invalid:XML is still invalid.
+
+#***** ConfigFileEditor *****
+ConfigFileEditor.Tooltip:Greenstone 3 collection configuration XML editor.
+ConfigFileEditor.Save_Tooltip:Save changes and close.
+ConfigFileEditor.Cancel_Tooltip:Close without saving.
 
 #**********************
Index: main/trunk/gli/src/org/greenstone/gatherer/gui/ConfigFileEditor.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/gui/ConfigFileEditor.java	(revision 30701)
+++ main/trunk/gli/src/org/greenstone/gatherer/gui/ConfigFileEditor.java	(revision 30701)
@@ -0,0 +1,269 @@
+/**
+ *#########################################################################
+ *
+ * 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.gui;
+
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfigXMLReadWrite;
+import org.greenstone.gatherer.util.Codec;
+import org.greenstone.gatherer.util.InputStreamGobbler;
+import org.greenstone.gatherer.util.OutputStreamGobbler;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.greenstone.gatherer.util.XMLTools;
+
+import org.w3c.dom.*;
+
+
+import java.io.File;
+import java.io.IOException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+
+
+public class ConfigFileEditor extends ModalDialog 
+    implements ActionListener, DocumentListener
+{
+    public final File config_file;
+
+    private GLIButton cancel_button = null;
+    private GLIButton save_button = null;
+    private NumberedJTextArea editor = null;
+    private JTextArea editor_msgarea;
+
+    //private final String DEFAULT_PROCESSING_INSTRUCTION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";    
+
+    private static final Dimension SIZE = new Dimension(640,480);
+
+    public ConfigFileEditor() {
+
+	String collection_folder_path = Gatherer.getCollectDirectoryPath();
+	String collectionName  = Gatherer.c_man.getCollection().getName(); // we won't be in this constructor if collection is null.
+	this.config_file = new File(collection_folder_path + File.separator + collectionName, Utility.CONFIG_GS3_FILE); // col config editing is a GS3 feature
+
+	
+	// Most of the validation_msg_panel code is from Format4gs3Manager.java
+	JPanel validation_msg_panel = new JPanel();
+	JLabel validation_label = new JLabel(Dictionary.get("CDM.FormatManager.MessageBox"));
+	validation_label.setBorder(new EmptyBorder(10,10,10,10)); // add some margin
+	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);
+	
+	// the all important XML editor
+	editor = new NumberedJTextArea(Dictionary.get("ConfigFileEditor.Tooltip"));
+	editor.getDocument().addDocumentListener(this);
+
+	save_button = new GLIButton(Dictionary.get("General.Save"), Dictionary.get("ConfigFileEditor.Save_Tooltip"));
+	cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("ConfigFileEditor.Cancel_Tooltip")); // tooltip is the same
+	cancel_button.addActionListener(this);
+	save_button.addActionListener(this);
+
+	// LAYOUT
+	JPanel button_panel = new JPanel(new FlowLayout());
+	button_panel.setComponentOrientation(Dictionary.getOrientation());
+	button_panel.add(editor.undoButton);
+	button_panel.add(editor.redoButton);
+	button_panel.add(cancel_button);
+	button_panel.add(save_button);
+
+	JPanel content_pane = (JPanel) getContentPane();
+	content_pane.setComponentOrientation(Dictionary.getOrientation());
+	content_pane.setLayout(new BorderLayout());
+	content_pane.add(validation_msg_panel, BorderLayout.NORTH);
+	content_pane.add(new JScrollPane(editor), BorderLayout.CENTER); // make editor scrollable
+	content_pane.add(button_panel, BorderLayout.SOUTH);
+
+	
+	// NOW WE CAN PREPARE THE ACTUAL CONTENT TO GO INTO THE XML EDITOR TEXTAREA
+	Document xmlDoc = null;
+	xmlDoc = XMLTools.parseXMLFile(config_file);
+
+	if(xmlDoc != null) { 
+
+	    // Save the collectionConfig.xml before loading it.
+	    String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
+	    XMLTools.writeXMLFile(this.config_file, xmlDoc, nonEscapingTagNames); //prepends proc instruction
+
+	    // now load the contents
+	    editor_msgarea.setBackground(Color.white);
+	    StringBuffer sb = new StringBuffer();
+	    XMLTools.xmlNodeToString(sb, xmlDoc.getDocumentElement(), true, "  ", 0);
+	    editor.setText(sb.toString());
+	    //editor.setText(elementToString(xmlDoc.getDocumentElement(), true)); // reading in file this way adds processing instruction, which the parser doesn't like.
+
+	} else { // parsing failed
+	    // so read in file as text and display as text and show the validation box in red
+
+	    // Won't re-save file that is already invalid before it's even been edited through this Editor
+	    System.err.println("*** Warning: Parsing error in file " + config_file + ".");
+	    System.err.println("*** Not saving ahead of editing.");
+
+	    String xml_str = Utility.readFile(config_file);
+
+	    // re-parse to get error msg to display in validation field
+
+	    // but first get rid of the preprocessing instruction, as this will fail parsing
+	    String processingInstruction = getProcessingInstruction(xml_str);
+	    xml_str = xml_str.replace(processingInstruction, "").trim(); // also trim newline at start
+
+	    if(!xml_str.equals("")) {
+
+		String msg = XMLTools.parse(xml_str); // re-parse
+		editor_msgarea.setBackground(Color.red);
+		editor_msgarea.setText(msg); // display the parsing error
+		save_button.setEnabled(false);
+
+		editor.setText(xml_str); // display the xml contents with error and all		
+		
+	    } else {
+		editor.setText("");
+		editor_msgarea.setText("Empty collection config file");
+	    }	    
+	}
+
+
+	// Final dialog setup & positioning.
+	setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose())
+	getRootPane().setDefaultButton(save_button);
+	Dimension screen_size = Configuration.screen_size;
+	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
+	
+    }
+
+    public void actionPerformed(ActionEvent e) {
+	
+	if(e.getSource() == cancel_button) {
+	    this.dispose(); // get rid of this dialog, we're done
+	} 
+	else if(e.getSource() == save_button) {
+	    // write out the XML to the collectionConfig.xml file and we're done.
+
+	    // already know the xml in the textarea is valid, else the save_button would have been inactive
+	    Document xml_file_doc = XMLTools.getDOM(editor.getText());
+	    // This code from FormatConversionDialog.dispose() 
+	    // saves the XML, prepending the processing instruction (<?xml ... ?>)
+	    String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
+	    XMLTools.writeXMLFile(this.config_file, xml_file_doc, nonEscapingTagNames);
+
+	    this.dispose(); // get rid of this dialog, we're done
+	}
+    }
+
+    
+   
+    // This method returns a proper processing instruction (starts with <?xml and ends with ?>)
+    // else the empty string is returned
+    public String getProcessingInstruction(String xmlStr) {
+	String processingInstruction = "";
+
+	xmlStr = xmlStr.trim();
+	if(xmlStr.startsWith("<?xml")) {
+	    int endIndex = xmlStr.indexOf("?>"); // end of processing instruction
+	    
+	    if(endIndex != -1) {
+		endIndex += 2;
+		processingInstruction = xmlStr.substring(0, endIndex);
+	    }
+	}	
+	return processingInstruction;
+    }
+
+
+    // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener
+    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)
+    {
+	
+	editor.undoButton.setEnabled(true);
+    }
+    
+    public void update()
+    {
+	
+	String xml_str = editor.getText();
+
+	// Processing instructions "<?xml...?>" are no longer displayed in the editor (but
+	// they are written out to the file on save) so we don't need to deal with them here.
+	
+	// can't parse with processing instruction <?xml...?>, so remove that
+	///String processingInstruction = getProcessingInstruction(xml_str);
+	///xml_str = xml_str.replace(processingInstruction, "");	
+	// now can parse the XML portion without the processing instruction,
+	// while the original XML remains unchanged in the editor area	
+	// If the processing instruction was incomplete, then xml_str would still
+	// include the incomplete processing instruction, and parsing below will catch the error.
+	
+	String msg = XMLTools.parse(xml_str);
+	editor_msgarea.setText(msg);
+	
+	if (msg.startsWith(XMLTools.WELLFORMED))
+	    {		   
+		editor_msgarea.setBackground(Color.white);
+		FormatPane.setPreviewButton(true);
+		save_button.setEnabled(true);
+	    }
+	else
+	    {		 
+		editor_msgarea.setBackground(Color.red);
+		save_button.setEnabled(false);
+	    }
+    }
+    
+}
Index: main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 30700)
+++ main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 30701)
@@ -257,4 +257,13 @@
 		DebugStream.println(cce.toString());
 	    }
+	}
+	else if(esrc == menu_bar.edit_config) {	   
+	    if(Gatherer.c_man.getCollection() != null) {
+		//JOptionPane.showMessageDialog(this, "Not yet implemented", "Edit config", JOptionPane.INFORMATION_MESSAGE);
+		ConfigFileEditor configEditor = new ConfigFileEditor();
+		configEditor.setVisible(true);
+		configEditor.setSize(900,700);
+		
+	    }	   
 	}
 
@@ -1133,4 +1142,14 @@
 		}
 	    }
+	    else if(e.getSource() == menu_bar.edit) { // someone clicked the toplevel Edit menu
+		// gray out the Edit Config File menu if there's no collection currently open.
+		if(Gatherer.c_man.getCollection() == null) {
+		    menu_bar.edit_config.setEnabled(false);
+		}
+		else { // don't forget to reenable the Edit ConfigXML menu item when applicable
+		    menu_bar.edit_config.setEnabled(true);
+		}
+	    }
+
 	}
     }
Index: main/trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java	(revision 30700)
+++ main/trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java	(revision 30701)
@@ -76,4 +76,5 @@
     public JMenuItem edit_cut;
     public JMenuItem edit_paste;
+    public JMenuItem edit_config;
     public JMenuItem help_general;
     public JMenuItem help_download;
@@ -162,5 +163,4 @@
 	edit.setText(Dictionary.get("Menu.Edit"));
 	edit.setComponentOrientation(Dictionary.getOrientation());
-
 	String modkey = "ctrl";
 	if(Utility.isMac()) { // on Mac, we now use the Apple key instead of Ctrl for GLI/GEMS edit shortcuts
@@ -182,8 +182,25 @@
 	edit_paste.addActionListener(Gatherer.g_man);
 	edit_paste.setComponentOrientation(Dictionary.getOrientation());
+
 	// Layout (edit menu)
 	edit.add(edit_cut);
 	edit.add(edit_copy);
 	edit.add(edit_paste);
+
+	// if GS3, then we have a menu item that allows editing of config files
+	if(Gatherer.GS3) {   
+
+	    edit_config = new JMenuItem(Dictionary.get("Menu.Edit_Config"));
+	    // handle the actual Edit > ColConfig.xml menu item
+	    edit_config.addActionListener(Gatherer.g_man);
+	    edit_config.setComponentOrientation(Dictionary.getOrientation());
+	    edit.add(edit_config);
+	    // The Edit menu itself now listens, in order to gray out the
+	    // Edit > collectionConfig.xml option when no collection is open
+	    // (JMenu doesn't work with ActionListener only with MenuListener, see
+	    // http://stackoverflow.com/questions/9862165/jmenu-actionlistener)
+	    edit.addMenuListener(menu_listener);
+
+	}
 
 	// Help menu
Index: main/trunk/gli/src/org/greenstone/gatherer/util/Utility.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/util/Utility.java	(revision 30700)
+++ main/trunk/gli/src/org/greenstone/gatherer/util/Utility.java	(revision 30701)
@@ -74,4 +74,36 @@
     static final public String PERL_EXECUTABLE_WINDOWS = "Perl.exe";
 
+
+    /** 
+     * Reads in a text file and returns the contents as a String
+     */
+    static public String readFile(File file) {
+	BufferedReader fin = null;
+	StringBuffer contents = new StringBuffer();
+	
+	try {
+	    fin = new BufferedReader(new FileReader(file));
+	    String line = null;
+	    while((line = fin.readLine()) != null) {
+		contents.append(line);
+		contents.append("\n");
+	    }
+	} catch(IOException e) {
+	    System.err.println("*** Could not read in file: " + file.toString());
+	    System.err.println("*** Exception occurred: " + e.getMessage());
+	} finally {
+	    try {
+		if(fin != null) {
+		    fin.close();
+		    fin = null;
+		}
+	    } catch(IOException ioe) {
+		fin = null;
+		
+	    }
+	}
+
+	return contents.toString();
+    }
 
     /**
Index: main/trunk/gli/src/org/greenstone/gatherer/util/XMLTools.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/util/XMLTools.java	(revision 30700)
+++ main/trunk/gli/src/org/greenstone/gatherer/util/XMLTools.java	(revision 30701)
@@ -13,4 +13,5 @@
 import java.io.IOException;
 import java.io.StringReader;
+import java.io.StringWriter; // for elementToString()
 
 // SAX
@@ -28,4 +29,10 @@
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
+// for elementToString():
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
 
 
@@ -523,4 +530,5 @@
 		if (xml_file.exists() == false)
 		{
+		    System.err.println("@@@ file " + xml_file + " does not exist.");
 			return null;
 		}
@@ -1224,3 +1232,38 @@
 		}
 	}
+
+
+    
+    // This method will convert an Element to a String too, like xmlNodeToString() above.
+    // But for a document root element (doc.getDocumentElement()), this method will additionally
+    // return its processing instruction line at the start (<?xml ... ?>).
+    // This method copied into GLI from src/java/org/greenstone/gsdl3/util/GSXML.java
+    public static String elementToString(Element e, boolean indent)
+    {
+	String str = "";
+	try
+	    {
+		TransformerFactory tf = TransformerFactory.newInstance();
+		Transformer trans = tf.newTransformer();
+		StringWriter sw = new StringWriter();
+		if (indent)
+		    {
+			trans.setOutputProperty(OutputKeys.INDENT, "yes");
+		    }
+		else
+		    {
+			trans.setOutputProperty(OutputKeys.INDENT, "no");
+		    }
+		trans.transform(new DOMSource(e), new StreamResult(sw));
+		str += sw.toString();
+	    }
+	catch (Exception ex)
+	    {
+		str += "Exception: couldn't write " + e + " to log";
+	    }
+	finally
+	    {
+		return str;
+	    }
+    }
 }
