Index: debug-testing/gli-ctrlC-issue/ArgumentControl.java
===================================================================
--- debug-testing/gli-ctrlC-issue/ArgumentControl.java	(revision 31736)
+++ debug-testing/gli-ctrlC-issue/ArgumentControl.java	(revision 31736)
@@ -0,0 +1,640 @@
+package org.greenstone.gatherer.cdm;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Iterator;
+
+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.GComboBox;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.greenstone.gatherer.metadata.MetadataElement;
+import org.greenstone.gatherer.metadata.MetadataSet;
+import org.greenstone.gatherer.metadata.MetadataSetManager;
+import org.greenstone.gatherer.metadata.MetadataTools;
+
+/** This class encapsulates all the technical difficulty of creating a specific control based on an Argument. */
+public class ArgumentControl
+  extends JPanel {
+
+  static protected Dimension LABEL_SIZE = new Dimension(175, 25);
+  static protected Dimension ROW_SIZE = new Dimension(400, 30);
+  /** The Argument this control will be based on. */
+  private Argument argument = null;
+
+  /** A checkbox to allow enabling or diabling of this Argument. */
+  private JCheckBox enabled = null;
+  /** Some form of editor component, such as a JComboBox or JTextField, used to set parameters to an Argument. */
+  private JComponent value_control = null;
+
+  /** Constructor.
+   */
+  public ArgumentControl(Argument argument, boolean is_enabled, String preset_value) {
+    this.setComponentOrientation(Dictionary.getOrientation());
+    this.argument = argument;
+
+    String tip = "<html>" + argument.getName()+": "+argument.getDescription() + "</html>";
+    tip = Utility.formatHTMLWidth(tip, 80);
+
+    setBackground(Configuration.getColor("coloring.collection_tree_background", false));
+    setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
+    setLayout(new BorderLayout());
+    setPreferredSize(ROW_SIZE);
+		
+    // display the name set in the disp option if there is one
+    // otherwise display name option value instead
+    String dispName = argument.getDisplayName();
+    if(dispName.equals("")){
+      dispName = argument.getName();
+    }
+
+    if (argument.isRequired()) {		
+      //JLabel label = new JLabel(argument.getName());
+      JLabel label = new JLabel(dispName);
+      label.setComponentOrientation(Dictionary.getOrientation());
+      label.setOpaque(false);
+      label.setPreferredSize(LABEL_SIZE);
+      label.setToolTipText(tip);
+      add(label, BorderLayout.LINE_START);
+    } else {
+      //enabled = new JCheckBox(argument.getName());
+      enabled = new JCheckBox(dispName);
+      enabled.setComponentOrientation(Dictionary.getOrientation());
+      enabled.setOpaque(false);
+      enabled.setPreferredSize(LABEL_SIZE);
+      enabled.setToolTipText(tip);
+      add(enabled, BorderLayout.LINE_START);
+    }
+
+    String initial_value;
+    if (preset_value != null && !preset_value.equals("")) {
+      initial_value = preset_value;
+    }
+    else {
+      initial_value = argument.getValue();
+    }
+    if (initial_value == null || initial_value.equals("")) {
+      initial_value = argument.getDefaultValue();
+    }
+    if (initial_value == null) {
+      initial_value = "";
+    }
+
+    switch(argument.getType()) {
+    case Argument.ENUM_STRING:
+    case Argument.ENUM:
+      ArrayList option_list = argument.getOptions();
+      boolean editable = false;
+      if (argument.getType() == Argument.ENUM_STRING) {
+	editable = true;
+      }
+      value_control = new GComboBox(option_list.toArray(), editable, false);
+      value_control.setComponentOrientation(Dictionary.getOrientation());
+			
+      boolean found = selectValue((JComboBox)value_control, initial_value); // also sets the tooltip
+      if (!found && argument.getType() == Argument.ENUM_STRING) {
+	// Its a custom item
+	((JComboBox) value_control).addItem(initial_value);
+	((JComboBox) value_control).setSelectedItem(initial_value);
+      }
+
+      ((JComboBox)value_control).addActionListener(new ToolTipUpdater());
+      break;
+
+    case Argument.FLAG:
+      // Only need the check box.
+      break;
+
+    case Argument.INTEGER:
+      // Build a spinner
+      int initial_int=0;
+      // If there was an original value, set it.
+      try {
+	initial_int = Integer.parseInt(initial_value);
+      } catch (Exception error) {
+	DebugStream.println("ArgumentControl Error: "+error);
+      }
+      if (initial_int < argument.getMinimum()) {
+	initial_int = argument.getMinimum();
+      } else if (initial_int > argument.getMaximum()) {
+	initial_int = argument.getMaximum();
+      }
+
+      JSpinner spinner = new JSpinner(new SpinnerNumberModel(initial_int, argument.getMinimum(), argument.getMaximum(), 1));
+      spinner.setComponentOrientation(Dictionary.getOrientation());
+      // And remember it
+      value_control = spinner;
+      break;
+
+    case Argument.REGEXP:
+    case Argument.STRING:
+      value_control = new JTextField(initial_value);
+      value_control.setComponentOrientation(Dictionary.getOrientation());
+      break;
+
+    case Argument.LANGUAGE:
+      value_control = new GComboBox(CollectionDesignManager.language_manager.getLanguageCodes().toArray(), false);
+      value_control.setComponentOrientation(Dictionary.getOrientation());
+      // we want to display the language name not the code
+      ((JComboBox)value_control).setRenderer(new LanguageListCellRenderer());
+      // Now ensure we have the existing value or default value selected if either exist in our known languages
+      String lang_name = CollectionDesignManager.language_manager.getLanguageName(initial_value);
+      if (lang_name != null) {
+	((JComboBox)value_control).setSelectedItem(initial_value);
+      }
+      break;
+
+    case Argument.METADATA:
+      value_control = new GComboBox(MetadataSetManager.getEveryMetadataSetElement(), false);
+      value_control.setComponentOrientation(Dictionary.getOrientation());
+      // Editable for advanced modes (allows things like dc.Title,ex.Title)
+      if (Configuration.getMode() > Configuration.ASSISTANT_MODE) {
+	((JComboBox) value_control).setEditable(true);
+      }
+      // Now ensure we have the existing value or default value selected if either exist.
+      String existing_value = preset_value;
+      if (existing_value == null || existing_value.equals("")) {
+	existing_value = argument.getValue();
+      }
+      if (existing_value == null || existing_value.equals("")) {
+	// try default value
+	String default_value = argument.getDefaultValue();
+	if (default_value != null) {
+	  // if no namespace for default value, add ex.
+	  // won't work if we want to set a non-metadata value
+	  if (MetadataTools.getMetadataSetNamespace(default_value).equals("")) {
+	    default_value = StaticStrings.EXTRACTED_NAMESPACE+default_value;
+	  }
+	  existing_value = default_value;
+	}
+      }
+	
+      if (existing_value != null && !existing_value.equals("")) {
+	
+	found = selectValue((JComboBox) value_control, existing_value);
+	// It's possible that this is a custom value and so doesn't exist in the combobox
+	if (!found) {
+	  // If so, add it then select it
+	  ((JComboBox) value_control).addItem(existing_value);
+	  ((JComboBox) value_control).setSelectedItem(existing_value);
+	}
+      }
+      break;
+
+    case Argument.METADATA_SET_NAMESPACE:
+      value_control = new JComboBox();
+      value_control.setComponentOrientation(Dictionary.getOrientation());
+      // !! Hack for exploding metadata databases: add the (empty) exploded metadata set
+      File exploded_metadata_set_file = new File(Gatherer.getGLIMetadataDirectoryPath() + "exp" + StaticStrings.METADATA_SET_EXTENSION);
+      MetadataSet exploded_metadata_set = new MetadataSet(exploded_metadata_set_file);
+      Gatherer.c_man.importMetadataSet(exploded_metadata_set);
+
+      // All the loaded metadata sets except the extracted metadata set are applicable
+      ArrayList metadata_sets = MetadataSetManager.getMetadataSets();
+      for (int i = metadata_sets.size() - 1; i >= 0; i--) {
+	MetadataSet metadata_set = (MetadataSet) metadata_sets.get(i);
+	if (!(metadata_set.getNamespace().equals(MetadataSetManager.EXTRACTED_METADATA_NAMESPACE))) {
+	  ((JComboBox)value_control).addItem(metadata_set);
+	}
+      }
+
+      selectValue((JComboBox) value_control, initial_value);
+
+    } // end of switch
+
+    // Enable or disable as necessary.
+    if(argument.isRequired() || argument.isAssigned() || is_enabled) {
+      if (enabled != null) {
+	enabled.setSelected(true);
+      }
+      if(value_control != null) {
+	value_control.setOpaque(true);
+	value_control.setBackground(Color.white);
+	value_control.setEnabled(true);
+	if(value_control instanceof JSpinner) {
+	  // Set enabled
+	  JComponent c = ((JSpinner)value_control).getEditor();
+	  if ( c instanceof JSpinner.DefaultEditor ) {
+	    JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) c;
+	    JFormattedTextField field = editor.getTextField();
+	    field.setEditable(true);
+	    field.setBackground(Color.white);
+	  }
+	}
+      }
+    }
+    else {
+      if (enabled != null) {
+	enabled.setSelected(false);
+      }
+      if(value_control != null) {
+	value_control.setOpaque(true);
+	value_control.setBackground(Color.lightGray);
+	value_control.setEnabled(false);
+	if(value_control instanceof JSpinner) {
+	  // Set enabled
+	  JComponent c = ((JSpinner)value_control).getEditor();
+	  if ( c instanceof JSpinner.DefaultEditor ) {
+	    JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) c;
+	    JFormattedTextField field = editor.getTextField();
+	    field.setEditable(false);
+	    field.setBackground(Color.lightGray);
+	  }
+	}
+      }
+    }
+
+    // Listener
+    if(value_control != null) {
+      if (argument.getType() != Argument.ENUM && argument.getType() != Argument.ENUM_STRING) {
+	// enums have already set tooltips based on option value
+	value_control.setToolTipText(tip);
+      }
+      add(value_control, BorderLayout.CENTER);
+      if (!argument.isRequired()) {
+	//enabled.addActionListener(new EnabledListener(value_control));
+      }
+    }
+  }
+
+  public Argument getArgument() {
+    return argument;
+  }
+
+  public String getArgumentName() {
+    return argument.getName();
+  }
+
+  public String getValue() {
+    if(value_control == null) {
+      return null;
+    }
+    if (value_control instanceof JSpinner) {
+      return ((JSpinner)value_control).getValue().toString();
+    }
+    if(value_control instanceof JComboBox) {
+      Object selected_item = ((JComboBox)value_control).getSelectedItem();
+      if (selected_item != null) {
+	if (argument.getType() == Argument.METADATA_SET_NAMESPACE) {
+	  return ((MetadataSet) selected_item).getNamespace();
+	}
+	if (selected_item instanceof Argument.ArgumentOption) {
+	  return ((Argument.ArgumentOption)selected_item).name;
+	}
+	if (selected_item instanceof MetadataElement) {
+	  return ((MetadataElement) selected_item).getFullName();
+	}
+	return selected_item.toString();
+      }    
+      return null;
+    }
+    if(value_control instanceof JTextField) {
+      return ((JTextField)value_control).getText();
+    }
+    return null;
+  }
+  /** Retrieve the control used for storing values.
+   * @return JComponent
+   */
+  public JComponent getValueControl() {
+    return value_control;
+  }
+
+  public boolean isEnabled() {
+    if (enabled == null) {
+      return true; // always enabled
+    } 
+    return enabled.isSelected();
+  }
+
+  /// START DEBUG
+  public String getType() {	  
+	  
+	if(value_control == null) {
+			return "null";
+    }
+	
+	String cName = "";
+    if (value_control instanceof JSpinner) {
+      cName = "JSpinner";
+    }
+    else if(value_control instanceof JComboBox) {
+      Object selected_item = ((JComboBox)value_control).getSelectedItem();
+	  cName = "JComboBox";
+      if (selected_item != null) {
+		if (argument.getType() == Argument.METADATA_SET_NAMESPACE) {
+		  cName += " - MetadataSet";
+		}
+		if (selected_item instanceof Argument.ArgumentOption) {
+		  cName += " - Argument.ArgumentOption";
+		}
+		if (selected_item instanceof MetadataElement) {
+		  cName += " - MetadataElement";
+		}
+		cName += " - String";
+      }    
+      cName = "";
+    }
+    else if(value_control instanceof JTextField) {
+      cName = "JTextField";
+    }
+	return cName;
+	  
+  }
+  /// END DEBUG
+
+  /** Identifies this control by returning the name of the Argument it is based on.
+   * @return The name of the Argument as a <strong>String</strong>.
+   * @see org.greenstone.gatherer.cdm.Argument
+   */
+  public String toString() {
+    return argument.getName();
+  }
+  /** Updates the enwrapped Argument using the values provided by the controls.
+   * @return <i>true</i> if the update was successful, <i>false</i> otherwise.
+   * @see org.greenstone.gatherer.cdm.ArgumentConfiguration.Argument.ArgumentOption
+   * @see org.greenstone.gatherer.cdm.Language
+   */
+  public boolean updateArgument() {
+    if(argument.isRequired() || enabled.isSelected() ) {
+      argument.setAssigned(false);
+      String result = null;
+      switch(argument.getType()) {
+      case Argument.ENUM:
+	Argument.ArgumentOption option = (Argument.ArgumentOption)((JComboBox)value_control).getSelectedItem();
+	// its impossible not to choose an entry
+	argument.setValue(option.name);
+	argument.setAssigned(true);
+	return true;
+      case Argument.ENUM_STRING:
+	Object new_value_raw = ((JComboBox)value_control).getSelectedItem();
+	if (new_value_raw instanceof Argument.ArgumentOption) {
+	  argument.setValue(((Argument.ArgumentOption)new_value_raw).name);
+	} else {
+	  // have entered a new string
+	  String new_value = new_value_raw.toString();
+	  if (new_value.length() > 0) {
+	    argument.setValue(new_value);
+	  }
+	  else {
+	    String args[] = new String[1];
+	    args[0] = argument.getName();
+	    if(argument.isRequired()) {
+	      JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.Required_Argument", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	    }
+	    // They've left the field blank
+	    else {
+	      JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.No_Value", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	      argument.setValue(null);
+	    }
+	    args = null;
+	    return false;
+	  }
+	}
+	argument.setAssigned(true);
+	return true;
+      case Argument.FLAG:
+	// Should have already been handled above.
+	argument.setAssigned(true);
+	return true;
+      case Argument.INTEGER:
+	result = ((JSpinner)value_control).getValue().toString();
+	if(result.length() > 0) {
+	  // Test if the value entered is a valid int.
+	  try {
+	    int x = Integer.parseInt(result);
+	  }
+	  catch(NumberFormatException nfe) {
+	    String args[] = new String[2];
+	    args[0] = argument.getName();
+	    args[1] = result;
+	    JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.Bad_Integer", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	    args = null;
+	    return false;
+	  }
+	  argument.setValue(result);
+	}
+	else {
+	  String args[] = new String[1];
+	  args[0] = argument.getName();
+	  if(argument.isRequired()) {
+	    JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.Required_Argument", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	  }
+	  // They've left the field blank
+	  else {
+	    JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.No_Value", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	    argument.setValue(null);
+	  }
+	  args = null;
+	  return false;
+	}
+	argument.setAssigned(true);
+	return true;
+      case Argument.LANGUAGE:
+	String language = (((JComboBox)value_control).getSelectedItem()).toString();
+	argument.setValue(language);
+	// Kinda lucked out here. Its impossible not to choose an entry from these comboboxes as they are restricted.
+	argument.setAssigned(true);
+	return true;
+      case Argument.METADATA:
+	  String meta_value = (((JComboBox)value_control).getSelectedItem()).toString();
+	  if(meta_value.length() > 0) {
+	      
+	      argument.setValue(meta_value);
+	  }
+	  else {
+	    String args[] = new String[1];
+	    args[0] = argument.getName();
+	    if(argument.isRequired()) {
+	      JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.Required_Argument", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	    }
+	    // They've left the field blank
+	    else {
+	      JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.No_Value", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	      argument.setValue(null);
+	    }
+	    args = null;
+	    return false;
+	  }
+      
+	argument.setAssigned(true);
+	return true;
+      case Argument.REGEXP:
+      case Argument.STRING:
+	result = ((JTextField)value_control).getText();
+	if(result.length() > 0) {
+	  argument.setValue(result);
+	}
+	else {
+	  String args[] = new String[1];
+	  args[0] = argument.getName();
+	  if(argument.isRequired()) {
+	    JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.Required_Argument", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	  }
+	  // They've left the field blank
+	  else {
+	    JOptionPane.showMessageDialog(this, Dictionary.get("CDM.ArgumentConfiguration.No_Value", args), Dictionary.get("CDM.ArgumentConfiguration.Error_Title"), JOptionPane.ERROR_MESSAGE);
+	    argument.setValue(null);
+	  }
+	  args = null;
+	  return false;
+	}
+	argument.setAssigned(true);
+	return true;
+      }
+      return false;
+    }
+    else {
+      argument.setAssigned(false);
+      return true;
+    }
+  }
+
+
+  public boolean updateArgument(boolean checkRequired) {
+
+
+    if (checkRequired){
+      return updateArgument();
+    }
+    else{
+      if (argument.getType() == Argument.STRING){
+	String result = ((JTextField)value_control).getText();
+
+	if(result.length() > 0) {
+	  argument.setValue(result);
+	  argument.setAssigned(true);                    
+	}
+      }	
+    }
+
+    return true;
+
+  }
+
+
+
+  /** Method to ensure that a certain value is selected, if it exists within that combobox to begin with.
+   * @param combobox The <strong>JComboBox</strong> whose selection we are trying to preset.
+   * @param target The desired value of the selection as a <strong>String</strong>.
+   * @return true if the item was found and selected, false otherwise
+   * @see org.greenstone.gatherer.cdm.ArgumentConfiguration.Argument.ArgumentOption
+   */
+  public static boolean selectValue(JComboBox combobox, String target)
+  {
+    for (int i = 0; i < combobox.getItemCount(); i++) {
+      Object object = combobox.getItemAt(i);
+
+      if (object instanceof Argument.ArgumentOption) {
+	Argument.ArgumentOption opt = (Argument.ArgumentOption) object;
+	if (opt.name.startsWith(target)) {
+	  combobox.setSelectedIndex(i);
+	  combobox.setToolTipText(opt.getToolTip());
+	  return true;
+	}
+      }
+      else if (object instanceof MetadataElement) {
+	if(((MetadataElement)object).getFullName().equals(target)) {
+	  combobox.setSelectedIndex(i);
+	  return true;
+	}
+      }
+      else if (object.toString().equals(target)) {
+	combobox.setSelectedIndex(i);
+	return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  /** Forces the control into an 'enabled' mode. */
+  public void setEnabled() {
+    enabled.setSelected(true);
+  }
+  /** Explicitly sets the value of a JTextField type control to the given String.
+   * @param value_str The new value of the control as a <strong>String</strong>.
+   */
+  public void setValue(String value_str) {
+    ((JTextField)value_control).setText(value_str);
+  }
+
+  /** Listens for actions upon the enable checkbox, and if detected enables or disables control appropriately. */
+  private class EnabledListener
+    implements ActionListener {
+    /** An editor component, such as a JComboBox or JTextField, that might have its enabled state changed by this listener. */
+    private JComponent target = null;
+
+    /** Constructor. */
+    public EnabledListener(JComponent target) {
+      this.target = target;
+    }
+
+    /** Any implementation of ActionListener must include this method so that we can be informed when an action has been performed on or registered check box, prompting us to change the state of the other controls as per the users request.
+     * @param event An <strong>ActionEvent</strong> containing information about the click.
+     */
+    public void actionPerformed(ActionEvent event) {
+      if (this.target == null) {
+	return;
+      }
+      JCheckBox source = (JCheckBox)event.getSource();
+      if(source.isSelected()) {
+	target.setBackground(Color.white);
+	target.setEnabled(true);
+      }
+      else {
+	target.setBackground(Color.lightGray);
+	target.setEnabled(false);
+      }
+      // Special case of stupid JSpinners who don't let their backgrounds change properly.
+      if(target instanceof JSpinner) {
+	JSpinner spinner = (JSpinner) target;
+	JComponent c = spinner.getEditor();
+	if ( c instanceof JSpinner.DefaultEditor ) {
+	  JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) c;
+	  JFormattedTextField field = editor.getTextField();
+	  field.setEditable(source.isSelected());
+	  if(source.isSelected()) {
+	    field.setBackground(Color.white);
+	  }
+	  else {
+	    field.setBackground(Color.lightGray);
+	  }
+	}
+      }
+    }
+  }
+
+
+  /** Listener that sets the tooltip associated to a combobox to the tooltip relevant to the selected item. */
+  private class ToolTipUpdater
+    implements ActionListener {
+    /** Any implementation of an ActionListener must include this method so that we can be informed when the selection in a combobox has changed and update the tooltip accordingly.
+     * @param event An <strong>ActionEvent</strong> containing information about the action that fired this call.
+     */
+    public void actionPerformed(ActionEvent event) {
+      JComboBox source = (JComboBox)event.getSource();
+      Object object = source.getSelectedItem();
+      if(object instanceof Argument.ArgumentOption) {
+	Argument.ArgumentOption opt = (Argument.ArgumentOption)object;
+	if(opt != null) {
+	  source.setToolTipText(opt.getToolTip());
+	}
+	else {
+	  source.setToolTipText(StaticStrings.EMPTY_STR);
+	}
+      }
+    }
+  }
+}
Index: debug-testing/gli-ctrlC-issue/DownloadPane.java
===================================================================
--- debug-testing/gli-ctrlC-issue/DownloadPane.java	(revision 31736)
+++ debug-testing/gli-ctrlC-issue/DownloadPane.java	(revision 31736)
@@ -0,0 +1,948 @@
+/**
+ *#########################################################################
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * <BR><BR>
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * <BR><BR>
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * 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 java.awt.event.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.tree.*;
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.file.WorkspaceTree;
+import org.greenstone.gatherer.greenstone.LocalGreenstone;
+import org.greenstone.gatherer.util.SafeProcess;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.greenstone.gatherer.download.Download;
+import org.greenstone.gatherer.download.DownloadScrollPane;
+import org.greenstone.gatherer.download.ServerInfoDialog;
+import org.greenstone.gatherer.util.XMLTools;
+import org.greenstone.gatherer.cdm.*;
+import org.greenstone.gatherer.gui.*;
+import org.w3c.dom.*;
+import org.xml.sax.*;
+import org.greenstone.gatherer.GAuthenticator;
+
+/**
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.1
+ */
+public class DownloadPane
+    extends JPanel {
+
+    static final private Dimension LABEL_SIZE = new Dimension(225, 25);
+    static final private Dimension TREE_SIZE = new Dimension(150, 500);
+    static final private String CONTENTS[] = { "DOWNLOAD.MODE.WebDownload", "DOWNLOAD.MODE.MediaWikiDownload", "DOWNLOAD.MODE.OAIDownload", "DOWNLOAD.MODE.ZDownload" , "DOWNLOAD.MODE.SRWDownload"};
+    //private String CONTENTS[] = null;
+
+    private boolean download_button_enabled = false;
+    private boolean ready = false;
+
+    private JPanel options_pane;
+    // TODO should use Vector to store all loaded downloads!!
+
+    private DesignTree tree;
+    private HashMap download_map;
+    private ServerInfoDialog server_info;
+    private JScrollPane list_scroll;
+    private DownloadScrollPane getter;
+    private String mode = null;
+    private TreePath previous_path;
+    private String proxy_url = "";
+	    
+    /** Main System code */
+    public DownloadPane() {
+	super();
+        JScrollPane scrol_tmp;
+        this.setComponentOrientation(Dictionary.getOrientation());
+	// TODO: Download the WDownload and the download panel fixed!!
+	getter = new DownloadScrollPane();
+        getter.start();
+	list_scroll = getter.getDownloadJobList();      
+        list_scroll.setComponentOrientation(Dictionary.getOrientation());
+
+	// TODO should use Vector to store all loaded downloads!!
+	String lang = Configuration.getLanguage();
+	download_map = new HashMap();
+
+// START DEBUG - http://trac.greenstone.org/changeset/29972/main/trunk/gli/src/org/greenstone/gatherer/gui/DownloadPane.java
+download_map.put("Web", loadDownload("WebDownload",lang));  // just test one for now
+/*download_map.put("MediaWiki", loadDownload("MediaWikiDownload",lang)); 
+download_map.put("OAI", loadDownload("OAIDownload",lang)); 
+download_map.put("Z3950", loadDownload("Z3950Download",lang)); 
+download_map.put("SRW", loadDownload("SRWDownload",lang)); 	
+*/
+// END DEBUG	
+
+	// run downloadinfo.pl -describeall, load the downloaders into the download_map, 
+	// and get back their list of names, which are of the form "<downloader>Download". 
+	// Store these names in the CONTENTS[] array as "DOWNLOAD.MODE.<downloader>Download", 
+	// with z3950 as a minor exception: DOWNLOAD.MODE.ZDownload.
+	/*ArrayList<String> downloaderNamesList = loadDownloadersInfo(lang);	
+	int size = downloaderNamesList.size();
+	CONTENTS = new String[size];
+	for(int i = 0; i < size; i++) {
+	    String downloadName = downloaderNamesList.get(i); // e.g. "WebDownload"	    
+	    CONTENTS[i] = "DOWNLOAD.MODE."+downloadName.replace("3950", ""); // A special case is Z3950Download,
+	                                 // which has to be stored in CONTENTS array as DOWNLOAD.MODE.ZDownload
+	}*/
+
+	// Creation
+	tree = new DesignTree();
+        tree.setComponentOrientation(Dictionary.getOrientation());
+	options_pane = new JPanel();
+        options_pane.setComponentOrientation(Dictionary.getOrientation());
+	
+	JButton clear_cache_button = new GLIButton(Dictionary.get("Mirroring.ClearCache"), Dictionary.get("Mirroring.ClearCache_Tooltip"));
+	clear_cache_button.setEnabled(true);
+	clear_cache_button.setMnemonic(KeyEvent.VK_C);
+
+	JButton download_button = new GLIButton(Dictionary.get("Mirroring.Download"), Dictionary.get("Mirroring.Download_Tooltip"));
+	download_button.setEnabled(true);
+	download_button.setMnemonic(KeyEvent.VK_D);
+
+	JButton information_button =  new GLIButton(Dictionary.get("Download.ServerInformation"), Dictionary.get("Download.ServerInformation_Tooltip"));
+	information_button.setEnabled(true);
+	information_button.setMnemonic(KeyEvent.VK_S);
+
+
+       	JButton preferences_button =  new GLIButton(Dictionary.get("Mirroring.Preferences"), Dictionary.get("Mirroring.Preferences_Tooltip"));
+	preferences_button.setEnabled(true);
+	preferences_button.setMnemonic(KeyEvent.VK_P);
+
+	// Connect
+	clear_cache_button.addActionListener(new ClearCacheListener());
+	download_button.addActionListener(new DownloadButtonListener());
+	preferences_button.addActionListener(new PreferencesButtonActionListener());
+	information_button.addActionListener(new InformationButtonActionListener());
+	tree.addTreeSelectionListener(new TreeListener());
+
+	// Add to Panel
+	JPanel button_pane = new JPanel();
+        button_pane.setComponentOrientation(Dictionary.getOrientation());
+	button_pane.setLayout(new GridLayout(1,4)); // GridLayout so button pane resizes with window-width
+	button_pane.setBorder(BorderFactory.createEtchedBorder());
+	button_pane.add(clear_cache_button);
+	button_pane.add(download_button);
+	button_pane.add(information_button);
+	button_pane.add(preferences_button);
+
+	JPanel tree_pane = new JPanel();
+        tree_pane.setComponentOrientation(Dictionary.getOrientation());
+	tree_pane.setLayout(new BorderLayout());
+        scrol_tmp = new JScrollPane(tree);
+        scrol_tmp.setComponentOrientation(Dictionary.getOrientation());
+       	tree_pane.add(scrol_tmp, BorderLayout.CENTER);
+	tree_pane.setPreferredSize(TREE_SIZE);
+ 
+
+	Color colour_two = Configuration.getColor("coloring.collection_tree_background", false);
+	options_pane.setBackground(colour_two);
+	options_pane.setBorder(BorderFactory.createEtchedBorder());
+
+
+	JScrollPane options_scroll_pane = new JScrollPane(options_pane);
+        options_scroll_pane.setComponentOrientation(Dictionary.getOrientation());
+	JSplitPane mode_pane = new JSplitPane();
+        mode_pane.setComponentOrientation(Dictionary.getOrientation());
+	mode_pane.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+        if (Dictionary.getOrientation().isLeftToRight()){
+            mode_pane.add(tree_pane,JSplitPane.LEFT);
+            mode_pane.add(options_scroll_pane,JSplitPane.RIGHT); 
+            mode_pane.setDividerLocation(TREE_SIZE.width);
+        }else{
+            mode_pane.add(tree_pane,JSplitPane.RIGHT);
+            mode_pane.add(options_scroll_pane,JSplitPane.LEFT);
+            mode_pane.setDividerLocation(1-TREE_SIZE.width);
+        }
+		//mode_pane.add(new JTextField("Pinky"));
+   
+	JPanel edit_pane = new JPanel();
+        edit_pane.setComponentOrientation(Dictionary.getOrientation());
+	edit_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(2,0,0,0), BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Download Setting"), BorderFactory.createEmptyBorder(2,2,2,2))));
+	edit_pane.setLayout(new BorderLayout());
+	edit_pane.add(mode_pane,BorderLayout.CENTER);
+	edit_pane.add(button_pane,BorderLayout.PAGE_END);
+	
+	// Add to "this"
+	setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	setLayout(new GridLayout(2,1));
+	add(edit_pane);
+	add(list_scroll);
+
+	//set the mode to the first downloader in the list
+	mode = convertCONTENTStoMode(CONTENTS[0]); // e.g. Web
+
+	generateOptions(options_pane,(Download)download_map.get(mode));
+	
+	
+	previous_path = tree.getSelectionPath();
+	
+    }
+
+    /** System Utilities */
+    public void modeChanged(int gli_mode) {
+	// do nothing at this stage - should we be renewing download options??
+    }
+
+    private void addHeader(String name, Color color, JPanel target_pane) {
+	JPanel header = new JPanel();
+        header.setComponentOrientation(Dictionary.getOrientation());
+	header.setBackground(color);
+	JPanel inner_pane = new JPanel();
+        inner_pane.setComponentOrientation(Dictionary.getOrientation());
+	inner_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,5,5,5), BorderFactory.createRaisedBevelBorder()));
+	inner_pane.setBackground(color);
+	JLabel header_label = new JLabel("<html><strong>" + name + "</strong></html>");
+        header_label.setComponentOrientation(Dictionary.getOrientation());
+	header_label.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
+	header_label.setHorizontalAlignment(JLabel.CENTER);
+	header_label.setOpaque(true);
+
+	// Layout
+	inner_pane.setLayout(new BorderLayout());
+	inner_pane.add(header_label, BorderLayout.CENTER);
+
+	header.setLayout(new BorderLayout());
+	header.add(inner_pane, BorderLayout.CENTER);
+	target_pane.add(header);
+    }
+
+    /** Supporting Functions */
+    private ArrayList<String> loadDownloadersInfo(String lang) {
+	Document document = null;
+	
+	try {
+	    if (Gatherer.isGsdlRemote) {
+		String output = Gatherer.remoteGreenstoneServer.getScriptOptions("downloadinfo.pl", "&describeall");
+		Reader reader = new StringReader(output);
+		document = XMLTools.parseXML(reader);
+	    }
+	    else {
+		ArrayList args_list = new ArrayList();
+		String args[] = null;
+		if(Configuration.perl_path != null) {
+		    args_list.add(Configuration.perl_path);
+		} else if(Utility.isWindows()) {
+		    args_list.add("Perl.exe");
+		} else {
+		    args_list.add("perl");
+		}
+		args_list.add("-S");
+		args_list.add(LocalGreenstone.getBinScriptDirectoryPath()+"downloadinfo.pl");
+		args_list.add("-describeall");
+		args_list.add("-xml");
+		args_list.add("-language");
+		args_list.add(lang);		
+		
+		// Create the process.
+		args = (String []) args_list.toArray(new String[0]);
+
+		SafeProcess process = new SafeProcess(args);
+		DebugStream.println("Getting Download Info: "+args_list);
+
+		// run the SafeProcess
+		int exitVal = process.runProcess();
+		if(exitVal != 0) {
+		    throw new Exception("*** Error running Download Info process, process exited with: "
+					+ exitVal);
+		}		
+		
+		// get the result and process it.
+		// This time we expect XML to have come out of the process std error stream.
+		String errStreamOutput = process.getStdError();
+		///System.err.println("*********\nDownload Pane data, got:\n" + errStreamOutput + "\n**********\n");
+		StringReader xmlStrReader = new StringReader(errStreamOutput);
+		document = XMLTools.parseXML(xmlStrReader);
+		xmlStrReader.close();
+		
+	    }
+
+            
+	}
+	catch (Exception error) {
+	    System.err.println("Failed when trying to parse downloadinfo.pl -describeall");
+	    error.printStackTrace();
+	}
+
+	if(document != null) {
+	    return parseXML(document.getDocumentElement());
+	}
+
+	return null;
+    }
+
+// START DEBUG	- http://trac.greenstone.org/browser/main/trunk/gli/src/org/greenstone/gatherer/gui/DownloadPane.java?rev=29972
+	private Download loadDownload(String download_name, String lang) {
+	Document document = null;
+	Process process = null;
+	try {
+	    if (Gatherer.isGsdlRemote) {
+		String output = Gatherer.remoteGreenstoneServer.getScriptOptions("downloadinfo.pl", "&downloader="+download_name); //OR: "&listall";
+		Reader reader = new StringReader(output);
+		document = XMLTools.parseXML(reader);
+	    }
+	    else {
+		ArrayList args_list = new ArrayList();
+		String args[] = null;
+		if(Configuration.perl_path != null) {
+		    args_list.add(Configuration.perl_path);
+		} else if(Utility.isWindows()) {
+		    args_list.add("Perl.exe");
+		} else {
+		    args_list.add("perl");
+		}
+		args_list.add("-S");
+		args_list.add(LocalGreenstone.getBinScriptDirectoryPath()+"downloadinfo.pl");
+		args_list.add("-xml");
+		args_list.add("-language");
+		args_list.add(lang);
+		args_list.add(download_name);
+		
+		// Create the process.
+		args = (String []) args_list.toArray(new String[0]);
+		Runtime runtime = Runtime.getRuntime();
+		DebugStream.println("Getting Download Info: "+args_list);
+		System.err.println("Getting Download Info: "+args_list);
+		/*process = runtime.exec(args);
+		
+		InputStream input_stream = process.getErrorStream();
+		document = XMLTools.parseXML(input_stream);
+		*/
+		
+String outputstr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<DownloadInfo>\n  <Name>WebDownload</Name>\n  <Desc>A module for downloading from the Internet via HTTP or FTP</Desc>\n  <Abstract>no</Abstract>\n  <Inherits>yes</Inherits>\n  <Arguments>\n    <Option>\n      <Name>url</Name>\n      <DisplayName>Source URL</DisplayName>\n      <Desc>Source URL. In case of http redirects, this value may change</Desc>\n      <Type>string</Type>\n      <Required>yes</Required>\n    </Option>\n    <Option>\n      <Name>depth</Name>\n      <DisplayName>Download Depth</DisplayName>\n      <Desc>How many hyperlinks deep to go when downloading</Desc>\n      <Type>int</Type>\n      <Required>no</Required>\n      <Range>0,</Range>\n      <Default>0</Default>\n    </Option>\n    <Option>\n      <Name>below</Name>\n      <DisplayName>Only files below URL</DisplayName>\n      <Desc>Only mirror files below this URL</Desc>\n      <Type>flag</Type>\n      <Required>no</Required>\n    </Option>\n    <Option>\n      <Name>within</Name>\n      <DisplayName>Only files within site</DisplayName>\n      <Desc>Only mirror files within the same site</Desc>\n      <Type>flag</Type>\n      <Required>no</Required>\n    </Option>\n    <Option>\n      <Name>html_only</Name>\n      <DisplayName>Only HTML files</DisplayName>\n      <Desc>Download only HTML files, and ignore associated files e.g images and stylesheets</Desc>\n      <Type>flag</Type>\n      <Required>no</Required>\n    </Option>\n  </Arguments>\n<DownloadInfo>\n  <Name>WgetDownload</Name>\n  <Desc> Base class that handles calls to wget</Desc>\n  <Abstract>yes</Abstract>\n  <Inherits>yes</Inherits>\n  <Arguments>\n    <Option>\n      <Name>proxy_on</Name>\n      <Desc>Proxy on</Desc>\n      <Type>flag</Type>\n      <Required>no</Required>\n      <HiddenGLI>yes</HiddenGLI>\n    </Option>\n    <Option>\n      <Name>proxy_host</Name>\n      <Desc>Proxy host</Desc>\n      <Type>string</Type>\n      <Required>no</Required>\n      <HiddenGLI>yes</HiddenGLI>\n    </Option>\n    <Option>\n      <Name>proxy_port</Name>\n      <Desc>Proxy port</Desc>\n      <Type>string</Type>\n      <Required>no</Required>\n      <HiddenGLI>yes</HiddenGLI>\n    </Option>\n    <Option>\n      <Name>user_name</Name>\n      <Desc>User name</Desc>\n      <Type>string</Type>\n      <Required>no</Required>\n      <HiddenGLI>yes</HiddenGLI>\n    </Option>\n    <Option>\n      <Name>user_password</Name>\n      <Desc>User password</Desc>\n      <Type>string</Type>\n      <Required>no</Required>\n      <HiddenGLI>yes</HiddenGLI>\n    </Option>\n  </Arguments>\n<DownloadInfo>\n  <Name>BaseDownload</Name>\n  <Desc>Base class for Download modules</Desc>\n  <Abstract>yes</Abstract>\n  <Inherits>no</Inherits>\n  <Arguments>\n  </Arguments>\n</DownloadInfo>\n</DownloadInfo>\n</DownloadInfo>\n";
+
+		StringReader xmlStrReader = new StringReader(outputstr);
+		document = XMLTools.parseXML(xmlStrReader);
+		xmlStrReader.close();
+		
+	    }
+            
+	}
+	catch (Exception error) {
+	    System.err.println("Failed when trying to parse: " + download_name);
+	    error.printStackTrace();
+	}
+	finally {
+	    SafeProcess.closeProcess(process);
+	}
+
+	if(document != null) {
+	    return old_parseXML(document.getDocumentElement());
+	}
+
+	return null;
+    }
+	
+private Download old_parseXML(Node root) {
+	Download download = new Download();
+	String node_name = null;
+	for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) {
+	    node_name = node.getNodeName();
+	    if(node_name.equalsIgnoreCase("Name")) {
+		String name = XMLTools.getValue(node);
+		download.setName(name);
+	    }
+	    else if (node_name.equalsIgnoreCase("Desc")) {
+		download.setDescription(XMLTools.getValue(node));
+	    }
+	    else if (node_name.equalsIgnoreCase("Abstract")) {
+		download.setIsAbstract(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
+	    }
+	    else if(node_name.equalsIgnoreCase("Arguments")) {
+		for(Node arg = node.getFirstChild(); arg != null; arg = arg.getNextSibling()) {
+		    node_name = arg.getNodeName();
+                     if(node_name.equalsIgnoreCase("Option")) {
+			Argument argument = new Argument((Element)arg);
+			argument.parseXML((Element)arg);
+			argument.setValue(argument.getDefaultValue());
+			download.addArgument(argument);
+		     }
+		   
+		}
+	    }
+            else if(node_name.equalsIgnoreCase("DownloadInfo")) {
+                Download super_download = old_parseXML(node);
+		download.setSuper(super_download);
+	    }
+	}
+
+	if(download.getName() != null) {
+		System.err.println("@@@@ FOUND Download name: " + download.getName());
+	    return download;
+	}
+	return null;
+    }
+
+	
+// END DEBUG
+ 
+	
+    private ArrayList<String> parseXML(Node root) {
+	ArrayList<String> downloaders = null;
+	Element downloadList = (Element)root;
+	int length = -1;
+	if(downloadList.hasAttribute("length")) {
+	    length = Integer.parseInt(downloadList.getAttribute("length"));
+	    downloaders = new ArrayList<String>(length);		
+	    
+	    for (Node node = downloadList.getFirstChild(); node != null; node = node.getNextSibling()) {
+		// goes through each <DownloadInfo> of describeAll
+		
+		String download_name = null;
+		
+		for(Node infoNode = node.getFirstChild(); 
+		    infoNode != null; infoNode = infoNode.getNextSibling()) {
+		    
+		    String node_name = infoNode.getNodeName();
+		    if(node_name.equalsIgnoreCase("Name")) { // <Name>WebDownload</Name>
+			download_name = XMLTools.getValue(infoNode); // e.g. WebDownload		
+		    }
+		    
+		    // At this top level of <DownloadInfo> elements,
+		    // skip all the downloaders that are Abstract, as these are pure superclasses		
+		    else if(node_name.equalsIgnoreCase("Abstract")) {
+			String isAbstract = XMLTools.getValue(infoNode);
+			
+			if(isAbstract.equalsIgnoreCase("no") && download_name != null) {
+			    downloaders.add(download_name);
+			    Download downloader = parseDownloadInfoXML(node); // parse the <DownloadInfo> node properly
+			    // now embedded references to abstract superclasses (embedded <DownloadInfo> nodes)
+			    // will be handled
+			    
+			    String shortName = download_name.replace("Download", ""); // e.g. "Web"
+			    download_map.put(shortName, downloader);
+			}
+		    }
+		}
+	    }
+	}
+
+	return downloaders;
+    }
+
+    private Download parseDownloadInfoXML(Node root) {
+
+	Download download = new Download();
+	String node_name = null;
+	for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) {
+	    node_name = node.getNodeName();
+	    if(node_name.equalsIgnoreCase("Name")) {
+		String name = XMLTools.getValue(node);
+		download.setName(name);
+	    }
+	    else if (node_name.equalsIgnoreCase("Desc")) {
+		download.setDescription(XMLTools.getValue(node));
+	    }
+	    else if (node_name.equalsIgnoreCase("Abstract")) {
+		download.setIsAbstract(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
+	    }
+	    else if(node_name.equalsIgnoreCase("Arguments")) {
+		for(Node arg = node.getFirstChild(); arg != null; arg = arg.getNextSibling()) {
+		    node_name = arg.getNodeName();
+                     if(node_name.equalsIgnoreCase("Option")) {
+			Argument argument = new Argument((Element)arg);
+			argument.parseXML((Element)arg);
+			argument.setValue(argument.getDefaultValue());
+			download.addArgument(argument);
+		     }
+		   
+		}
+	    }
+            else if(node_name.equalsIgnoreCase("DownloadInfo")) {
+                Download super_download = parseDownloadInfoXML(node);
+		download.setSuper(super_download);
+	    }
+	}
+
+	if(download.getName() != null) {
+	    return download;
+	}
+	return null;
+    }
+
+    /** Update the previous setup */
+    private boolean updateArguments(boolean checkRequired)
+    {
+	boolean cont = true;
+	for(int i = 0; i < options_pane.getComponentCount(); i++) {
+	   
+	    Component component = options_pane.getComponent(i);
+	    if(component instanceof ArgumentControl) {
+		cont = cont && ((ArgumentControl)component).updateArgument(checkRequired);
+	    }
+	}
+
+	if(cont){return true; }
+
+	return false;
+    }
+    
+    /** Generate Controls for Options */
+    /* at some stage we should think about which options should be shown for
+     * different modes. Currently, always show all options (unless hidden)*/
+    private void generateOptions(JPanel options_pane, ArgumentContainer data) {
+	options_pane.removeAll();
+	/** Create the current option panel */
+
+	ArrayList arguments = data.getArguments(true, false);
+      	int mode = Configuration.getMode();
+        ArrayList added_arguments = new ArrayList();
+     
+	for(int i = 0; i < arguments.size(); i++) {
+	    Argument argument = (Argument) arguments.get(i);
+
+	    if (argument.isHiddenGLI()) continue;
+	    ArgumentControl argument_control = new ArgumentControl(argument,false,null);
+	    added_arguments.add(argument_control);
+	}
+	
+
+       	options_pane.setLayout(new GridLayout(added_arguments.size(),1));
+        for(int i = 0; i <  added_arguments.size(); i++) { 
+			String controlType = ((ArgumentControl)added_arguments.get(i)).getType();
+			System.err.println("Adding argcontrol: " + controlType);
+			//if(controlType.startsWith("null")) {
+			//options_pane.add((ArgumentControl)added_arguments.get(i));  // the one problematic line in DownloadPane.java (there are other classes that are problematic too) that makes a GLI that's run through DOS unresponsive to Ctrl-C on Windows
+			//}
+	   
+	}
+	
+	options_pane.add(new JTextField("Pinky")); 
+    }
+
+    /** Behaviour Functions */
+    public void afterDisplay() {
+	ready = true;        
+    }
+
+
+    public void gainFocus() {
+	if(!ready) {
+	    return;
+	}
+
+	// It is also a good time to determine if the download should be enabled - ie if its allowed to be enabled and a valid URL is present in the field.
+	download_button_enabled = true;
+	//download_button.setEnabled(download_button_enabled);
+    }
+
+
+
+    public void refresh(int refresh_reason, boolean ready)
+    {
+    }
+
+    /** Private classes */
+    /** This tree provides a 'table of contents' for the various components of the design process (collection configuration in more technical terms). */
+    private class DesignTree extends JTree {
+
+	private DesignNode root = null;
+	/** Constructor. Automatically generates all of the nodes, in the order of CONTENTS. */
+	public DesignTree() {
+	    super();
+            this.setComponentOrientation(Dictionary.getOrientation());
+	    resetModel(Configuration.getMode());
+	    expandRow(0);
+	    setRootVisible(false);
+	    setSelectionRow(0);
+	}
+
+	/** Reset the model used by the design page contents tree. This is necessary to hide the partitions entry when in lower detail modes
+	 * @param mode the current detail mode as an int
+	 */
+	public void resetModel(int mode) {
+	    root = new DesignNode("DOWNLOAD.MODE.Root");
+	    // Now add the design categories.
+	    for(int i = 0; i < CONTENTS.length; i++) {
+		root.add(new DesignNode(CONTENTS[i]));
+	    }
+	    this.setModel(new DefaultTreeModel(root));
+	    updateUI();
+	}
+	/** Set the current view to the one specified.
+	 * @param type the name of the desired view as a String
+	 */
+	public void setSelectedView(String type) {
+	    type = Dictionary.get(type);
+	    for(int i = 0; i < root.getChildCount(); i++) {
+		DesignNode child = (DesignNode) root.getChildAt(i);
+		if(child.toString().equals(type)) {
+		    TreePath path = new TreePath(child.getPath());
+		    setSelectionPath(path);
+		}
+	    }
+	}
+    }
+
+    /** A tree node that retains a reference to one of the possible design sub-views relating to the different sub-managers. */
+    private class DesignNode extends DefaultMutableTreeNode {
+	/** Constructor.
+	 * @param object The <strong>Object</strong> assigned to this node.
+	 */
+	public DesignNode(String object) {
+	    super(object);
+	}
+	/** Retrieve a textual representation of the object.
+	 * @return a String
+	 */
+	public String toString() {
+	    // return Dictionary.get("CDM.GUI." + (String)getUserObject());
+	    return Dictionary.get((String) getUserObject());
+	}
+    }
+
+    /** Listens for selection changes in the 'contents' tree, and switches to the appropriate view. */
+    private class TreeListener
+	implements TreeSelectionListener {
+	/** Called whenever the selection changes, we must update the view so it matches the node selected.
+	 * @param event A <strong>TreeSelectionEvent</strong> containing more information about the tree selection.
+	 * @see org.greenstone.gatherer.cdm.ClassifierManager
+	 * @see org.greenstone.gatherer.cdm.CollectionDesignManager
+	 * @see org.greenstone.gatherer.cdm.CollectionMetaManager
+	 * @see org.greenstone.gatherer.cdm.FormatManager
+	 * @see org.greenstone.gatherer.cdm.LanguageManager
+	 * @see org.greenstone.gatherer.cdm.MetadataSetView
+	 * @see org.greenstone.gatherer.cdm.SubcollectionManager
+	 * @see org.greenstone.gatherer.cdm.TranslationView
+	 * @see org.greenstone.gatherer.cdm.PlugInManager
+	 */
+	public void valueChanged(TreeSelectionEvent event) {
+	    if(!tree.isSelectionEmpty()) {
+		TreePath path = tree.getSelectionPath();
+
+		DesignNode node = (DesignNode)path.getLastPathComponent();
+		String type = (String)node.getUserObject();
+		Gatherer.g_man.wait(true);
+
+// START DEBUG	- http://trac.greenstone.org/changeset/29972/main/trunk/gli/src/org/greenstone/gatherer/gui/DownloadPane.java
+if(type == CONTENTS[0]) { 
+	 	            mode = "Web"; 
+	 	            generateOptions(options_pane,(Download)download_map.get(mode)); 
+	 	        } 
+	 	        else if(type == CONTENTS[1]) { 
+	 	            mode = "MediaWiki"; 
+	 	            generateOptions(options_pane,(Download)download_map.get(mode)); 
+	 	        } 
+	 	        else if(type == CONTENTS[2]) { 
+	 	            mode = "OAI"; 
+	 	            generateOptions(options_pane,(Download)download_map.get(mode)); 
+	 	        } 
+	 	        else if(type == CONTENTS[3]) { 
+	 	            mode = "Z3950"; 
+	 	            generateOptions(options_pane,(Download)download_map.get(mode)); 
+	 	        } 
+	 	        else if(type == CONTENTS[4]) { 
+	 	            mode = "SRW"; 
+	 	            generateOptions(options_pane,(Download)download_map.get(mode)); 
+	 	        } 		
+// END DEBUG
+				
+	/*
+		// type has the value DOWNLOAD.MODE.<downloader>Download,
+		// mode should then be of the form <downloader>
+		mode = convertCONTENTStoMode(type);
+		generateOptions(options_pane,(Download)download_map.get(mode));
+	*/
+		tree.setSelectionPath(path);
+		previous_path = path;
+		repaint();
+
+		Gatherer.g_man.wait(false);
+	    }
+	}
+    }
+
+    private String convertCONTENTStoMode(String content) {
+	return content.replace("DOWNLOAD.MODE.", "").replace("ZDownload", "Z3950").replace("Download", "");
+    }
+
+    private class ClearCacheListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    // Retrieve the cache folder and delete it.
+	    Utility.delete(Utility.getCacheDir());
+	    // ...and refresh the node in the workspace tree to show it's all gone
+	    Gatherer.g_man.refreshWorkspaceTree(WorkspaceTree.DOWNLOADED_FILES_CHANGED);
+	}
+    }
+
+    private class DownloadButtonListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+
+	    if(checkURL(true) && checkProxy() == true) {
+		
+		// Proxy settings are now set. Check that the url is not a redirect, else get
+		// redirect url (we do this step in order to avoid some unintuitive behaviour from wget)
+		Download current_download = (Download)download_map.get(mode);
+		Argument arg_url = current_download.getArgument("url");
+		if(arg_url != null) { // it's null for z3950 and possibly for other downloaders
+		    String url_str = arg_url.getValue();
+		    String redirect_url_str = getRedirectURL(url_str);
+		    
+		    // only update the Argument and its GUI ArgumentControl if the URL
+		    // has in fact changed
+		    if(!url_str.equals(redirect_url_str)) {
+			arg_url.setValue(redirect_url_str);
+			updateArgument(arg_url, redirect_url_str);
+		    }
+		}
+		getter.newDownloadJob((Download)download_map.get(mode) ,mode,proxy_url);
+	    }
+	}
+    }
+
+    /**
+     * The Java code here will retrieve the page at the given url. If the response code is 
+     * a redirect, it will get the redirect url so that wget may be called with the proper url. 
+     * This preprocessing of the URL is necessary because:
+     * Wget does not behave the way the browser does when faced with urls of the form
+     * http://www.englishhistory.net/tudor/citizens and if that page does not exist.
+     * The directory listing with a slash at the end (http://www.englishhistory.net/tudor/citizens/)
+     * does exist, however. In order to prevent wget from assuming that the root URL
+     * to traverse is http://www.englishhistory.net/tudor/ instead of the intended
+     * http://www.englishhistory.net/tudor/citizens/, we need give wget the redirect location
+     * that's returned when we initially make a request for http://www.englishhistory.net/tudor/citizens
+     * The proper url is sent back in the Location header, allowing us to bypass wget's 
+     * unexpected behaviour.
+     * This method ensures that urls like http://www.nzdl.org/niupepa also continue to work:
+     * there is no http://www.nzdl.org/niupepa/ page, because this url actually redirects to an 
+     * entirely different URL.
+     * @return the redirect url for the given url if any redirection is involved, or the
+     * url_str.
+     */
+    private String getRedirectURL(String url_str) {
+	HttpURLConnection connection = null;
+	if(url_str.startsWith("http:")) { // only test http urls
+	    try {
+		URL url = new URL(url_str); 
+		connection = (HttpURLConnection)url.openConnection(); //new HttpURLConnection(url);
+		// don't let it automatically follow redirects, since we want to
+		// find out whether we are dealing with redirects in the first place
+		connection.setInstanceFollowRedirects(false);
+		
+		// now check for whether we get a redirect response
+		// HTTP Codes 3xx are redirects, http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+		int responseCode = connection.getResponseCode();
+		if(responseCode >= 300 && responseCode < 400) { 
+		    String responseMsg = connection.getResponseMessage();
+
+		    // Get the Location header since this specifies the new location of the resource
+		    String location = connection.getHeaderField("Location");
+		    
+		    // this becomes the url that wget should download from
+		    url_str = location.trim();
+		}
+		
+		connection.disconnect();
+	    } catch(Exception e) {
+		if(connection != null) {
+		    connection.disconnect();
+		}
+		System.err.println("Checking redirection. Tried to connect to "
+				   + url_str + ",\nbut got exception: " + e);
+	    }	    
+	} 
+
+	return url_str;
+    }
+
+    
+    /** For a string-based Argument whose value has changed, this method
+     * updates the GUI ArgumentControl's value correspondingly. */
+    private void updateArgument(Argument arg, String value) {
+	for(int i = 0; i < options_pane.getComponentCount(); i++) {
+	    Component component = options_pane.getComponent(i);
+	    if(component instanceof ArgumentControl) {
+		ArgumentControl control = (ArgumentControl)component;
+		if(control.getArgument() == arg) {
+		    control.setValue(value);
+		    control.repaint();
+		}
+	    }
+	}
+    }
+
+    private boolean checkURL(boolean checkRequired){
+	
+	if (!updateArguments(checkRequired)){
+	    return false;
+	}
+
+	Download current_download = (Download)download_map.get(mode);
+	Argument arg_url = current_download.getArgument("url");
+	
+        if (arg_url == null) return true;
+ 
+	String url_str = arg_url.getValue();
+	URL url = null;
+	try {
+	    url = new URL(url_str);
+	}
+	catch(MalformedURLException error) {
+	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Mirroring.Invalid_URL"), Dictionary.get("Mirroring.Invalid_URL_Title"), JOptionPane.ERROR_MESSAGE);
+	    return false;
+	}
+
+	return true;
+    }
+
+    
+    private boolean checkProxy(){
+	 
+	proxy_url = null;
+
+	Download current_download = (Download)download_map.get(mode);
+	
+        Argument arg = current_download.getArgument("proxy_on");
+  
+	if (arg == null) return true;
+	
+	// Determine if we have to use a proxy.
+	if(Configuration.get("general.use_proxy", true)) {
+          
+	    String proxy_host = Configuration.getString("general.proxy_host", true);
+	    String proxy_port = Configuration.getString("general.proxy_port", true);
+	    // Find out whether the user has already authenticated themselves
+	    String user_pass = "";
+	    String address = proxy_host + ":" + proxy_port;
+	    
+            int count = 0;
+	    // Only for wget, need to avoid a second automatic authentication popup (first asks
+	    // the proxy authentication for wget, and the second will ask the same for the realm)
+	    // Once the authentication has been reused, it will set the GAuthenticator state back to REGULAR
+	    GAuthenticator.setMode(GAuthenticator.DOWNLOAD);
+	    while(count < 3 && (user_pass = (String) GAuthenticator.authentications.get(address)) == null) {
+		Authenticator.requestPasswordAuthentication(proxy_host, null, Integer.parseInt(proxy_port), "http://", Dictionary.get("WGet.Prompt"), "HTTP");
+		count++;
+	    }
+	    if(count >= 3) {
+		return false;
+	    }
+
+	    if(user_pass.indexOf("@") != -1) {
+			
+		// Write the use proxy command - we don't do this anymore, instead we set environment variables - hopefully these can't be spied on like the following can (using ps) - actually the environment stuff didn't work for windows, so lets go back to this
+		if (Utility.isWindows()) {
+		     
+		   arg.setValue("true");
+		   arg.setAssigned(true);
+
+		   arg = current_download.getArgument("proxy_host");
+		   arg.setValue(proxy_host);
+		   arg.setAssigned(true);
+
+		   arg = current_download.getArgument("proxy_port");
+		   arg.setValue(proxy_port);
+		   arg.setAssigned(true);
+
+		   arg = current_download.getArgument("user_name");
+		   arg.setValue(user_pass.substring(0, user_pass.indexOf("@")));
+		   arg.setAssigned(true);
+
+		   arg = current_download.getArgument("user_password");
+		   arg.setValue(user_pass.substring(user_pass.indexOf("@") + 1));
+		   arg.setAssigned(true);
+		  }
+	       else{
+ 		    String user_name = user_pass.substring(0, user_pass.indexOf("@"));
+ 		    String user_pwd = user_pass.substring(user_pass.indexOf("@") + 1);
+ 		    proxy_url = user_name+":"+user_pwd+"@"+proxy_host+":"+proxy_port+"/";
+		   
+ 	       }
+		
+	       return true;
+	    }
+	    else{
+		return false;
+	    }
+
+	}
+	
+	return true;
+    }
+
+    /*
+    private class PreferencesButtonActionListener
+    implements ActionListener {
+    public void actionPerformed(ActionEvent event) {
+    new Preferences(Preferences.CONNECTION_PREFS);
+    }
+    }*/
+    
+    private class InformationButtonActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    //turn off the check for find argument
+	    Download current_download = (Download)download_map.get(mode);
+
+            if (!checkProxy() || !checkURL(false) )return;
+
+
+	    if(server_info != null) {
+		server_info.dispose();
+	    }
+
+	   
+	    Argument arg_url = current_download.getArgument("url");
+	    String str_url = "";
+
+	    if( arg_url!= null && arg_url.isAssigned()) {
+		str_url = arg_url.getValue();
+	    }
+
+            
+ 	    server_info = new ServerInfoDialog(str_url ,proxy_url, mode,(Download)download_map.get(mode));
+ 	    
+	}
+    }
+
+     private class PreferencesButtonActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    new Preferences(Preferences.CONNECTION_PREFS);
+	}
+    }
+}
Index: debug-testing/gli-ctrlC-issue/GUIManager.java
===================================================================
--- debug-testing/gli-ctrlC-issue/GUIManager.java	(revision 31736)
+++ debug-testing/gli-ctrlC-issue/GUIManager.java	(revision 31736)
@@ -0,0 +1,1373 @@
+/**
+ *#########################################################################
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * <BR><BR>
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * <BR><BR>
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * 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.
+ *
+ * <BR><BR>
+ *
+ * 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 java.awt.datatransfer.*;
+import java.awt.event.*;
+import java.io.File;
+import java.lang.*;
+import java.net.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.filechooser.*;
+import javax.swing.plaf.*;
+import javax.swing.text.*;
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.collection.Collection;
+import org.greenstone.gatherer.collection.CollectionManager;
+import org.greenstone.gatherer.file.FileOpenActionListener;
+import org.greenstone.gatherer.file.WorkspaceTree;
+import org.greenstone.gatherer.gui.metaaudit.MetaAuditFrame;
+import org.greenstone.gatherer.gui.tree.DragTree;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
+import org.greenstone.gatherer.metadata.MetadataSet;
+import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
+import org.greenstone.gatherer.util.JarTools;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+
+/** The GUIManager is in charge of creating the Gatherer window frame then filling it with the goodness of the view panes. GUIManager not only creates these panes, but allows some messaging between them. Furthermore GUIManager includes functionality from menu driven choices, simply as it was easier to put it here once and have it accessible from all pane children. */
+public class GUIManager
+    extends JFrame
+    implements ActionListener, ChangeListener,  WindowFocusListener{
+    /** The download pane contains controls for downloading internet sites. */
+    public DownloadPane download_pane = null;
+    /** The gather pane is more like a file manager where you drag files from one tree to another. */
+    public GatherPane gather_pane = null;
+    /** The enrich pane is used to assign, edit and remove metadata from files within the collection. */
+    public EnrichPane enrich_pane = null;
+    /** The design pane allows you to edit the design of the library in terms of the collection configuration file. - the stuff that requires rebuilding */
+    public DesignPane design_pane = null;
+    /** The create pane contains scripting options for importing and building collections into libraries. */
+    public CreatePane create_pane = null;
+    /** The format pane allows you to edit the design of the library in terms of the collection configuration file. - the stuff that doesn't require rebuilding */
+    public FormatPane format_pane = null;
+    
+    public FileOpenActionListener foa_listener = new FileOpenActionListener();
+
+
+    /** A reference to the currently instantiated help window, if any. */
+    private HelpFrame help = null;
+    /** The menu bar. */
+    public MenuBar menu_bar = null;
+    public MetaAuditFrame meta_audit;
+    /** Are certain panes currently locked? */
+    private boolean locked = false;
+    /** The size of the Gatherer window. */
+    private Dimension size = null;
+    /** The panel within the window that other components are placed on. */
+    private JPanel content_pane = null;
+    /** The last view pane selected. */
+    private JPanel previous_pane;
+    /** The main tab pane containing the different views, available here to trap view change events. */
+    private JTabbedPane tab_pane = null;
+    /** A threaded tab changer to try and avoid NPE on exit. */
+    private TabUpdater tab_updater = null;
+
+  final static String newline = "\n";
+    final static String space = "    ";
+   
+
+    /**Constructor. Enable window events and arranges all other components.
+     * @param size The intial <strong>Dimension</strong> of the screen.
+     */
+    public GUIManager(Dimension size) {
+	super();
+        this.setComponentOrientation(Dictionary.getOrientation());
+	// Initialization
+	this.help = new HelpFrame();
+	this.size = size;
+	
+	this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+	// Add a focus listener to ourselves. Thus if we gain focus when a Modal Dialog should instead have it, we can try to bring the modal dialog to the fore.
+	this.addFocusListener(new GLIGUIFocusListener());
+
+	this.addWindowFocusListener(this);
+     
+	// Make the Tool tip hang around for a rediculous amount of time.
+	ToolTipManager.sharedInstance().setDismissDelay(10000);
+
+	// Set up some other UI stuff. (fonts handled in Gatherer.main())
+	UIManager.put("FileChooser.lookInLabelText", Dictionary.get("SaveCollectionBox.Look_In"));
+	UIManager.put("FileChooser.filesOfTypeLabelText", Dictionary.get("SaveCollectionBox.Files_Of_Type"));
+	UIManager.put("FileChooser.fileNameLabelText", Dictionary.get("SaveCollectionBox.File_Name"));
+
+	// JOptionPane options
+	// http://www.java2s.com/Tutorial/Java/0240__Swing/SettingJOptionPanebuttonlabelstoFrench.htm
+	UIManager.put("OptionPane.cancelButtonText", Dictionary.get("General.Cancel"));
+	UIManager.put("OptionPane.noButtonText", Dictionary.get("General.No"));
+	UIManager.put("OptionPane.okButtonText", Dictionary.get("General.OK"));
+	UIManager.put("OptionPane.yesButtonText", Dictionary.get("General.Yes"));
+    }
+
+
+    public void windowGainedFocus(WindowEvent e)
+    {
+    }
+
+
+    public void windowLostFocus(WindowEvent e)
+    {
+	// Save the loaded collection
+	if (Gatherer.c_man != null && Gatherer.c_man.ready() && Gatherer.c_man.getCollection().cdm != null) {
+	    Gatherer.c_man.saveCollection();
+	}
+    }
+
+
+    private class GLIGUIFocusListener
+	extends FocusAdapter {
+	public void focusGained(FocusEvent e) {
+            if (ModalDialog.current_modal != null) {
+		ModalDialog.current_modal.makeVisible();
+		ModalDialog.current_modal.toFront();
+	    }
+	}
+    }
+
+    /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured. In this case we are listening to actions from the menu-bar, and should react appropriately.
+     * @param event An <strong>ActionEvent</strong> containing information about the action that has occured.
+     */
+    public void actionPerformed(ActionEvent event) {
+	Object esrc = event.getSource();
+	// *************
+	// File Options.
+	// *************
+	if (esrc == menu_bar.file_associations) {
+	    Gatherer.assoc_man.edit();
+	}
+	else if (esrc == menu_bar.file_close) {
+	    saveThenCloseCurrentCollection();
+	}
+	else if (esrc == menu_bar.file_delete) {
+	    new DeleteCollectionTask().start();
+	}
+	else if (esrc == menu_bar.file_cdimage) {
+	    WriteCDImagePrompt wcdip = new WriteCDImagePrompt();
+	    wcdip.display();
+	    wcdip.destroy();
+	    wcdip = null;
+	}
+	else if (esrc == menu_bar.file_exportas) {
+	    ExportAsPrompt eap = new ExportAsPrompt();
+	    eap.display();
+	    eap.destroy();
+	    eap = null;
+	}
+	else if (esrc == menu_bar.file_exit) {
+	    exit();
+	}
+        else if (esrc == menu_bar.file_new) {
+	    new NewCollectionTask().start();
+	}
+	else if (esrc == menu_bar.file_open) {
+	    new OpenCollectionTask().start();
+	}
+	else if (esrc == menu_bar.file_options) {
+	    new Preferences();
+	}
+	else if (esrc == menu_bar.file_save) {
+	  // Very important: make sure metadata values are saved too
+	  enrich_pane.stopEditingAndRebuild();
+	  // Make sure all the metadata has been saved to file
+	  MetadataXMLFileManager.saveMetadataXMLFiles();
+	  
+	  Gatherer.c_man.saveCollection();
+	}
+
+	// *************
+	// Edit Options.
+	// *************
+	else if(esrc == menu_bar.edit_copy) {
+	    try {
+		KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+		// Get the component with selected text as a JTextComponent
+		JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();//getFocusOwner();
+		text.copy();
+	    }
+	    catch (Exception cce) {
+		// If the component is not a text component ignore the copy command
+		DebugStream.println(cce.toString());
+	    }
+	}
+	else if(esrc == menu_bar.edit_cut) {
+	    try {
+		KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+		// Get the component with selected text as a JTextComponent
+		JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();
+		// Cut the text to the clipboard
+		text.cut();
+	    }
+	    catch (ClassCastException cce) {
+		// If the component is not a text component ignore the cut command
+		DebugStream.println(cce.toString());
+	    }
+	}
+	else if(esrc == menu_bar.edit_paste) {
+	    try {
+		KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+		// Get the component with selected text as a JTextComponent
+		JTextComponent text = (JTextComponent) kfm.getPermanentFocusOwner();
+		// Cut the text to the clipboard
+		text.paste();
+	    }
+	    catch (ClassCastException cce) {
+		// If the component is not a text component ignore the paste command
+		DebugStream.println(cce.toString());
+	    }
+	}
+	else if(esrc == menu_bar.edit_config) {	   
+	    if(Gatherer.c_man.getCollection() != null) {
+
+		ConfigFileEditor configEditor = new ConfigFileEditor();
+		configEditor.setVisible(true);
+		configEditor.setSize(900,700);
+	    }	   
+	}
+
+	// *************
+	// Help Options.
+	// *************
+	else if (esrc == menu_bar.help_general) {
+	    HelpFrame.setView("introduction");
+	}
+	else if (esrc == menu_bar.help_download) {
+	    HelpFrame.setView("themirrorview");
+	}
+	else if (esrc == menu_bar.help_gather) {
+	    HelpFrame.setView("collectingfiles");
+	}
+	else if (esrc == menu_bar.help_enrich) {
+	    HelpFrame.setView("enrichingacollection");
+	}
+	else if (esrc == menu_bar.help_design) {
+	    HelpFrame.setView("designingacollection");
+	}
+	else if (esrc == menu_bar.help_create) {
+	    HelpFrame.setView("producingthecollection");
+	}
+	else if (esrc == menu_bar.help_format) {
+	    HelpFrame.setView("formattingacollection");
+	}
+	else if (esrc == menu_bar.help_about) {
+	    new AboutDialog(this);
+	}
+    }
+
+
+    /** Any actions that should happen after the display of the Gatherer window can be called here. Currently only updates the browser pane if it is active to work around bug in Mozilla renderer implementation.
+     */
+    public void afterDisplay() {
+	if (download_pane != null) {
+	    download_pane.afterDisplay();
+	}
+	enrich_pane.afterDisplay();
+    }
+
+
+    public void closeCurrentCollection()
+    {
+	tab_pane.setSelectedComponent(gather_pane);
+	Gatherer.c_man.closeCollection();
+	FilenameEncoding.closeCollection(); // clear filename-to-encodings map
+    }
+
+
+    public void saveThenCloseCurrentCollection()
+    {
+   	Gatherer.c_man.saveCollection();
+	closeCurrentCollection();
+    }
+
+
+    /** This is called when we're absolutely finished with the GLI. It is *not* called when the applet is suspended.
+     */
+    public void destroy()
+    {
+	// Destroying create pane ensures the latest log has been closed
+	if (create_pane != null) {
+	    create_pane.destroy();
+	}
+
+	// Deal to help
+	if (help != null) {
+	    help.destroy();
+	    help = null;
+	}
+    }
+
+	 public void UNUSED_NEW_display() {
+	content_pane = (JPanel) this.getContentPane();
+        content_pane.setComponentOrientation(Dictionary.getOrientation());
+	 }
+
+	 /** Enabled events on the window to be trapped, creates all the visual components, then builds the tab and other layouts.
+     */
+    public void display() {
+	content_pane = (JPanel) this.getContentPane();
+        content_pane.setComponentOrientation(Dictionary.getOrientation());
+
+	// Enable window-type events to be fired.
+	enableEvents(AWTEvent.WINDOW_EVENT_MASK);
+	// Initialise and layout sub-components, plus other window dressing.
+	try {
+	    this.setSize(size);
+
+	    // Set the title
+	    String collection_title = null;
+	    String collection_name = null;
+	    if (Gatherer.c_man.ready()) {
+		Collection collection = Gatherer.c_man.getCollection();
+		collection_title = collection.getTitle();
+		collection_name = collection.getGroupQualifiedName(true);
+		collection = null;
+	    }
+	    setTitle(collection_title, collection_name);
+	    collection_title = null;
+	    collection_name = null;
+
+	    // Pretty corner icon
+	    String gsmall_image = "gatherer.png";
+	    if (Configuration.fedora_info.isActive()) {
+		gsmall_image = "fli-" + gsmall_image;
+	    }
+		if(Gatherer.isGsdlRemote) {
+	    gsmall_image = "client-" + gsmall_image;
+	    }
+	    this.setIconImage(JarTools.getImage(gsmall_image).getImage());
+	    // BorderLayout for the main screen. I'll try my best to avoid these in subcomponents as they're space greedy.
+	    content_pane.setLayout(new BorderLayout());
+	    // Create the menu-bar and stick it up the top.
+	    menu_bar = new MenuBar(new MenuListenerImpl());
+            menu_bar.setComponentOrientation(Dictionary.getOrientation());
+
+	    //feedback changes
+	    //content_pane.add(menu_bar, BorderLayout.NORTH);
+	    this.setJMenuBar(menu_bar);
+	    // end feedback changes
+
+	    // Create the tabbed pane and plop it in the center where it will
+	    // expand to consume all available space like any good gas would.
+	    tab_pane = new JTabbedPane();
+            tab_pane.setComponentOrientation(Dictionary.getOrientation());
+	    tab_pane.addChangeListener(this);
+	    tab_pane.setFont(Configuration.getFont("general.font", false));
+
+	    if (Configuration.get("workflow.download", true) && Gatherer.isDownloadEnabled) {
+		download_pane = new DownloadPane();
+		// "GUI.Download_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Download"), JarTools.getImage("download.gif"), download_pane, Dictionary.get("GUI.Download_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(download_pane), Configuration.get("workflow.download", false));
+	    }
+
+	    gather_pane = new GatherPane();          
+	    gather_pane.display();
+	    if (Configuration.get("workflow.gather", true)) {
+		// "GUI.Gather_Tooltip" is used automatically
+		//tab_pane.addTab(Dictionary.get("GUI.Gather"), JarTools.getImage("gather.gif"), gather_pane, Dictionary.get("GUI.Gather_Tooltip"));
+		//tab_pane.setEnabledAt(tab_pane.indexOfComponent(gather_pane), Configuration.get("workflow.gather", false));
+	    }
+
+	    enrich_pane = new EnrichPane();
+	    enrich_pane.display();
+	    if (Configuration.get("workflow.enrich", true)) {
+		// "GUI.Enrich_Tooltip" is used automatically
+		//tab_pane.addTab(Dictionary.get("GUI.Enrich"), JarTools.getImage("enrich.gif"), enrich_pane, Dictionary.get("GUI.Enrich_Tooltip"));
+		//tab_pane.setEnabledAt(tab_pane.indexOfComponent(enrich_pane), false);
+	    }
+
+	    design_pane = new DesignPane();
+	    design_pane.display();
+	    if (Configuration.get("workflow.design", true)) {
+		// "GUI.Design_Tooltip" is used automatically
+		if (Configuration.fedora_info.isActive()) {
+		    tab_pane.addTab("Plugins", JarTools.getImage("design.gif"), design_pane, Dictionary.get("GUI.Design_Tooltip"));
+		}
+		else {
+		    tab_pane.addTab(Dictionary.get("GUI.Design"), JarTools.getImage("design.gif"), design_pane, Dictionary.get("GUI.Design_Tooltip"));
+		}
+
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(design_pane), false);
+	    }
+
+	    create_pane = new CreatePane();
+            create_pane.setComponentOrientation(Dictionary.getOrientation());
+	    //create_pane.display();
+	    if (Configuration.get("workflow.create", true)) {
+		// "GUI.Create_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Create"), JarTools.getImage("create.gif"), create_pane, Dictionary.get("GUI.Create_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(create_pane), false);
+	    }
+
+	    format_pane = new FormatPane();
+            format_pane.setComponentOrientation(Dictionary.getOrientation());
+	    format_pane.display();
+	    if (Configuration.get("workflow.format", true)) {
+		tab_pane.addTab(Dictionary.get("GUI.Format"), JarTools.getImage("format.gif"), format_pane, Dictionary.get("GUI.Format_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(format_pane), false);
+	    }
+	    
+	    // The MetaAuditFrame must be created after the gather/enrich panes but before they get focus
+	    meta_audit = new MetaAuditFrame();
+            meta_audit.setComponentOrientation(Dictionary.getOrientation());
+	    // Select the collect pane if it is available
+	    if (tab_pane.indexOfComponent(gather_pane) != -1) {
+		tab_pane.setSelectedComponent(gather_pane);
+	    }
+	    // Otherwise find the first tab that is enabled and select that.
+	    else {
+		for (int i = 0; i < tab_pane.getTabCount(); i++) {
+		    if (tab_pane.isEnabledAt(i)) {
+			tab_pane.setSelectedIndex(i);
+			break;
+		    }
+		}
+	    }
+
+	    content_pane.add(tab_pane, BorderLayout.CENTER); // the one problematic line that makes a GLI that's run through DOS unresponsive to Ctrl-C on Windows
+
+	    // Add an extra progress bar at the bottom of every screen when using a remote Greenstone server
+	    if (Gatherer.isGsdlRemote) {
+		JPanel remote_greenstone_server_progress_panel = new JPanel();
+               //remote_greenstone_server_progress_panel.setComponentOrientation(Dictionary.getOrientation());
+		JLabel remote_greenstone_server_progress_label = new JLabel(Dictionary.get("RemoteGreenstoneServer.Progress"));
+                //remote_greenstone_server_progress_label.setComponentOrientation(Dictionary.getOrientation());
+		remote_greenstone_server_progress_panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		remote_greenstone_server_progress_panel.setLayout(new BorderLayout());
+		remote_greenstone_server_progress_panel.add(remote_greenstone_server_progress_label, BorderLayout.LINE_START);
+		remote_greenstone_server_progress_panel.add(Gatherer.remoteGreenstoneServer.getProgressBar(), BorderLayout.CENTER);
+		content_pane.add(remote_greenstone_server_progress_panel, BorderLayout.SOUTH);
+	    }
+
+	    // Call refresh to update all controls to reflect current collection status.
+	    refresh(-1, Gatherer.c_man.ready());
+		
+	}
+	catch (Exception e) {
+	    DebugStream.printStackTrace(e);
+	    // The GUI failing to build is an app killer
+	    e.printStackTrace();
+	    System.exit(1);
+	}
+    }
+
+	 
+    /** Enabled events on the window to be trapped, creates all the visual components, then builds the tab and other layouts.
+     */
+    public void old_display() {
+	content_pane = (JPanel) this.getContentPane();
+        content_pane.setComponentOrientation(Dictionary.getOrientation());
+
+	// Enable window-type events to be fired.
+	enableEvents(AWTEvent.WINDOW_EVENT_MASK);
+	// Initialise and layout sub-components, plus other window dressing.
+	try {
+	    this.setSize(size);
+
+	    // Set the title
+	    String collection_title = null;
+	    String collection_name = null;
+	    if (Gatherer.c_man.ready()) {
+		Collection collection = Gatherer.c_man.getCollection();
+		collection_title = collection.getTitle();
+		collection_name = collection.getGroupQualifiedName(true);
+		collection = null;
+	    }
+	    setTitle(collection_title, collection_name);
+	    collection_title = null;
+	    collection_name = null;
+
+	    // Pretty corner icon
+	    String gsmall_image = "gatherer.png";
+	    if (Configuration.fedora_info.isActive()) {
+		gsmall_image = "fli-" + gsmall_image;
+	    }
+		if(Gatherer.isGsdlRemote) {
+	    gsmall_image = "client-" + gsmall_image;
+	    }
+	    this.setIconImage(JarTools.getImage(gsmall_image).getImage());
+	    // BorderLayout for the main screen. I'll try my best to avoid these in subcomponents as they're space greedy.
+	    content_pane.setLayout(new BorderLayout());
+	    // Create the menu-bar and stick it up the top.
+	    menu_bar = new MenuBar(new MenuListenerImpl());
+            menu_bar.setComponentOrientation(Dictionary.getOrientation());
+
+	    //feedback changes
+	    //content_pane.add(menu_bar, BorderLayout.NORTH);
+	    this.setJMenuBar(menu_bar);
+	    // end feedback changes
+
+	    // Create the tabbed pane and plop it in the center where it will
+	    // expand to consume all available space like any good gas would.
+	    tab_pane = new JTabbedPane();
+            tab_pane.setComponentOrientation(Dictionary.getOrientation());
+	    tab_pane.addChangeListener(this);
+	    tab_pane.setFont(Configuration.getFont("general.font", false));
+
+	    if (Configuration.get("workflow.download", true) && Gatherer.isDownloadEnabled) {
+		download_pane = new DownloadPane();
+		// "GUI.Download_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Download"), JarTools.getImage("download.gif"), download_pane, Dictionary.get("GUI.Download_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(download_pane), Configuration.get("workflow.download", false));
+	    }
+
+	    gather_pane = new GatherPane();          
+	    gather_pane.display();
+	    if (Configuration.get("workflow.gather", true)) {
+		// "GUI.Gather_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Gather"), JarTools.getImage("gather.gif"), gather_pane, Dictionary.get("GUI.Gather_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(gather_pane), Configuration.get("workflow.gather", false));
+	    }
+
+	    enrich_pane = new EnrichPane();
+	    enrich_pane.display();
+	    if (Configuration.get("workflow.enrich", true)) {
+		// "GUI.Enrich_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Enrich"), JarTools.getImage("enrich.gif"), enrich_pane, Dictionary.get("GUI.Enrich_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(enrich_pane), false);
+	    }
+
+	    design_pane = new DesignPane();
+	    design_pane.display();
+	    if (Configuration.get("workflow.design", true)) {
+		// "GUI.Design_Tooltip" is used automatically
+		if (Configuration.fedora_info.isActive()) {
+		    tab_pane.addTab("Plugins", JarTools.getImage("design.gif"), design_pane, Dictionary.get("GUI.Design_Tooltip"));
+		}
+		else {
+		    tab_pane.addTab(Dictionary.get("GUI.Design"), JarTools.getImage("design.gif"), design_pane, Dictionary.get("GUI.Design_Tooltip"));
+		}
+
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(design_pane), false);
+	    }
+
+	    create_pane = new CreatePane();
+            create_pane.setComponentOrientation(Dictionary.getOrientation());
+	    create_pane.display();
+	    if (Configuration.get("workflow.create", true)) {
+		// "GUI.Create_Tooltip" is used automatically
+		tab_pane.addTab(Dictionary.get("GUI.Create"), JarTools.getImage("create.gif"), create_pane, Dictionary.get("GUI.Create_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(create_pane), false);
+	    }
+
+	    format_pane = new FormatPane();
+            format_pane.setComponentOrientation(Dictionary.getOrientation());
+	    format_pane.display();
+	    if (Configuration.get("workflow.format", true)) {
+		tab_pane.addTab(Dictionary.get("GUI.Format"), JarTools.getImage("format.gif"), format_pane, Dictionary.get("GUI.Format_Tooltip"));
+		tab_pane.setEnabledAt(tab_pane.indexOfComponent(format_pane), false);
+	    }
+	    
+	    // The MetaAuditFrame must be created after the gather/enrich panes but before they get focus
+	    meta_audit = new MetaAuditFrame();
+            meta_audit.setComponentOrientation(Dictionary.getOrientation());
+	    // Select the collect pane if it is available
+	    if (tab_pane.indexOfComponent(gather_pane) != -1) {
+		tab_pane.setSelectedComponent(gather_pane);
+	    }
+	    // Otherwise find the first tab that is enabled and select that.
+	    else {
+		for (int i = 0; i < tab_pane.getTabCount(); i++) {
+		    if (tab_pane.isEnabledAt(i)) {
+			tab_pane.setSelectedIndex(i);
+			break;
+		    }
+		}
+	    }
+
+	    content_pane.add(tab_pane, BorderLayout.CENTER);
+
+	    // Add an extra progress bar at the bottom of every screen when using a remote Greenstone server
+	    if (Gatherer.isGsdlRemote) {
+		JPanel remote_greenstone_server_progress_panel = new JPanel();
+               //remote_greenstone_server_progress_panel.setComponentOrientation(Dictionary.getOrientation());
+		JLabel remote_greenstone_server_progress_label = new JLabel(Dictionary.get("RemoteGreenstoneServer.Progress"));
+                //remote_greenstone_server_progress_label.setComponentOrientation(Dictionary.getOrientation());
+		remote_greenstone_server_progress_panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		remote_greenstone_server_progress_panel.setLayout(new BorderLayout());
+		remote_greenstone_server_progress_panel.add(remote_greenstone_server_progress_label, BorderLayout.LINE_START);
+		remote_greenstone_server_progress_panel.add(Gatherer.remoteGreenstoneServer.getProgressBar(), BorderLayout.CENTER);
+		content_pane.add(remote_greenstone_server_progress_panel, BorderLayout.SOUTH);
+	    }
+
+	    // Call refresh to update all controls to reflect current collection status.
+	    refresh(-1, Gatherer.c_man.ready());
+	}
+	catch (Exception e) {
+	    DebugStream.printStackTrace(e);
+	    // The GUI failing to build is an app killer
+	    e.printStackTrace();
+	    System.exit(1);
+	}
+    }
+
+    public void exit() 
+    {
+	exit(0);
+    }
+	
+  // some cases we have already saved the collection, but don't want to 
+  // override the general.open_collection value here
+  public void exitNoCollectionSave(int exit_status) {
+	// Store the current position and size of the GLI for next time
+	Configuration.setBounds("general.bounds", true, getBounds());
+
+	// Save configuration
+	Configuration.save();
+
+	// Hide the main window
+	setVisible(false);
+
+	// If we're running as an applet we don't quit here (we quit when the browser calls GathererApplet.destroy())
+	if (!Gatherer.isApplet) {
+	    Gatherer.exit(exit_status);
+	}
+
+  }
+    /** This method ensures that all the things needing saving are saved before Gatherer.exit() is called.
+     */
+    public void exit(int exit_status)
+    {
+	boolean collection_open = false;
+	// If we have a collection open remember it for next time, then save it and close it
+	if (Gatherer.c_man.ready()) {
+		// but we no longer remember open collections for remote GLI, as this causes problems
+		// in applets when other users on a machine want to use an applet and GLI wants to load
+		// a collection left open from a previous session and *requires* the old user to authenticate
+		if(Gatherer.isGsdlRemote) {
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+				    true, "");			
+		} else {
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+				    true, CollectionManager.getLoadedCollectionColFilePath());
+		}
+	    saveThenCloseCurrentCollection();
+		collection_open = true;
+	}
+	else {
+	    // if there was no open collection, then write out the collect dir path for next time
+		// IF this is not the default collect directory and not a remote GLI/applet
+		if(Gatherer.isGsdlRemote || Gatherer.getCollectDirectoryPath().equals(
+				Gatherer.getDefaultGSCollectDirectoryPath(true))) {	
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+					true, "");
+		} else {
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+					true, Gatherer.getCollectDirectoryPath());
+		}
+	}
+	
+	
+	// Basically, if GLI coldir != gsdlsite_colhome, we ask them if they want to save GLI coldir as gsdlsite_colhome
+	// If gsdlsite_colhome = "" (meaning default GS coldir) and GLI is also set to defaultGScoldir, no saving necessary.
+	// We ONLY do this for the local included apache web server, since the issue revolves around gsdlsite.cfg containing
+	// the global collecthome when using the apache web server. This is not an issue for remote GS or server.exe (the latter
+	// has separate config files specifying collecthome for when GLI is running whereas when just the server.exe is running).
+	if(!Gatherer.isGsdlRemote && !Gatherer.isPersistentServer()) {		
+	
+		String defaultColDir = Gatherer.getDefaultGSCollectDirectoryPath(false); // no filesep at end
+		String gliColDir = Gatherer.getCollectDirectoryPath();
+		String serverColDir = Gatherer.gsdlsite_collecthome;
+		
+		boolean showSettingsDlg = true;
+		// if collectdir was changed during GLI session (to something different from what's in gsdlsite), need to ask what to store
+		String gsdlsiteColdir = Gatherer.gsdlsite_collecthome.replace("\"", "");
+		if(gliColDir.equals(gsdlsiteColdir+File.separator)) {	
+			showSettingsDlg = false;
+		} else if(gsdlsiteColdir.equals("") // both set to default coldir
+				&& gliColDir.equals(defaultColDir+File.separator)) {
+			showSettingsDlg = false;
+		} 
+		if(showSettingsDlg) {
+			
+			// else we will be showing the Collect Directory Settings Dialog
+			if(gliColDir.endsWith(File.separator)) {
+				gliColDir = gliColDir.substring(0, gliColDir.length()-1);
+			}
+			
+			if(serverColDir.equals("")) {
+				serverColDir = defaultColDir;
+			}	
+			collectDirSettingsDialog(defaultColDir, serverColDir, gliColDir, collection_open);
+		}
+	}
+
+	// Store the current position and size of the GLI for next time
+	Configuration.setBounds("general.bounds", true, getBounds());
+
+	// Save configuration
+	Configuration.save();
+
+	// Hide the main window
+	setVisible(false);
+
+	// If we're running as an applet we don't quit here (we quit when the browser calls GathererApplet.destroy())
+	if (!Gatherer.isApplet) {
+	    Gatherer.exit(exit_status);
+	}
+    }
+
+	public void collectDirSettingsDialog(final String defaultColDir,
+		final String from, final String to, final boolean collection_open) 
+	{	
+		final JDialog colHomeDialog 
+			= new JDialog(this, Dictionary.get("GUI.CollectHome.title"), true); // this = Gatherer.g_man
+		colHomeDialog.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+		
+		
+		JRadioButton gliYes = new JRadioButton(Dictionary.get("General.Yes"), true);
+						// the default selection for GLI, collecthome=to
+		JRadioButton serverYes = new JRadioButton(Dictionary.get("General.Yes")); // not selected for the server		
+		JRadioButton gliNo = null;
+		JRadioButton gliThirdOption = null;
+		JRadioButton serverNo = null;
+		JRadioButton serverThirdOption = null;
+		
+		if(from.equals(defaultColDir)) { 
+			gliNo = new JRadioButton(Dictionary.get("GUI.CollectHome.resetToDefault"));
+			serverNo = new JRadioButton(Dictionary.get("GUI.CollectHome.leaveAtDefault"), true);
+						// default selection for server, collecthome=from
+		} else {
+			gliNo = new JRadioButton(Dictionary.get("General.No"));
+			serverNo = new JRadioButton(Dictionary.get("General.No"), true); // default selection for server 
+			if(!to.equals(defaultColDir)) { // neither from nor to is the default GS collect dir, so give them that as the third option
+				gliThirdOption = new JRadioButton(Dictionary.get("GUI.CollectHome.reset"));
+				serverThirdOption = new JRadioButton(Dictionary.get("GUI.CollectHome.reset"));		
+			}
+		}
+		
+		JPanel gliPanel = new JPanel(); // flowlayout by default
+		ButtonGroup gliGroup = new ButtonGroup();
+		JPanel serverPanel = new JPanel(); // flowlayout by default
+		ButtonGroup serverGroup = new ButtonGroup();
+		
+		gliGroup.add(gliYes);
+		gliPanel.add(gliYes);
+		serverGroup.add(serverYes);
+		serverPanel.add(serverYes);		
+		
+		gliGroup.add(gliNo);
+		gliPanel.add(gliNo);
+		serverGroup.add(serverNo);
+		serverPanel.add(serverNo);
+			
+		if(gliThirdOption != null) {
+			gliThirdOption = new JRadioButton("Reset to default");
+			serverThirdOption = new JRadioButton("Reset to default");
+			
+			gliGroup.add(gliThirdOption);
+			gliPanel.add(gliThirdOption);
+			serverGroup.add(serverThirdOption);
+			serverPanel.add(serverThirdOption);
+		}
+		
+		// vars need to be final to use them in the actionlistener below
+		final JRadioButton gli_yes = gliYes;
+		final JRadioButton gli_no = gliNo;
+		final JRadioButton gli_optional = gliThirdOption;
+		final JRadioButton server_yes = serverYes;
+		final JRadioButton server_no = serverNo;
+		final JRadioButton server_optional = serverThirdOption;
+		
+		JButton okButton = new JButton(Dictionary.get("General.OK"));
+		okButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				// store the option chosen for GLI
+				String gliColDir = to;	
+				if(gli_optional != null && gli_optional.isSelected()) {
+					// defaultColDir
+					gliColDir = "";
+				} else if(gli_yes.isSelected()) {
+					gliColDir = to;
+				} else if (gli_no.isSelected()) {
+					gliColDir = from;
+				}
+				if(defaultColDir.equals(gliColDir)) {
+					gliColDir = "";
+				}
+				if(!(collection_open && gli_yes.isSelected())) { // don't overwrite open collections
+					Configuration.setString(
+						"general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+						true, gliColDir);
+				}
+				
+				// store the option chosen for the server's settings
+				String serverColDir = from;
+				if(server_optional != null && server_optional.isSelected()) {
+					// defaultColDir
+					serverColDir = null;
+				} else if(server_yes.isSelected()) {
+					serverColDir = to;
+				} else if (server_no.isSelected()) {
+					serverColDir = from;
+				}
+				if(serverColDir != null && defaultColDir.equals(serverColDir)) {					
+					serverColDir = null;
+				}				
+				
+				if(serverColDir != null) {
+					serverColDir = serverColDir.replace("\"","");
+					serverColDir = "\""+serverColDir+"\"";
+				}
+								
+				Gatherer.gsdlsite_collecthome = Utility.updatePropertyConfigFile(
+					Gatherer.getGsdlSiteConfigFile(), "collecthome", serverColDir);	
+				
+				colHomeDialog.dispose();
+			}
+		});
+		
+		//String[] dirs = {from, to};		
+		//JLabel message = new JLabel(Dictionary.get("GUI.CollectHome.message", dirs));
+		JLabel message = new JLabel(Dictionary.get("GUI.CollectHome.message"));
+		JLabel fromDirLine = new JLabel(Dictionary.get("GUI.CollectHome.dir", from));
+		fromDirLine.setBorder(BorderFactory.createEmptyBorder(0,25,0,0)); // padding
+		JLabel toLine = new JLabel(Dictionary.get("GUI.CollectHome.to"));
+		JLabel toDirLine = new JLabel(Dictionary.get("GUI.CollectHome.dir", to));
+		toDirLine.setBorder(BorderFactory.createEmptyBorder(0,25,0,0)); // padding
+		JLabel gliOption = new JLabel(Dictionary.get("GUI.CollectHome.gli"));
+		JLabel serverOption = new JLabel(Dictionary.get("GUI.CollectHome.server"));		
+		
+		JPanel mainPanel = new JPanel();
+		mainPanel.setLayout(new GridLayout(9, 1));
+		mainPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); // padding
+		mainPanel.add(message);
+		mainPanel.add(fromDirLine);		
+		mainPanel.add(toLine);
+		mainPanel.add(toDirLine);
+		mainPanel.add(gliOption);
+		mainPanel.add(gliPanel);
+		mainPanel.add(serverOption);
+		mainPanel.add(serverPanel);
+		mainPanel.add(okButton);		
+		Container c = colHomeDialog.getContentPane();
+		c.setLayout(new BorderLayout());
+		c.add(mainPanel, BorderLayout.CENTER);
+		colHomeDialog.getRootPane().setDefaultButton(okButton);
+		
+		colHomeDialog.pack();
+		colHomeDialog.setVisible(true);
+	}
+	
+
+    /** This method is called when the collection is being built, and is used to disable all controls in all pane which could change the state of the collection.
+     */
+    public void lockCollection(boolean import_stage, boolean lock)
+    {
+	locked = lock;
+
+	if (import_stage) {
+	    int gather_pos = tab_pane.indexOfComponent(gather_pane);
+	    if (gather_pos != -1) {
+		tab_pane.setEnabledAt(gather_pos, !lock);
+	    }
+	    int enrich_pos = tab_pane.indexOfComponent(enrich_pane);
+	    if (enrich_pos != -1) {
+		tab_pane.setEnabledAt(enrich_pos, !lock);
+	    }
+	}
+
+	int design_pos = tab_pane.indexOfComponent(design_pane);
+	if (design_pos != -1) {
+	    tab_pane.setEnabledAt(design_pos, !lock);
+	}
+    }
+
+
+    public void modeChanged(int mode) {
+	// Set the title
+	String collection_title = null;
+	String collection_name = null;
+	if (Gatherer.c_man.ready()) {
+	    Collection collection = Gatherer.c_man.getCollection();
+	    collection_title = collection.getTitle();
+	    collection_name = collection.getGroupQualifiedName(true);
+	    collection = null;
+	}
+	setTitle(collection_title, collection_name);
+	collection_title = null;
+	collection_name = null;
+	// Now pass on the message to anyone who cares
+	if (download_pane != null) {
+	    download_pane.modeChanged(mode);
+	}
+	if (gather_pane != null) {
+	    gather_pane.modeChanged(mode);
+	}
+	if (enrich_pane != null) {
+	    enrich_pane.modeChanged(mode);
+	}
+	if (design_pane != null) {
+	    design_pane.modeChanged(mode);
+	}
+	if (create_pane != null) {
+	    create_pane.modeChanged(mode);
+	}
+	if (format_pane != null) {
+	    format_pane.modeChanged(mode);
+	}
+    }
+
+
+    public void refresh(int refresh_reason, boolean collection_loaded)
+    {
+	// Set the collection information in the title bar
+	if (collection_loaded) {
+	    Collection collection = Gatherer.c_man.getCollection();
+	    setTitle(collection.getTitle(), collection.getGroupQualifiedName(true));
+	}
+	else {
+	    setTitle(null, null);
+	}
+
+	// Update the menu bar
+	menu_bar.refresh(refresh_reason, collection_loaded);
+
+	// Update the loaded panes
+	if (download_pane != null) {
+	    download_pane.refresh(refresh_reason, collection_loaded);
+	}
+	if (gather_pane != null) {
+	    gather_pane.refresh(refresh_reason, collection_loaded);
+	}
+	if (enrich_pane != null) {
+	    enrich_pane.refresh(refresh_reason, collection_loaded);
+	}
+	if (design_pane != null) {
+	    design_pane.refresh(refresh_reason, collection_loaded);
+	}
+	if (create_pane != null) {
+	    create_pane.refresh(refresh_reason, collection_loaded);
+	}
+	if (format_pane != null) {
+	    format_pane.refresh(refresh_reason, collection_loaded);
+	}
+
+	// Now enable tabs as necessary. Do this on event queue to prevent crazy NPEs
+	if (!locked) {
+	    if (tab_updater == null) {
+		tab_updater = new TabUpdater(tab_pane, collection_loaded);
+	    }
+	    else {
+		tab_updater.setReady(collection_loaded);
+	    }
+	    SwingUtilities.invokeLater(tab_updater);
+	}
+    }
+
+
+    public void refreshCollectionTree(int refresh_reason)
+    {
+	if (gather_pane != null) {
+	    gather_pane.refreshCollectionTree(refresh_reason);
+	}
+    }
+
+
+    public void refreshWorkspaceTree(int refresh_reason)
+    {
+	if (gather_pane != null) {
+	    gather_pane.refreshWorkspaceTree(refresh_reason);
+	}
+    }
+
+    public void refreshWorkspaceTreeGreenstoneCollections()
+    {
+	refreshWorkspaceTree(WorkspaceTree.LIBRARY_CONTENTS_CHANGED);
+    }
+
+    /** Specifies whether a certain tab is enabled or not. */
+    private void setTabEnabled(String rawname, boolean state) {
+	// Retrieve the dictionary based name.
+	String name = Dictionary.get("GUI." + rawname);
+	int index = tab_pane.indexOfTab(name);
+	// Of course we may not have this tab available.
+	if(index != -1) {
+	    // Some tabs are also dependant on if a collection is ready
+	    Component component = tab_pane.getComponentAt(index);
+	    if(component == enrich_pane || component == design_pane || component == create_pane || component == format_pane) {
+		tab_pane.setEnabledAt(index, state && Gatherer.c_man != null && Gatherer.c_man.ready());
+	    }
+	    else {
+		tab_pane.setEnabledAt(index, state);
+	    }
+	    // If this was the currently selected tab and it is now disabled, change the view to the first enabled tab.
+	    if(tab_pane.getSelectedIndex() == index && !state) {
+		boolean found = false;
+		for(int i = 0; !found && i < tab_pane.getTabCount(); i++) {
+		    if(tab_pane.isEnabledAt(i)) {
+			tab_pane.setSelectedIndex(i);
+			found = true;
+		    }
+		}
+		// If there are no tabs enabled, which should be impossible, then select the first tab
+		if(!found) {
+		    tab_pane.setSelectedIndex(0);
+		}
+	    }
+	}
+    }
+
+    /** Change the string shown in the title bar of the main gui frame. If either value is null, the 'No Collection' string is shown instead.
+     * @param title
+     * @param name
+     */
+    public void setTitle(String title, String name) {
+      
+	// Finally display the collection name in the title bar.
+	StringBuffer title_buffer = new StringBuffer(Configuration.getApplicationTitle());
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	title_buffer.append(Gatherer.PROGRAM_VERSION);
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	// Server version information
+	title_buffer.append(Gatherer.getServerVersionAsString());
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);	
+	// Describe the current user mode
+	title_buffer.append(StaticStrings.MODE_STR);
+	title_buffer.append(Configuration.getModeAsString());
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	// Now for the current collection
+	title_buffer.append(StaticStrings.COLLECTION_STR);
+	if (title != null && name != null) {
+	    title_buffer.append(title);
+	    title_buffer.append(StaticStrings.SPACE_CHARACTER);
+	    title_buffer.append(StaticStrings.OPEN_PARENTHESIS_CHARACTER);
+	    title_buffer.append(name);
+	    title_buffer.append(StaticStrings.CLOSE_PARENTHESIS_CHARACTER);
+	}
+	else {
+	    title_buffer.append(Dictionary.get("Collection.No_Collection"));
+	}
+	this.setTitle(title_buffer.toString());
+	title_buffer = null;
+    }
+
+
+    private class OpenCollectionTask
+        extends Thread
+    {
+	public void run()
+	{
+	    String collection_file_path = showOpenCollectionDialog();
+
+	    // User has selected a collection to open
+	    if (collection_file_path != null) {
+		// If there is already a collection open, save and close it
+		if (Gatherer.c_man.ready()) {
+		    saveThenCloseCurrentCollection();
+		}
+
+		// Open the selected collection
+		Gatherer.c_man.loadCollection(collection_file_path);
+	    }
+	}
+    }
+
+
+    /** When the load collection option is chosen this method is called to produce the modal file load prompt.
+     */
+    private String showOpenCollectionDialog()
+    {
+	OpenCollectionDialog dialog = new OpenCollectionDialog();
+        dialog.setComponentOrientation(Dictionary.getOrientation());
+	if (dialog.display() == OpenCollectionDialog.OK_OPTION) {
+	    return dialog.getFileName();
+	}
+
+	// User must have cancelled the action
+	return null;
+    }
+
+
+    /** When called this method causes the MetadataAuditTable to display a nice dialog box which contains all the metadata assigned in the collection.
+     */
+    public void showMetaAuditBox() {
+	wait(true);
+	meta_audit.display();
+	wait(false);
+    }
+
+
+    private class NewCollectionTask
+	extends Thread
+    {
+	public void run()
+	{
+	    // Create the collection details prompt from new collection prompt
+	    NewCollectionDetailsPrompt ncd_prompt = new NewCollectionDetailsPrompt();
+
+	    // Create the new collection (if not cancelled) in a new thread.
+	    if (!ncd_prompt.isCancelled()) {
+		// If there is already a collection open, save and close it.
+		if (Gatherer.c_man.ready()) {
+		    saveThenCloseCurrentCollection();
+		}
+		
+		// Create new collection.
+		Gatherer.c_man.createCollection(ncd_prompt.getDescription(), Configuration.getEmail(), ncd_prompt.getName(), ncd_prompt.getTitle(), ncd_prompt.getBase(), new ArrayList());
+		ncd_prompt.dispose();
+	    }
+
+	    // Done
+	    ncd_prompt = null;
+	}
+    }
+
+
+    private class DeleteCollectionTask
+	extends Thread
+    {
+	public void run()
+	{
+	    // The rest is handled by the DeleteCollectionPrompt
+	    DeleteCollectionPrompt dc_prompt = new DeleteCollectionPrompt();
+	    if (dc_prompt.display()) {
+		//closeCurrentCollection();
+	    }
+	    dc_prompt.destroy();
+	    dc_prompt = null;
+	}
+    }
+
+
+    /** Any implementation of ChangeListener must include this method so we can be informed when the state of one of the registered objects changes. In this case we are listening to view changes within the tabbed pane.
+     * @param event A ChangeEvent containing information about the event that fired this call.
+     */
+    public void stateChanged(ChangeEvent event)
+    {
+	if (previous_pane != null) {
+	    if (previous_pane == gather_pane) {
+		gather_pane.loseFocus();
+	    }
+	    else if (previous_pane == enrich_pane) {
+		enrich_pane.loseFocus();
+	    }
+	    else if (previous_pane == design_pane) {
+		design_pane.loseFocus();
+	    }
+	    else if (previous_pane == create_pane) {
+		create_pane.loseFocus();
+	    }
+	    else if (previous_pane == format_pane) {
+		format_pane.loseFocus();
+	    }
+	}
+
+	menu_bar.tabSelected(tab_pane.getSelectedIndex());
+	int selected_index = tab_pane.getSelectedIndex();
+	if (selected_index == tab_pane.indexOfComponent(download_pane)) {
+	    download_pane.gainFocus();
+	}
+	else if (selected_index == tab_pane.indexOfComponent(gather_pane)) {
+	    gather_pane.gainFocus();
+	}
+	else if (selected_index == tab_pane.indexOfComponent(enrich_pane)) {
+	    enrich_pane.gainFocus();
+	}
+	else if (selected_index == tab_pane.indexOfComponent(design_pane)) {
+	    design_pane.gainFocus();
+	}
+	else if (selected_index == tab_pane.indexOfComponent(create_pane)) {
+	    create_pane.gainFocus();
+	}
+	else if (selected_index == tab_pane.indexOfComponent(format_pane)) {
+	    format_pane.gainFocus();
+	}
+
+	previous_pane = (JPanel) tab_pane.getSelectedComponent();
+	
+    }
+
+
+    private MouseListener mouse_blocker_listener = new MouseAdapter() {};
+
+    public void updateUI()
+    {
+	JPanel pane = (JPanel) getContentPane();
+	pane.updateUI();
+	// Also update all of the tabs according to workflow.
+	workflowUpdate("Download", Configuration.get("workflow.download", false));
+	workflowUpdate("Gather", Configuration.get("workflow.gather", false));
+	workflowUpdate("Enrich", Configuration.get("workflow.enrich", false));
+	workflowUpdate("Design", Configuration.get("workflow.design", false));
+	workflowUpdate("Create", Configuration.get("workflow.create", false));
+	workflowUpdate("Format", Configuration.get("workflow.format", false));
+    }
+
+    public void wait(boolean waiting) {
+	Component glass_pane = getGlassPane();
+	if(waiting) {
+	    // Show wait cursor.
+	    glass_pane.addMouseListener(mouse_blocker_listener);
+	    glass_pane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+	    glass_pane.setVisible(true);
+	}
+	else {
+	    // Hide wait cursor.
+	    glass_pane.setVisible(false);
+	    glass_pane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+	    glass_pane.removeMouseListener(mouse_blocker_listener);
+	}
+	glass_pane = null;
+    }
+
+    public void workflowUpdate(String raw, boolean state) {
+	WorkflowUpdater task = new WorkflowUpdater(raw, state);
+	SwingUtilities.invokeLater(task);
+	task = null;
+    }
+
+
+    /**Overridden from JFrame so we can exit safely when window is closed (or destroyed).
+     * @param event A <strong>WindowEvent</strong> containing information about the event that fired this call.
+     */
+    protected void processWindowEvent(WindowEvent event) {
+	if(event.getID() == WindowEvent.WINDOW_CLOSING) {
+	    exit();
+	}
+    }
+
+
+    /** Listens to actions upon the menu bar, and if it detects a click over the help menu brings the help window to the front if it has become hidden.
+     */
+    private class MenuListenerImpl
+	implements MenuListener {
+	/** Called whenever a popup menu is hidden, but we don't care.
+	 * @param e Some <strong>MenuEvent</strong> that we could care less about.
+	 */
+	public void menuCanceled(MenuEvent e) {
+	}
+	/** Called whenever a menu header (ie button) becomes unselected, but we don't care.
+	 * @param e Some <strong>MenuEvent</strong> that we could care less about.
+	 */
+	public void menuDeselected(MenuEvent e) {
+	}
+	/** This method, when a menu is first opened, is the only one we respond to by bringing the help window to the front if possible, but only if there is a help window and the help menu is the one opening.
+	 * @param e The <strong>MenuEvent</strong> whose source is checked.
+	 */
+	public void menuSelected(MenuEvent e) {
+	    if(e.getSource() == menu_bar.help) {
+		if(menu_bar.help.isSelected()) {
+		    menu_bar.help.doClick(10);
+		}
+	    }
+	    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);
+		}
+	    }
+
+	}
+    }
+
+    private class TabUpdater
+	implements Runnable {
+	private boolean ready = false;
+	private int download_pos = -1;
+	private int enrich_pos = -1;
+	private int design_pos = -1;
+	private int create_pos = -1;
+	private int format_pos = -1;
+	private int export_pos = -1;
+	private JTabbedPane tab_pane = null;
+
+	public TabUpdater(JTabbedPane tab_pane, boolean ready) {
+	    this.ready = ready;
+	    this.tab_pane = tab_pane;
+	    download_pos = tab_pane.indexOfComponent(download_pane);
+	    enrich_pos = tab_pane.indexOfComponent(enrich_pane);
+	    design_pos = tab_pane.indexOfComponent(design_pane);
+	    create_pos = tab_pane.indexOfComponent(create_pane);
+	    format_pos = tab_pane.indexOfComponent(format_pane);
+	}
+
+	public void run()
+	{
+	    if (download_pos != -1) {
+		if (ready) {
+		    tab_pane.setEnabledAt(download_pos, Configuration.get("workflow.download", false));
+		}
+		else {
+		    tab_pane.setEnabledAt(download_pos, Configuration.get("workflow.download", true));
+		}
+	    }
+	    if (enrich_pos != -1) {
+		tab_pane.setEnabledAt(enrich_pos, ready && Configuration.get("workflow.enrich", false));
+	    }
+	    if (design_pos != -1) {
+		tab_pane.setEnabledAt(design_pos, ready && Configuration.get("workflow.design", false) && Configuration.getMode() > Configuration.ASSISTANT_MODE);
+	    }
+	    if (create_pos != -1) {
+		tab_pane.setEnabledAt(create_pos, ready && Configuration.get("workflow.create", false));
+	    }
+	    if (format_pos != -1) {
+		tab_pane.setEnabledAt(format_pos, ready && Configuration.get("workflow.format", false) && Configuration.getMode() > Configuration.ASSISTANT_MODE);
+	    }
+	}
+
+	public void setReady(boolean ready) {
+	    this.ready = ready;
+	}
+    }
+
+    private class WorkflowUpdater
+	implements Runnable {
+	private boolean state;
+	private String raw;
+	public WorkflowUpdater(String raw, boolean state) {
+	    this.raw = raw;
+	    this.state = state;
+	}
+	public void run() {
+	    setTabEnabled(raw, state);
+	}
+    }
+}
Index: debug-testing/gli-ctrlC-issue/Gatherer.java
===================================================================
--- debug-testing/gli-ctrlC-issue/Gatherer.java	(revision 31736)
+++ debug-testing/gli-ctrlC-issue/Gatherer.java	(revision 31736)
@@ -0,0 +1,1724 @@
+/**
+*#########################################################################
+*
+* 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;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.lang.*;
+import java.net.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.text.*;
+
+
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.GAuthenticator;
+import org.greenstone.gatherer.FedoraInfo;
+import org.greenstone.gatherer.collection.CollectionManager;
+import org.greenstone.gatherer.feedback.ActionRecorderDialog;
+import org.greenstone.gatherer.feedback.Base64;
+import org.greenstone.gatherer.file.FileManager;
+import org.greenstone.gatherer.file.FileAssociationManager;
+import org.greenstone.gatherer.file.RecycleBin;
+import org.greenstone.gatherer.greenstone.Classifiers;
+import org.greenstone.gatherer.greenstone.LocalGreenstone;
+import org.greenstone.gatherer.greenstone.LocalLibraryServer;
+import org.greenstone.gatherer.greenstone.Plugins;
+import org.greenstone.gatherer.greenstone3.ServletConfiguration;
+import org.greenstone.gatherer.gui.GUIManager;
+import org.greenstone.gatherer.gui.URLField;
+import org.greenstone.gatherer.gui.WarningDialog;
+import org.greenstone.gatherer.gui.FedoraLogin;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
+import org.greenstone.gatherer.util.GS3ServerThread;
+import org.greenstone.gatherer.util.JarTools;
+import org.greenstone.gatherer.util.SafeProcess;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+
+
+/** Containing the top-level "core" for the Gatherer, this class is the
+* common core for the GLI application and applet. It first parses the
+* command line arguments, preparing to update the configuration as
+* required. Next it loads several important support classes such as the
+* Configuration and Dictionary. Finally it creates the other important
+* managers and sends them on their way.
+* @author John Thompson, Greenstone Digital Library, University of Waikato
+* @version 2.3
+*/
+public class Gatherer
+{
+	/** The name of the GLI. */
+	static final public String PROGRAM_NAME = "Greenstone Librarian Interface";
+	/** The current version of the GLI.
+		* Note: the gs3-release-maker relies on this variable being declared
+		* in a line which matches this java regex:
+		* ^(.*)String\s*PROGRAM_VERSION\s*=\s*"trunk";
+		* If change the declaration and it no longer matches the regex, please
+		* change the regex in the gs3-release-maker code and in this message
+		*/
+
+	static final public String PROGRAM_VERSION = "trunk";
+
+	static private Dimension size = new Dimension(800, 540);
+	static public RemoteGreenstoneServer remoteGreenstoneServer = null;
+
+	/** Has the exit flag been set? */
+	static final public int EXIT_THEN_RESTART= 2;
+	static public boolean exit = false;
+	static public int exit_status = 0;
+
+	static private String gli_directory_path = null;
+	static private String gli_user_directory_path = null;
+
+	static public String client_operating_system = null;
+
+	/** All of the external applications that must exit before we close the Gatherer. */
+	static private Vector apps = new Vector();
+	static private String non_standard_collect_directory_path = null;
+	static public String open_collection_file_path = null;
+	static public String gsdlsite_collecthome = "";
+	/** A public reference to the FileAssociationManager. */
+	static public FileAssociationManager assoc_man;
+	/** A public reference to the CollectionManager. */
+	static public CollectionManager c_man;
+	/** A public reference to the RecycleBin. */
+	static public RecycleBin recycle_bin;
+	/** a reference to the Servlet Configuration is GS3 */
+	static public ServletConfiguration servlet_config;
+	/** A public reference to the FileManager. */
+	static public FileManager f_man;
+	/** A public reference to the GUIManager. */
+	static public GUIManager g_man = null;
+	static private boolean g_man_built = false;
+
+	/** We are using the GLI for GS3 */
+	static public boolean GS3 = false;
+
+	static public boolean isApplet = false;
+	static public boolean isGsdlRemote = false;
+	static public boolean isLocalLibrary = false;
+
+	/* TODO: If we're using local GLI, collections are built locally. If we're using client-GLI
+	* and it contains a gs2build folder in it, then localBuild will also be true (if this is not
+	* turned off in Preferences). If we're remote and this is turned off in Prefs, build remotely. */
+	/*static public boolean buildingLocally = true;*/
+	/** If we're using local GLI, we can always download. If we're using client-GLI, we can only
+	* download if we have a gs2build folder inside it. And if we don't turn off downloadEnabling
+	* in the preferences.
+	*/
+	static public boolean isDownloadEnabled = true;
+
+	// feedback stuff
+	/** is the feedback feature enabled? */
+	static public boolean feedback_enabled = true;
+	/** the action recorder dialog */
+	static public ActionRecorderDialog feedback_dialog = null;
+
+	// Refresh reasons
+	static public final int COLLECTION_OPENED   = 0;
+	static public final int COLLECTION_CLOSED   = 1;
+	static public final int COLLECTION_REBUILT  = 2;
+	static public final int PREFERENCES_CHANGED = 3;
+
+	//////kk added////////  
+	static public String cgiBase="";
+	/////////////////
+
+	/** Magic to allow Enter to fire the default button. */
+	static {
+		KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+		Keymap map = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
+		map.removeKeyStrokeBinding(enter);
+	}
+
+	static private URL default_gliserver_url=null;
+
+	public Gatherer(String[] args) 
+	{
+		// Display the version to make error reports a lot more useful
+		System.err.println("Version: " + PROGRAM_VERSION + "\n");
+
+		JarTools.initialise(this); 
+
+		GetOpt go = new GetOpt(args);
+
+		// Remember the GSDLOS value
+		client_operating_system = go.client_operating_system;
+
+		// If feedback is enabled, set up the recorder dialog
+		if (go.feedback_enabled) {
+			// Use the default locale for now - this will be changed by the Gatherer run method
+			feedback_enabled = true;
+			feedback_dialog = new ActionRecorderDialog(Locale.getDefault());
+		}
+
+		// Are we using a remote Greenstone?
+		if (go.use_remote_greenstone) {
+			isGsdlRemote = true;
+
+			// We don't have a local Greenstone!
+			go.gsdl3_path=null;
+			go.gsdl3_src_path=null;
+
+			// Don't set go.gsdl_path to null, since gdsl_path may still be set 
+			// if we have a client-gli containing gs2build folder. 
+			// However, keep track of whether we can download.
+			if(go.gsdl_path == null) {
+				isDownloadEnabled = false;
+			}
+
+			// We have to use our own collect directory since we can't use the Greenstone one
+			setCollectDirectoryPath(getGLIUserDirectoryPath() + "collect" + File.separator);
+		}
+		// We have a local Greenstone. OR we have a gs2build folder inside 
+		// the client-GLI folder (with which the Download panel becomes enabled) 
+		if(isDownloadEnabled) {
+			LocalGreenstone.setDirectoryPath(go.gsdl_path);
+		}
+
+		// Users may specify a non-standard collect directory (eg. when running one GLI in a network environment)
+		// Processing of custom non-standard collect directory passed into gli now happens in init()
+		//if (go.collect_directory_path != null) { setCollectDirectoryPath(go.collect_directory_path); }
+
+		// More special code for running with a remote Greenstone
+		if (isGsdlRemote) {
+			if (go.fedora_info.isActive()) {
+				Configuration.TEMPLATE_CONFIG_XML = "xml/" + Configuration.FEDORA_CONFIG_PREFIX + "configRemote.xml";
+			}
+			else {
+				Configuration.TEMPLATE_CONFIG_XML = "xml/configRemote.xml";
+			}
+
+			Configuration.CONFIG_XML = "configRemote.xml"; 
+
+			File collect_directory = new File(Gatherer.getCollectDirectoryPath());
+			if (!collect_directory.exists() && !collect_directory.mkdir()) {
+				System.err.println("Warning: Unable to make directory: " + collect_directory);
+			}
+		}
+		else {	    
+			if (go.fedora_info.isActive()) {
+				Configuration.TEMPLATE_CONFIG_XML = "xml/" + Configuration.FEDORA_CONFIG_PREFIX + Configuration.CONFIG_XML;
+			}
+			// else, the CONFIG_XML uses the default config file, which is for the local GS server
+		}
+
+		init(go.gsdl_path, go.gsdl3_path, go.gsdl3_src_path, 
+		go.fedora_info,
+		go.local_library_path, go.library_url_string, 
+		go.gliserver_url_string, go.debug, go.perl_path, go.no_load, go.filename, go.site_name,
+		go.servlet_path, go.collect_directory_path);
+	}
+
+
+	public void init(String gsdl_path, String gsdl3_path, String gsdl3_src_path, 
+	FedoraInfo fedora_info,
+	String local_library_path,
+	String library_url_string, String gliserver_url_string, boolean debug_enabled,
+	String perl_path, boolean no_load, String open_collection, 
+	String site_name, String servlet_path, String collect_directory_path) 
+	{
+		if (gsdl3_path != null && !gsdl3_path.equals("")) {
+			this.GS3 = true;
+		} else {
+			gsdl3_path = null;
+			gsdl3_src_path = null;
+		}
+
+		// Create the debug stream if required
+		if (debug_enabled) {
+			DebugStream.enableDebugging();
+
+			Calendar now = Calendar.getInstance();
+			String debug_file_path = "debug" + now.get(Calendar.DATE) + "-" + now.get(Calendar.MONTH) + "-" + now.get(Calendar.YEAR) + ".txt";
+
+			// Debug file is created in the user's GLI directory
+			debug_file_path = getGLIUserDirectoryPath() + debug_file_path;
+			DebugStream.println("Debug file path: " + debug_file_path);
+			DebugStream.setDebugFile(debug_file_path);
+			DebugStream.print(System.getProperties());
+		}
+
+		// Delete plugins.dat and classifiers.dat files from previous versions of the GLI (no longer used)
+		File plugins_dat_file = new File(Gatherer.getGLIUserDirectoryPath() + "plugins.dat");
+		if (plugins_dat_file.exists()) {
+			System.err.println("Deleting plugins.dat file...");
+			Utility.delete(plugins_dat_file);
+		}
+		File classifiers_dat_file = new File(Gatherer.getGLIUserDirectoryPath() + "classifiers.dat");
+		if (classifiers_dat_file.exists()) {
+			System.err.println("Deleting classifiers.dat file...");
+			Utility.delete(classifiers_dat_file);
+		}
+
+		try {
+			// Load GLI config file
+			new Configuration(getGLIUserDirectoryPath(), gsdl_path, gsdl3_path, gsdl3_src_path, site_name,
+			fedora_info);
+			
+			// Check we know where Perl is
+			Configuration.perl_path = perl_path;
+			if (isGsdlRemote && !isDownloadEnabled && Utility.isWindows() && Configuration.perl_path != null) {
+				if (Configuration.perl_path.toLowerCase().endsWith("perl.exe")) {
+					Configuration.perl_path = Configuration.perl_path.substring(0, Configuration.perl_path.length() - "perl.exe".length());
+				}
+				if (Configuration.perl_path.endsWith(File.separator)) {
+					Configuration.perl_path = Configuration.perl_path.substring(0, Configuration.perl_path.length() - File.separator.length());
+				}
+			}
+
+			// the feedback dialog has been loaded with a default locale, 
+			// now set the user specified one
+			if (feedback_enabled && feedback_dialog != null) {
+				feedback_dialog.setLocale(Configuration.getLocale("general.locale", true));
+			}
+
+			// Read Dictionary
+			new Dictionary(Configuration.getLocale("general.locale", true), Configuration.getFont("general.font", true));
+
+			// check that we are using Sun Java
+			String java_vendor = System.getProperty("java.vendor");
+			if (!java_vendor.equals("Sun Microsystems Inc.")
+			    && !java_vendor.equals("Oracle Corporation")) {
+				System.err.println(Dictionary.get("General.NotSunJava", java_vendor));
+			}
+
+			// Unless we're using remote building, we need to know where the local Greenstone is
+			if (!isGsdlRemote && gsdl_path == null) {
+				missingGSDL();
+			}
+
+			if (fedora_info.isActive()) {
+
+			    // if we have either a remote GS 2/3 server, or if we are dealing with a 
+			    // local GS2, or else if it's a local GS3 but with fedora installed elsewhere, 
+			    // we ask for fedora login details now
+			    File fedora_webapp = new File(gsdl3_src_path+File.separator+"packages"+File.separator+"tomcat"+File.separator+"webapps"+File.separator+"fedora");
+			    
+			    if(isGsdlRemote || !GS3 || !fedora_webapp.exists()) {
+				popupFedoraInfo();
+			    }
+
+			    // else if we have a local GS3 with fedora installed within it, we'll be starting 
+			    // up fedora further below, together with the local tomcat
+			}
+			
+			// the no_load flag to GLI is processed at the end of handling open_collection_file_path
+			open_collection_file_path = open_collection;
+			if (open_collection_file_path == null) {
+				open_collection_file_path = Configuration.getString(
+				"general.open_collection"+Configuration.gliPropertyNameSuffix(), true);
+			}
+			
+			initCollectDirectoryPath(); 
+			
+			if (no_load || (isGsdlRemote && open_collection_file_path.equals(""))) {
+				open_collection_file_path = null;
+			}
+			
+			// Finally, we're ready to find out the version of the remote Greenstone server 
+			if(isGsdlRemote) {
+				// instantiate the RemoteGreenstoneServer object first
+				remoteGreenstoneServer = new RemoteGreenstoneServer();
+
+				// Set up proxy
+				setProxy();
+				// Now we set up an Authenticator
+				Authenticator.setDefault(new GAuthenticator());
+
+				int greenstoneVersion = 2;
+				requestGLIServerURL();
+				gliserver_url_string = Configuration.gliserver_url.toString();
+				
+				greenstoneVersion = remoteGreenstoneServer.getGreenstoneVersion();
+				// Display the version to make error reports a lot more useful
+				System.err.println("Remote Greenstone server version: " + greenstoneVersion);
+				if(greenstoneVersion == -1) { // remote server not running
+					Gatherer.exit();
+				}
+				if(greenstoneVersion >= 3) {
+					this.GS3 = true;
+					Configuration.prepareForGS3();
+				} 
+				
+				if(fedora_info.isActive()) { 
+					// when GS server is remote, FEDORA_HOME resides on the remote server side,
+					// but we know the library URL from user-provided information
+					library_url_string = fedora_info.getLibraryURL();
+				} else {
+					library_url_string = remoteGreenstoneServer.getLibraryURL(Configuration.gliserver_url.toString());
+				}
+				// write it into the config file
+				Configuration.setString("general.library_url", true, library_url_string);
+			}
+			else { // local greenstone: Start up the local library server, if that's what we want
+				if (!GS3) {		
+					isLocalLibrary = LocalLibraryServer.start(gsdl_path, local_library_path);
+				}
+				else if (!isGsdlRemote) { // local GS3, start the local tomcat
+				    
+					GS3ServerThread thread = new GS3ServerThread(gsdl3_src_path, "restart");
+					thread.start();
+	
+					// The Java virtual machine shuts down in response to two kinds of events:
+					// - The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or
+					// - The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.
+					// https://coderanch.com/t/328888/java/Killing-process-spawned-Runtime-exec
+					Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+						
+						public void run() {					    
+System.err.println("ShutDownHook called...");	
+
+						    if(Gatherer.exit != true) { // unexpected termination, such as Ctrl-C won't set Gatherer.exit
+							// so still need to at least issue the ant call to stop any running GS3
+							// (tomcat and networked Derby Server) by calling GS3ServerThread.stopServer()
+						
+							System.err.println("Attempting to forcibly terminate the GS3 server...");
+							GS3ServerThread.stopServer();
+						    }
+						}
+					    }));
+					
+
+
+					// If fedora installed inside this local GS3, then ask for fedora login details now.
+					if (fedora_info.isActive()) { 
+					    File fedora_webapp = new File(gsdl3_src_path+File.separator+"packages"+File.separator+"tomcat"+File.separator+"webapps"+File.separator+"fedora");
+					    
+					    if(fedora_webapp.exists()) {
+						System.err.println("**** Waiting for the local Greenstone server to start up fedora...");
+						popupFedoraInfo();
+					    }
+					}
+				}
+				// else web library: GS server is local, but doesn't use the webserver included with GS2
+			}
+			
+			// The "-library_url" option overwrites anything in the config files
+			if (library_url_string != null && library_url_string.length() > 0) {
+				try {
+					System.err.println("Setting library_url to " + library_url_string + "...");
+					Configuration.library_url = new URL(library_url_string);
+				}
+				catch (MalformedURLException error) {
+					DebugStream.printStackTrace(error);
+				}
+			}
+
+
+			// Check that we now know the Greenstone library URL, since we need this for previewing collections
+			// It is not necessary if an independent GSI was launched and the user hasn't pressed Enter Library yet
+			DebugStream.println("Configuration.library_url = " + Configuration.library_url);
+			if (Configuration.library_url == null) {
+				if(isLocalLibrary) {
+					if(!LocalLibraryServer.isURLPending()) {
+						missingEXEC();
+					}
+					// else LocalLibraryServer is expecting a URL soon, so don't need to display the dialog
+				} else { // GS2 webLibrary or GS3 or remote Greenstone
+					missingEXEC();
+				}
+			}
+
+			// The "-gliserver_url" option overwrites anything in the config files
+			if (gliserver_url_string != null && gliserver_url_string.length() > 0) {
+				try {
+					System.err.println("Setting gliserver_url to " + gliserver_url_string + "...");
+					Configuration.gliserver_url = new URL(gliserver_url_string);
+				}
+				catch (MalformedURLException error) {
+					DebugStream.printStackTrace(error);
+				}
+			}
+
+			// If we're using a remote Greenstone we need to know where the gliserver script is
+			DebugStream.println("Configuration.gliserver_url = " + Configuration.gliserver_url);
+
+			if (GS3) {
+				// Load Greenstone 3 servlet configuration
+				if (isGsdlRemote){
+					servlet_config = new ServletConfiguration(Configuration.gli_user_directory_path);
+				}else{
+					servlet_config= new ServletConfiguration(gsdl3_path);
+				}
+			}
+
+			if (GS3 && Configuration.servlet_path == null) {
+				Configuration.servlet_path = servlet_config.getServletPath(Configuration.site_name);
+			}
+			
+			// ensure that a directory called 'cache' exists in the GLI user directory  
+			File user_cache_dir = new File(Gatherer.getGLIUserCacheDirectoryPath());
+			System.err.println("User cache dir: " + Gatherer.getGLIUserCacheDirectoryPath());
+			if (!user_cache_dir.exists() && !user_cache_dir.mkdir()) {
+				System.err.println("Warning: Unable to make directory: " + user_cache_dir);
+			}
+
+
+			if (Gatherer.isGsdlRemote) {
+				DebugStream.println("Not checking for perl path/exe");
+			}
+			else {
+				// Perl path is a little different as it is perfectly ok to
+				// start the GLI without providing a perl path
+				boolean found_perl = false;
+				if (Configuration.perl_path != null) {
+					// See if the file pointed to actually exists
+					File perl_file = new File(Configuration.perl_path);
+					found_perl = perl_file.exists();
+					perl_file = null;
+				}
+				if (Configuration.perl_path == null || !found_perl) {
+					// Run test to see if we can run perl as is.
+					PerlTest perl_test = new PerlTest();
+					if (perl_test.found()) {
+						// If so replace the perl path with the system
+						// default (or null for unix).
+						Configuration.perl_path = perl_test.toString();
+						found_perl = true;
+					}
+				}
+				if (!found_perl) {
+					// Time for an error message.
+					missingPERL();
+				}
+			}
+
+
+			// Check for ImageMagick - dependent on perl_path
+			if (Gatherer.isGsdlRemote) {
+				DebugStream.println("Not checking for ImageMagick.");
+			}
+			else if (!(new ImageMagickTest()).found()) {
+				// Time for a warning message
+				missingImageMagick();
+			}
+			
+			// Check for PDFBox
+			if (Gatherer.isGsdlRemote) {
+				DebugStream.println("Not checking for PDFBox.");
+			}
+			else {			    
+				String gs_dir = gsdl_path; // for GS3, pdf-box is in gs2build/ext (not in GS3/ext), for GS2 it's also in GS2/ext
+			    File pdfboxExtensionFolder = new File(gs_dir+File.separator+"ext"+File.separator+"pdf-box");
+			    if (!(pdfboxExtensionFolder.exists() && pdfboxExtensionFolder.isDirectory())) {
+				// The user doesn't have PDFBox, inform them of it
+				String zipExtension = Utility.isWindows() ? "zip" : "tar.gz";
+				missingPDFBox(zipExtension, pdfboxExtensionFolder.getParent());
+			    }
+			}
+
+			// Check that the local can support multiple filename encodings
+			//System.err.println("#### Java identifies current Locale as (file.encoding): "
+			//	+ System.getProperty("file.encoding"));
+			if(System.getProperty("file.encoding").equals("UTF-8")){
+				// If the locale is UTF-8, Java will interpret all filename bytes as UTF-8, 
+				// which is a destructive process as it will convert characters not recognised
+				// by UTF-8 into the invalid character, rather than preserving the bytecodes.
+				// This has the effect that non-UTF8 encoded filenames on a system set to a 
+				// UTF-8 locale are not 'seen' by Java (if they contain non-ASCII characters).
+				multipleFilenameEncodingsNotSupported();		
+				FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED = false; 
+				FilenameEncoding.URL_FILE_SEPARATOR = File.separator;
+			} else {
+				FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED = true;
+				FilenameEncoding.URL_FILE_SEPARATOR = "/"; // URL file separator is always "/" 
+			}
+			
+			// Set the default font for all Swing components.
+			FontUIResource default_font = Configuration.getFont("general.font", true);
+			Enumeration keys = UIManager.getDefaults().keys();
+			while (keys.hasMoreElements()) {
+				Object key = keys.nextElement();
+				Object value = UIManager.get(key);
+				if (value instanceof FontUIResource) {
+					UIManager.put(key, default_font);
+				}
+			}
+
+			// At this point (which is where this code originally used to be), we can set up the proxy for the
+			// non-remote case. The remote Greenstone server would already have setup its proxy when required.
+			if(!isGsdlRemote) {
+				setProxy();
+				// Now we set up an Authenticator
+				Authenticator.setDefault(new GAuthenticator());
+			}
+			assoc_man = new FileAssociationManager();
+			// Create File Manager
+			f_man = new FileManager();
+			// Create Collection Manager
+			c_man = new CollectionManager();
+			// Create Recycle Bin
+			recycle_bin = new RecycleBin();
+
+			if (GS3) {
+				if (site_name==null) {
+					site_name = Configuration.site_name;
+					servlet_path = null; // need to reset this
+				}
+				if (servlet_path == null) {
+					servlet_path = Configuration.getServletPath();
+				}
+			}
+			
+			
+		} catch (Exception exception) {
+			DebugStream.printStackTrace(exception);
+		}
+
+		
+		// Create GUI Manager (last) or else suffer the death of a thousand NPE's
+		g_man = new GUIManager(size);
+
+		// Get a list of the core Greenstone classifiers and plugins
+		Classifiers.loadClassifiersList(null);
+		Plugins.loadPluginsList(null);
+
+		// Users may specify a non-standard collect directory (eg. when running one GLI in a network environment)
+		// Set to any custom collection directory provided to gli. This happens if gli was run as:
+		// ./gli.sh -collectdir </full/path/to/custom/collect>
+		if(collect_directory_path != null) {
+
+		    // create a version of the collect_dir_path without a file-separator at end
+		    String collectDir = collect_directory_path; 
+		    if(collectDir.endsWith(File.separator)) {
+			collectDir = collectDir.substring(0, collectDir.length()-1); // remove file separator at end
+		    }
+
+		    // update .gli/config.xml to contain the version of the path without file-separator at end
+		    if(collect_directory_path.equals(getDefaultGSCollectDirectoryPath(false))) {
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+						true, "");
+		    } else {
+			Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+						true, collectDir);
+		    }
+
+		    // set non_standard_collect_directory_path variable. Ensures file_separator at end
+		    Gatherer.setCollectDirectoryPath(collect_directory_path); 
+
+		    // Use version of path without file-separator at end to set collecthome in gsdlsite(3).cfg
+		    if(collectDir != null) {
+			collectDir = "\""+collectDir+"\"";
+		    }
+		    Utility.updatePropertyConfigFile(getGsdlSiteConfigFile(), "collecthome", collectDir);
+		    // if gsdlsite.cfg does not exist (if using server.exe for instance), the above method will just return
+		}
+
+		// If using a remote Greenstone we need to download the collection configurations now
+		if (Gatherer.isGsdlRemote) {
+			if (remoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
+				// !! Something went wrong downloading the collection configurations
+				System.err.println("Error: Could not download collection configurations.");
+				if(!Gatherer.isApplet) { // don't close the browser if it is an applet!
+					System.exit(0);
+				}
+			}
+		}
+	}
+
+	
+	/** Returns the correct version of the (local or remote) Greenstone server if init() has already been called. */
+	public static int serverVersionNumber() { 
+		return GS3 ? 3 : 2;
+	} 
+
+	/** Returns "Server: version number" if init() has already been called. */
+	public static String getServerVersionAsString() { 
+		return "Server: v" + serverVersionNumber();
+	} 
+
+	public void openGUI()
+	{
+		// Size and place the frame on the screen
+		Rectangle bounds = Configuration.getBounds("general.bounds", true);
+		if (bounds == null) {
+			// Choose a sensible default value
+			bounds = new Rectangle(0, 0, 640, 480);
+		}
+		
+		// Ensure width and height are reasonable
+		size = bounds.getSize();
+		if (size.width < 640) {
+			size.width = 640;
+		}
+		else if (size.width > Configuration.screen_size.width && Configuration.screen_size.width > 0) {
+			size.width = Configuration.screen_size.width;
+		}
+		if (size.height < 480) {
+			size.height = 480;
+		}
+		else if (size.height > Configuration.screen_size.height && Configuration.screen_size.height > 0) {
+			size.height = Configuration.screen_size.height;
+		}
+
+		if (!g_man_built) {
+
+			g_man.display();
+
+			// Place the window in the desired location on the screen, if this is do-able (not under most linux window managers apparently. In fact you're lucky if they listen to any of your screen size requests).
+			g_man.setLocation(bounds.x, bounds.y);
+			g_man.setVisible(true);
+
+			// After the window has been made visible, check that it is in the correct place
+			// sometimes java places a window not in the correct place, 
+			// but with an offset. If so, we work out what the offset is
+			// and change the desired location to take that into account 
+			Point location = g_man.getLocation();
+			int x_offset = bounds.x - location.x;
+			int y_offset = bounds.y - location.y;
+			// If not, offset the window to move it into the correct location
+			if (x_offset > 0 || y_offset > 0) {
+				///ystem.err.println("changing the location to "+(bounds.x + x_offset)+" "+ (bounds.y + y_offset));
+				g_man.setLocation(bounds.x + x_offset, bounds.y + y_offset);
+			}
+
+			// The 'after-display' triggers several events which don't occur until after the visual components are actually available on screen. Examples of these would be the various html renderings, as they can't happen offscreen.
+			g_man.afterDisplay();
+			g_man_built = true;
+		}
+		else {
+			g_man.setVisible(true);
+		}
+
+		// Get a list of the core Greenstone classifiers and plugins
+		/*Classifiers.loadClassifiersList(null);
+	Plugins.loadPluginsList(null);
+
+	// If using a remote Greenstone we need to download the collection configurations now
+	if (Gatherer.isGsdlRemote) {
+		if (remoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
+		// !! Something went wrong downloading the collection configurations
+		System.err.println("Error: Could not download collection configurations.");
+		System.exit(0);
+		}
+	}*/
+
+		// If there was a collection left open last time, reopen it	
+		if (open_collection_file_path == null || new File(Gatherer.open_collection_file_path).isDirectory()) {
+			
+			//the menu bar items, file and edit, are disabled from the moment of their creation. if there is no left-over collection from the last session, enable them; otherwise it's disabled until the collection finishes loading. They will be enabled in collectionManager.java
+			setMenuBarEnabled(true);
+		} else {
+
+			// If there was a collection left open last time, reopen it
+			c_man.openCollectionFromLastTime();
+		}
+	}
+
+	public static void setMenuBarEnabled(boolean enabled) {
+		g_man.menu_bar.file.setEnabled(enabled);
+		g_man.menu_bar.edit.setEnabled(enabled);
+	}
+
+	/** Exits the Gatherer after ensuring that things needing saving are saved.
+	* @see java.io.FileOutputStream
+	* @see java.io.PrintStream
+	* @see java.lang.Exception
+	* @see javax.swing.JOptionPane
+	* @see org.greenstone.gatherer.Configuration
+	* @see org.greenstone.gatherer.collection.CollectionManager
+	* @see org.greenstone.gatherer.gui.GUIManager
+	*/
+	static public void exit(int new_exit_status)
+	{
+		System.err.println("In Gatherer.exit()...");
+		DebugStream.println("In Gatherer.exit()...");
+		exit = true;
+		if (new_exit_status != 0) {
+			// default exit_status is already 0 
+			// only remember a new exit status if it is non-trivial
+			exit_status = new_exit_status;
+		}
+
+		// Save the file associations
+		if (assoc_man != null) {
+			assoc_man.save();
+			assoc_man = null;
+		}
+
+		// Get the gui to deallocate
+		if(g_man != null) {
+			g_man.destroy();
+			g_man_built = false;
+		}
+
+		// Flush debug
+		DebugStream.closeDebugStream();
+		
+		// If we started a server, we should try to stop it.
+		if (LocalLibraryServer.isRunning() == true) {		
+			LocalLibraryServer.stop();
+		}
+
+		// If we're using a remote Greenstone server we need to make sure that all jobs have completed first
+		if (isGsdlRemote) {
+			remoteGreenstoneServer.exit();
+		} else if (GS3) { // stop the local tomcat web server when running GS3
+		    // can't call ant stop from its own thread - what if GLI has exited by then?
+		    // issue call to ant stop from the main GLI thread
+		    //GS3ServerThread thread = new GS3ServerThread(Configuration.gsdl_path, "stop");
+		    //thread.start();
+
+		    GS3ServerThread.stopServer();
+		    
+		}
+
+		// Make sure we haven't started up any processes that are still running
+		if (apps.size() == 0) {
+			// If we're running as an applet we don't actually quit (just hide the main GLI window)
+			if (!Gatherer.isApplet) {
+				// This is the end...
+				System.exit(exit_status);
+			}
+		}
+		else {
+			JOptionPane.showMessageDialog(g_man, Dictionary.get("General.Outstanding_Processes"), Dictionary.get("General.Outstanding_Processes_Title"), JOptionPane.ERROR_MESSAGE);
+			g_man.setVisible(false);
+		}
+	}
+
+	static public void exit()
+	{
+		exit(0);
+	}
+
+	/** Returns the path of the current collect directory. */
+	static public String getCollectDirectoryPath()
+	{
+		if (non_standard_collect_directory_path != null) {
+			return non_standard_collect_directory_path;
+		}
+		
+		return getDefaultGSCollectDirectoryPath(true); // file separator appended
+		
+	}
+	
+	// if we need to know whether the local server we are running is server.exe vs apache web server
+	static public boolean isPersistentServer() {
+		return (!isGsdlRemote && LocalLibraryServer.isPersistentServer());
+	}
+
+	/** Returns the path of the Greenstone "collect" directory. */
+	static public String getDefaultGSCollectDirectoryPath(boolean appendSeparator) {
+		String colDir;
+		if (GS3) {
+			colDir = getSitesDirectoryPath() + Configuration.site_name + File.separator + "collect";			
+		}
+		else {
+			colDir = Configuration.gsdl_path + "collect";
+		}
+		
+		if(appendSeparator) {
+			colDir += File.separator;
+		}
+		return colDir;
+	}	
+
+	/** Returns the path of the GLI directory. */
+	static public String getGLIDirectoryPath()
+	{
+		return gli_directory_path;
+	}
+
+
+	/** Returns the path of the GLI "metadata" directory. */
+	static public String getGLIMetadataDirectoryPath()
+	{
+		return getGLIDirectoryPath() + "metadata" + File.separator;
+	}
+
+
+	/** Returns the path of the GLI user directory. */
+	static public String getGLIUserDirectoryPath()
+	{
+		return gli_user_directory_path;
+	}
+
+
+	/** Returns the path of the GLI user "cache" directory. */
+	static public String getGLIUserCacheDirectoryPath()
+	{
+		return getGLIUserDirectoryPath() + "cache" + File.separator;
+	}
+
+
+	/** Returns the path of the GLI user "log" directory. */
+	static public String getGLIUserLogDirectoryPath()
+	{
+		return getGLIUserDirectoryPath() + "log" + File.separator;
+	}
+
+
+	static public String getSitesDirectoryPath()
+	{
+		return Configuration.gsdl3_path + "sites" + File.separator;
+	}
+
+
+	static public void setCollectDirectoryPath(String collect_directory_path)
+	{
+		non_standard_collect_directory_path = collect_directory_path;
+		if (!non_standard_collect_directory_path.endsWith(File.separator)) {
+			non_standard_collect_directory_path = non_standard_collect_directory_path + File.separator;
+		}
+	}
+
+
+	static public void setGLIDirectoryPath(String gli_directory_path_arg)
+	{
+		gli_directory_path = gli_directory_path_arg;
+	}
+
+
+	static public void setGLIUserDirectoryPath(String gli_user_directory_path_arg)
+	{
+		gli_user_directory_path = gli_user_directory_path_arg;
+
+		// Ensure the GLI user directory exists
+		File gli_user_directory = new File(gli_user_directory_path);
+		if (!gli_user_directory.exists() && !gli_user_directory.mkdirs()) {
+			System.err.println("Error: Unable to make directory: " + gli_user_directory);
+		}
+	}
+
+
+	public static void initCollectDirectoryPath() {		
+		String defaultColdir = getDefaultGSCollectDirectoryPath(false); // no file separator at end
+		String coldir = defaultColdir;
+		// If local GS and opening a collection outside the standard GS collect folder,
+		// need to open the non-standard collect folder that the collection resides in
+		if (!isGsdlRemote 
+		    && !open_collection_file_path.startsWith(defaultColdir))
+		{  
+			File collectFolder = null;
+			
+			if(!open_collection_file_path.equals("")) {
+				if(!open_collection_file_path.endsWith("gli.col")) { // then it's a collect folder
+					collectFolder = new File(open_collection_file_path);
+				} else {
+					// the filepath is a gli.col file. To get the collect folder: the 1st level up
+					// is the collection folder, 2 two levels up is the containing collect folder
+					collectFolder = new File(open_collection_file_path).getParentFile().getParentFile();
+				}	
+				
+				// Need to deal with colgroups as well: while there's an etc/collect.cfg
+				// in the current collectFolder, move one level up
+				String cfg_file = (Gatherer.GS3)? Utility.CONFIG_GS3_FILE : Utility.CONFIG_FILE;
+				if(new File(collectFolder.getAbsolutePath()+File.separator+cfg_file).exists()) { // colgroup
+					collectFolder = collectFolder.getParentFile();		    
+				}
+
+				// Inform the user that their collecthome is non-standard (not inside GS installation)
+				nonStandardCollectHomeMessage(collectFolder.getAbsolutePath(), defaultColdir); // display message
+			}		
+			
+			if(collectFolder == null || !collectFolder.exists()) {
+				// if GLI config file specified no collectDir (open_collection_file_path is "")
+				// OR if dealing with a local server but the collectdir no longer exists, 
+				// use the default greenstone collect directory, and write that to affected files
+				
+				open_collection_file_path = defaultColdir;		// default GS collect dir
+				// Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), true, "");
+			} else { // use the coldir value specified in the flags to GLI or from the last GLI session				
+				coldir = collectFolder.getAbsolutePath();
+			}			
+			// set it as the current folder
+			setCollectDirectoryPath(coldir); // will ensure the required file separator at end
+		}
+		
+		if(!isGsdlRemote) {
+			// LocalLibraryServer would  already have set glisite.cfg to the correct collecthome for server.exe
+			// Here we set collecthome in gsdl(3)site.cfg for the GS2 apache web server and GS3 tomcat server
+			String gsdlsitecfg = getGsdlSiteConfigFile();			
+			// update the gsdlsite config file and store the old value for use when we exit GLI
+			if(coldir.equals(defaultColdir)) {				
+				gsdlsite_collecthome = Utility.updatePropertyConfigFile(
+				gsdlsitecfg, "collecthome", null);
+			} else {
+				gsdlsite_collecthome = Utility.updatePropertyConfigFile(
+				gsdlsitecfg, "collecthome", "\""+coldir+"\""); // no file separator
+				// if gsdlsite.cfg does not exist (if using server.exe for instance), the above method will just return
+			}
+		}		
+	}
+
+	/** depending on the version of GS being run, return the path to the current GS' installation's gsdl(3)site.cfg */
+	public static String getGsdlSiteConfigFile() {
+		if(Gatherer.GS3) { // web/WEB-INF/cgi/gsdl3site.cfg
+			return Configuration.gsdl3_path + File.separator + "WEB-INF" 
+			+ File.separator + "cgi" + File.separator + "gsdl3site.cfg";
+		} else { // cgi-bin/gsdlsite.cfg
+		    String gsdlarch = System.getenv("GSDLARCH");
+		    if(gsdlarch == null) {
+			gsdlarch = "";
+		    }
+		    return Configuration.gsdl_path /* + File.separator */
+			+ "cgi-bin" + File.separator + client_operating_system+gsdlarch + File.separator + "gsdlsite.cfg";
+		}
+	}
+
+	public static void collectDirectoryHasChanged(
+	String oldCollectPath, String newCollectPath, final Component container) 
+	{		
+		if(oldCollectPath.equals(newCollectPath)) {
+			return; // nothing to be done
+		}
+		
+		// Will use a busy cursor if the process of changing the collect directory takes more 
+		// than half a second/500ms. See http://www.catalysoft.com/articles/busyCursor.html
+		Cursor originalCursor = container.getCursor();
+		java.util.TimerTask timerTask = new java.util.TimerTask() {
+			public void run() {
+				// set the cursor on the container:
+				container.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+			}
+		};
+		java.util.Timer timer = new java.util.Timer(); 
+		
+		try {
+			timer.schedule(timerTask, 500);
+			
+			// first save any open collection in the old location, then close it
+			if(Gatherer.c_man.getCollection() != null) {
+				Gatherer.g_man.saveThenCloseCurrentCollection(); // close the current collection first
+			}
+			
+			// change to new collect path
+			if(newCollectPath.equals(getDefaultGSCollectDirectoryPath(true))) {
+				Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+				true, "");
+			} else {
+				Configuration.setString("general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+				true, newCollectPath);
+			}
+			Gatherer.setCollectDirectoryPath(newCollectPath);
+			
+
+			// refresh the Documents in Greenstone Collections
+			//WorkspaceTreeModel.refreshGreenstoneCollectionsNode();
+			Gatherer.g_man.refreshWorkspaceTreeGreenstoneCollections();
+			
+			// The web server needs to be told where a new (non-standard) collecthome home is.
+			// The web server reads collecthome from cgi-bin/<OS>/gsdlsite.cfg, where the property
+			// collecthome can be specified if a non-standard collecthome is to be used. If no 
+			// such property is specified in the file, then it assumes the standard GS collecthome.
+			// This method does nothing for a remote Greenstone.	
+			if(Gatherer.isGsdlRemote) {
+				return;
+			}	
+			
+			// non-destructive update of gsdl(3)site.cfg (comments preserved)
+			String collectDir = Gatherer.getCollectDirectoryPath();
+			//collectDir = "\"" + collectDir.substring(0, collectDir.length()-1) + "\""; // remove file separator at end	
+			collectDir = collectDir.substring(0, collectDir.length()-1); // remove file separator at end
+			if(collectDir != null) {
+				collectDir = "\""+collectDir+"\"";
+			}
+			Utility.updatePropertyConfigFile(getGsdlSiteConfigFile(), "collecthome", collectDir);
+			// if gsdlsite.cfg does not exist (if using server.exe for instance), the above method will just return
+			
+			if(!Gatherer.GS3 && Gatherer.isLocalLibrary) {
+				// for Images in the collection to work, the apache web server 
+				// configuration's COLLECTHOME should be updated on collectdir change. 
+				// Does nothing for server.exe at the moment
+
+				LocalLibraryServer.reconfigure();
+			}
+		} finally { // Note try-finally section without catch:
+			// "Java's finally clause is guaranteed to be executed even when
+			// an exception is thrown and not caught in the current scope."
+			// See http://www.catalysoft.com/articles/busyCursor.html
+			// the following code fragment is guaranteed to restore the original
+			// cursor now the custom actionPerformed() processing is complete, regardless
+			// of whether the processing method terminates normally or throws an exception
+			// and regardless of where in the call stack the exception is caught.			
+			
+			timer.cancel();
+			container.setCursor(originalCursor);
+		}
+	}
+
+
+	static public void refresh(int refresh_reason)
+	{
+		if (g_man != null) {
+
+			g_man.refresh(refresh_reason, c_man.ready());
+		}
+
+		// Now is a good time to force a garbage collect
+		System.gc();
+	}
+
+
+	// used to send reload coll messages to the tomcat server
+	static public void configGS3Server(String site, String command) {
+
+	    // Do not do configGS3Server for a GS3 solr collection. Not at present at least, when we're stopping
+	    // and starting the GS3 server from GLI for solr cols, since this function clears the solr.xml file.
+	    /*if(Gatherer.c_man.isSolrCollection()) {
+		return;
+		}*/
+
+		if (Configuration.library_url == null){
+			System.out.println("Error: you have not provided the Greenstone Library address.");  
+			return;
+
+		} 
+		
+		try {
+			// need to add the servlet name to the exec address
+			String raw_url = Configuration.library_url.toString() + Configuration.getServletPath() + command;
+			URL url = new URL(raw_url);
+			DebugStream.println("Action: " + raw_url);
+			HttpURLConnection library_connection = (HttpURLConnection) url.openConnection();
+			int response_code = library_connection.getResponseCode();
+			if(HttpURLConnection.HTTP_OK <= response_code && response_code < HttpURLConnection.HTTP_MULT_CHOICE) {
+				DebugStream.println("200 - Complete.");
+			}
+			else {
+				DebugStream.println("404 - Failed.");
+			}
+			url = null;
+		}
+		catch(java.net.ConnectException connectException) {
+			JOptionPane.showMessageDialog(g_man, Dictionary.get("Preferences.Connection.Library_Path_Connection_Failure", Configuration.library_url.toString()), Dictionary.get("General.Warning"), JOptionPane.WARNING_MESSAGE);
+			DebugStream.println(connectException.getMessage());
+		}
+		catch (Exception exception) {            
+			DebugStream.printStackTrace(exception);
+		}
+	}
+
+
+	/** Used to 'spawn' a new child application when a file is double clicked.
+	* @param file The file to open 
+	* @see org.greenstone.gatherer.Gatherer.ExternalApplication
+	*/
+	static public void spawnApplication(File file) {
+		String [] commands = assoc_man.getCommand(file);
+		if(commands != null) {
+			ExternalApplication app = new ExternalApplication(commands);
+			apps.add(app);
+			app.start();
+		}
+		else {
+			///ystem.err.println("No open command available.");
+		}
+	}
+
+
+	static public void spawnApplication(String command)
+	{
+		ExternalApplication app = new ExternalApplication(command);
+		apps.add(app);
+		app.start();
+	}
+
+	static public void spawnApplication(String command, String ID)
+	{
+		ExternalApplication app = new ExternalApplication(command, ID);
+		apps.add(app);
+		app.start();
+	}
+
+	static public void spawnApplication(String[] commands, String ID)
+	{
+		ExternalApplication app = new ExternalApplication(commands, ID);
+		apps.add(app);
+		app.start();
+	}
+
+	static public void terminateApplication(String ID) {
+		for(int i = 0; i < apps.size(); i++) {
+			ExternalApplication app = (ExternalApplication)apps.get(i);
+			if(app.getID() != null && app.getID().equals(ID)) {
+				app.stopExternalApplication();
+				apps.remove(app);
+			}
+		}
+	}
+
+
+	/** Used to 'spawn' a new  browser application or reset an existing one when the preview button is clicked 
+	* @param url The url to open the browser at
+	* @see org.greenstone.gatherer.Gatherer.BrowserApplication
+	*/    
+	static public void spawnBrowser(String url) {
+		String command = assoc_man.getBrowserCommand(url);
+		if (command != null) {
+			BrowserApplication app = new BrowserApplication(command, url);
+			apps.add(app);
+			app.start();
+		}
+		else {
+			///ystem.err.println("No browser command available."); 
+		}
+	}
+
+
+	/** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer.
+	*/
+	static public void missingEXEC() {
+		WarningDialog dialog;
+		String configPropertyName = "general.library_url"+Configuration.gliPropertyNameSuffix();
+
+		if (GS3) {
+			// Warning dialog with no cancel button and no "turn off warning" checkbox 
+			dialog = new WarningDialog("warning.MissingEXEC", Dictionary.get("MissingEXEC_GS3.Title"), Dictionary.get("MissingEXEC_GS3.Message"), configPropertyName, false, false);
+		} else { // local case
+			dialog = new WarningDialog("warning.MissingEXEC", Dictionary.get("MissingEXEC.Title"), Dictionary.get("MissingEXEC.Message"), configPropertyName, false);
+		}
+
+		JTextField field = new URLField.Text(Configuration.getColor("coloring.editable_foreground", false), Configuration.getColor("coloring.editable_background", false));
+
+		// Set the default library URL to the tomcat server and port number
+		// specified in the build.properties located in the gsdl3_src_path 
+		if (GS3) {
+			String host = "localhost";
+			String port = "8383";
+			
+			File buildPropsFile = new File(Configuration.gsdl3_src_path + File.separator + "build.properties");
+			if(buildPropsFile.exists()) {
+				Properties props = new Properties();
+				try{
+					props.load(new FileInputStream(buildPropsFile));
+					host = props.getProperty("tomcat.server", host); 
+					port = props.getProperty("tomcat.port", port);
+				}catch(Exception e){
+					DebugStream.println("Could not load build.properties file");
+					System.err.println("Could not load build.properties file");
+				}
+				props = null;
+			}
+			String defaultURL = "http://"+host+":"+port+"/"+"greenstone3";
+			field.setText(defaultURL);
+			field.selectAll();
+		}
+		dialog.setValueField(field);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+
+		String library_url_string = Configuration.getString(configPropertyName, true);
+		if (!library_url_string.equals("")) {
+			try {
+				// WarningDialog does not allow invalid URLs, so the following is ignored:
+				// make sure the URL the user provided contains the http:// prefix
+				// and then save the corrected URL
+				if(!library_url_string.startsWith("http://") 
+						&& !library_url_string.startsWith("https://")) {
+					library_url_string = "http://"+library_url_string;
+					Configuration.setString(configPropertyName, true, configPropertyName);
+				}
+				Configuration.library_url = new URL(library_url_string);
+			}
+			catch (MalformedURLException exception) {
+				DebugStream.printStackTrace(exception);
+			}
+		}
+	}
+
+
+
+	/** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer.
+	*/
+	static private void popupFedoraInfo() {
+
+		FedoraLogin dialog = new FedoraLogin("Fedora Login", false);
+
+		if (Configuration.library_url == null) {
+			
+			String library_url_string = dialog.getLibraryURL();
+			if (!library_url_string.equals("")) {
+				try {
+					Configuration.library_url = new URL(library_url_string);
+				}
+				catch (MalformedURLException exception) {
+					DebugStream.printStackTrace(exception);
+				}
+			}
+		}
+		
+		boolean showLogin = true;
+		do {
+			if(!dialog.loginRequested()) { // user pressed cancel to exit the FedoraLogin dialog
+				System.exit(0);
+			} else {
+				showLogin = dialog.loginRequested();
+				String hostname = dialog.getHostname();
+				String port     = dialog.getPort();
+				String username = dialog.getUsername();
+				String password = dialog.getPassword();
+				String protocol = dialog.getProtocol();
+				
+				Configuration.fedora_info.setHostname(hostname);
+				Configuration.fedora_info.setPort(port);
+				Configuration.fedora_info.setUsername(username);
+				Configuration.fedora_info.setPassword(password);
+				Configuration.fedora_info.setProtocol(protocol);
+				
+				String ping_url_str = protocol + "://" + hostname + ":" + port + "/fedora";
+				String login_str = username + ":" + password;
+				
+				String login_encoding = Base64.encodeBytes(login_str.getBytes());
+				
+				try {
+					URL ping_url = new URL(ping_url_str);
+					URLConnection uc = ping_url.openConnection();
+					uc.setRequestProperty  ("Authorization", "Basic " + login_encoding);
+					// Attempt to access some content ...
+					InputStream content = (InputStream)uc.getInputStream();
+					
+					// if no exception occurred in the above, we would have come here:
+					showLogin = false;
+					dialog.dispose();
+				}
+				catch (Exception exception) {
+					// TODO: move into dictionary
+					String[] errorMessage = {"Failed to connect to the Fedora server.", "It might not be running, or",
+						"incorrect username and/or password."};
+					dialog.setErrorMessage(errorMessage);
+					//DebugStream.printStackTrace(exception);
+					// exception occurred, show the dialog again (do this after printing to
+					// debugStream, else the above does not get done for some reason).
+					dialog.setVisible(true);
+				}
+			} 
+		} while(showLogin);
+
+		dialog = null; // no more need of the dialog
+		
+		// Now we are connected.
+	}
+
+
+
+	static private void requestGLIServerURL()
+	{
+		WarningDialog dialog;
+		String[] defaultURLs = {
+			"http://localhost:8080/greenstone3/cgi-bin/gliserver.pl", 
+			"http://localhost:8080/gsdl/cgi-bin/gliserver.pl"
+		};
+
+		// Warning dialog with no cancel button and no "turn off warning" checkbox 
+		// (since user-input of the gliserver script is mandatory)
+		dialog = new WarningDialog("warning.MissingGLIServer", Dictionary.get("MissingGLIServer.Title"), Dictionary.get("MissingGLIServer.Message"), "general.gliserver_url", false, false); 
+
+		dialog.setValueField(new URLField.DropDown(Configuration.getColor("coloring.editable_foreground", false), 
+		Configuration.getColor("coloring.editable_background", false), 
+		defaultURLs, "general.gliserver_url", 
+		"general.open_collection"+Configuration.gliPropertyNameSuffix(), 
+		"gliserver.pl")); 
+
+		if (Gatherer.default_gliserver_url!=null){
+			dialog.setValueField(Gatherer.default_gliserver_url.toString());
+		}
+
+		// A WarningDialog cannot always be made to respond (let alone to exit the program) on close. We
+		// handle the response of this particular WarningDialog here: a URL for gliserver.pl is a crucial
+		// piece of user-provided data. Therefore, if no URL was entered for gliserver.pl, it'll exit safely.
+		dialog.addWindowListener(new WindowAdapter() {
+			public void windowClosing(WindowEvent e) {
+				Gatherer.exit();
+			}
+		});
+		
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+
+		
+		String gliserver_url_string = Configuration.getString("general.gliserver_url", true);
+		if (!gliserver_url_string.equals("")) {
+			try {
+				Configuration.gliserver_url = new URL(gliserver_url_string);
+				Configuration.setString("general.gliserver_url", true, gliserver_url_string);
+			}
+			catch (MalformedURLException exception) {
+				DebugStream.printStackTrace(exception);
+			}
+		}
+	}
+
+
+	/** Prints a warning message about a missing GSDL path, which although not fatal pretty much ensures nothing will work properly in the GLI.
+	*/
+	static private void missingGSDL() {
+		WarningDialog dialog = new WarningDialog("warning.MissingGSDL", Dictionary.get("MissingGSDL.Title"), Dictionary.get("MissingGSDL.Message"), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+	/** Prints a warning message about missing a valid ImageMagick path, which although not fatal means building image collections won't work */
+	static private void missingImageMagick() {
+		WarningDialog dialog = new WarningDialog("warning.MissingImageMagick", Dictionary.get("MissingImageMagick.Title"), Dictionary.get("MissingImageMagick.Message"), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+    	/** Prints a message informing the user where they can get PDFBox from to process PDF files of v1.5 and greater */
+    static private void missingPDFBox(String zipExtension, String extFolder) {
+	// point to the correct version of the PDFBox extension for this Greenstone release
+	String releaseTag = "";
+	if(!PROGRAM_VERSION.equals("trunk")) { // assume it's a release version
+	    releaseTag = "main/tags/"+PROGRAM_VERSION+"/";
+	}
+
+	WarningDialog dialog = new WarningDialog("warning.MissingPDFBox", Dictionary.get("MissingPDFBox.Title"), Dictionary.get("MissingPDFBox.Message", new String[]{releaseTag, zipExtension, extFolder}), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+	/** Prints a warning message about missing a valid PERL path, which although not fatal pretty much ensures no collection creation/building will work properly in the GLI. */
+	static private void missingPERL() {
+		WarningDialog dialog = new WarningDialog("warning.MissingPERL", Dictionary.get("MissingPERL.Title"), Dictionary.get("MissingPERL.Message"), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+    /** Prints a message informing the user that their collecthome is non-standard (not inside GS installation) */
+    static private void nonStandardCollectHomeMessage(String open_collection_file_path, String defaultColDir) {
+	WarningDialog dialog = new WarningDialog("warning.NonStandardCollectHome", Dictionary.get("NonStandardCollectHome.Title"), Dictionary.get("NonStandardCollectHome.Message", new String[]{open_collection_file_path, defaultColDir}), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+	/** Prints a warning message about the OS not supporting multiple filename encodings.  */
+	static private void multipleFilenameEncodingsNotSupported() {	
+		WarningDialog dialog = new WarningDialog("warning.NoEncodingSupport", 
+		Dictionary.get("NoEncodingSupport.Title"), 
+		Dictionary.get("NoEncodingSupport.Message"), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+	}
+
+	/** Sets up the proxy connection by setting JVM Environment flags and creating a new Authenticator.
+	* @see java.lang.Exception
+	* @see java.lang.System
+	* @see java.net.Authenticator
+	* @see org.greenstone.gatherer.Configuration
+	* @see org.greenstone.gatherer.GAuthenticator
+	*/
+	static public void setProxy() {
+		try {// Can throw several exceptions
+			if(Configuration.get("general.use_proxy", true)) {
+				System.setProperty("http.proxyType", "4");
+				System.setProperty("http.proxyHost", Configuration.getString("general.proxy_host", true));
+				System.setProperty("http.proxyPort", Configuration.getString("general.proxy_port", true));
+				System.setProperty("http.proxySet", "true");
+			} else {
+				System.setProperty("http.proxyHost", "");
+				System.setProperty("http.proxyPort", "");
+				System.setProperty("http.proxySet", "false");
+			}
+		} catch (Exception error) {
+			DebugStream.println("Error in Gatherer.initProxy(): " + error);
+			DebugStream.printStackTrace(error);
+		}
+	}
+
+	
+	/** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has voluntarily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
+	static private class ExternalApplication
+	extends Thread {
+		private SafeProcess process = null;
+		/** The initial command string given to this sub-process. */
+		private String command = null;
+		private String[] commands = null;
+
+		private String ID = null;
+
+		/** Constructor.
+	* @param command The initial command <strong>String</strong>.
+	*/
+		public ExternalApplication(String command) {
+			this.command = command;
+		}
+
+		public ExternalApplication(String[] commands) {
+			this.commands = commands;
+		}
+
+		public ExternalApplication(String command, String ID) {
+			this.command = command;
+			this.ID = ID;
+		}
+
+		public ExternalApplication(String[] commands, String ID) {
+			this.commands = commands;
+			this.ID = ID;
+		}
+		
+		public String getID() { 
+			return ID; 
+		}
+
+		/** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
+	* @see java.lang.Exception
+	* @see java.lang.Process
+	* @see java.lang.Runtime
+	* @see java.lang.System
+	* @see java.util.Vector
+	*/
+		public void run() {
+			// Call an external process using the args.
+			try {
+				if(commands != null) {
+					StringBuffer whole_command = new StringBuffer();
+					for(int i = 0; i < commands.length; i++) {						
+						// get rid of any quotes around parameters in file associations
+						if(commands[i].startsWith("\"") || commands[i].startsWith("\'")) {
+						    commands[i] = commands[i].substring(1);
+						}
+						if(commands[i].endsWith("\"") || commands[i].endsWith("\'")) {
+						    commands[i] = commands[i].substring(0, commands[i].length()-1);
+						}
+
+						if (i>0) {
+						    whole_command.append(" ");
+						}
+						whole_command.append(commands[i]);		    
+					}
+					DebugStream.println("Running " + whole_command.toString());
+					process = new SafeProcess(commands);
+				}
+				else {
+					DebugStream.println("Running " + command);
+					process = new SafeProcess(command);
+				}
+				process.runProcess();
+			}
+			catch (Exception exception) {
+				DebugStream.printStackTrace(exception);
+			}
+			// Remove ourself from Gatherer list of threads.
+			apps.remove(this);
+			// Call exit if we were the last outstanding child process thread.
+			if (apps.size() == 0 && exit == true) {
+				// In my opinion (DB) there is no need to exit here,
+				// the 'run' method ending naturally brings this
+				// thread to an end.  In fact it is potentially
+				// dangerous to exit here, as the main thread in the
+				// Gatherer class may be stopped prematurely.  As it so
+				// happens the point at which the ExternalApplication thread
+				// is asked to stop (Back in the main Gatherer thread) is after
+				// various configuration files have been saved.
+				// 
+				// A similar argument holds for BrowserApplication thread below.
+				System.exit(exit_status);
+			}
+		}
+		public void stopExternalApplication() {
+			if(process != null) {
+			    SafeProcess.log("*** stopExternalApplication called.");
+			    process.cancelRunningProcess();
+			}
+		}
+	}
+	/** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
+	static private class BrowserApplication
+	extends Thread {
+		private SafeProcess process = null;
+		/** The initial command string given to this sub-process. */
+		private String command = null;
+		private String url = null;
+		private String[] commands = null;
+
+		public BrowserApplication(String command, String url) {
+			StringTokenizer st = new StringTokenizer(command);
+			int num_tokens = st.countTokens();
+			this.commands = new String [num_tokens];
+			int i=0;
+			while (st.hasMoreTokens()) {
+				commands[i] = st.nextToken();
+				i++;
+			}
+			//this.commands = commands;
+			this.url = url;
+		}
+		/** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
+	* @see java.lang.Exception
+	* @see java.lang.Process
+	* @see java.lang.Runtime
+	* @see java.lang.System
+	* @see java.util.Vector
+	*/
+		public void run() {
+			// Call an external process using the args.
+			if(commands == null) {
+				apps.remove(this);
+				return;
+			}
+			try {
+				String prog_name = commands[0];
+				String lower_name = prog_name.toLowerCase();
+				if (lower_name.indexOf("mozilla") != -1 || lower_name.indexOf("netscape") != -1) { 
+					DebugStream.println("found mozilla or netscape, trying remote it");
+					// mozilla and netscape, try using a remote command to get things in the same window
+					String [] new_commands = new String[] {prog_name, "-raise", "-remote", "openURL("+url+",new-tab)"};
+					printArray(new_commands);
+
+					process = new SafeProcess(new_commands);
+					int exitCode = process.runProcess();
+					if (exitCode != 0) { // if Netscape or mozilla was not open
+						DebugStream.println("couldn't do remote, trying original command");
+						printArray(commands);
+						process = null;
+						process = new SafeProcess(commands); // try the original command
+						process.runProcess();
+					}
+				} else {
+					// just run what we have been given
+					StringBuffer whole_command = new StringBuffer();
+					for(int i = 0; i < commands.length; i++) {
+						whole_command.append(commands[i]);
+						whole_command.append(" ");
+					}
+					DebugStream.println("Running " + whole_command.toString());
+					process = new SafeProcess(commands);
+					process.runProcess();
+				}
+			}
+			
+			catch (Exception exception) {
+				DebugStream.printStackTrace(exception);
+			}
+			// Remove ourself from Gatherer list of threads.
+			apps.remove(this);
+			// Call exit if we were the last outstanding child process thread.
+			if (apps.size() == 0 && exit == true) {
+				System.exit(exit_status);
+			}
+		}
+		public void printArray(String [] array) {
+			for(int i = 0; i < array.length; i++) {
+				DebugStream.print(array[i]+" ");
+				System.err.println(array[i]+" ");
+			}
+		}
+		public void stopBrowserApplication() {
+			if(process != null) {
+			    SafeProcess.log("*** stopBrowserApplication called.");
+			    process.cancelRunningProcess();
+			}
+		}
+	}
+
+
+	private class ImageMagickTest
+	{
+		public boolean found()
+		{
+		    // at this stage, GLI has already sourced setup.bash, and the necessary
+		    // env variables will be available to the perl process we're about to launch
+		    boolean found = false;
+
+		    
+			    // run the command `/path/to/perl -S gs-magick.pl identify -version`
+			    ArrayList cmd_list = new ArrayList();
+			    if (!Gatherer.isGsdlRemote) {
+				if(Configuration.perl_path != null) {
+				    cmd_list.add(Configuration.perl_path);
+				} else {
+				    System.err.println("***** ImageMagickTest Warning: Perl_path not set, calling 'perl' instead.");
+				    cmd_list.add("perl");
+				}
+				cmd_list.add("-S");
+			    }
+			    cmd_list.add("gs-magick.pl");
+			    if(Utility.isWindows()) {
+				cmd_list.add("identify.exe");
+			    } else {
+				cmd_list.add("identify");
+			    }
+			    cmd_list.add("-version");
+
+			    String[] command_parts = (String[]) cmd_list.toArray(new String[0]);
+
+			    String cmd_str = "";
+			    for(int i = 0; i < command_parts.length; i++) {
+			    	cmd_str += command_parts[i] + " ";
+			    }
+			    DebugStream.println("***** Running ImageMagickTest command: " + cmd_str);
+			   
+			    SafeProcess image_magick_process = new SafeProcess(command_parts);
+			    int exitValue = image_magick_process.runProcess(); // default process iostream handling
+			    //new way of detection of ImageMagick
+			    // Inspect the standard output of the process and seach for two particular occurrences: Version and ImageMagick.
+			    String output = image_magick_process.getStdOutput().toLowerCase();
+			    if (output.indexOf("version") != -1 || output.indexOf("imagemagick") != -1) {
+				found = true;
+			    } // else found var remains false	
+			    
+			    return found;
+			    //return (image_magick_process.exitValue() == 0);
+			    
+		}
+	}
+
+
+	private class PerlTest
+	{
+		private String[] command = new String[2];
+
+		public PerlTest()
+		{
+			command[0] = (Utility.isWindows() ? Utility.PERL_EXECUTABLE_WINDOWS : Utility.PERL_EXECUTABLE_UNIX);
+			command[1] = "-version";
+		}
+
+		public boolean found()
+		{
+			try {
+			    SafeProcess perl_process = new SafeProcess(command);
+			    int exitValue = perl_process.runBasicProcess();
+			    return (exitValue == 0);
+			}
+			catch (Exception exception) {
+				return false;
+			}
+		}
+
+		public String toString() {
+			return command[0];
+		}
+	}
+	
+}
