Index: /trunk/gli/src/org/greenstone/gatherer/sarm/SearchAndReplace.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/sarm/SearchAndReplace.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/sarm/SearchAndReplace.java	(revision 4364)
@@ -60,361 +60,361 @@
  */
 public class SearchAndReplace
-	 extends JDialog {
-	 /** <i>true</i> if this dialog should allow for the replace and replace all buttons to be validated, <i>false</i> otherwise. */
-	 private boolean enable_replace = false;
-	 /** <i>true</i> if the last call to find() found something, <i>false</i> otherwise. */
-	 private boolean found = false;
-	 /** The size of the current screen real-estate. */
-	 private Dimension screen_size = Toolkit.getDefaultToolkit().getScreenSize();
-	 /** The metadata element that we are currently inspecting. If we are anywhere but within the find() function, then this is the last element found that matched the search string. */
-	 private ElementWrapper current_element = null;
-	 /** A vector containing the result of a depth first enumeration of the records in the collection record set tree. */
-	 private EnumeratedVector records = null;
-	 /** The file record that we are currently inspecting. If we are anywhere but within the find() function, and the target type is a FileNode, then this is the last record that matched our search string. Otherwise this is the record that the last metadata value that matched resides in. */
-	 private FileNode current_record = null;
-	 /** The button used to close the dialog. Has the same action as the destroy button. */
-	 private JButton close_button = null;
-	 /** The button used to begin a find() function call. Something must be typed in the search field before this button becomes active. */
-	 private JButton find_button = null;
-	 /** The button used to repeat the find() call in order to find the next matching object. Note this only becomes active after a successful find button press. */
-	 private JButton find_next_button = null;
-	 /** Redoes the action the was last undoes. Doesn't become active until there are job that are undone to be redone. :P */
-	 private JButton redo_button = null;
-	 /** Replaces the currently selected object with the given replacement text. The selection was determined by find() either as part of a previous action, or immediately before the replace() is called. This button only become active if there is something typed in the search and replace fields and if enable_replace is set. */
-	 private JButton replace_button = null;
-	 /** Replace all occurances of the search string with the replace string. This returns to the start of the collection and replaced every single matching reference. It only becomes enabled if something is typed in the search and replace fields and enable_replace is set. */
-	 private JButton replace_all_button = null;
-	 /** When pressed this button returns the 'searching pointer' back to the start of the collection. This is used when there are no further matches for your query, or at anytime you wish to start a new query. This button only becomes active after a success find(). */
-	 private JButton reset_button = null;
-	 /** This button undoes any changes made by the previous action, such as restoring all objects affected by a replace all. It only makes sense for this button to be active after a successful action. */
-	 private JButton undo_button = null;
-	 /** When checked this control indicates the search comparisons should be made taking into account case. Otherwise the case becomes irrelevant to matching. */
-	 private JCheckBox case_sensitive = null;
-	 /** When checked the SARM treats both search and replace string as regular expression when matching and replacing. */
-	 private JCheckBox regular_expression = null;
-	 /** When checked the SARM will search the metadata element identifiers for values that match your search string. Note that at least one of the search domains is selected at all times. */
-	 private JCheckBox search_elements = null;
-	 /** When checked the SARM will search the file records in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */
-	 private JCheckBox search_records = null;
-	 /** When checked the SARM will search the metadata values associated with each file record in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */ 
-	 private JCheckBox search_values = null;
-	 /** The status label shows the result of the last action. */
-	 private JLabel status = null;
-	 /** This field is used to enter a string which is in turn used to replace any text value that matches the search string. If enable_replace is not set, this field remains grayed out. If enabled, and you type within this field the replace buttons will become active. */
-	 private JTextField replace = null;
-	 /** The value entered in the search field is used to determine what object should be selected and brought to your attention. Note that this field can contain plain text or a regular expression, and that any typing in this field automatically resets the find() functions state to the start of the collection. */
-	 private JTextField search = null;
-	 /** Whenever a string is tested against the compiled regular Pattern, a Matcher is created. Not only does this matcher allow us to determine if the test string matches the pattern, but is can be subsequently used during a replace action to evaluate special 'capture group flags' within a regular expression based replace string.<BR><BR>For instance:Seach - '(.*)fug(.*)' will match 'Refugee'<BR>and<BR>Replace - '\1\1\1. \2' will replace the match with 'ReReRe. ee'. */
-	 private Matcher matcher = null;
-	 /** This is the current metadata value that we are inspecting. If non-null anywhere other then in the find() method, then this is the last metadata value that matched on search string. However it makes no sense by itself, and current_record is needed to actually reference this piece of metadata. */
-	 private Metadata current_value = null;
-	 /** This is used to determine the target class of any object whose text component we are trying to replace. It is actually set to the last object that matched the search string. */
-	 private Object target = null;
-	 /** This object is created by compiling a regular expression string retrieved from the search field, and in only non-null when regualr_expressions is checked. By compiling it once at the very beginning of the find() method, we lower processing time. Note that pattern is set to null whenever the find() state is reset. */ 
-	 private Pattern pattern = null;
-	 /** A reference to ourselves so that our inner classes can dispose of us for the insurance money. */
-	 private SearchAndReplace self = null;
-	 /** A stack of actions that have been undone, wait to be done again. */
-	 private Stack redo = new Stack();
-	 /** A stack of actions that have been done, waiting to undo. */
-	 private Stack undo = new Stack();
-	 /** A vector of the metadata elements attached to this collection, in the form of ElementWrappers for ease of data access. */ 
-	 private Vector elements = null;
-	 /** The default size for a label, used to produce purty column alignment. */
-	 static final private Dimension LABEL_SIZE = new Dimension(120, 30);
-	 /** The default size for the dialog. */
-	 static final private Dimension SIZE = new Dimension(640,210);
-	 /** Constructor.
-	  * @param enable_replace <i>true</i> if this dialog can be used for replace actions, <i>false</i> otherwise.
-	  */
-	 public SearchAndReplace(boolean enable_replace) {
-		  super();
-		  ///ystem.err.println("Creating a new search and replace dialog.");
-		  this.enable_replace = enable_replace;
-		  this.self = this;
-		  // Creation
-		  setModal(false);
-		  setSize(SIZE);
-		  setTitle(get("Title"));
-		  JPanel content_pane = (JPanel) getContentPane();
-
-		  JPanel central_pane = new JPanel();
-
-		  JPanel search_pane = new JPanel();
-		  JLabel search_label = new JLabel(get("Search"));
-		  search_label.setPreferredSize(LABEL_SIZE);
-		  search = new JTextField("");
-
-		  JPanel replace_pane = new JPanel();
-		  JLabel replace_label = new JLabel(get("Replace"));
-		  replace_label.setPreferredSize(LABEL_SIZE);
-		  replace = new JTextField("");
-		  replace.setEnabled(enable_replace);	  
-		  if(!enable_replace) {
-				replace.setBackground(Color.lightGray);
-		  }
-
-		  JPanel options_pane = new JPanel();
-		  JLabel options_label = new JLabel(get("Options"));
-		  options_label.setPreferredSize(LABEL_SIZE);
-		  JPanel inner_options_pane = new JPanel();
-		  case_sensitive = new JCheckBox(get("Case_Sensitive"));
-		  regular_expression = new JCheckBox(get("Regular_Expression"));
+    extends JDialog {
+    /** <i>true</i> if this dialog should allow for the replace and replace all buttons to be validated, <i>false</i> otherwise. */
+    private boolean enable_replace = false;
+    /** <i>true</i> if the last call to find() found something, <i>false</i> otherwise. */
+    private boolean found = false;
+    /** The size of the current screen real-estate. */
+    private Dimension screen_size = Toolkit.getDefaultToolkit().getScreenSize();
+    /** The metadata element that we are currently inspecting. If we are anywhere but within the find() function, then this is the last element found that matched the search string. */
+    private ElementWrapper current_element = null;
+    /** A vector containing the result of a depth first enumeration of the records in the collection record set tree. */
+    private EnumeratedVector records = null;
+    /** The file record that we are currently inspecting. If we are anywhere but within the find() function, and the target type is a FileNode, then this is the last record that matched our search string. Otherwise this is the record that the last metadata value that matched resides in. */
+    private FileNode current_record = null;
+    /** The button used to close the dialog. Has the same action as the destroy button. */
+    private JButton close_button = null;
+    /** The button used to begin a find() function call. Something must be typed in the search field before this button becomes active. */
+    private JButton find_button = null;
+    /** The button used to repeat the find() call in order to find the next matching object. Note this only becomes active after a successful find button press. */
+    private JButton find_next_button = null;
+    /** Redoes the action the was last undoes. Doesn't become active until there are job that are undone to be redone. :P */
+    private JButton redo_button = null;
+    /** Replaces the currently selected object with the given replacement text. The selection was determined by find() either as part of a previous action, or immediately before the replace() is called. This button only become active if there is something typed in the search and replace fields and if enable_replace is set. */
+    private JButton replace_button = null;
+    /** Replace all occurances of the search string with the replace string. This returns to the start of the collection and replaced every single matching reference. It only becomes enabled if something is typed in the search and replace fields and enable_replace is set. */
+    private JButton replace_all_button = null;
+    /** When pressed this button returns the 'searching pointer' back to the start of the collection. This is used when there are no further matches for your query, or at anytime you wish to start a new query. This button only becomes active after a success find(). */
+    private JButton reset_button = null;
+    /** This button undoes any changes made by the previous action, such as restoring all objects affected by a replace all. It only makes sense for this button to be active after a successful action. */
+    private JButton undo_button = null;
+    /** When checked this control indicates the search comparisons should be made taking into account case. Otherwise the case becomes irrelevant to matching. */
+    private JCheckBox case_sensitive = null;
+    /** When checked the SARM treats both search and replace string as regular expression when matching and replacing. */
+    private JCheckBox regular_expression = null;
+    /** When checked the SARM will search the metadata element identifiers for values that match your search string. Note that at least one of the search domains is selected at all times. */
+    private JCheckBox search_elements = null;
+    /** When checked the SARM will search the file records in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */
+    private JCheckBox search_records = null;
+    /** When checked the SARM will search the metadata values associated with each file record in the collection for values that match your search string. Note that at least one of the search domains is selected at all times. */ 
+    private JCheckBox search_values = null;
+    /** The status label shows the result of the last action. */
+    private JLabel status = null;
+    /** This field is used to enter a string which is in turn used to replace any text value that matches the search string. If enable_replace is not set, this field remains grayed out. If enabled, and you type within this field the replace buttons will become active. */
+    private JTextField replace = null;
+    /** The value entered in the search field is used to determine what object should be selected and brought to your attention. Note that this field can contain plain text or a regular expression, and that any typing in this field automatically resets the find() functions state to the start of the collection. */
+    private JTextField search = null;
+    /** Whenever a string is tested against the compiled regular Pattern, a Matcher is created. Not only does this matcher allow us to determine if the test string matches the pattern, but is can be subsequently used during a replace action to evaluate special 'capture group flags' within a regular expression based replace string.<BR><BR>For instance:Seach - '(.*)fug(.*)' will match 'Refugee'<BR>and<BR>Replace - '\1\1\1. \2' will replace the match with 'ReReRe. ee'. */
+    private Matcher matcher = null;
+    /** This is the current metadata value that we are inspecting. If non-null anywhere other then in the find() method, then this is the last metadata value that matched on search string. However it makes no sense by itself, and current_record is needed to actually reference this piece of metadata. */
+    private Metadata current_value = null;
+    /** This is used to determine the target class of any object whose text component we are trying to replace. It is actually set to the last object that matched the search string. */
+    private Object target = null;
+    /** This object is created by compiling a regular expression string retrieved from the search field, and in only non-null when regualr_expressions is checked. By compiling it once at the very beginning of the find() method, we lower processing time. Note that pattern is set to null whenever the find() state is reset. */ 
+    private Pattern pattern = null;
+    /** A reference to ourselves so that our inner classes can dispose of us for the insurance money. */
+    private SearchAndReplace self = null;
+    /** A stack of actions that have been undone, wait to be done again. */
+    private Stack redo = new Stack();
+    /** A stack of actions that have been done, waiting to undo. */
+    private Stack undo = new Stack();
+    /** A vector of the metadata elements attached to this collection, in the form of ElementWrappers for ease of data access. */ 
+    private Vector elements = null;
+    /** The default size for a label, used to produce purty column alignment. */
+    static final private Dimension LABEL_SIZE = new Dimension(120, 30);
+    /** The default size for the dialog. */
+    static final private Dimension SIZE = new Dimension(640,210);
+    /** Constructor.
+     * @param enable_replace <i>true</i> if this dialog can be used for replace actions, <i>false</i> otherwise.
+	  */
+    public SearchAndReplace(boolean enable_replace) {
+	super();
+	///ystem.err.println("Creating a new search and replace dialog.");
+	this.enable_replace = enable_replace;
+	this.self = this;
+	// Creation
+	setModal(false);
+	setSize(SIZE);
+	setTitle(get("Title"));
+	JPanel content_pane = (JPanel) getContentPane();
+
+	JPanel central_pane = new JPanel();
+
+	JPanel search_pane = new JPanel();
+	JLabel search_label = new JLabel(get("Search"));
+	search_label.setPreferredSize(LABEL_SIZE);
+	search = new JTextField("");
+
+	JPanel replace_pane = new JPanel();
+	JLabel replace_label = new JLabel(get("Replace"));
+	replace_label.setPreferredSize(LABEL_SIZE);
+	replace = new JTextField("");
+	replace.setEnabled(enable_replace);	  
+	if(!enable_replace) {
+	    replace.setBackground(Color.lightGray);
+	}
+
+	JPanel options_pane = new JPanel();
+	JLabel options_label = new JLabel(get("Options"));
+	options_label.setPreferredSize(LABEL_SIZE);
+	JPanel inner_options_pane = new JPanel();
+	case_sensitive = new JCheckBox(get("Case_Sensitive"));
+	regular_expression = new JCheckBox(get("Regular_Expression"));
 		  
-		  JPanel options2_pane = new JPanel();
-		  JLabel options2_label = new JLabel(get("Search_In"));
-		  options2_label.setPreferredSize(LABEL_SIZE);
-		  JPanel inner_options2_pane = new JPanel();
-		  XORToggleButtonGroup check_box_group = new XORToggleButtonGroup();
-		  search_elements = new JCheckBox(get("Search_Elements"));
-		  search_elements.setSelected(true);
-		  check_box_group.add(search_elements);
-		  search_records = new JCheckBox(get("Search_Records"));
-		  search_records.setSelected(true);
-		  check_box_group.add(search_records);
-		  search_values = new JCheckBox(get("Search_Values"));
-		  search_values.setSelected(true);
-		  check_box_group.add(search_values);
-
-		  JPanel results_pane = new JPanel();
-		  status = new JLabel(get("Status"));
-
-		  JPanel buttons_pane = new JPanel();
-		  find_button = new JButton(get("Find_Button"));
-		  find_button.setMnemonic(KeyEvent.VK_F);
-		  find_next_button = new JButton(get("Find_Next_Button"));
-		  find_next_button.setEnabled(false);
-		  find_next_button.setMnemonic(KeyEvent.VK_N);
-		  replace_button = new JButton(get("Replace_Button"));
-		  replace_button.setEnabled(false);
-		  replace_button.setMnemonic(KeyEvent.VK_R);
-		  replace_all_button = new JButton(get("Replace_All_Button"));
-		  replace_all_button.setEnabled(false);
-		  replace_all_button.setMnemonic(KeyEvent.VK_N);
-		  reset_button = new JButton(get("Reset"));
-		  reset_button.setEnabled(false);
-		  reset_button.setMnemonic(KeyEvent.VK_ESCAPE);
-		  undo_button = new JButton(get("Undo"));
-		  undo_button.setEnabled(false);
-		  undo_button.setMnemonic(KeyEvent.VK_U);
-		  redo_button = new JButton(get("Redo"));
-		  redo_button.setEnabled(false);
-		  redo_button.setMnemonic(KeyEvent.VK_D);
-		  close_button = new JButton(get("General.Close"));
-		  close_button.setMnemonic(KeyEvent.VK_C);
-		  // Connection
-		  close_button.addActionListener(new CloseActionListener());
-		  find_button.addActionListener(new FindActionListener());
-		  find_next_button.addActionListener(new FindNextActionListener());
-		  redo_button.addActionListener(new RedoActionListener());
-		  replace.addActionListener(new FindActionListener());
-		  replace_button.addActionListener(new ReplaceActionListener());
-		  replace_all_button.addActionListener(new ReplaceAllActionListener());
-		  reset_button.addActionListener(new ResetActionListener());
-		  undo_button.addActionListener(new UndoActionListener());
-		  replace.addKeyListener(new ReplaceKeyListener());
-		  search.addKeyListener(new SearchKeyListener());
-		  // Layout
-		  search_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
-
-		  search_pane.setLayout(new BorderLayout());
-		  search_pane.add(search_label, BorderLayout.WEST);
-		  search_pane.add(search, BorderLayout.CENTER);
-
-		  replace_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
+	JPanel options2_pane = new JPanel();
+	JLabel options2_label = new JLabel(get("Search_In"));
+	options2_label.setPreferredSize(LABEL_SIZE);
+	JPanel inner_options2_pane = new JPanel();
+	XORToggleButtonGroup check_box_group = new XORToggleButtonGroup();
+	search_elements = new JCheckBox(get("Search_Elements"));
+	search_elements.setSelected(true);
+	check_box_group.add(search_elements);
+	search_records = new JCheckBox(get("Search_Records"));
+	search_records.setSelected(true);
+	check_box_group.add(search_records);
+	search_values = new JCheckBox(get("Search_Values"));
+	search_values.setSelected(true);
+	check_box_group.add(search_values);
+
+	JPanel results_pane = new JPanel();
+	status = new JLabel(get("Status"));
+
+	JPanel buttons_pane = new JPanel();
+	find_button = new JButton(get("Find_Button"));
+	find_button.setMnemonic(KeyEvent.VK_F);
+	find_next_button = new JButton(get("Find_Next_Button"));
+	find_next_button.setEnabled(false);
+	find_next_button.setMnemonic(KeyEvent.VK_N);
+	replace_button = new JButton(get("Replace_Button"));
+	replace_button.setEnabled(false);
+	replace_button.setMnemonic(KeyEvent.VK_R);
+	replace_all_button = new JButton(get("Replace_All_Button"));
+	replace_all_button.setEnabled(false);
+	replace_all_button.setMnemonic(KeyEvent.VK_N);
+	reset_button = new JButton(get("Reset"));
+	reset_button.setEnabled(false);
+	reset_button.setMnemonic(KeyEvent.VK_ESCAPE);
+	undo_button = new JButton(get("Undo"));
+	undo_button.setEnabled(false);
+	undo_button.setMnemonic(KeyEvent.VK_U);
+	redo_button = new JButton(get("Redo"));
+	redo_button.setEnabled(false);
+	redo_button.setMnemonic(KeyEvent.VK_D);
+	close_button = new JButton(get("General.Close"));
+	close_button.setMnemonic(KeyEvent.VK_C);
+	// Connection
+	close_button.addActionListener(new CloseActionListener());
+	find_button.addActionListener(new FindActionListener());
+	find_next_button.addActionListener(new FindNextActionListener());
+	redo_button.addActionListener(new RedoActionListener());
+	replace.addActionListener(new FindActionListener());
+	replace_button.addActionListener(new ReplaceActionListener());
+	replace_all_button.addActionListener(new ReplaceAllActionListener());
+	reset_button.addActionListener(new ResetActionListener());
+	undo_button.addActionListener(new UndoActionListener());
+	replace.addKeyListener(new ReplaceKeyListener());
+	search.addKeyListener(new SearchKeyListener());
+	// Layout
+	search_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
+
+	search_pane.setLayout(new BorderLayout());
+	search_pane.add(search_label, BorderLayout.WEST);
+	search_pane.add(search, BorderLayout.CENTER);
+
+	replace_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
 		  
-		  replace_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
-		  replace_pane.setLayout(new BorderLayout());
-		  replace_pane.add(replace_label, BorderLayout.WEST);
-		  replace_pane.add(replace, BorderLayout.CENTER);
-
-		  inner_options_pane.setLayout(new GridLayout(1,2));
-		  inner_options_pane.add(case_sensitive);
-		  inner_options_pane.add(regular_expression);
-
-		  options_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
-		  options_pane.setLayout(new BorderLayout());
-		  options_pane.add(options_label, BorderLayout.WEST);
-		  options_pane.add(inner_options_pane, BorderLayout.CENTER);
-
-		  inner_options2_pane.setLayout(new GridLayout(1,3));
-		  inner_options2_pane.add(search_elements);
-		  inner_options2_pane.add(search_records);
-		  inner_options2_pane.add(search_values);
-
-		  options2_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
-		  options2_pane.setLayout(new BorderLayout());
-		  options2_pane.add(options2_label, BorderLayout.WEST);
-		  options2_pane.add(inner_options2_pane, BorderLayout.CENTER);
-
-		  results_pane.setBorder(BorderFactory.createRaisedBevelBorder());
-		  results_pane.setLayout(new BorderLayout());
-		  results_pane.add(status, BorderLayout.CENTER);
-
-		  central_pane.setLayout(new GridLayout(5,1));
-		  central_pane.add(search_pane);
-		  central_pane.add(replace_pane);
-		  central_pane.add(options_pane);
-		  central_pane.add(options2_pane);
-		  central_pane.add(results_pane);
+	replace_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
+	replace_pane.setLayout(new BorderLayout());
+	replace_pane.add(replace_label, BorderLayout.WEST);
+	replace_pane.add(replace, BorderLayout.CENTER);
+
+	inner_options_pane.setLayout(new GridLayout(1,2));
+	inner_options_pane.add(case_sensitive);
+	inner_options_pane.add(regular_expression);
+
+	options_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
+	options_pane.setLayout(new BorderLayout());
+	options_pane.add(options_label, BorderLayout.WEST);
+	options_pane.add(inner_options_pane, BorderLayout.CENTER);
+
+	inner_options2_pane.setLayout(new GridLayout(1,3));
+	inner_options2_pane.add(search_elements);
+	inner_options2_pane.add(search_records);
+	inner_options2_pane.add(search_values);
+
+	options2_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
+	options2_pane.setLayout(new BorderLayout());
+	options2_pane.add(options2_label, BorderLayout.WEST);
+	options2_pane.add(inner_options2_pane, BorderLayout.CENTER);
+
+	results_pane.setBorder(BorderFactory.createRaisedBevelBorder());
+	results_pane.setLayout(new BorderLayout());
+	results_pane.add(status, BorderLayout.CENTER);
+
+	central_pane.setLayout(new GridLayout(5,1));
+	central_pane.add(search_pane);
+	central_pane.add(replace_pane);
+	central_pane.add(options_pane);
+	central_pane.add(options2_pane);
+	central_pane.add(results_pane);
 		  
-		  buttons_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
-		  buttons_pane.setLayout(new GridLayout(2,4));
-		  buttons_pane.add(find_button);
-		  buttons_pane.add(find_next_button);
-		  buttons_pane.add(replace_button);
-		  buttons_pane.add(replace_all_button);
-		  buttons_pane.add(reset_button);
-		  buttons_pane.add(undo_button);
-		  buttons_pane.add(redo_button);
-		  buttons_pane.add(close_button);
-
-		  content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-		  content_pane.setLayout(new BorderLayout());
-		  content_pane.add(central_pane, BorderLayout.CENTER);
-		  content_pane.add(buttons_pane, BorderLayout.SOUTH);
-		  // Reset data.
-		  reset();
-		  // Restore visual position.
-		  setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
-		  show();
-	 }
-	 /** Updates the status bar to reflect the result of the latest action. Includes the ability to reset the status label whenever the find() state resets.
+	buttons_pane.setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
+	buttons_pane.setLayout(new GridLayout(2,4));
+	buttons_pane.add(find_button);
+	buttons_pane.add(find_next_button);
+	buttons_pane.add(replace_button);
+	buttons_pane.add(replace_all_button);
+	buttons_pane.add(reset_button);
+	buttons_pane.add(undo_button);
+	buttons_pane.add(redo_button);
+	buttons_pane.add(close_button);
+
+	content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	content_pane.setLayout(new BorderLayout());
+	content_pane.add(central_pane, BorderLayout.CENTER);
+	content_pane.add(buttons_pane, BorderLayout.SOUTH);
+	// Reset data.
+	reset();
+	// Restore visual position.
+	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
+	show();
+    }
+    /** Updates the status bar to reflect the result of the latest action. Includes the ability to reset the status label whenever the find() state resets.
 	  * @param key A <strong>String</strong> of text to use as the explaination of the actions result.
 	  * @param optional_count An <i>int</i> which if any value, other than -1, shows the number of objects the action was performed on. 
 	  */
-	 private void changeStatus(String key, int optional_count) {
-		  String args[] = null;
-		  if(optional_count >= 0) {
-				args = new String[2];
-				args[1] = "" + optional_count + "";
-		  }
-		  else {
-				args = new String[1];
-		  }
-		  if(key != null) {
-				args[0] = search.getText();
-				status.setText(get("Status") + ": " + get(key, args));
-		  }
-		  else {
-				status.setText(get("Status"));
-		  }
-	 }
-	 /** The guts of this module, the find method, in conjunction with the matches method, searches through the several targetted domains looking for values which match the search string, returning true while leaving itself in a state that points to the matching object if one is found. What exact part of each object is matched against is decided by the objects type, and in one case this is made even more complex as one type of object exists inside the other so we need to carefully iterate through the inner before increamenting the outer. 
+    private void changeStatus(String key, int optional_count) {
+	String args[] = null;
+	if(optional_count >= 0) {
+	    args = new String[2];
+	    args[1] = "" + optional_count + "";
+	}
+	else {
+	    args = new String[1];
+	}
+	if(key != null) {
+	    args[0] = search.getText();
+	    status.setText(get("Status") + ": " + get(key, args));
+	}
+	else {
+	    status.setText(get("Status"));
+	}
+    }
+    /** The guts of this module, the find method, in conjunction with the matches method, searches through the several targetted domains looking for values which match the search string, returning true while leaving itself in a state that points to the matching object if one is found. What exact part of each object is matched against is decided by the objects type, and in one case this is made even more complex as one type of object exists inside the other so we need to carefully iterate through the inner before increamenting the outer. 
 	  * @param <i>true</i> if a match was found thus the current_<object> parameters are pointing to it, otherwise <i>false</i>.
 	  */
-	 private boolean find() {
-		  boolean local_found = false;
-		  String find_str = search.getText();
-		  if(find_str.length() > 0) {
+    private boolean find() {
+	boolean local_found = false;
+	String find_str = search.getText();
+	if(find_str.length() > 0) {
 				// If a pattern currently hasn't been compiled, compile one.
-				if(pattern == null) {
-					 // If no actual regular expression script is present add .* to either end of the search string so we actually search for content of the target string not exact equality.
-					 if(!regular_expression.isSelected()) {
-						  find_str = "(.*)("+find_str+")(.*)";  
-					 }
-					 // Compile pattern
-					 try {
-						  if(case_sensitive.isSelected()) {
-								pattern = Pattern.compile(find_str);
-						  }
-						  else {
-								pattern = Pattern.compile(find_str.toLowerCase());
-						  }
-					 }
-					 catch(Exception error) {
-					 }
+	    if(pattern == null) {
+		// If no actual regular expression script is present add .* to either end of the search string so we actually search for content of the target string not exact equality.
+		if(!regular_expression.isSelected()) {
+		    find_str = "(.*)("+find_str+")(.*)";  
+		}
+		// Compile pattern
+		try {
+		    if(case_sensitive.isSelected()) {
+			pattern = Pattern.compile(find_str);
+		    }
+		    else {
+			pattern = Pattern.compile(find_str.toLowerCase());
+		    }
+		}
+		catch(Exception error) {
+		}
+	    }
+				// Search through the metadata elements if required for a matching entry.
+	    if(search_elements.isSelected()) {
+		for(int i = (current_element != null ? elements.indexOf(current_element) + 1 : 0); !local_found && i < elements.size(); i++) {
+		    current_element = (ElementWrapper) elements.get(i);
+		    if(match(current_element.getIdentity())) {
+			local_found = true;
+			target = current_element;
+			fireSelect(current_element);
+		    }
+		}
+	    }
+				// Search through file names.
+	    if(!local_found && (search_records.isSelected() || search_values.isSelected()) && (records.hasMoreElements() || current_record != null)) {
+		if(current_record == null) {
+		    current_record = (FileNode)records.nextElement();
+		}
+		while(!local_found && (current_record != null || records.hasMoreElements())) {
+		    // Try to match current record (if we haven't already).
+		    if(match(current_record.toString()) && search_records.isSelected() && current_record != target) {
+			local_found = true;
+			target = current_record;
+			fireSelect(current_record);
+		    }
+		    else {
+			// Search through metadata values is required.
+			if(search_values.isSelected()) {
+			    ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getMetadata(current_record.getFile());
+			    for(int i = (current_value != null ? metadatum.indexOf(current_value) + 1 : 0); !local_found && i < metadatum.size(); i++) {
+				current_value = (Metadata)metadatum.get(i);
+				if(match(current_value.getValue())) {
+				    local_found = true;
+				    target = current_value;
+				    fireSelect(current_record, current_value);
 				}
-				// Search through the metadata elements if required for a matching entry.
-				if(search_elements.isSelected()) {
-					 for(int i = (current_element != null ? elements.indexOf(current_element) + 1 : 0); !local_found && i < elements.size(); i++) {
-						  current_element = (ElementWrapper) elements.get(i);
-						  if(match(current_element.getIdentity())) {
-								local_found = true;
-								target = current_element;
-								fireSelect(current_element);
-						  }
-					 }
-				}
-				// Search through file names.
-				if(!local_found && (search_records.isSelected() || search_values.isSelected()) && (records.hasMoreElements() || current_record != null)) {
-					 if(current_record == null) {
-						  current_record = (FileNode)records.nextElement();
-					 }
-					 while(!local_found && (current_record != null || records.hasMoreElements())) {
-						  // Try to match current record (if we haven't already).
-						  if(match(current_record.toString()) && search_records.isSelected() && current_record != target) {
-								local_found = true;
-								target = current_record;
-								fireSelect(current_record);
-						  }
-						  else {
-								// Search through metadata values is required.
-								if(search_values.isSelected()) {
-									 ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getMetadata(current_record.getFile());
-									 for(int i = (current_value != null ? metadatum.indexOf(current_value) + 1 : 0); !local_found && i < metadatum.size(); i++) {
-										  current_value = (Metadata)metadatum.get(i);
-										  if(match(current_value.getValue())) {
-												local_found = true;
-												target = current_value;
-												fireSelect(current_record, current_value);
-										  }
-									 }
-									 if(!local_found) {
-										  current_value = null;
-									 }
-								}
-						  }
-						  if(!local_found) {
-								current_record = (FileNode)records.nextElement();
-						  }
-					 }
-				}
-		  }
-		  return local_found;
-	 }
-	 /** When a metadata element that matches our search string is found, this method is called to provide a visual cue as to what element matched in which collection. This is done by changing the tab view of the main gui and selecting the correct line from the list of sets/elements on one screen of the CDM. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
+			    }
+			    if(!local_found) {
+				current_value = null;
+			    }
+			}
+		    }
+		    if(!local_found) {
+			current_record = (FileNode)records.nextElement();
+		    }
+		}
+	    }
+	}
+	return local_found;
+    }
+    /** When a metadata element that matches our search string is found, this method is called to provide a visual cue as to what element matched in which collection. This is done by changing the tab view of the main gui and selecting the correct line from the list of sets/elements on one screen of the CDM. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
 	  * @param element The <strong>ElementWrapper</strong> that matches our search string.
 	  */
-	 private void fireSelect(ElementWrapper element) {
-		  // Ensure the CDM view is visible.
-		  Gatherer.g_man.setSelectedView(Gatherer.g_man.config_pane);
-		  // Select the correct metadata element
-		  Rectangle bounds = Gatherer.g_man.config_pane.setSelectedElement(element);
-		  unhideSelection(bounds);
-	 }
-	 /** When a file record that matches our search string is found, this method is called to provide a visual cue as to what file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
+    private void fireSelect(ElementWrapper element) {
+	// Ensure the CDM view is visible.
+	Gatherer.g_man.setSelectedView(Gatherer.g_man.config_pane);
+	// Select the correct metadata element
+	Rectangle bounds = Gatherer.g_man.config_pane.setSelectedElement(element);
+	unhideSelection(bounds);
+    }
+    /** When a file record that matches our search string is found, this method is called to provide a visual cue as to what file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
 	  * @param record The <strong>FileNode</strong> that matches our search string.
 	  */
-	 private void fireSelect(FileNode record) {
-		  // Ensure that either the CollectionPane or the MetaEditPane is visible. If not make the CollectionPane visible.
-		  Gatherer.g_man.setSelectedView(Gatherer.g_man.collection_pane);
-		  // Select the appropriate record.
-		  TreePath path = new TreePath(record.getPath());
-		  Rectangle bounds = Gatherer.g_man.collection_pane.expandPath(path);
-		  unhideSelection(bounds);
-	 }
-	 /** When a metadata value that matches our search string is found, this method is called to provide a visual cue as to what value from which file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records, and then highlighting the appropriate line of the metadata table. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
+    private void fireSelect(FileNode record) {
+	// Ensure that either the CollectionPane or the MetaEditPane is visible. If not make the CollectionPane visible.
+	Gatherer.g_man.setSelectedView(Gatherer.g_man.collection_pane);
+	// Select the appropriate record.
+	TreePath path = new TreePath(record.getPath());
+	Rectangle bounds = Gatherer.g_man.collection_pane.expandPath(path);
+	unhideSelection(bounds);
+    }
+    /** When a metadata value that matches our search string is found, this method is called to provide a visual cue as to what value from which file matched. This is done by changing the tab view of the main gui and expanding and selecting the correct line from the tree of file records, and then highlighting the appropriate line of the metadata table. This method subsequently calls unhideBound() passing in the bounds of the selected component (cell or row). 
 	  * @param record The <strong>FileNode</strong> that matches our search string.
 	  */
-	 private void fireSelect(FileNode record, Metadata metadata) {
-		  // Ensure that either the MetaEditPane is visible.
-		  Gatherer.g_man.setSelectedView(Gatherer.g_man.metaedit_pane);
-		  // Select the appropriate record in the metaedit pane tree, which will cause the Metadata table to update with the appropriate bunch of metadata... 
-		  TreePath path = new TreePath(record.getPath());
-		  Gatherer.g_man.metaedit_pane.expandPath(path);
-		  // Now select the correct metadata.
-		  Rectangle bounds = Gatherer.g_man.metaedit_pane.setSelectedMetadata(metadata);
-		  unhideSelection(bounds);
-	 }
-	 /** Retrieve a pharse string from the Dictionary matching the given key.
+    private void fireSelect(FileNode record, Metadata metadata) {
+	// Ensure that either the MetaEditPane is visible.
+	Gatherer.g_man.setSelectedView(Gatherer.g_man.metaedit_pane);
+	// Select the appropriate record in the metaedit pane tree, which will cause the Metadata table to update with the appropriate bunch of metadata... 
+	TreePath path = new TreePath(record.getPath());
+	Gatherer.g_man.metaedit_pane.expandPath(path);
+	// Now select the correct metadata.
+	Rectangle bounds = Gatherer.g_man.metaedit_pane.setSelectedMetadata(metadata);
+	unhideSelection(bounds);
+    }
+    /** Retrieve a pharse string from the Dictionary matching the given key.
 	  * @param key The <strong>String</strong> which maps to a certain phrase.
 	  * @return The required, locale specific, phrase as a <strong>String</strong>.
 	  * @see org.greenstone.gatherer.Dictionary
 	  */
-	 private String get(String key) {
-		  return get(key, null);
-	 }
-	 /** Retrieve a pharse string from the Dictionary matching the given key and based apon the given arguments.
+    private String get(String key) {
+	return get(key, null);
+    }
+    /** Retrieve a pharse string from the Dictionary matching the given key and based apon the given arguments.
 	  * @param key The <strong>String</strong> which maps to a certain phrase.
 	  * @param args The argument Strings to the Dictionary, as a <strong>String[]</strong>.
@@ -422,428 +422,427 @@
 	  * @see org.greenstone.gatherer.Dictionary
 	  */
-	 private String get(String key, String args[]) {
-		  if(key.indexOf(".") == -1) {
-				key = "SearchAndReplace." + key;
-		  }
-		  return Gatherer.dictionary.get(key, args);
-	 }
-	 /** Used to determine is one string matches another tkaing into account the options of case sensitivity and regular expressions.
+    private String get(String key, String args[]) {
+	if(key.indexOf(".") == -1) {
+	    key = "SearchAndReplace." + key;
+	}
+	return Gatherer.dictionary.get(key, args);
+    }
+    /** Used to determine is one string matches another tkaing into account the options of case sensitivity and regular expressions.
 	  * @param find_str The search <strong>String</strong> we are trying to find. This may be a regular expression.
 	  * @param target_str The <strong>String</strong> we are matching it against, as provided by some object.
 	  * @return <i>true</i> if the Strings match given the option settings, <i>false</i> otherwise.
 	  */
-	 private boolean match(String target_str) {
-		  ///ystem.err.println("Matching " + target_str + " against " + pattern.pattern());
-		  boolean result = false;
-		  if(!case_sensitive.isSelected()) {
-				target_str = target_str.toLowerCase();
-		  }
-		  try {
-				matcher = pattern.matcher(target_str);
-				result = matcher.matches();
-		  }
-		  catch(Exception error) {
-		  }
-		  return result;
-	 }
-	 /** Called once by the replace button, or repeatedly by replace all, in order to replace the text of the target object, as referenced by the current_<object>s, with the replace text string. This takes into account if regular expressions are expected, and modifies the replacement string with appropriate 'capture groups'. 
+    private boolean match(String target_str) {
+	///ystem.err.println("Matching " + target_str + " against " + pattern.pattern());
+	boolean result = false;
+	if(!case_sensitive.isSelected()) {
+	    target_str = target_str.toLowerCase();
+	}
+	try {
+	    matcher = pattern.matcher(target_str);
+	    result = matcher.matches();
+	}
+	catch(Exception error) {
+	}
+	return result;
+    }
+    /** Called once by the replace button, or repeatedly by replace all, in order to replace the text of the target object, as referenced by the current_<object>s, with the replace text string. This takes into account if regular expressions are expected, and modifies the replacement string with appropriate 'capture groups'. 
 	  * @return <i>true</i> if the replace action was successful, <i>false</i> otherwise.
 	  */
-	 private boolean replace() {
-		  boolean result = false;
-		  String replace_str = replace.getText();
-		  // If regular expressions are allowed, we have to search for regular expression commands and deal with them.
-		  if(matcher != null) {
-				StringBuffer temp = new StringBuffer("");
-				int index_ptr = 0;
-				while(index_ptr < replace_str.length()) {
-					 char c = replace_str.charAt(index_ptr);
-					 // First we check for escaped regualr expression commands which start '\\'
-					 if(c == '\\') {
-						  index_ptr++;
-						  char s = replace_str.charAt(index_ptr);
-						  if(s == '\\') {
-								index_ptr++;
-								char i = replace_str.charAt(index_ptr);
-								// I currently only support one regular expression command, 'capture groups'.
-								if(Character.isDigit(i)) {
-									 try {
-										  int group_num = Character.getNumericValue(i);
-										  if(group_num <= matcher.groupCount()) {
-												temp.append(matcher.group(group_num));
-										  }
-									 }
-									 catch (Exception error) {
-									 }
-								}
-						  }
-						  // The \ had nothing to do with groups, so I guess its a path marker.
-						  else {
-								temp.append(c);
-								temp.append(s);
-						  }
-					 }
-					 else {
-						  temp.append(c);
-					 }
-					 index_ptr++;
+    private boolean replace() {
+	boolean result = false;
+	String replace_str = replace.getText();
+	// If regular expressions are allowed, we have to search for regular expression commands and deal with them.
+	if(matcher != null) {
+	    StringBuffer temp = new StringBuffer("");
+	    int index_ptr = 0;
+	    while(index_ptr < replace_str.length()) {
+		char c = replace_str.charAt(index_ptr);
+		// First we check for escaped regualr expression commands which start '\\'
+		if(c == '\\') {
+		    index_ptr++;
+		    char s = replace_str.charAt(index_ptr);
+		    if(s == '\\') {
+			index_ptr++;
+			char i = replace_str.charAt(index_ptr);
+			// I currently only support one regular expression command, 'capture groups'.
+			if(Character.isDigit(i)) {
+			    try {
+				int group_num = Character.getNumericValue(i);
+				if(group_num <= matcher.groupCount()) {
+				    temp.append(matcher.group(group_num));
 				}
-				replace_str = temp.toString();
-		  }
-		  if(found && replace_str.length() > 0) {
-				if(target instanceof ElementWrapper) {
-					 undo.push(new UndoItem(current_element, current_element.getIdentity(), replace_str));
-					 updateElement(current_element, replace_str);
-					 result = true;
-				}
-				if(target instanceof FileNode) {
-					 undo.push(new UndoItem(current_record, current_record.toString(), replace_str));
-					 updateRecord(current_record, replace_str);
-					 result = true;
-				}
-				if(target instanceof Metadata) {
-					 // If we are replacing metadata value there is a bit more messaging we need to do to any replacement string to ensure that any hierarchy context is transferred to the new value, so...
-					 String absolute_value = current_value.getAbsoluteValue();
-					 if(absolute_value.indexOf("\\") != -1) {
-						  String absolute_path = absolute_value.substring(0, absolute_value.lastIndexOf("\\") + 1);
-						  replace_str = absolute_path + replace_str;
-						  ///ystem.err.println("I replaced a hierarchy value like so:\n"+absolute_value+"\nto\n"+replace_str);
-					 }
-					 undo.push(new UndoItem(current_value, current_record, current_value.getValue(), replace_str));
-					 updateMetadata(current_value, current_record, replace_str);
-					 result = true;
-				}
-		  }
-		  ///ystem.err.println("Undo queue length = " + undo.size());
-		  return result;
-	 }
-	 /** Used to restore the search space within the collection to its initial state for find(). 
-	  */
-	 private void reset() {
-		  current_element = null;
-		  current_record = null;
-		  current_value = null;
-		  elements = Gatherer.c_man.getCollection().msm.getElements();
-		  found = false;
-		  matcher = null;
-		  pattern = null;
-		  records = Utility.depthFirstEnumeration((TreeNode)Gatherer.c_man.getRecordSet().getRoot(), new EnumeratedVector());
-		  target = null;
-	 }
-	 /** Attempts to move the dialog so that it doesn't obscure the coordinates detailed. Can't always do so. 
+			    }
+			    catch (Exception error) {
+			    }
+			}
+		    }
+		    // The \ had nothing to do with groups, so I guess its a path marker.
+		    else {
+			temp.append(c);
+			temp.append(s);
+		    }
+		}
+		else {
+		    temp.append(c);
+		}
+		index_ptr++;
+	    }
+	    replace_str = temp.toString();
+	}
+	if(found && replace_str.length() > 0) {
+	    if(target instanceof ElementWrapper) {
+		undo.push(new UndoItem(current_element, current_element.getIdentity(), replace_str));
+		updateElement(current_element, replace_str);
+		result = true;
+	    }
+	    if(target instanceof FileNode) {
+		undo.push(new UndoItem(current_record, current_record.toString(), replace_str));
+		updateRecord(current_record, replace_str);
+		result = true;
+	    }
+	    if(target instanceof Metadata) {
+		// If we are replacing metadata value there is a bit more messaging we need to do to any replacement string to ensure that any hierarchy context is transferred to the new value, so...
+		String absolute_value = current_value.getAbsoluteValue();
+		if(absolute_value.indexOf("\\") != -1) {
+		    String absolute_path = absolute_value.substring(0, absolute_value.lastIndexOf("\\") + 1);
+		    replace_str = absolute_path + replace_str;
+		    ///ystem.err.println("I replaced a hierarchy value like so:\n"+absolute_value+"\nto\n"+replace_str);
+		}
+		undo.push(new UndoItem(current_value, current_record, current_value.getValue(), replace_str));
+		updateMetadata(current_value, current_record, replace_str);
+		result = true;
+	    }
+	}
+	///ystem.err.println("Undo queue length = " + undo.size());
+	return result;
+    }
+    /** Used to restore the search space within the collection to its initial state for find(). 
+	  */
+    private void reset() {
+	current_element = null;
+	current_record = null;
+	current_value = null;
+	elements = Gatherer.c_man.getCollection().msm.getElements();
+	found = false;
+	matcher = null;
+	pattern = null;
+	records = Utility.depthFirstEnumeration((TreeNode)Gatherer.c_man.getRecordSet().getRoot(), new EnumeratedVector());
+	target = null;
+    }
+    /** Attempts to move the dialog so that it doesn't obscure the coordinates detailed. Can't always do so. 
 	  * @param bounds A <strong>Rectangle</strong> around the selected component, with co-ordinates corrected to be absolute to the screen rather than relative to whatever parent component the selection was in. 
 	  */
-	 private void unhideSelection(Rectangle bounds) {
-		  /** @TODO - Elsewhere generate absolute references. Here make use of them. */
-		  ///ystem.err.println("Unhide selection with bounds " + bounds);
-	 }
-	 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of an element it uses this method. Hows that for code reuse.
+    private void unhideSelection(Rectangle bounds) {
+	/** @TODO - Elsewhere generate absolute references. Here make use of them. */
+	///ystem.err.println("Unhide selection with bounds " + bounds);
+    }
+    /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of an element it uses this method. Hows that for code reuse.
 	  * @param element The <strong>ElementWrapper</strong> containing the metadata element we want to change.
 	  * @param value The new text value for the elements identifier as a <strong>String</strong>.
 	  */
-	 private void updateElement(ElementWrapper element, String value) {
-		  Gatherer.c_man.getCollection().msm.renameElement(element, value);
-	 }
-	 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a metadata value it uses this method. Hows that for code reuse.
+    private void updateElement(ElementWrapper element, String value) {
+	Gatherer.c_man.getCollection().msm.renameElement(element, value);
+    }
+    /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a metadata value it uses this method. Hows that for code reuse.
 	  * @param metadata The <strong>Metadata</strong> we want to change.
 	  * @param record The <strong>FileNode</strong> the target metadata will be found in.
 	  * @param value The new text value for the metadata as a <strong>String</strong>.
 	  */
-	 private void updateMetadata(Metadata metadata, FileNode record, String value) {
-		  FileNode array[] = new FileNode[1];
-		  array[0] = record;
-		  Gatherer.c_man.getCollection().msm.updateMetadata(0L, metadata, array, value, MetaEditPrompt.UPDATE_ONCE, metadata.isFileLevel());
-	 }
-	 /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a file record it uses this method. Hows that for code reuse.
+    private void updateMetadata(Metadata metadata, FileNode record, String value) {
+	FileNode array[] = new FileNode[1];
+	array[0] = record;
+	Gatherer.c_man.getCollection().msm.updateMetadata(0L, metadata, array, value, MetaEditPrompt.UPDATE_ONCE, metadata.isFileLevel());
+    }
+    /** Whenever replace(), the undo action listener or the redo action listener wants to actually change the value of a file record it uses this method. Hows that for code reuse.
 	  * @param record The <strong>FileNode</strong> we want to change.
 	  * @param value The new text value for the files name as a <strong>String</strong>.
 	  */
-	 private void updateRecord(FileNode record, String value) {
-		  TreeModel model = Gatherer.c_man.getRecordSet();
-		  /* @todo
-			  model.rename(record, value);
-		  */
-	 }
-	 /** Several actions methods call this to check if the current state of the dialog allows for the replace butons to become active.
-	  */
-	 private void validateReplaceButtons() {
-		  boolean value = (replace.getText().length() > 0) && enable_replace;
-		  ///ystem.err.println("Validating replace buttons to " + value);
-		  replace_button.setEnabled(value);
-		  replace_all_button.setEnabled(value);
-	 }
-
-	 private class CloseActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				self.dispose();
-		  }
-	 }
-
-	 private class FindActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				reset();
-				if(find()) {
-					 find_button.setEnabled(false);
-					 find_next_button.setEnabled(true);
-					 if(enable_replace) {
-						  replace_button.setEnabled(true);
-						  replace_all_button.setEnabled(true);
-					 }
-					 reset_button.setEnabled(true);
-					 changeStatus("Found", -1);
-					 found = true;
-				}
-				else {
-					 changeStatus("Not_Found", -1);
-					 found = false;
-				}
-		  }
-	 }
-
-	 private class FindNextActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				if(find()) {
-					 changeStatus("Found", -1);
-					 found = true;
-				}
-				else {
-					 find_next_button.setEnabled(false);
-					 changeStatus("Not_Found", -1);
-				}
-		  }
-	 }
-
-	 private class RedoActionListener
-		  implements ActionListener {
-		  private boolean start = false;
-		  public void actionPerformed(ActionEvent event) {
-				if(!redo.empty()) {
-					 start = true;
-					 UndoItem redo_item = (UndoItem) redo.pop();
-					 while(redo_item != null && !redo_item.isDummy()) {
-						  // Determine the type of action we have to undo, then undo it.
-						  ElementWrapper element = redo_item.getElement();
-						  if(element != null) {
-								updateElement(element, redo_item.getAfter());
-								addToUndo(redo_item);
-						  }
-						  FileNode record = redo_item.getRecord();
-						  Metadata metadata = redo_item.getMetadata();
-						  if(metadata != null && record != null) {
-								updateMetadata(metadata, record, redo_item.getAfter());
-								addToUndo(redo_item);
-						  }
-						  else if(record != null) {
-								updateRecord(record, redo_item.getAfter());
-								addToUndo(redo_item);
-						  }
-						  if(!redo.empty()) {
-								redo_item = (UndoItem) redo.pop();
-						  }
-						  else {
-								redo_item = null;
-						  }
-					 }
-					 if(redo.empty()) {
-						  redo_button.setEnabled(false);
-					 }
-				}
-		  }
-		  private void addToUndo(UndoItem redo_item) {
-				if(start) {
-					 undo.push(new UndoItem());
-					 start = false;
-				}
-				undo.push(redo_item);
-				undo_button.setEnabled(true);
-		  }
-	 }
-
-	 private class ReplaceActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				if(!found) {
-					 found = find();
-				}
-				if(found) {
-					 undo.push(new UndoItem());
-					 replace();
-					 replace_button.setEnabled(false);
-					 reset_button.setEnabled(true);
-					 undo_button.setEnabled(true);
-					 changeStatus("Replaced", -1);
-				}
-				else {
-					 find_next_button.setEnabled(false);
-					 replace_button.setEnabled(false);
-					 replace_all_button.setEnabled(false);
-					 changeStatus("Not_Found", -1);
-				}
-		  }
-	 }
-
-	 private class ReplaceAllActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				reset();
-				int count = 0;
-				boolean start = true;
-				while((found = find()) == true) {
-					 if(start) {
-						  undo.push(new UndoItem());
-						  start = false;
-					 }
-					 count++;
-					 replace();
-				}
-				find_button.setEnabled(false);
-				find_next_button.setEnabled(false);
-				replace_button.setEnabled(false);
-				replace_all_button.setEnabled(false);
-				reset_button.setEnabled(true);
-				if(!undo.empty()) {
-					 undo_button.setEnabled(true);
-				}
-				changeStatus("Replaced_All", count);
-		  }
-	 }
-
-	 private class ResetActionListener
-		  implements ActionListener {
-		  public void actionPerformed(ActionEvent event) {
-				reset();
-				find_button.setEnabled(true);
-				find_next_button.setEnabled(false);
-				replace_button.setEnabled(false);
-				replace_all_button.setEnabled(false);
-				reset_button.setEnabled(false);
-				changeStatus(null, -1);
-		  }
-	 }
-
-	 private class UndoActionListener
-		  implements ActionListener {
-		  private boolean start = false;
-		  public void actionPerformed(ActionEvent event) {
-				if(!undo.empty()) {
-					 start = true;
-					 UndoItem undo_item = (UndoItem) undo.pop();
-					 while(undo_item != null && !undo_item.isDummy()) {
-						  // Determine the type of action we have to undo, then undo it.
-						  ElementWrapper element = undo_item.getElement();
-						  if(element != null) {
-								updateElement(element, undo_item.getBefore());
-								addToRedo(undo_item);
-						  }
-						  else {
-								FileNode record = undo_item.getRecord();
-								Metadata metadata = undo_item.getMetadata();
-								if(metadata != null && record != null) {
-									 // The old metadata doesn't actually exist anymore. We have to generate a new metadata object based on the old metadata objects element and the 'after' string.
-									 GValueModel model = Gatherer.c_man.getCollection().msm.getValueTree(metadata.getElement());
-									 GValueNode value = model.getValue(undo_item.getAfter());
-									 Metadata new_metadata = new Metadata(metadata.getElement(), value);
-									 updateMetadata(new_metadata, record, undo_item.getBefore());
-									 addToRedo(undo_item);
-								}
-								else if(record != null) {
-									 updateRecord(record, undo_item.getBefore());
-									 addToRedo(undo_item);
-								}
-								else {
-									 ///ystem.err.println("Unknown undo item.");
-								}
-						  }
-						  if(!undo.empty()) {
-								undo_item = (UndoItem) undo.pop();
-						  }
-						  else {
-								undo_item = null;
-						  }
-					 }
-					 if(undo.empty()) {
-						  undo_button.setEnabled(false);
-					 }
-				}
-		  }
-		  private void addToRedo(UndoItem undo_item) {
-				if(start) {
-					 redo.push(new UndoItem());
-					 start = false;
-				}
-				redo.push(undo_item);
-				redo_button.setEnabled(true);
-		  }
-	 }
-
-	 private class ReplaceKeyListener
-		  extends KeyAdapter {
-		  public void keyTyped(KeyEvent event) {
-				validateReplaceButtons();
-		  }
-	 }
-
-	 private class SearchKeyListener
-		  extends KeyAdapter {
-		  public void keyTyped(KeyEvent event) {
-				if(current_element != null || current_record != null || current_value != null) {
-					 reset();
-					 find_button.setEnabled(true);
-					 find_next_button.setEnabled(false);
-					 replace_button.setEnabled(false);
-					 replace_all_button.setEnabled(false);
-					 reset_button.setEnabled(false);
-					 changeStatus(null, -1);
-				}
-		  }
-	 }
+    private void updateRecord(FileNode record, String value) {
+	TreeModel model = Gatherer.c_man.getRecordSet();
+	/* @todo
+	   model.rename(record, value);
+	*/
+    }
+    /** Several actions methods call this to check if the current state of the dialog allows for the replace butons to become active.
+	  */
+    private void validateReplaceButtons() {
+	boolean value = (replace.getText().length() > 0) && enable_replace;
+	///ystem.err.println("Validating replace buttons to " + value);
+	replace_button.setEnabled(value);
+	replace_all_button.setEnabled(value);
+    }
+
+    private class CloseActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    self.dispose();
+	}
+    }
+
+    private class FindActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    reset();
+	    if(find()) {
+		find_button.setEnabled(false);
+		find_next_button.setEnabled(true);
+		if(enable_replace) {
+		    replace_button.setEnabled(true);
+		    replace_all_button.setEnabled(true);
+		}
+		reset_button.setEnabled(true);
+		changeStatus("Found", -1);
+		found = true;
+	    }
+	    else {
+		changeStatus("Not_Found", -1);
+		found = false;
+	    }
+	}
+    }
+
+    private class FindNextActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    if(find()) {
+		changeStatus("Found", -1);
+		found = true;
+	    }
+	    else {
+		find_next_button.setEnabled(false);
+		changeStatus("Not_Found", -1);
+	    }
+	}
+    }
+
+    private class RedoActionListener
+	implements ActionListener {
+	private boolean start = false;
+	public void actionPerformed(ActionEvent event) {
+	    if(!redo.empty()) {
+		start = true;
+		UndoItem redo_item = (UndoItem) redo.pop();
+		while(redo_item != null && !redo_item.isDummy()) {
+		    // Determine the type of action we have to undo, then undo it.
+		    ElementWrapper element = redo_item.getElement();
+		    if(element != null) {
+			updateElement(element, redo_item.getAfter());
+			addToUndo(redo_item);
+		    }
+		    FileNode record = redo_item.getRecord();
+		    Metadata metadata = redo_item.getMetadata();
+		    if(metadata != null && record != null) {
+			updateMetadata(metadata, record, redo_item.getAfter());
+			addToUndo(redo_item);
+		    }
+		    else if(record != null) {
+			updateRecord(record, redo_item.getAfter());
+			addToUndo(redo_item);
+		    }
+		    if(!redo.empty()) {
+			redo_item = (UndoItem) redo.pop();
+		    }
+		    else {
+			redo_item = null;
+		    }
+		}
+		if(redo.empty()) {
+		    redo_button.setEnabled(false);
+		}
+	    }
+	}
+	private void addToUndo(UndoItem redo_item) {
+	    if(start) {
+		undo.push(new UndoItem());
+		start = false;
+	    }
+	    undo.push(redo_item);
+	    undo_button.setEnabled(true);
+	}
+    }
+
+    private class ReplaceActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    if(!found) {
+		found = find();
+	    }
+	    if(found) {
+		undo.push(new UndoItem());
+		replace();
+		replace_button.setEnabled(false);
+		reset_button.setEnabled(true);
+		undo_button.setEnabled(true);
+		changeStatus("Replaced", -1);
+	    }
+	    else {
+		find_next_button.setEnabled(false);
+		replace_button.setEnabled(false);
+		replace_all_button.setEnabled(false);
+		changeStatus("Not_Found", -1);
+	    }
+	}
+    }
+
+    private class ReplaceAllActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    reset();
+	    int count = 0;
+	    boolean start = true;
+	    while((found = find()) == true) {
+		if(start) {
+		    undo.push(new UndoItem());
+		    start = false;
+		}
+		count++;
+		replace();
+	    }
+	    find_button.setEnabled(false);
+	    find_next_button.setEnabled(false);
+	    replace_button.setEnabled(false);
+	    replace_all_button.setEnabled(false);
+	    reset_button.setEnabled(true);
+	    if(!undo.empty()) {
+		undo_button.setEnabled(true);
+	    }
+	    changeStatus("Replaced_All", count);
+	}
+    }
+
+    private class ResetActionListener
+	implements ActionListener {
+	public void actionPerformed(ActionEvent event) {
+	    reset();
+	    find_button.setEnabled(true);
+	    find_next_button.setEnabled(false);
+	    replace_button.setEnabled(false);
+	    replace_all_button.setEnabled(false);
+	    reset_button.setEnabled(false);
+	    changeStatus(null, -1);
+	}
+    }
+
+    private class UndoActionListener
+	implements ActionListener {
+	private boolean start = false;
+	public void actionPerformed(ActionEvent event) {
+	    if(!undo.empty()) {
+		start = true;
+		UndoItem undo_item = (UndoItem) undo.pop();
+		while(undo_item != null && !undo_item.isDummy()) {
+		    // Determine the type of action we have to undo, then undo it.
+		    ElementWrapper element = undo_item.getElement();
+		    if(element != null) {
+			updateElement(element, undo_item.getBefore());
+			addToRedo(undo_item);
+		    }
+		    else {
+			FileNode record = undo_item.getRecord();
+			Metadata metadata = undo_item.getMetadata();
+			if(metadata != null && record != null) {
+			    // The old metadata doesn't actually exist anymore. We have to generate a new metadata object based on the old metadata objects element and the 'after' string.
+			    GValueModel model = Gatherer.c_man.getCollection().msm.getValueTree(metadata.getElement());
+			    GValueNode value = model.getValue(undo_item.getAfter());
+			    Metadata new_metadata = new Metadata(metadata.getElement(), value);
+			    updateMetadata(new_metadata, record, undo_item.getBefore());
+			    addToRedo(undo_item);
+			}
+			else if(record != null) {
+			    updateRecord(record, undo_item.getBefore());
+			    addToRedo(undo_item);
+			}
+			else {
+			    ///ystem.err.println("Unknown undo item.");
+			}
+		    }
+		    if(!undo.empty()) {
+			undo_item = (UndoItem) undo.pop();
+		    }
+		    else {
+			undo_item = null;
+		    }
+		}
+		if(undo.empty()) {
+		    undo_button.setEnabled(false);
+		}
+	    }
+	}
+	private void addToRedo(UndoItem undo_item) {
+	    if(start) {
+		redo.push(new UndoItem());
+		start = false;
+	    }
+	    redo.push(undo_item);
+	    redo_button.setEnabled(true);
+	}
+    }
+
+    private class ReplaceKeyListener
+	extends KeyAdapter {
+	public void keyTyped(KeyEvent event) {
+	    validateReplaceButtons();
+	}
+    }
+
+    private class SearchKeyListener
+	extends KeyAdapter {
+	public void keyTyped(KeyEvent event) {
+	    if(current_element != null || current_record != null || current_value != null) {
+		reset();
+		find_button.setEnabled(true);
+		find_next_button.setEnabled(false);
+		replace_button.setEnabled(false);
+		replace_all_button.setEnabled(false);
+		reset_button.setEnabled(false);
+		changeStatus(null, -1);
+	    }
+	}
+    }
 	 
-	 private class UndoItem {
-		  private ElementWrapper element = null;
-		  private FileNode record = null;
-		  private Metadata metadata = null;
-		  private String after = null;
-		  private String before = null;
-		  /** Constructor for a dummy item, which separates different batches of changes. */
-		  public UndoItem() {
-		  }
-		  public UndoItem(ElementWrapper element, String before, String after) {
-				this.after = after;
-				this.before = before;
-				this.element = element;
-		  }
-		  public UndoItem(FileNode record, String before, String after) {
-				this.after = after;
-				this.before = before;
-				this.record = record;
-		  }
-		  public UndoItem(Metadata metadata, FileNode record, String before, String after) {
-				this.after = after;
-				this.before = before;
-				this.metadata = metadata;
-				this.record = record;
-		  }
-		  public String getAfter() {
-				return after;
-		  }
-		  public String getBefore() {
-				return before;
-		  }
-		  public ElementWrapper getElement() {
-				return element;
-		  }
-		  public Metadata getMetadata() {
-				return metadata;
-		  }
-		  public FileNode getRecord() {
-				return record;
-		  }
-		  public boolean isDummy() {
-				return (element == null && record == null && metadata == null);
-		  }
-	 }
+    private class UndoItem {
+	private ElementWrapper element = null;
+	private FileNode record = null;
+	private Metadata metadata = null;
+	private String after = null;
+	private String before = null;
+	/** Constructor for a dummy item, which separates different batches of changes. */
+	public UndoItem() {
+	}
+	public UndoItem(ElementWrapper element, String before, String after) {
+	    this.after = after;
+	    this.before = before;
+	    this.element = element;
+	}
+	public UndoItem(FileNode record, String before, String after) {
+	    this.after = after;
+	    this.before = before;
+	    this.record = record;
+	}
+	public UndoItem(Metadata metadata, FileNode record, String before, String after) {
+	    this.after = after;
+	    this.before = before;
+	    this.metadata = metadata;
+	    this.record = record;
+	}
+	public String getAfter() {
+	    return after;
+	}
+	public String getBefore() {
+	    return before;
+	}
+	public ElementWrapper getElement() {
+	    return element;
+	}
+	public Metadata getMetadata() {
+	    return metadata;
+	}
+	public FileNode getRecord() {
+	    return record;
+	}
+	public boolean isDummy() {
+	    return (element == null && record == null && metadata == null);
+	}
+    }
 }
-
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GBasicProgressMonitor.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GBasicProgressMonitor.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GBasicProgressMonitor.java	(revision 4364)
@@ -60,66 +60,66 @@
  */
 public class GBasicProgressMonitor 
-	 extends JProgressBar 
-	 implements GShellProgressMonitor {
-	 /** Whether the process has been asked to stop. */
-	 private boolean stop = false;
-	 /** Constructor **/
-	 public GBasicProgressMonitor() {
-		  super();
-		  //this.setIcon(ready);
-	 }
-	 /** Don't do anything if we are given a progress bar, we have our own.
+    extends JProgressBar 
+    implements GShellProgressMonitor {
+    /** Whether the process has been asked to stop. */
+    private boolean stop = false;
+    /** Constructor **/
+    public GBasicProgressMonitor() {
+	super();
+	//this.setIcon(ready);
+    }
+    /** Don't do anything if we are given a progress bar, we have our own.
 	  * @param progress_bar A JProgressBar
 	  */
-	 public void addProgressBar(JProgressBar progress_bar) {
-	 }
-	 /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
+    public void addProgressBar(JProgressBar progress_bar) {
+    }
+    /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
 	  * @return A <i>int</i> with a value of zero if and only if the script was successful.
 	  */
-	 public int exitValue() {
-		  // Always return true as we don't know any better.
-		  return 0;
-	 }
-	 /** As we are probably accessing this via an interface we need to gain
+    public int exitValue() {
+	// Always return true as we don't know any better.
+	return 0;
+    }
+    /** As we are probably accessing this via an interface we need to gain
 	  * access to the component itself.
 	  */
-	 public Component getProgress() {
-		  return this;
-	 }
-	 /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
+    public Component getProgress() {
+	return this;
+    }
+    /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
 	  * @return A <strong>boolean</strong> indicating if the process has been asked to stop.
 	  */
-	 public boolean hasSignalledStop() {
-		  return stop;
-	 }
-	 /** Inform the progress bar that it should programatically increment progress by one step. */
-	 public void increment() {
-		  // We can't do anything about this.
-	 }
-	 /** The parse method is how more complex progress bars figure out how far they have progressed. However we don't.
+    public boolean hasSignalledStop() {
+	return stop;
+    }
+    /** Inform the progress bar that it should programatically increment progress by one step. */
+    public void increment() {
+	// We can't do anything about this.
+    }
+    /** The parse method is how more complex progress bars figure out how far they have progressed. However we don't.
 	  * @param line A String representing the latest line of output from
 	  * the external process we are monitoring.
 	  */
-	 public void parse(String line) {
-	 }
-	 /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
+    public void parse(String line) {
+    }
+    /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
 	  * @param state The desired state of the stop flag as a <strong>boolean</strong>.
 	  */
-	 public void setStop(boolean state) {
-		  stop = state;
-	 }
-	 /** This method tells this class that the job it is showing the progress
+    public void setStop(boolean state) {
+	stop = state;
+    }
+    /** This method tells this class that the job it is showing the progress
 	  * for has begun.
 	  */
-	 public void start() {
-		  //this.setIcon(busy);
-		  setIndeterminate(true);
-	 }
-	 /** This method tells this class that the job it is showing the progress
+    public void start() {
+	//this.setIcon(busy);
+	setIndeterminate(true);
+    }
+    /** This method tells this class that the job it is showing the progress
 	  * for has come to an end.
 	  */
-	 public void stop() {
-		  //this.setIcon(ready);
-		  setIndeterminate(false);
-	 }
+    public void stop() {
+	//this.setIcon(ready);
+	setIndeterminate(false);
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GBuildProgressMonitor.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GBuildProgressMonitor.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GBuildProgressMonitor.java	(revision 4364)
@@ -46,285 +46,283 @@
  */
 public class GBuildProgressMonitor
-	 implements GShellProgressMonitor {
-	 /** Indicates if the GUI has asked the process this object monitors to stop. */
-	 private boolean stop = false;
-
-	 private GImportProgressMonitor import_progress = null;
-
-	 private int num_docs = -1;
-
-	 private int num_indexes = -1;
-
-	 private int state = 0;
+    implements GShellProgressMonitor {
+    /** Indicates if the GUI has asked the process this object monitors to stop. */
+    private boolean stop = false;
+
+    private GImportProgressMonitor import_progress = null;
+
+    private int num_docs = -1;
+
+    private int num_indexes = -1;
+
+    private int state = 0;
 
 	 /** The progress bar this monitor updates. */
-	 private JProgressBar progress_bar;
-
-	 static final private int MIN = 0;
-	 static final private int MAX = 1000000;
-
-	 static final int BASE        =  0;
-	 static final int CTCT        =  1; // Creating the compressed text
-	 static final int CTCT_CTS    =  2; // Creating the compressed text - collecting text statistics
-	 static final int CTCT_CTS_PS =  3; // Creating the compressed text - collecting text statistics - parsed successfully
-	 static final int CTCT_CTCD   =  4; // Creating the compressed text - creating the compression dictionary
-	 static final int CTCT_CTT    =  5; // Creating the compressed text - compressing the text
-	 static final int CTCT_CTT_PS =  6; // Creating the compressed text - compressing the text - parsed successfully
-	 static final int BI          =  7; // Building index
-	 static final int BI_CID      =  8; // Building index - creating index dictionary
-	 static final int BI_CID_PS   =  9; // Building index - creating index dictionary - parsed successfully
-	 static final int BI_ITT      = 10; // Building index - inverting the text
-	 static final int BI_ITT_PS   = 11; // Building index - inverting the text - parsed successfully
-	 static final int BI_CTWF     = 12; // Building index - create the weights file
-	 static final int BI_CODSD    = 13; // Building index - creating 'on-disk' stemmed dictionary
-	 static final int BI_CSI      = 14; // Building index - creating stem indexes
-	 static final int CTID        = 15; // Creating the info database
-	 static final int CTID_PS     = 16; // Creating the info database - parsed successfully
-	 static final int CAF         = 17; // Creating auxiliary files
-
-	 static final String BI_STR   = "*** building index";
-	 static final String CTS_STR  = "collecting text statistics";
-	 static final String CTT_STR  = "compressing the text";
-	 static final String CTWF_STR = "create the weights file";
-	 static final String CAF_STR = "creating auxiliary files";
-	 static final String CID_STR  = "creating index dictionary";
-	 static final String CSI_STR  = "creating stem indexes";
-	 static final String CTCT_STR = "*** creating the compressed text";
-	 static final String CTCD_STR = "creating the compression dictionary";
-	 static final String CTID_STR = "*** creating the info database and processing associated files";
-	 static final String CODSD_STR = "creating 'on-disk' stemmed dictionary";
-	 static final String ITT_STR  = "inverting the text";
-	 static final String PS_STR   = "hash";
-
-	 public GBuildProgressMonitor(GImportProgressMonitor import_progress) {
-		  this.import_progress = import_progress;
-		  progress_bar = new JProgressBar();
-		  progress_bar.setMaximum(MAX);
-		  progress_bar.setMinimum(MIN);
-		  progress_bar.setValue(MIN);
-	 }
-
-	 /** Method to register a new progress bar with this monitor.
+    private JProgressBar progress_bar;
+
+    static final private int MIN = 0;
+    static final private int MAX = 1000000;
+
+    static final int BASE        =  0;
+    static final int CTCT        =  1; // Creating the compressed text
+    static final int CTCT_CTS    =  2; // Creating the compressed text - collecting text statistics
+    static final int CTCT_CTS_PS =  3; // Creating the compressed text - collecting text statistics - parsed successfully
+    static final int CTCT_CTCD   =  4; // Creating the compressed text - creating the compression dictionary
+    static final int CTCT_CTT    =  5; // Creating the compressed text - compressing the text
+    static final int CTCT_CTT_PS =  6; // Creating the compressed text - compressing the text - parsed successfully
+    static final int BI          =  7; // Building index
+    static final int BI_CID      =  8; // Building index - creating index dictionary
+    static final int BI_CID_PS   =  9; // Building index - creating index dictionary - parsed successfully
+    static final int BI_ITT      = 10; // Building index - inverting the text
+    static final int BI_ITT_PS   = 11; // Building index - inverting the text - parsed successfully
+    static final int BI_CTWF     = 12; // Building index - create the weights file
+    static final int BI_CODSD    = 13; // Building index - creating 'on-disk' stemmed dictionary
+    static final int BI_CSI      = 14; // Building index - creating stem indexes
+    static final int CTID        = 15; // Creating the info database
+    static final int CTID_PS     = 16; // Creating the info database - parsed successfully
+    static final int CAF         = 17; // Creating auxiliary files
+
+    static final String BI_STR   = "*** building index";
+    static final String CTS_STR  = "collecting text statistics";
+    static final String CTT_STR  = "compressing the text";
+    static final String CTWF_STR = "create the weights file";
+    static final String CAF_STR = "creating auxiliary files";
+    static final String CID_STR  = "creating index dictionary";
+    static final String CSI_STR  = "creating stem indexes";
+    static final String CTCT_STR = "*** creating the compressed text";
+    static final String CTCD_STR = "creating the compression dictionary";
+    static final String CTID_STR = "*** creating the info database and processing associated files";
+    static final String CODSD_STR = "creating 'on-disk' stemmed dictionary";
+    static final String ITT_STR  = "inverting the text";
+    static final String PS_STR   = "hash";
+
+    public GBuildProgressMonitor(GImportProgressMonitor import_progress) {
+	this.import_progress = import_progress;
+	progress_bar = new JProgressBar();
+	progress_bar.setMaximum(MAX);
+	progress_bar.setMinimum(MIN);
+	progress_bar.setValue(MIN);
+    }
+
+    /** Method to register a new progress bar with this monitor.
 	  * @param progress_bar The new <strong>JProgressBar</strong>.
 	  */	 
-	 public void addProgressBar(JProgressBar progress_bar) {
-		  this.progress_bar = progress_bar;
-		  progress_bar.setMaximum(MAX);
-		  progress_bar.setMinimum(MIN);
-		  progress_bar.setValue(MIN);
-	 }
-	 /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
+    public void addProgressBar(JProgressBar progress_bar) {
+	this.progress_bar = progress_bar;
+	progress_bar.setMaximum(MAX);
+	progress_bar.setMinimum(MIN);
+	progress_bar.setValue(MIN);
+    }
+    /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
 	  * @return A <i>int</i> with a value of zero if and only if the script was successful.
 	  */
-	 public int exitValue() {
-		  if(state == BASE) {
-				return 0;
-		  }
-		  return 1;
-	 }
-	 /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
+    public int exitValue() {
+	if(state == BASE) {
+	    return 0;
+	}
+	return 1;
+    }
+    /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
 	  * @return A <strong>Component</strong> on which the progress of the process is being displayed.
 	  */
-	 public Component getProgress() {
-		  return progress_bar;
-	 }
-	 /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
+    public Component getProgress() {
+	return progress_bar;
+    }
+    /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
 	  * @return A <strong>boolean</strong> indicating if the process has been asked to stop.
 	  */
-	 public boolean hasSignalledStop() {
-		  return stop;
-	 }
-	 /** Inform the progress bar that it should programatically increment progress by one step. */
-	 public void increment() {
-		  // There is no reason this should be called for a build.
-	 }
-
-	 /** This method is used to 'feed in' a line of text captured from the process.
+    public boolean hasSignalledStop() {
+	return stop;
+    }
+    /** Inform the progress bar that it should programatically increment progress by one step. */
+    public void increment() {
+	// There is no reason this should be called for a build.
+    }
+
+    /** This method is used to 'feed in' a line of text captured from the process.
 	  * @param line A <strong>String</strong> of text captured from either standard out or standard error.
 	  */
-	 public void parse(String raw_line) {
-		  String line = raw_line.toLowerCase();
-		  // We first check if we can parse, which depends on num_docs > 0 and num_indexes > 0.
-		  if(num_docs > 0 && num_indexes > 0) {
-				switch(state) {
-				case BASE:
-					 // We are watching for the beginning of a phase
-					 if(line.indexOf(CTCT_STR) != -1) {
-						  // No progress really.
-						  state = CTCT;
-						  ///ystem.err.println("CTCT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 else if(line.indexOf(BI_STR) != -1) {
-						  // Skip the progress up to this point.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / (3 + num_indexes)));
-						  state = BI;
-						  ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 else if(line.indexOf(CTID_STR) != -1) {
-						  // Skip the progress up to this point
-						  progress_bar.setValue(progress_bar.getValue() + (((1 + num_indexes ) * MAX) / (3 + num_indexes)));
-						  state = CTID;
-						  ///ystem.err.println("CTID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 else if(line.indexOf(CAF_STR) != -1) {
-						  // Skip the progres up to this point
-						  progress_bar.setValue(progress_bar.getValue() + (((2 + num_indexes ) * MAX) / (3 + num_indexes)));
-						  state = CAF;
-						  ///ystem.err.println("CAF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CTCT:
-					 if(line.indexOf(CTS_STR) != -1) {
-						  // No progress really.
-						  state = CTCT_CTS;
-						  ///ystem.err.println("CTS [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CTCT_CTS:
-					 if(line.indexOf(PS_STR) != -1) {
-						  // We have finally found something we can increment because of.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3 * num_docs)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3 * num_docs)));
-					 }
-					 else if(line.indexOf(CTCD_STR) != -1) {
-						  state = CTCT_CTCD;
-						  ///ystem.err.println("CTCD [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CTCT_CTCD:
-					 if(line.indexOf(CTT_STR) != -1) {
-						  // Finished creating the compression dictionary.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3)));
-						  state = CTCT_CTT;
-						  ///ystem.err.println("CTT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CTCT_CTT:
-					 if(line.indexOf(PS_STR) != -1) {
-						  // We have finally found something we can increment because of.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3 * num_docs)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3 * num_docs)));
-					 }
-					 else if(line.indexOf(BI_STR) != -1) {
-						  state = BI;
-						  ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI:
-					 if(line.indexOf(CID_STR) != -1) {
-						  state = BI_CID;
-						  ///ystem.err.println("CID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI_CID:
-					 if(line.indexOf(PS_STR) != -1) {
-						  // We have finally found something we can increment because of.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5 * num_docs)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5 * num_docs)));
-					 }
-					 else if(line.indexOf(ITT_STR) != -1) {
-						  state = BI_ITT;
-						  ///ystem.err.println("ITT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI_ITT:
-					 if(line.indexOf(PS_STR) != -1) {
-						  // We have finally found something we can increment because of.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5 * num_docs)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5 * num_docs)));
-					 }
-					 else if(line.indexOf(CTWF_STR) != -1) {
-						  state = BI_CTWF;
-						  ///ystem.err.println("CTWF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI_CTWF:
-					 if(line.indexOf(CODSD_STR) != -1) {
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
-						  state = BI_CODSD;
-						  ///ystem.err.println("CODSD [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI_CODSD:
-					 if(line.indexOf(CSI_STR) != -1) {
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
-						  state = BI_CSI;
-						  ///ystem.err.println("CSI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case BI_CSI:
-					 if(line.indexOf(BI_STR) != -1) {
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
-						  state = BI;
-						  ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 else if(line.indexOf(CTID_STR) != -1) {
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
-						  state = CTID;
-						  ///ystem.err.println("CTID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CTID:
-					 if(line.indexOf(PS_STR) != -1) {
-						  // We have finally found something we can increment because of.
-						  progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * num_docs)));
-						  ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * num_docs)));
-					 }
-					 else if(line.indexOf(CAF_STR) != -1) {
-						  state = CAF;
-						  ///ystem.err.println("CAF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
-					 }
-					 break;
-				case CAF:
-					 progress_bar.setValue(MAX);
-					 state = BASE;
-					 ///ystem.err.println("Done! 100%");
-					 break;
-				}
-		  }
-		  else {
+    public void parse(String raw_line) {
+	String line = raw_line.toLowerCase();
+	// We first check if we can parse, which depends on num_docs > 0 and num_indexes > 0.
+	if(num_docs > 0 && num_indexes > 0) {
+	    switch(state) {
+	    case BASE:
+		// We are watching for the beginning of a phase
+		if(line.indexOf(CTCT_STR) != -1) {
+		    // No progress really.
+		    state = CTCT;
+		    ///ystem.err.println("CTCT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		else if(line.indexOf(BI_STR) != -1) {
+		    // Skip the progress up to this point.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / (3 + num_indexes)));
+		    state = BI;
+		    ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		else if(line.indexOf(CTID_STR) != -1) {
+		    // Skip the progress up to this point
+		    progress_bar.setValue(progress_bar.getValue() + (((1 + num_indexes ) * MAX) / (3 + num_indexes)));
+		    state = CTID;
+		    ///ystem.err.println("CTID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		else if(line.indexOf(CAF_STR) != -1) {
+		    // Skip the progres up to this point
+		    progress_bar.setValue(progress_bar.getValue() + (((2 + num_indexes ) * MAX) / (3 + num_indexes)));
+		    state = CAF;
+		    ///ystem.err.println("CAF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CTCT:
+		if(line.indexOf(CTS_STR) != -1) {
+		    // No progress really.
+		    state = CTCT_CTS;
+		    ///ystem.err.println("CTS [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CTCT_CTS:
+		if(line.indexOf(PS_STR) != -1) {
+		    // We have finally found something we can increment because of.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3 * num_docs)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3 * num_docs)));
+		}
+		else if(line.indexOf(CTCD_STR) != -1) {
+		    state = CTCT_CTCD;
+		    ///ystem.err.println("CTCD [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CTCT_CTCD:
+		if(line.indexOf(CTT_STR) != -1) {
+		    // Finished creating the compression dictionary.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3)));
+		    state = CTCT_CTT;
+		    ///ystem.err.println("CTT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CTCT_CTT:
+		if(line.indexOf(PS_STR) != -1) {
+		    // We have finally found something we can increment because of.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 3 * num_docs)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 3 * num_docs)));
+		}
+		else if(line.indexOf(BI_STR) != -1) {
+		    state = BI;
+		    ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI:
+		if(line.indexOf(CID_STR) != -1) {
+		    state = BI_CID;
+		    ///ystem.err.println("CID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI_CID:
+		if(line.indexOf(PS_STR) != -1) {
+		    // We have finally found something we can increment because of.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5 * num_docs)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5 * num_docs)));
+		}
+		else if(line.indexOf(ITT_STR) != -1) {
+		    state = BI_ITT;
+		    ///ystem.err.println("ITT [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI_ITT:
+		if(line.indexOf(PS_STR) != -1) {
+		    // We have finally found something we can increment because of.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5 * num_docs)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5 * num_docs)));
+		}
+		else if(line.indexOf(CTWF_STR) != -1) {
+		    state = BI_CTWF;
+		    ///ystem.err.println("CTWF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI_CTWF:
+		if(line.indexOf(CODSD_STR) != -1) {
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
+		    state = BI_CODSD;
+		    ///ystem.err.println("CODSD [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI_CODSD:
+		if(line.indexOf(CSI_STR) != -1) {
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
+		    state = BI_CSI;
+		    ///ystem.err.println("CSI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case BI_CSI:
+		if(line.indexOf(BI_STR) != -1) {
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
+		    state = BI;
+		    ///ystem.err.println("BI [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		else if(line.indexOf(CTID_STR) != -1) {
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * 5)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * 5)));
+		    state = CTID;
+		    ///ystem.err.println("CTID [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CTID:
+		if(line.indexOf(PS_STR) != -1) {
+		    // We have finally found something we can increment because of.
+		    progress_bar.setValue(progress_bar.getValue() + (MAX / ((3 + num_indexes) * num_docs)));
+		    ///ystem.err.println("Increase " + (MAX / ((3 + num_indexes) * num_docs)));
+		}
+		else if(line.indexOf(CAF_STR) != -1) {
+		    state = CAF;
+		    ///ystem.err.println("CAF [" + state + "] - " + ((progress_bar.getValue() * 100) / MAX) + "%");
+		}
+		break;
+	    case CAF:
+		progress_bar.setValue(MAX);
+		state = BASE;
+		///ystem.err.println("Done! 100%");
+		break;
+	    }
+	}
+	else {
 				///ystem.err.println("Number of documents = " + num_docs);
 				///ystem.err.println("Number of indexes = " + num_indexes);
-				progress_bar.setIndeterminate(true);
-		  }
-	 }
-
-	 public void reset() {
-		  state = BASE;
-		  progress_bar.setValue(MIN);
-		  num_docs = -1;
-		  num_indexes = -1;
-		  stop = false;
-		  progress_bar.setIndeterminate(false);
-	 }
-
-	 /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
+	    progress_bar.setIndeterminate(true);
+	}
+    }
+
+    public void reset() {
+	state = BASE;
+	progress_bar.setValue(MIN);
+	num_docs = -1;
+	num_indexes = -1;
+	stop = false;
+	progress_bar.setIndeterminate(false);
+    }
+
+    /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
 	  * @param state The desired state of the stop flag as a <strong>boolean</strong>.
 	  */
-	 public void setStop(boolean state) {
-		  stop = state;
-		  progress_bar.setIndeterminate(false);
-	 }
-	 /** This method resets this monitor to the start, reseting the process parsing and progress bar.
+    public void setStop(boolean state) {
+	stop = state;
+	progress_bar.setIndeterminate(false);
+    }
+    /** This method resets this monitor to the start, reseting the process parsing and progress bar.
 	  * TODO Everthing.
 	  */
-	 public void start() {
-		  progress_bar.setValue(MIN);
-		  num_indexes = Gatherer.g_man.config_pane.getIndexes().size();
-		  num_docs = import_progress.getNumberOfDocuments();
-	 }
-	 /** This method indicates the process is complete. 
+    public void start() {
+	progress_bar.setValue(MIN);
+	num_indexes = Gatherer.g_man.config_pane.getIndexes().size();
+	num_docs = import_progress.getNumberOfDocuments();
+    }
+    /** This method indicates the process is complete. 
 	  * TODO Everthing.
 	  */
-	 public void stop() {
-		  progress_bar.setIndeterminate(false);
-		  progress_bar.setValue(MAX); // Not max, or else it won't blank properly for the next build.
-	 }
+    public void stop() {
+	progress_bar.setIndeterminate(false);
+	progress_bar.setValue(MAX); // Not max, or else it won't blank properly for the next build.
+    }
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GImportProgressMonitor.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GImportProgressMonitor.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GImportProgressMonitor.java	(revision 4364)
@@ -46,128 +46,128 @@
  */
 public class GImportProgressMonitor
-	 implements GShellProgressMonitor {
-	 /** Indicates if the GUI has asked the process this object monitors to stop. */
-	 private boolean stop = false;
-	 /** The number of directories that are expected to be scanned by this import process. May not be correct for various reasons, such as a custom RecPlug setup, but its seems the most reliable for general cases. */
-	 private int num_dirs = 0;
-	 /** This holds the number of documents actually processed by the import command, as garnered from the final block of text output. */
-	 private int num_docs = 0;
-	 /** The progress bar this monitor updates. */
-	 private JProgressBar progress_bar;
-	 /** The maximum value for this progress bar. */
-	 static final private int MAX = 1000000;
-	 /** The minimum value for this progress bar. */
-	 static final private int MIN = 0;
-	 /** A String fragment which is used to determine if the current output process line is referring to the number of documents actually processed by the import script. */
-	 static final private String NUM_DOCS_PROCESSED = "processed and included in the collection";
-	 /** The Sentinel value is the String fragment an output process line must start with to be considered a milestone. In this case we are counting directories, so the fragment refers to a directory scan. */
-	 static final private String SENTINEL = "recplug: getting directory ";
-	 public GImportProgressMonitor() {
-		  progress_bar = new JProgressBar();
-		  progress_bar.setMaximum(MAX);
-		  progress_bar.setMinimum(MIN);
-		  progress_bar.setValue(MIN);
-	 }
-	 /** Method to register a new progress bar with this monitor.
+    implements GShellProgressMonitor {
+    /** Indicates if the GUI has asked the process this object monitors to stop. */
+    private boolean stop = false;
+    /** The number of directories that are expected to be scanned by this import process. May not be correct for various reasons, such as a custom RecPlug setup, but its seems the most reliable for general cases. */
+    private int num_dirs = 0;
+    /** This holds the number of documents actually processed by the import command, as garnered from the final block of text output. */
+    private int num_docs = 0;
+    /** The progress bar this monitor updates. */
+    private JProgressBar progress_bar;
+    /** The maximum value for this progress bar. */
+    static final private int MAX = 1000000;
+    /** The minimum value for this progress bar. */
+    static final private int MIN = 0;
+    /** A String fragment which is used to determine if the current output process line is referring to the number of documents actually processed by the import script. */
+    static final private String NUM_DOCS_PROCESSED = "processed and included in the collection";
+    /** The Sentinel value is the String fragment an output process line must start with to be considered a milestone. In this case we are counting directories, so the fragment refers to a directory scan. */
+    static final private String SENTINEL = "recplug: getting directory ";
+    public GImportProgressMonitor() {
+	progress_bar = new JProgressBar();
+	progress_bar.setMaximum(MAX);
+	progress_bar.setMinimum(MIN);
+	progress_bar.setValue(MIN);
+    }
+    /** Method to register a new progress bar with this monitor.
 	  * @param progress_bar The new <strong>JProgressBar</strong>.
 	  */	 
-	 public void addProgressBar(JProgressBar progress_bar) {
-		  this.progress_bar = progress_bar;
-		  progress_bar.setMaximum(MAX);
-		  progress_bar.setMinimum(MIN);
-		  progress_bar.setValue(MIN);
-	 }
-	 /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
+    public void addProgressBar(JProgressBar progress_bar) {
+	this.progress_bar = progress_bar;
+	progress_bar.setMaximum(MAX);
+	progress_bar.setMinimum(MIN);
+	progress_bar.setValue(MIN);
+    }
+    /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
 	  * @return A <i>int</i> with a value of zero if and only if the script was successful.
 	  */
-	 public int exitValue() {
-		  if(num_docs > 0) {
-				return 0;
-		  }
-		  return 1;
-	 }
-	 /** Retrieve the number of documents recorded by the import monitor during the execution of the import scripts.
+    public int exitValue() {
+	if(num_docs > 0) {
+	    return 0;
+	}
+	return 1;
+    }
+    /** Retrieve the number of documents recorded by the import monitor during the execution of the import scripts.
 	  * @return An <i>int</i> giving the document number.
 	  */
-	 public int getNumberOfDocuments() {
-		  return num_docs;
-	 }
+    public int getNumberOfDocuments() {
+	return num_docs;
+    }
 
-	 /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
+    /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
 	  * @return A <strong>Component</strong> on which the progress of the process is being displayed.
 	  */
-	 public Component getProgress() {
-		  return progress_bar;
-	 }
-	 /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
+    public Component getProgress() {
+	return progress_bar;
+    }
+    /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
 	  * @return A <strong>boolean</strong> indicating if the process has been asked to stop.
 	  */
-	 public boolean hasSignalledStop() {
-		  return stop;
-	 }
-	 /** Inform the progress bar that it should programatically increment progress by one step. This is only called during the metadata archive extraction so each step should be (100 / 5) / num_docs. */
-	 public void increment() {
-		  if(num_docs > 0) {
-				progress_bar.setValue(progress_bar.getValue() + ((MAX / 5) / num_docs));
-		  }
-		  else {
-				progress_bar.setIndeterminate(true);
-		  }
-	 }
-	 /** This method is used to 'feed in' a line of text captured from the process.
+    public boolean hasSignalledStop() {
+	return stop;
+    }
+    /** Inform the progress bar that it should programatically increment progress by one step. This is only called during the metadata archive extraction so each step should be (100 / 5) / num_docs. */
+    public void increment() {
+	if(num_docs > 0) {
+	    progress_bar.setValue(progress_bar.getValue() + ((MAX / 5) / num_docs));
+	}
+	else {
+	    progress_bar.setIndeterminate(true);
+	}
+    }
+    /** This method is used to 'feed in' a line of text captured from the process.
 	  * @param line A <strong>String</strong> of text captured from either standard out or standard error.
 	  * TODO Everthing.
 	  */
-	 public void parse(String line_raw) {
-		  String line = line_raw.toLowerCase();
-		  if(line.indexOf(SENTINEL) != -1) {
+    public void parse(String line_raw) {
+	String line = line_raw.toLowerCase();
+	if(line.indexOf(SENTINEL) != -1) {
 				///ystem.err.println("Sentinal value detected: ");
-				if(num_dirs > 0) {
-					 ///ystem.err.println(count + " of " + num_dirs);
-					 ///ystem.err.println("Value before: " + progress_bar.getValue());
-					 progress_bar.setValue(progress_bar.getValue() + ((4 * (MAX / 5)) / num_dirs));
-					 ///ystem.err.println("Value after:  " + progress_bar.getValue());
-				}
-				else {
-					 progress_bar.setIndeterminate(true);
-				}
-		  }
-		  else if(line.indexOf(NUM_DOCS_PROCESSED) != -1) {
+	    if(num_dirs > 0) {
+		///ystem.err.println(count + " of " + num_dirs);
+		///ystem.err.println("Value before: " + progress_bar.getValue());
+		progress_bar.setValue(progress_bar.getValue() + ((4 * (MAX / 5)) / num_dirs));
+		///ystem.err.println("Value after:  " + progress_bar.getValue());
+	    }
+	    else {
+		progress_bar.setIndeterminate(true);
+	    }
+	}
+	else if(line.indexOf(NUM_DOCS_PROCESSED) != -1) {
 				// Increment the import because we have finished a directory
 				
-				num_docs = -1;
-				StringTokenizer tokenizer = new StringTokenizer(line);
-				while(num_docs == -1 && tokenizer.hasMoreTokens()) {
-					 String pos_num_docs_str = tokenizer.nextToken();
-					 try {
-						  num_docs = Integer.parseInt(pos_num_docs_str);
-					 }
-					 catch (Exception error) {
-					 }
-				}
+	    num_docs = -1;
+	    StringTokenizer tokenizer = new StringTokenizer(line);
+	    while(num_docs == -1 && tokenizer.hasMoreTokens()) {
+		String pos_num_docs_str = tokenizer.nextToken();
+		try {
+		    num_docs = Integer.parseInt(pos_num_docs_str);
+		}
+		catch (Exception error) {
+		}
+	    }
 				///ystem.err.println("Parsed num_docs to be " + num_docs);
-		  }
-	 }
-	 /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
+	}
+    }
+    /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
 	  * @param state The desired state of the stop flag as a <strong>boolean</strong>.
 	  */
-	 public void setStop(boolean state) {
-		  stop = state;
-		  progress_bar.setIndeterminate(false);
-	 }
-	 /** This method resets this monitor to the start, reseting the process parsing and progress bar.
+    public void setStop(boolean state) {
+	stop = state;
+	progress_bar.setIndeterminate(false);
+    }
+    /** This method resets this monitor to the start, reseting the process parsing and progress bar.
 	  * TODO Everthing.
 	  */
-	 public void start() {
-		  num_dirs = Gatherer.c_man.getCollection().getFolderCount();
-		  progress_bar.setValue(MIN);
-		  System.err.println("Number of directories is " + num_dirs);
-	 }
-	 /** This method indicates the process is complete. 
+    public void start() {
+	num_dirs = Gatherer.c_man.getCollection().getFolderCount();
+	progress_bar.setValue(MIN);
+	System.err.println("Number of directories is " + num_dirs);
+    }
+    /** This method indicates the process is complete. 
 	  * TODO Everthing.
 	  */
-	 public void stop() {
-		  progress_bar.setIndeterminate(false);
-		  progress_bar.setValue(MAX);
-	 }
+    public void stop() {
+	progress_bar.setIndeterminate(false);
+	progress_bar.setValue(MAX);
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GShell.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GShell.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GShell.java	(revision 4364)
@@ -48,289 +48,287 @@
  */
 public class GShell
-	 extends Thread {
-	 /** A flag used to determine if this process has been asked to cancel. */
-	 private boolean cancel = false;
-	 /** The list of listeners associated with this class. */
-	 private EventListenerList listeners = null;
-	 /** The current status of this shell process. */
-	 private int status = -1;
-	 /** The type of message being sent. */
-	 private int msg_type = -1;
-	 /** The type of shell process. */
-	 private int type = -1;
-	 /** The caller of this process, and thus the class most interested in messages. */
-	 private GShellListener caller = null;
-	 /** The progress monitor associated with this process. */
-	 private GShellProgressMonitor progress = null;
-	 /** Arguments to be given to the process (including the executable you are calling. */
-	 private String args[] = null;
-	 /** Element in process type enumeration. */
-	 static final public int BUILD = 0;
-	 /** Element in process type enumeration. */
-	 static final public int IMPORT = 1;
-	 /** Element in process type enumeration. */
-	 static final public int NEW = 2;
-	 /** Element in process type enumeration. */
-	 static final public int OTHER = 3;
-	 /** Element in status type enumeration. */
-	 static final public int ERROR = 0;
-	 /** Element in status type enumeration. */
-	 static final public int OK = 1;
-	 /** Element in process type name enumeration. */
-	 static public String GSHELL_BUILD = "gshell_build";
-	 /** Element in process type name enumeration. */
-	 static public String GSHELL_IMPORT = "gshell_import";
-	 /** Element in process type name enumeration. */
-	 static public String GSHELL_NEW = "gshell_new";
-	 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
-	  * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
-	  * @param type An <strong>int</strong> that indicates what group of processes this process belongs to, as some are treated slightly differently (i.e an IMPORT type process is always followed by a BUILD one).
-	  * @param msg_type As process threads may be background (like a makecol.pl call) or important processes in their own right (such as an IMPORT-BUILD) we must be able to set what level messages posted by this class will have by usings this <strong>int</strong>.
-	  * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
-	  * @param progress The <i>GShellProgressMonitor</i> associated with this process.
-	  * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
-	  */
-	 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
-		  super(name);
-		  this.args = args;
-		  this.msg_type = msg_type;
-		  this.type = type;
-		  this.caller = caller;
-		  this.progress = progress;
-		  this.status = 0;
-		  // Lower this jobs priority
-		  this.setPriority(Thread.MIN_PRIORITY);
-		  listeners = new EventListenerList();
-		  listeners.add(GShellListener.class, caller);
-	 }
-	 /** This method adds another shell listener to this process.
+    extends Thread {
+    /** A flag used to determine if this process has been asked to cancel. */
+    private boolean cancel = false;
+    /** The list of listeners associated with this class. */
+    private EventListenerList listeners = null;
+    /** The current status of this shell process. */
+    private int status = -1;
+    /** The type of message being sent. */
+    private int msg_type = -1;
+    /** The type of shell process. */
+    private int type = -1;
+    /** The caller of this process, and thus the class most interested in messages. */
+    private GShellListener caller = null;
+    /** The progress monitor associated with this process. */
+    private GShellProgressMonitor progress = null;
+    /** Arguments to be given to the process (including the executable you are calling. */
+    private String args[] = null;
+    /** Element in process type enumeration. */
+    static final public int BUILD = 0;
+    /** Element in process type enumeration. */
+    static final public int IMPORT = 1;
+    /** Element in process type enumeration. */
+    static final public int NEW = 2;
+    /** Element in process type enumeration. */
+    static final public int OTHER = 3;
+    /** Element in status type enumeration. */
+    static final public int ERROR = 0;
+    /** Element in status type enumeration. */
+    static final public int OK = 1;
+    /** Element in process type name enumeration. */
+    static public String GSHELL_BUILD = "gshell_build";
+    /** Element in process type name enumeration. */
+    static public String GSHELL_IMPORT = "gshell_import";
+    /** Element in process type name enumeration. */
+    static public String GSHELL_NEW = "gshell_new";
+    /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
+     * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
+     * @param type An <strong>int</strong> that indicates what group of processes this process belongs to, as some are treated slightly differently (i.e an IMPORT type process is always followed by a BUILD one).
+     * @param msg_type As process threads may be background (like a makecol.pl call) or important processes in their own right (such as an IMPORT-BUILD) we must be able to set what level messages posted by this class will have by usings this <strong>int</strong>.
+     * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
+     * @param progress The <i>GShellProgressMonitor</i> associated with this process.
+     * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
+     */
+    public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
+	super(name);
+	this.args = args;
+	this.msg_type = msg_type;
+	this.type = type;
+	this.caller = caller;
+	this.progress = progress;
+	this.status = 0;
+	// Lower this jobs priority
+	this.setPriority(Thread.MIN_PRIORITY);
+	listeners = new EventListenerList();
+	listeners.add(GShellListener.class, caller);
+    }
+    /** This method adds another shell listener to this process.
 	  * @param listener The new <i>GShellListener</i>.
 	  */
-	 public void addGShellListener(GShellListener listener) {
-		  listeners.add(GShellListener.class, listener);
-	 }
-	 /** This method removes a certain shell listener from this process.
+    public void addGShellListener(GShellListener listener) {
+	listeners.add(GShellListener.class, listener);
+    }
+    /** This method removes a certain shell listener from this process.
 	  * @param listener The <i>GShellListener</i> to be removed.
 	  */
-	 public void removeGShellListener(GShellListener listener) {
-		  listeners.remove(GShellListener.class, listener);
-	 }
-	 /** Any threaded class must include this method to allow the thread body to be run.
+    public void removeGShellListener(GShellListener listener) {
+	listeners.remove(GShellListener.class, listener);
+    }
+    /** Any threaded class must include this method to allow the thread body to be run.
 	  */
     public void run() {
-		  // Setup
-		  if(progress != null) {
-				progress.start();
-		  }
-		  // Determine if the user has asked for an outfile.
-		  String out_name = null;
-		  FileOutputStream fos = null;
-		  BufferedOutputStream bos = null;
-		  if(type == IMPORT || type == BUILD) {
-				if(type == IMPORT) {
-					 out_name = (String) Gatherer.c_man.getCollection().build_options.getImportValue("out");
-				}
-				else {
-					 out_name = (String) Gatherer.c_man.getCollection().build_options.getBuildValue("out");
-				}
-				if(out_name != null && out_name.length() > 0) {
-					 File out = new File(out_name);
-					 try {
-						  if(out.exists()) {
-								boolean append = true;
-								fos = new FileOutputStream(out, append);
-								bos = new BufferedOutputStream(fos);
-						  }
-					 } 
-					 catch (Exception error) {
-						  error.printStackTrace();
-					 }
-				}
-		  }
-		  // Issue a processBegun event
-		  fireProcessBegun(type, status);
-		  try {
-				String command = "";
-				for(int i = 0; i < args.length; i++) {
-					 command = command + args[i] + " ";
-				}
+	// Setup
+	if(progress != null) {
+	    progress.start();
+	}
+	// Determine if the user has asked for an outfile.
+	String out_name = null;
+	FileOutputStream fos = null;
+	BufferedOutputStream bos = null;
+	if(type == IMPORT || type == BUILD) {
+	    if(type == IMPORT) {
+		out_name = (String) Gatherer.c_man.getCollection().build_options.getImportValue("out");
+	    }
+	    else {
+		out_name = (String) Gatherer.c_man.getCollection().build_options.getBuildValue("out");
+	    }
+	    if(out_name != null && out_name.length() > 0) {
+		File out = new File(out_name);
+		try {
+		    if(out.exists()) {
+			boolean append = true;
+			fos = new FileOutputStream(out, append);
+			bos = new BufferedOutputStream(fos);
+		    }
+		} 
+		catch (Exception error) {
+		    error.printStackTrace();
+		}
+	    }
+	}
+	// Issue a processBegun event
+	fireProcessBegun(type, status);
+	try {
+	    String command = "";
+	    for(int i = 0; i < args.length; i++) {
+		command = command + args[i] + " ";
+	    }
 				///ystem.err.println("Command: " + command);
-				fireMessage(type, get("Command") + ": " + command, status);
-				message(Message.EVENT, get("Command") + ": " + command);
+	    fireMessage(type, get("Command") + ": " + command, status);
+	    message(Message.EVENT, get("Command") + ": " + command);
 				
-				Runtime rt = Runtime.getRuntime();
-				Process prcs = rt.exec(args);
-				InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream() );
-				InputStreamReader stdinisr = new InputStreamReader( prcs.getInputStream() );
-				BufferedReader ebr = new BufferedReader( eisr );
-				BufferedReader stdinbr = new BufferedReader( stdinisr );
+	    Runtime rt = Runtime.getRuntime();
+	    Process prcs = rt.exec(args);
+	    InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream() );
+	    InputStreamReader stdinisr = new InputStreamReader( prcs.getInputStream() );
+	    BufferedReader ebr = new BufferedReader( eisr );
+	    BufferedReader stdinbr = new BufferedReader( stdinisr );
 				// Captures the std err of a program and pipes it into
 				// std in of java
-				String eline = null;
-				String stdinline = null;
-				while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !hasSignalledStop()) {
-					 if(eline != null) {
-						  if(progress != null) {
-								progress.parse(eline);
-						  }
-						  if(bos != null) {
-								bos.write(eline.getBytes(), 0, eline.length());
-						  }
-						  ///ystem.err.println("stderr: " + eline);
-						  fireMessage(type, typeAsString(type) + "> " + eline, 
-										  status);
-						  message(Message.INFO, eline);
-					 }
-					 if(stdinline != null) {
-						  if(bos != null) {
-								//bos.write(stdinline.getBytes(), 0, stdinline.length());
-						  }
-						  ///ystem.err.println("stdin: " + stdinline);
-						  fireMessage(type, typeAsString(type) + "> " + stdinline,
-										  status);
-						  message(Message.INFO, stdinline);
-					 }
-				}
+	    String eline = null;
+	    String stdinline = null;
+	    while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !hasSignalledStop()) {
+		if(eline != null) {
+		    if(progress != null) {
+			progress.parse(eline);
+		    }
+		    if(bos != null) {
+			bos.write(eline.getBytes(), 0, eline.length());
+		    }
+		    ///ystem.err.println("stderr: " + eline);
+		    fireMessage(type, typeAsString(type) + "> " + eline, 
+				status);
+		    message(Message.INFO, eline);
+		}
+		if(stdinline != null) {
+		    if(bos != null) {
+			//bos.write(stdinline.getBytes(), 0, stdinline.length());
+		    }
+		    ///ystem.err.println("stdin: " + stdinline);
+		    fireMessage(type, typeAsString(type) + "> " + stdinline,
+				status);
+		    message(Message.INFO, stdinline);
+		}
+	    }
 
-				if(!hasSignalledStop()) {
-					 // Now display final message based on exit value
-					 prcs.waitFor();
+	    if(!hasSignalledStop()) {
+		// Now display final message based on exit value
+		prcs.waitFor();
 					 
-					 if(prcs.exitValue() == 0) {
-						  status = OK;
-						  fireMessage(type, typeAsString(type) + "> " + get("Success"), status);
-						  message(Message.MAIN, get("Success"));
-					 } else {
-						  status = ERROR;
-						  fireMessage(type, typeAsString(type) + "> " + get("Failure"), status);
-						  message(Message.ERROR, get("Failure"));
-					 }
-				}
-				else {
-					 // I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)
-					 prcs.getOutputStream().close();
-					 prcs.destroy();
-					 status = ERROR;
-				}
-		  }
-		  // Exception
-		  catch (Exception error) {
-				message(Message.ERROR, get("Failure"));
-				message(Message.ERROR, error.toString());
-				Gatherer.printStackTrace(error);
-				status = ERROR;
-		  }
-		  // If no error occured, and this was an import process we now extract any new metadata from the archive directory.
-		  if(status == OK && type == IMPORT) {
-				fireMessage(type, typeAsString(type) + "> " + get("Parsing_Metadata_Start"), status);
-				new GreenstoneArchiveParser(progress, this);
-				fireMessage(type, typeAsString(type) + "> " + get("Parsing_Metadata_Complete"), status);
-		  }
-		  // Tidy up.
-		  if(progress != null) {
-				progress.stop();
-		  }
-		  if(bos != null) {
-				try {
-					 bos.close();
-				}
-				catch (Exception e) {
-				}
-		  }
-		  // We're done.
-		  fireProcessComplete(type, status);
-    }
-	 /** Method for firing a message to all interested listeners.
+		if(prcs.exitValue() == 0) {
+		    status = OK;
+		    fireMessage(type, typeAsString(type) + "> " + get("Success"), status);
+		    message(Message.MAIN, get("Success"));
+		} else {
+		    status = ERROR;
+		    fireMessage(type, typeAsString(type) + "> " + get("Failure"), status);
+		    message(Message.ERROR, get("Failure"));
+		}
+	    }
+	    else {
+		// I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)
+		prcs.getOutputStream().close();
+		prcs.destroy();
+		status = ERROR;
+	    }
+	}
+	// Exception
+	catch (Exception error) {
+	    message(Message.ERROR, get("Failure"));
+	    message(Message.ERROR, error.toString());
+	    Gatherer.printStackTrace(error);
+	    status = ERROR;
+	}
+	// If no error occured, and this was an import process we now extract any new metadata from the archive directory.
+	if(status == OK && type == IMPORT) {
+	    fireMessage(type, typeAsString(type) + "> " + get("Parsing_Metadata_Start"), status);
+	    new GreenstoneArchiveParser(progress, this);
+	    fireMessage(type, typeAsString(type) + "> " + get("Parsing_Metadata_Complete"), status);
+	}
+	// Tidy up.
+	if(progress != null) {
+	    progress.stop();
+	}
+	if(bos != null) {
+	    try {
+		bos.close();
+	    }
+	    catch (Exception e) {
+	    }
+	}
+	// We're done.
+	fireProcessComplete(type, status);
+    }
+    /** Method for firing a message to all interested listeners.
 	  * @param type An <strong>int</strong> indicating the process type.
 	  * @param message The message as a <strong>String</strong>.
 	  * @param status An <strong>int</strong> specifying the current status of the process.
 	  */
-	 public void fireMessage(int type, String message, int status) {
-		  GShellEvent event = new GShellEvent(this, 0, type, message, status);
-		  Object[] concerned = listeners.getListenerList();
-		  for(int i = 0; i < concerned.length ; i++) {
-				if(concerned[i] == GShellListener.class) {
-					 ((GShellListener)concerned[i+1]).message(event);
-				}
-		  }
-	 }
-	 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
+    public void fireMessage(int type, String message, int status) {
+	GShellEvent event = new GShellEvent(this, 0, type, message, status);
+	Object[] concerned = listeners.getListenerList();
+	for(int i = 0; i < concerned.length ; i++) {
+	    if(concerned[i] == GShellListener.class) {
+		((GShellListener)concerned[i+1]).message(event);
+	    }
+	}
+    }
+    /** Method for firing a process begun event which is called, strangly enough, when the process begins.
 	  * @param type An <strong>int</strong> indicating the process type.
 	  * @param status An <strong>int</strong> specifying the current status of the process.
 	  */
-	 protected void fireProcessBegun(int type, int status) {
-		  GShellEvent event = new GShellEvent(this, 0, type, "", status);
-		  Object[] concerned = listeners.getListenerList();
-		  for(int i = 0; i < concerned.length ; i++) {
-				if(concerned[i] == GShellListener.class) {
-					 ((GShellListener)concerned[i+1]).processBegun(event);
-				}
-		  }
-	 }
-	 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
+    protected void fireProcessBegun(int type, int status) {
+	GShellEvent event = new GShellEvent(this, 0, type, "", status);
+	Object[] concerned = listeners.getListenerList();
+	for(int i = 0; i < concerned.length ; i++) {
+	    if(concerned[i] == GShellListener.class) {
+		((GShellListener)concerned[i+1]).processBegun(event);
+	    }
+	}
+    }
+    /** Method for firing a process complete event which is called, no surprise here, when the process ends.
 	  * @param type An <strong>int</strong> indicating the process type.
 	  * @param status An <strong>int</strong> specifying the current status of the process.
 	  */
-	 protected void fireProcessComplete(int type, int status) {
-		  GShellEvent event = new GShellEvent(this, 0, type, "", status);
-		  Object[] concerned = listeners.getListenerList();
-		  for(int i = 0; i < concerned.length ; i++) {
-				if(concerned[i] == GShellListener.class) {
-					 ((GShellListener)concerned[i+1]).processComplete(event);
-				}
-		  }
-	 }
-	 /** Retrieve a phrase from the dictionary based on the given key.
+    protected void fireProcessComplete(int type, int status) {
+	GShellEvent event = new GShellEvent(this, 0, type, "", status);
+	Object[] concerned = listeners.getListenerList();
+	for(int i = 0; i < concerned.length ; i++) {
+	    if(concerned[i] == GShellListener.class) {
+		((GShellListener)concerned[i+1]).processComplete(event);
+	    }
+	}
+    }
+    /** Retrieve a phrase from the dictionary based on the given key.
 	  * @param key A <strong>String</strong> used to match against a phrase from the <strong>Dictionary</strong>.
 	  * @return The phrase as a <strong>String</strong>.
 	  */
-	 private String get(String key) {
-		  if(key.indexOf('.') == -1) {
-				key = "GShell." + key;
-		  }
-		  return Gatherer.dictionary.get(key);
-	 }
-	 /** Method to determine if the user, via the progress monitor, has signalled stop. 
+    private String get(String key) {
+	if(key.indexOf('.') == -1) {
+	    key = "GShell." + key;
+	}
+	return Gatherer.dictionary.get(key);
+    }
+    /** Method to determine if the user, via the progress monitor, has signalled stop. 
 	  * @return A <strong>boolean</strong> indicating if the user wanted to stop.
 	  */
-	 private boolean hasSignalledStop() {
-		  boolean has_signalled_stop = false;
-		  if(progress != null) {
-				return progress.hasSignalledStop();
-		  }
-		  return has_signalled_stop;
-	 }
-	 /** Creates and dispatches a message given the initial details.
+    private boolean hasSignalledStop() {
+	boolean has_signalled_stop = false;
+	if(progress != null) {
+	    return progress.hasSignalledStop();
+	}
+	return has_signalled_stop;
+    }
+    /** Creates and dispatches a message given the initial details.
 	  * @param level An <strong>int</strong> indicating the message level for this message.
 	  * @param message A <strong>String</strong> which contains the payload of this message.
 	  */
-	 private void message(int level, String message) {
-		  Message msg = new Message(msg_type, level, message);
-		  if(Gatherer.g_man != null) {
-				Gatherer.log.add(msg);
-		  }
-	 }
-	 /** Converts a type into a text representation.
+    private void message(int level, String message) {
+	Message msg = new Message(msg_type, level, message);
+	if(Gatherer.g_man != null) {
+	    Gatherer.log.add(msg);
+	}
+    }
+    /** Converts a type into a text representation.
 	  * @param type An <strong>int</strong> which maps to a shell process type.
 	  * @return A <strong>String</strong> which is the thread process's text name.
 	  */
-	 public String typeAsString(int type) {
-		  String name = null;
-		  switch(type) {
-		  case BUILD:
-				name = get("Build");
-				break;
-		  case IMPORT:
-				name = get("Import");
-				break;
-		  case NEW:
-				name = get("New");
-				break;
-		  default:
-				name = get("Other");
-		  }
-		  return name;
-	 }
+    public String typeAsString(int type) {
+	String name = null;
+	switch(type) {
+	case BUILD:
+	    name = get("Build");
+	    break;
+	case IMPORT:
+	    name = get("Import");
+	    break;
+	case NEW:
+	    name = get("New");
+	    break;
+	default:
+	    name = get("Other");
+	}
+	return name;
+    }
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GShellEvent.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GShellEvent.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GShellEvent.java	(revision 4364)
@@ -54,46 +54,46 @@
 /** This class encapsulates all the information created by an event within a <strong>GShell</strong> process. */
 public class GShellEvent 
-	 extends AWTEvent {
-	 /** The status of the process at the completion of event. */
-	 private int status = -1;
-	 /** The process type (such as COPY, BUILD or IMPORT). */
-	 private int type = -1;
-	 /** Any message associated with this event. */
-	 private String message = null;
-	 /* Constructor.
-	  * @param source The <strong>GShell</strong> that fired this message.
-	  * @param id The event identifier as an <strong>int</strong>.
-	  * @param type The process type as an <strong>int</strong>.
-	  * @param message A <strong>String</strong> representing any message attatched with this event.
-	  * @param status The status of the process post event, as an <strong>int</strong>.
+    extends AWTEvent {
+    /** The status of the process at the completion of event. */
+    private int status = -1;
+    /** The process type (such as COPY, BUILD or IMPORT). */
+    private int type = -1;
+    /** Any message associated with this event. */
+    private String message = null;
+    /* Constructor.
+     * @param source The <strong>GShell</strong> that fired this message.
+     * @param id The event identifier as an <strong>int</strong>.
+     * @param type The process type as an <strong>int</strong>.
+     * @param message A <strong>String</strong> representing any message attatched with this event.
+     * @param status The status of the process post event, as an <strong>int</strong>.
 	  */
-	 public GShellEvent(Object source, int id, int type, String message,
-							  int status) {
-		  super(source, id);
-		  this.message = message;
-		  this.status = status;
-		  this.type = type;
-	 }
-	 /** Gets the message associated with this event.
+    public GShellEvent(Object source, int id, int type, String message,
+		       int status) {
+	super(source, id);
+	this.message = message;
+	this.status = status;
+	this.type = type;
+    }
+    /** Gets the message associated with this event.
 	  * @return The message as a <strong>String</strong> or <i>null</i>.
 	  */
-	 public String getMessage() {
-		  return message;
-	 }
-	 /** Gets the status associated with this event. This status can then be matched back to the constants in <strong>GShell</strong>.
+    public String getMessage() {
+	return message;
+    }
+    /** Gets the status associated with this event. This status can then be matched back to the constants in <strong>GShell</strong>.
 	  * @return An <strong>int</strong> signifying the process status.
 	  */ 
-	 public int getStatus() {
-		  return status;
-	 }
-	 /** Gets the type associated with this event. This type can then be matched back to the constants in <strong>GShell</strong>.
+    public int getStatus() {
+	return status;
+    }
+    /** Gets the type associated with this event. This type can then be matched back to the constants in <strong>GShell</strong>.
 	  * @return An <strong>int</strong> signifying the process type.
 	  */ 
-	 public int getType() {
-		  return type;
-	 }
+    public int getType() {
+	return type;
+    }
 
-	 public String toString() {
-		  return "org.greenstone.gatherer.shell.GShellEvent[" + message + "," + status + "," + type + "]";
-	 }
+    public String toString() {
+	return "org.greenstone.gatherer.shell.GShellEvent[" + message + "," + status + "," + type + "]";
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GShellListener.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GShellListener.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GShellListener.java	(revision 4364)
@@ -57,16 +57,16 @@
  */
 public interface GShellListener 
-	 extends EventListener {
-	 /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed of messages from the <strong>GShell</strong>.
-	  * @param event A <strong>GShellEvent</strong> that contains, amoung other things, the message.
+    extends EventListener {
+    /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed of messages from the <strong>GShell</strong>.
+     * @param event A <strong>GShellEvent</strong> that contains, amoung other things, the message.
 	  */
-	 public void message(GShellEvent event);
-	 /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed when a <strong>GShell</strong> begins its task.
+    public void message(GShellEvent event);
+    /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed when a <strong>GShell</strong> begins its task.
 	  * @param event A <strong>GShellEvent</strong> that contains details of the initial state of the <strong>GShell</strong> before task comencement.
 	  */
-	 public void processBegun(GShellEvent event);
-	 /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed when a <strong>GShell</strong> completes its task.
+    public void processBegun(GShellEvent event);
+    /** All implementation of <i>GShellListener</i> must include this method so the listener can be informed when a <strong>GShell</strong> completes its task.
 	  * @param event A <strong>GShellEvent</strong> that contains details of the final state of the <strong>GShell</strong> after task completion.
 	  */
-	 public void processComplete(GShellEvent event);
+    public void processComplete(GShellEvent event);
 }
Index: /trunk/gli/src/org/greenstone/gatherer/shell/GShellProgressMonitor.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/shell/GShellProgressMonitor.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/shell/GShellProgressMonitor.java	(revision 4364)
@@ -58,36 +58,35 @@
  */
 public interface GShellProgressMonitor {
-	 /** Method to register a new progress bar with this monitor.
-	  * @param progress_bar The new <strong>JProgressBar</strong>.
-	  */
-	 public void addProgressBar(JProgressBar progress_bar);
-	 /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
-	  * @return A <i>int</i> with a value of zero if and only if the script was successful.
-	  */
-	 public int exitValue();
-	 /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
-	  * @return A <strong>Component</strong> on which the progress of the process is being displayed.
-	  */
-	 public Component getProgress();
-	 /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
-	  * @return A <strong>boolean</strong> indicating if the process has been asked to stop.
-	  */
-	 public boolean hasSignalledStop();
-	 /** Inform the progress bar that it should programatically increment progress by one step. */
-	 public void increment();
-	 /** This method is used to 'feed in' a line of text captured from the process.
-	  * @param line A <strong>String</strong> of text captured from either standard out or standard error.
-	  */
-	 public void parse(String line);
-	 /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
-	  * @param state The desired state of the stop flag as a <strong>boolean</strong>.
-	  */
-	 public void setStop(boolean state);
-	 /** This method resets this monitor to the start, reseting the process parsing and progress bar.
-	  */
-	 public void start();
-	 /** This method indicates the process is complete. 
-	  */
-	 public void stop();
+    /** Method to register a new progress bar with this monitor.
+     * @param progress_bar The new <strong>JProgressBar</strong>.
+     */
+    public void addProgressBar(JProgressBar progress_bar);
+    /** Determine the script exit value according to the progress monitor. This gets around a problem where several script failures actually result with a successful exit value.
+     * @return A <i>int</i> with a value of zero if and only if the script was successful.
+     */
+    public int exitValue();
+    /** Method to retrieve whatever control is being used as the progress indicator. Usually a <strong>JProgressBar</strong> but there may be others implemented later.
+     * @return A <strong>Component</strong> on which the progress of the process is being displayed.
+     */
+    public Component getProgress();
+    /** Method to determine the state of the stop flag, which may be set by the visual component that created this monitor.
+     * @return A <strong>boolean</strong> indicating if the process has been asked to stop.
+     */
+    public boolean hasSignalledStop();
+    /** Inform the progress bar that it should programatically increment progress by one step. */
+    public void increment();
+    /** This method is used to 'feed in' a line of text captured from the process.
+     * @param line A <strong>String</strong> of text captured from either standard out or standard error.
+     */
+    public void parse(String line);
+    /** Since the creator of this process monitor is actually in the GUI, this class provides the simpliest way to send a cancel process message between the two.
+     * @param state The desired state of the stop flag as a <strong>boolean</strong>.
+     */
+    public void setStop(boolean state);
+    /** This method resets this monitor to the start, reseting the process parsing and progress bar.
+     */
+    public void start();
+    /** This method indicates the process is complete. 
+     */
+    public void stop();
 }
-
Index: /trunk/gli/src/org/greenstone/gatherer/undo/UndoManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/undo/UndoManager.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/undo/UndoManager.java	(revision 4364)
@@ -63,486 +63,486 @@
  */
 public class UndoManager
-	 extends JButton
-	 implements ActionListener, DragComponent, DropTargetListener {
-	 private ArrayList redo_sources;
-	 private ArrayList undo_sources;
-	 private boolean ignore = false;
-	 private boolean ignore_next = false;
-	 /** The group encompasses all of the objects you plan to drag and drop within, and ensures that only one has focus (as clearly identified by the colour of the selection field or, in this particular case, the background) and that actions only occur between components in the same group. */
-	 private DragGroup group;
-/** In order to make this button a drop target we have to create a DropTarget instance with the button as its target. */
-	 private DropTarget drop_target;
-	 private FileQueue file_queue;
-	 private FileSystemModel model;
-	 private GDMDocument obsolete_metadata;
-	 /** What sort of action should a drag resemble. Not really used as we override with custom drag icon. */
-	 private int drag_action = DnDConstants.ACTION_MOVE;
-	 /** The last point the mouse was at. Used to repaint 'spoilt' area. */
-	 private Point pt_last = null;
-	 /** The area covered by the drag ghost, our custom drag icon. */
-	 private Rectangle ra_ghost = new Rectangle();
-	 private UndoStack redo;
-	 private UndoStack undo;
-	 static final public int FILE_COPY = 1;
-	 static final public int FILE_DELETE = 2;
-	 static final public int FILE_MOVE = 3;
-
-	 public UndoManager() {
-		  super(Utility.getImage("bin.gif"));
-		  this.file_queue = Gatherer.f_man.getQueue();
-		  this.drop_target = new DropTarget(this, drag_action, this, true);
-
-		  setBackground(Gatherer.config.getColor("coloring.button_background", true));
-		  setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
-		  setOpaque(true);
-
-		  // Creation
-		  File recycle_directory = new File(Utility.RECYCLE);
-		  if(!recycle_directory.exists()) {
-				recycle_directory.mkdirs();
-				recycle_directory.deleteOnExit();
-		  }
-		  this.model = new FileSystemModel(new FileNode(recycle_directory, "Undo"));
-		  obsolete_metadata = new GDMDocument(); // This GDM is never saved.
-		  redo = new UndoStack(false);
-		  redo_sources = new ArrayList();
-		  undo = new UndoStack(true);
-		  undo_sources = new ArrayList();
-		  if(Gatherer.debug != null) {
-				showTree();
-		  }
-	 }
-
-	 public void actionPerformed(ActionEvent event) {
-		  // Is this an undo event source...
-		  if(undo_sources.contains(event.getSource())) {
-				UndoJob undo_job = undo.pop();
-				undo_job.action(true, file_queue);
+    extends JButton
+    implements ActionListener, DragComponent, DropTargetListener {
+    private ArrayList redo_sources;
+    private ArrayList undo_sources;
+    private boolean ignore = false;
+    private boolean ignore_next = false;
+    /** The group encompasses all of the objects you plan to drag and drop within, and ensures that only one has focus (as clearly identified by the colour of the selection field or, in this particular case, the background) and that actions only occur between components in the same group. */
+    private DragGroup group;
+    /** In order to make this button a drop target we have to create a DropTarget instance with the button as its target. */
+    private DropTarget drop_target;
+    private FileQueue file_queue;
+    private FileSystemModel model;
+    private GDMDocument obsolete_metadata;
+    /** What sort of action should a drag resemble. Not really used as we override with custom drag icon. */
+    private int drag_action = DnDConstants.ACTION_MOVE;
+    /** The last point the mouse was at. Used to repaint 'spoilt' area. */
+    private Point pt_last = null;
+    /** The area covered by the drag ghost, our custom drag icon. */
+    private Rectangle ra_ghost = new Rectangle();
+    private UndoStack redo;
+    private UndoStack undo;
+    static final public int FILE_COPY = 1;
+    static final public int FILE_DELETE = 2;
+    static final public int FILE_MOVE = 3;
+
+    public UndoManager() {
+	super(Utility.getImage("bin.gif"));
+	this.file_queue = Gatherer.f_man.getQueue();
+	this.drop_target = new DropTarget(this, drag_action, this, true);
+
+	setBackground(Gatherer.config.getColor("coloring.button_background", true));
+	setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
+	setOpaque(true);
+
+	// Creation
+	File recycle_directory = new File(Utility.RECYCLE);
+	if(!recycle_directory.exists()) {
+	    recycle_directory.mkdirs();
+	    recycle_directory.deleteOnExit();
+	}
+	this.model = new FileSystemModel(new FileNode(recycle_directory, "Undo"));
+	obsolete_metadata = new GDMDocument(); // This GDM is never saved.
+	redo = new UndoStack(false);
+	redo_sources = new ArrayList();
+	undo = new UndoStack(true);
+	undo_sources = new ArrayList();
+	if(Gatherer.debug != null) {
+	    showTree();
+	}
+    }
+
+    public void actionPerformed(ActionEvent event) {
+	// Is this an undo event source...
+	if(undo_sources.contains(event.getSource())) {
+	    UndoJob undo_job = undo.pop();
+	    undo_job.action(true, file_queue);
 				// Now retrieve all other undo jobs with the same job-number and action them
-				while((undo_job = undo.getJob(undo_job.ID())) != null) {
-					 undo_job.action(true, file_queue);
-				}
-		  }
-		  // Or a redo one.
-		  else if(redo_sources.contains(event.getSource())) {
-				UndoJob redo_job = redo.pop();
-				redo_job.action(false, file_queue);
+	    while((undo_job = undo.getJob(undo_job.ID())) != null) {
+		undo_job.action(true, file_queue);
+	    }
+	}
+	// Or a redo one.
+	else if(redo_sources.contains(event.getSource())) {
+	    UndoJob redo_job = redo.pop();
+	    redo_job.action(false, file_queue);
 				// Now retrieve all other redo jobs with the same job-number and action them
-				while((redo_job = redo.getJob(redo_job.ID())) != null) {
-					 redo_job.action(false, file_queue);
-				}
-		  }
-	 }
-
-	 public void addMetadata(File file, ArrayList metadatum) {
-		  for(int i = 0; metadatum != null && i < metadatum.size(); i++) {
-				Metadata metadata = (Metadata) metadatum.get(i);
+	    while((redo_job = redo.getJob(redo_job.ID())) != null) {
+		redo_job.action(false, file_queue);
+	    }
+	}
+    }
+
+    public void addMetadata(File file, ArrayList metadatum) {
+	for(int i = 0; metadatum != null && i < metadatum.size(); i++) {
+	    Metadata metadata = (Metadata) metadatum.get(i);
 				///ystem.err.println("UndoMetadata: " + file.getAbsolutePath() + " => " + metadata);
-				obsolete_metadata.addMetadata(file.getAbsolutePath(), metadata);
-		  }
-	 }
-
-	 public void addUndo(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) {
-		  if(target_model == null) {
-				target_model = this;
-		  }
-		  UndoJobAdder job_adder = new UndoJobAdder(id, type, source_model, source_parent, target_model, record, undo_event);
-		  SwingUtilities.invokeLater(job_adder);
-	 }
-
-	 public void clear() {
-		  undo.clear();
-		  redo.clear();
-	 }
-
-	 /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the GGroup to tell this component to repair the 'spoilt' region left by its ghost. */
-	 public void clearGhost(){
-	 }
-
-	 public void destroy() {
-		  // Remove all references of this as a listener
-		  for(int i = 0; i < redo_sources.size(); i++) {
-				AbstractButton source = (AbstractButton) redo_sources.get(i);
-				source.removeActionListener(this);
-		  }
-		  for(int j = 0; j < undo_sources.size(); j++) {
-				AbstractButton source = (AbstractButton) undo_sources.get(j);
-				source.removeActionListener(this);
-		  }
-		  redo_sources = null;
-		  undo_sources = null;
-		  redo = null;
-		  undo = null;
-		  obsolete_metadata = null;
-		  file_queue = null;
-	 }
-
- 	 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus enters this component. We want to provide some sort of indication whether the current component is an acceptable drop target as well as indicating focus. */
-	 public void dragEnter(DropTargetDragEvent event) {
-		  //ystem.err.println("Drag entered");
-		  group.grabFocus(this);
-		  setBackground(Gatherer.config.getColor("coloring.button_selected_background", true));
-		  setForeground(Gatherer.config.getColor("coloring.button_selected_foreground", true));
-	 }
-
-	 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus leaves this component. We need to indicate that we have lost focus. */
-	 public void dragExit(DropTargetEvent event) {
-		  //ystem.err.println("Drag exitted");
-		  setBackground(Gatherer.config.getColor("coloring.button_background", true));
-		  setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
-	 }
-
-	 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag moves in this component. This is where we repaint our ghost icon at the tip of the mouse pointer. */
-	 public void dragOver(DropTargetDragEvent event) {
-		  Graphics2D g2 = (Graphics2D) getGraphics();
-		  Point pt = event.getLocation();
-		  if(pt_last != null && pt.equals(pt_last)) {
-				return;
-		  }
-		  pt_last = pt;
-		  if(!DragSource.isDragImageSupported()) {
+	    obsolete_metadata.addMetadata(file.getAbsolutePath(), metadata);
+	}
+    }
+
+    public void addUndo(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) {
+	if(target_model == null) {
+	    target_model = this;
+	}
+	UndoJobAdder job_adder = new UndoJobAdder(id, type, source_model, source_parent, target_model, record, undo_event);
+	SwingUtilities.invokeLater(job_adder);
+    }
+
+    public void clear() {
+	undo.clear();
+	redo.clear();
+    }
+
+    /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the GGroup to tell this component to repair the 'spoilt' region left by its ghost. */
+    public void clearGhost(){
+    }
+
+    public void destroy() {
+	// Remove all references of this as a listener
+	for(int i = 0; i < redo_sources.size(); i++) {
+	    AbstractButton source = (AbstractButton) redo_sources.get(i);
+	    source.removeActionListener(this);
+	}
+	for(int j = 0; j < undo_sources.size(); j++) {
+	    AbstractButton source = (AbstractButton) undo_sources.get(j);
+	    source.removeActionListener(this);
+	}
+	redo_sources = null;
+	undo_sources = null;
+	redo = null;
+	undo = null;
+	obsolete_metadata = null;
+	file_queue = null;
+    }
+
+    /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus enters this component. We want to provide some sort of indication whether the current component is an acceptable drop target as well as indicating focus. */
+    public void dragEnter(DropTargetDragEvent event) {
+	//ystem.err.println("Drag entered");
+	group.grabFocus(this);
+	setBackground(Gatherer.config.getColor("coloring.button_selected_background", true));
+	setForeground(Gatherer.config.getColor("coloring.button_selected_foreground", true));
+    }
+
+    /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus leaves this component. We need to indicate that we have lost focus. */
+    public void dragExit(DropTargetEvent event) {
+	//ystem.err.println("Drag exitted");
+	setBackground(Gatherer.config.getColor("coloring.button_background", true));
+	setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
+    }
+
+    /** Any implementation of DropTargetListener must include this method so we can be notified when the drag moves in this component. This is where we repaint our ghost icon at the tip of the mouse pointer. */
+    public void dragOver(DropTargetDragEvent event) {
+	Graphics2D g2 = (Graphics2D) getGraphics();
+	Point pt = event.getLocation();
+	if(pt_last != null && pt.equals(pt_last)) {
+	    return;
+	}
+	pt_last = pt;
+	if(!DragSource.isDragImageSupported()) {
 				// Erase the last ghost image and or cue line
-				paintImmediately(ra_ghost.getBounds());
+	    paintImmediately(ra_ghost.getBounds());
 				// Remember where you are about to draw the new ghost image
-				ra_ghost.setRect(pt.x - group.mouse_offset.x, pt.y - group.mouse_offset.y, group.image_ghost.getWidth(), group.image_ghost.getHeight());
+	    ra_ghost.setRect(pt.x - group.mouse_offset.x, pt.y - group.mouse_offset.y, group.image_ghost.getWidth(), group.image_ghost.getHeight());
 				// Draw the ghost image
-				g2.drawImage(group.image_ghost, AffineTransform.getTranslateInstance(ra_ghost.getX(), ra_ghost.getY()), null);
-		  }
-	 }
-
-	 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag ends, ie the transferable is dropped. This in turn triggers a series of add events preceded by a pre() and followed by a post(). */
-	 public void drop(DropTargetDropEvent event) {
-		  ignore = true;
-		  group.grabFocus(this);
-		  setBackground(Gatherer.config.getColor("coloring.button_background", true));
-		  setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
-		  Transferable transferable = event.getTransferable();
-		  try {
-				DragComponent source = group.getSource();
-				TreePath[] selection = group.getSelection();
-				FileNode[] source_nodes = new FileNode[selection.length];
-				for(int i = 0; i < source_nodes.length; i++) {
-					 source_nodes[i] = (FileNode) selection[i].getLastPathComponent();
-				}
+	    g2.drawImage(group.image_ghost, AffineTransform.getTranslateInstance(ra_ghost.getX(), ra_ghost.getY()), null);
+	}
+    }
+
+    /** Any implementation of DropTargetListener must include this method so we can be notified when the drag ends, ie the transferable is dropped. This in turn triggers a series of add events preceded by a pre() and followed by a post(). */
+    public void drop(DropTargetDropEvent event) {
+	ignore = true;
+	group.grabFocus(this);
+	setBackground(Gatherer.config.getColor("coloring.button_background", true));
+	setForeground(Gatherer.config.getColor("coloring.button_foreground", true));
+	Transferable transferable = event.getTransferable();
+	try {
+	    DragComponent source = group.getSource();
+	    TreePath[] selection = group.getSelection();
+	    FileNode[] source_nodes = new FileNode[selection.length];
+	    for(int i = 0; i < source_nodes.length; i++) {
+		source_nodes[i] = (FileNode) selection[i].getLastPathComponent();
+	    }
 				///ystem.err.println("Dropped files vector contains " + new_files.size() + " files.");
-				event.acceptDrop(drag_action);
+	    event.acceptDrop(drag_action);
 				// Action delete
-				Gatherer.f_man.action(source, source_nodes, this, null);
-				group.setSource(null);
-				group.setSelection(null);
-		  }
-		  catch(Exception error) {
-				error.printStackTrace();
-				event.rejectDrop();
-		  }
-		  ignore = false;
-		  // Clear up the group.image_ghost
-		  paintImmediately(ra_ghost.getBounds());
-		  event.getDropTargetContext().dropComplete(true);
-	 }
-
-	 /** Any implementation of DropTargetListener must include this method so we can be notified when the action to be taken upon drop changes. We never change so we don't do anything here. */
-	 public void dropActionChanged(DropTargetDragEvent event) {
-	 }
-
-	 public void fileCopied(long id, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) {
-		  ///ystem.err.println("fileCopied(" + id + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")");
-		  UndoJob job = new UndoJob(id, null, null, target_model, record, FILE_COPY);
-		  if(undo_event) {
+	    Gatherer.f_man.action(source, source_nodes, this, null);
+	    group.setSource(null);
+	    group.setSelection(null);
+	}
+	catch(Exception error) {
+	    error.printStackTrace();
+	    event.rejectDrop();
+	}
+	ignore = false;
+	// Clear up the group.image_ghost
+	paintImmediately(ra_ghost.getBounds());
+	event.getDropTargetContext().dropComplete(true);
+    }
+
+    /** Any implementation of DropTargetListener must include this method so we can be notified when the action to be taken upon drop changes. We never change so we don't do anything here. */
+    public void dropActionChanged(DropTargetDragEvent event) {
+    }
+
+    public void fileCopied(long id, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) {
+	///ystem.err.println("fileCopied(" + id + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")");
+	UndoJob job = new UndoJob(id, null, null, target_model, record, FILE_COPY);
+	if(undo_event) {
 				///ystem.err.println("Add undo job");
-				undo.push(job);
-		  }
-		  else {
+	    undo.push(job);
+	}
+	else {
 				///ystem.err.println("Add redo job");
-				redo.push(job);
-		  }
-	 }
-
-	 public void fileDeleted(long id, DragComponent source_model, FileNode source_parent, FileNode target_parent, FileNode record, boolean undo_event) {
-		  UndoJob job = new UndoJob(id, source_model, source_parent, this, record, FILE_DELETE);
-		  if(undo_event) {
+	    redo.push(job);
+	}
+    }
+
+    public void fileDeleted(long id, DragComponent source_model, FileNode source_parent, FileNode target_parent, FileNode record, boolean undo_event) {
+	UndoJob job = new UndoJob(id, source_model, source_parent, this, record, FILE_DELETE);
+	if(undo_event) {
 				///ystem.err.println("Add undo job");
-				undo.push(job);
-		  }
-		  else {
+	    undo.push(job);
+	}
+	else {
 				///ystem.err.println("Add redo job");
-				redo.push(job);
-		  }
-	 } 
-
-	 public void fileMoved(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) {
-		  ///ystem.err.println("fileMoved(" + id + ", " + source_model + ", " + source_parent + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")");
-		  UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, FILE_MOVE);
-		  if(undo_event) {
+	    redo.push(job);
+	}
+    } 
+
+    public void fileMoved(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) {
+	///ystem.err.println("fileMoved(" + id + ", " + source_model + ", " + source_parent + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")");
+	UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, FILE_MOVE);
+	if(undo_event) {
 				///ystem.err.println("Add undo job");
-				undo.push(job);
-		  }
-		  else {
+	    undo.push(job);
+	}
+	else {
 				///ystem.err.println("Add redo job");
-				redo.push(job);
-		  }
-	 }
-
-	 /** Used to notify this component that it has gained focus by some method other that mouse focus. */
-	 public void gainFocus(){
-	 }
-
-	 public ArrayList getMetadata(File file) {
-		  ///ystem.err.println("UndoMetadata: " + file.getAbsolutePath());
-		  return obsolete_metadata.getMetadata(file.getAbsolutePath(), true, new ArrayList(), file);
-	 }
-
-	 /** Any implementation of DragComponent must include this method so that a outsider can get at the underlying tree model behind the component. */
-	 public FileSystemModel getTreeModel(){
-		  return (FileSystemModel) model;
-	 }
-
-	 public boolean ignore() {
-		  return ignore;
-	 }
-
-	 /** This method is used to inform this component when it loses focus by means other than a drag mouse event, and should indicate this somehow. */
-	 public void loseFocus(){
-	 }
-
-	 public void registerRedoSource(AbstractButton source) {
-		  if(!redo_sources.contains(source)) {
-				redo_sources.add(source);
-				source.addActionListener(this);
-		  }
-	 }
-
-	 public void registerUndoSource(AbstractButton source) {
-		  if(!undo_sources.contains(source)) {
-				undo_sources.add(source);
-				source.addActionListener(this);
-		  }
-	 }
-
-	 public void setGroup(DragGroup group) {
-		  this.group = group;
-	 }
-
-	 public void undoAll() {
-		  FileQueue immediate_queue = new FileQueue(true);
-		  UndoJob undo_job = null;
-		  while((undo_job = undo.pop()) != null) {
-				undo_job.action(true, immediate_queue);
+	    redo.push(job);
+	}
+    }
+
+    /** Used to notify this component that it has gained focus by some method other that mouse focus. */
+    public void gainFocus(){
+    }
+
+    public ArrayList getMetadata(File file) {
+	///ystem.err.println("UndoMetadata: " + file.getAbsolutePath());
+	return obsolete_metadata.getMetadata(file.getAbsolutePath(), true, new ArrayList(), file);
+    }
+
+    /** Any implementation of DragComponent must include this method so that a outsider can get at the underlying tree model behind the component. */
+    public FileSystemModel getTreeModel(){
+	return (FileSystemModel) model;
+    }
+
+    public boolean ignore() {
+	return ignore;
+    }
+
+    /** This method is used to inform this component when it loses focus by means other than a drag mouse event, and should indicate this somehow. */
+    public void loseFocus(){
+    }
+
+    public void registerRedoSource(AbstractButton source) {
+	if(!redo_sources.contains(source)) {
+	    redo_sources.add(source);
+	    source.addActionListener(this);
+	}
+    }
+
+    public void registerUndoSource(AbstractButton source) {
+	if(!undo_sources.contains(source)) {
+	    undo_sources.add(source);
+	    source.addActionListener(this);
+	}
+    }
+
+    public void setGroup(DragGroup group) {
+	this.group = group;
+    }
+
+    public void undoAll() {
+	FileQueue immediate_queue = new FileQueue(true);
+	UndoJob undo_job = null;
+	while((undo_job = undo.pop()) != null) {
+	    undo_job.action(true, immediate_queue);
 				// Now retrieve all other undo jobs with the same job-number and action them
-				while((undo_job = undo.getJob(undo_job.ID())) != null) {
-					 undo_job.action(true, immediate_queue);
-				}
-		  }
-		  immediate_queue.run();
-		  // Returns only when all undo actions complete.
-	 }
-
-	 static final public File generateUniqueFile(FileNode record) {
-		  String filename_raw = ArrayTools.objectArrayToString(record.getPath());
-		  int hash_code = filename_raw.hashCode();
-		  File file = new File(Utility.RECYCLE, String.valueOf(hash_code));
-		  int offset = 65;
-		  while(file.exists() && offset != 90) {
-				file = new File(Utility.RECYCLE, String.valueOf(hash_code) + (char)offset);
-				offset++;
-		  }
-		  return file;
-	 }
-
-	 private void showTree() {
-		  JDialog dialog = new JDialog(Gatherer.g_man, "Recycle Bin Model");
-		  dialog.setSize(new Dimension(400,300));
-		  JPanel content_pane = (JPanel) dialog.getContentPane();
-		  JTree tree = new JTree(model);
-		  content_pane.setLayout(new BorderLayout());
-		  content_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
-		  dialog.show();
-	 }
-
-	 private class UndoJob {
-		  private FileNode record = null;
-		  private FileNode source_parent = null;
-		  private DragComponent source_model = null;
-		  private DragComponent target_model = null;
-		  private long id = 0;
-		  private int type = -1;
-		  private TreePath record_path = null;
-		  private TreePath source_parent_path = null;
-		  /** Undo file action Constructor.
-			* @param id A unique <i>long</i> id number for all actions associated with a certain gesture.
-			* @param source_model The source <strong>DragComponent</strong> where the new record originally came from. If this is <i>null</i> then it is assumed you are interested in using the 'recycle' bin model.
-			* @param source_parent The previous parent <strong>FileNode</strong> of the new record.
-			* @param target_model The target <strong>DragComponent</strong> where the new record is now.
-			* @param record The new <strong>FileNode</strong> itself.
-			* @param type An <i>int</i> indicating the action that has occured, not what undo action is needed.
-			*/
-		  public UndoJob(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, int type) {
-				this.id = id;
-				this.record = record;
-				if(record != null) {
-					 this.record_path = new TreePath(record.getPath());
-				}
-				this.source_model = source_model;
-				this.source_parent = source_parent;
-				if(source_parent != null) {
-					 this.source_parent_path = new TreePath(source_parent.getPath());
-				}
-				this.target_model = target_model;
-				this.type = type;
-		  }
+	    while((undo_job = undo.getJob(undo_job.ID())) != null) {
+		undo_job.action(true, immediate_queue);
+	    }
+	}
+	immediate_queue.run();
+	// Returns only when all undo actions complete.
+    }
+
+    static final public File generateUniqueFile(FileNode record) {
+	String filename_raw = ArrayTools.objectArrayToString(record.getPath());
+	int hash_code = filename_raw.hashCode();
+	File file = new File(Utility.RECYCLE, String.valueOf(hash_code));
+	int offset = 65;
+	while(file.exists() && offset != 90) {
+	    file = new File(Utility.RECYCLE, String.valueOf(hash_code) + (char)offset);
+	    offset++;
+	}
+	return file;
+    }
+
+    private void showTree() {
+	JDialog dialog = new JDialog(Gatherer.g_man, "Recycle Bin Model");
+	dialog.setSize(new Dimension(400,300));
+	JPanel content_pane = (JPanel) dialog.getContentPane();
+	JTree tree = new JTree(model);
+	content_pane.setLayout(new BorderLayout());
+	content_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
+	dialog.show();
+    }
+
+    private class UndoJob {
+	private FileNode record = null;
+	private FileNode source_parent = null;
+	private DragComponent source_model = null;
+	private DragComponent target_model = null;
+	private long id = 0;
+	private int type = -1;
+	private TreePath record_path = null;
+	private TreePath source_parent_path = null;
+	/** Undo file action Constructor.
+	 * @param id A unique <i>long</i> id number for all actions associated with a certain gesture.
+	 * @param source_model The source <strong>DragComponent</strong> where the new record originally came from. If this is <i>null</i> then it is assumed you are interested in using the 'recycle' bin model.
+	 * @param source_parent The previous parent <strong>FileNode</strong> of the new record.
+	 * @param target_model The target <strong>DragComponent</strong> where the new record is now.
+	 * @param record The new <strong>FileNode</strong> itself.
+	 * @param type An <i>int</i> indicating the action that has occured, not what undo action is needed.
+	 */
+	public UndoJob(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, int type) {
+	    this.id = id;
+	    this.record = record;
+	    if(record != null) {
+		this.record_path = new TreePath(record.getPath());
+	    }
+	    this.source_model = source_model;
+	    this.source_parent = source_parent;
+	    if(source_parent != null) {
+		this.source_parent_path = new TreePath(source_parent.getPath());
+	    }
+	    this.target_model = target_model;
+	    this.type = type;
+	}
 		  
-		  public void action(boolean is_undo, FileQueue file_queue) {
+	public void action(boolean is_undo, FileQueue file_queue) {
 				// Retrieve the lastest version of each file record
-				FileNode latest_record = null;
-				FileNode latest_source_parent = null;
-				if(target_model != null && record_path != null) {
-					 ///ystem.err.println("Retrieving latest version of record from " + target_model + ".");
-					 latest_record = ((FileSystemModel)target_model.getTreeModel()).getNode(record_path);
-				}
-				if(source_model != null && source_parent_path != null) {
-					 ///ystem.err.println("Retrieving latest version of source parent.");
-					 latest_source_parent = ((FileSystemModel)source_model.getTreeModel()).getNode(source_parent_path);
-				}
+	    FileNode latest_record = null;
+	    FileNode latest_source_parent = null;
+	    if(target_model != null && record_path != null) {
+		///ystem.err.println("Retrieving latest version of record from " + target_model + ".");
+		latest_record = ((FileSystemModel)target_model.getTreeModel()).getNode(record_path);
+	    }
+	    if(source_model != null && source_parent_path != null) {
+		///ystem.err.println("Retrieving latest version of source parent.");
+		latest_source_parent = ((FileSystemModel)source_model.getTreeModel()).getNode(source_parent_path);
+	    }
 				// Of course if there are no newer versions, stick to the ones we've already got.
-				if(latest_record == null) {
-					 ///ystem.err.println("Using original record.");
-					 latest_record = record;
-				}
-				if(latest_source_parent == null) {
-					 ///ystem.err.println("Using original source parent.");
-					 latest_source_parent = source_parent;
-				}
+	    if(latest_record == null) {
+		///ystem.err.println("Using original record.");
+		latest_record = record;
+	    }
+	    if(latest_source_parent == null) {
+		///ystem.err.println("Using original source parent.");
+		latest_source_parent = source_parent;
+	    }
 				// Heres the fraction, too much friction. 
-				switch(type) {
-				case FILE_COPY:					 
-					 // To undo a file copy we issue a delete file action on the destination file record.
-					 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false);
-					 break;
-				case FILE_DELETE:
-					 // To undo a file delete we issue a copy file action from our recycle bin.
-					 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false);
-					 break;
-				case FILE_MOVE:			 
-					 // This may be a legitimate move, or may be a side effect of an undelete. If the formed source model and parent will be non-null.
-					 if(source_model != null && source_parent != null) {
-						  // To undo a file move we issue a move file action to return it to where it was.
-						  file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false);
-					 }
-					 // Otherwise we perform another delete.
-					 else {
-						  file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false);
-					 }
-					 break;
-				default:
-					 System.err.println("Unknown code.");
-				}
-		  }
-
-		  public FileNode getRecord() {
-				return record;
-		  }
-
-		  public long ID() {
-				return id;
-		  }
-	 }
-
-	 private class UndoJobAdder 
-		  implements Runnable {
-		  private boolean undo_event;
-		  private FileNode record;
-		  private FileNode source_parent;
-		  private DragComponent source_model;
-		  private DragComponent target_model;
-		  private int type;
-		  private long id;
-		  public UndoJobAdder(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) {
-				this.id = id;
-				this.record = record;
-				this.source_model = source_model;
-				this.source_parent = source_parent;
-				this.target_model = target_model;
-				this.type = type;
-				this.undo_event = undo_event;
-		  }
-		  public void run() {
-				UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, type);
-				if(undo_event) {
-					 undo.push(job);
-				}
-				else {
-					 redo.push(job);
-				}
-		  }
-	 }
-
-	 private class UndoStack 
-		  extends LinkedList {
-		  private boolean enabled = false;
-		  private boolean undo;
-		  private int pos = 0;
-		  public UndoStack(boolean undo) {
-				this.undo = undo;
-		  }
-		  public void clear() {
-				super.clear();
-				pos = 0;
-				if(enabled) {
-					 setEnabled(false);
-				}				
-		  }
-		  public UndoJob getJob(long id) {
-				UndoJob job = null;
-				while(job == null && pos < size()) {
-					 UndoJob temp = (UndoJob) get(pos);
-					 if(temp.ID() == id) {
-						  job = temp;
-						  remove(temp);
-					 }
-					 else {
-						  pos++;
-					 }
-				}
-				if(size() == 0) {
-					 setEnabled(false);
-					 pos = 0;
-				}
-				return job;
-		  }
-		  public void push(UndoJob job) {
-				addFirst(job);
-				pos = 0;
-				if(!enabled) {
-					 setEnabled(true);
-				}
-		  }
-		  public UndoJob pop() {
-				UndoJob job = null;
-				if(size() > 0) {
-					 job = (UndoJob) removeFirst();
-					 if(size() == 0 && enabled) {
-						  setEnabled(false);
-						  pos = 0;
-					 }
-				}
-				return job;
-		  }
-		  public void reset() {
-				pos = 0;
-		  }
-		  private void setEnabled(boolean state) {
-				ArrayList sources;
-				if(undo) {
-					 sources = undo_sources;
-				}
-				else {
-					 sources = redo_sources;
-				}
-				for(int i = 0; i < sources.size(); i++) {
-					 AbstractButton source = (AbstractButton) sources.get(i);
-					 source.setEnabled(state);
-				}
-				enabled = state;
-		  }
-	 }
+	    switch(type) {
+	    case FILE_COPY:					 
+		// To undo a file copy we issue a delete file action on the destination file record.
+		file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false);
+		break;
+	    case FILE_DELETE:
+		// To undo a file delete we issue a copy file action from our recycle bin.
+		file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false);
+		break;
+	    case FILE_MOVE:			 
+		// This may be a legitimate move, or may be a side effect of an undelete. If the formed source model and parent will be non-null.
+		if(source_model != null && source_parent != null) {
+		    // To undo a file move we issue a move file action to return it to where it was.
+		    file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false);
+		}
+		// Otherwise we perform another delete.
+		else {
+		    file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false);
+		}
+		break;
+	    default:
+		System.err.println("Unknown code.");
+	    }
+	}
+
+	public FileNode getRecord() {
+	    return record;
+	}
+
+	public long ID() {
+	    return id;
+	}
+    }
+
+    private class UndoJobAdder 
+	implements Runnable {
+	private boolean undo_event;
+	private FileNode record;
+	private FileNode source_parent;
+	private DragComponent source_model;
+	private DragComponent target_model;
+	private int type;
+	private long id;
+	public UndoJobAdder(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) {
+	    this.id = id;
+	    this.record = record;
+	    this.source_model = source_model;
+	    this.source_parent = source_parent;
+	    this.target_model = target_model;
+	    this.type = type;
+	    this.undo_event = undo_event;
+	}
+	public void run() {
+	    UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, type);
+	    if(undo_event) {
+		undo.push(job);
+	    }
+	    else {
+		redo.push(job);
+	    }
+	}
+    }
+
+    private class UndoStack 
+	extends LinkedList {
+	private boolean enabled = false;
+	private boolean undo;
+	private int pos = 0;
+	public UndoStack(boolean undo) {
+	    this.undo = undo;
+	}
+	public void clear() {
+	    super.clear();
+	    pos = 0;
+	    if(enabled) {
+		setEnabled(false);
+	    }				
+	}
+	public UndoJob getJob(long id) {
+	    UndoJob job = null;
+	    while(job == null && pos < size()) {
+		UndoJob temp = (UndoJob) get(pos);
+		if(temp.ID() == id) {
+		    job = temp;
+		    remove(temp);
+		}
+		else {
+		    pos++;
+		}
+	    }
+	    if(size() == 0) {
+		setEnabled(false);
+		pos = 0;
+	    }
+	    return job;
+	}
+	public void push(UndoJob job) {
+	    addFirst(job);
+	    pos = 0;
+	    if(!enabled) {
+		setEnabled(true);
+	    }
+	}
+	public UndoJob pop() {
+	    UndoJob job = null;
+	    if(size() > 0) {
+		job = (UndoJob) removeFirst();
+		if(size() == 0 && enabled) {
+		    setEnabled(false);
+		    pos = 0;
+		}
+	    }
+	    return job;
+	}
+	public void reset() {
+	    pos = 0;
+	}
+	private void setEnabled(boolean state) {
+	    ArrayList sources;
+	    if(undo) {
+		sources = undo_sources;
+	    }
+	    else {
+		sources = redo_sources;
+	    }
+	    for(int i = 0; i < sources.size(); i++) {
+		AbstractButton source = (AbstractButton) sources.get(i);
+		source.setEnabled(state);
+	    }
+	    enabled = state;
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/ArrayTools.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/ArrayTools.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/ArrayTools.java	(revision 4364)
@@ -21,434 +21,430 @@
 public class ArrayTools {
 
-	 /** Append a Component onto an array of Components. */
-	 static public Component[] add(Component[] a, Component b) {
-		  Component[] c = null;
-		  if(a != null && b != null) {
-				c = new Component[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new Component[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficiently appends a new element onto the end of a element array.
-	  * @param a The initial <strong>Element[]</strong>.
-	  * @param b The new <strong>Element</strong>.
-	  * @return An <strong>Element[]</strong> containing a followed by b.
-	  */
-	 static public Element[] add(Element a[], Element b) {
-		  Element[] c = null;
-		  if(a != null && b != null) {
-				c = new Element[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new Element[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficently appends one element array onto the end of another.
-	  * @param a The initial <strong>Element[]</strong>.
-	  * @param b The <strong>Element[]</strong> to append.
-	  * @return An <strong>Element[]</strong> containing a followed by b.
-	  */
-	 static public Element[] add(Element a[], Element b[]) {
-		  Element[] c = null;
-		  if(a != null && b != null) {
-				c = new Element[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficiently appends a new file onto the end of a file array.
-	  * @param a The initial <strong>File[]</strong>.
-	  * @param b The new <strong>File</strong>.
-	  * @return A <strong>File[]</strong> containing a followed by b.
-	  */
-	 static public File[] add(File a[], File b) {
-		  File[] c = null;
-		  if(a != null && b != null) {
-				c = new File[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new File[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficently appends one file array onto the end of another.
-	  * @param a The initial <strong>File[]</strong>.
-	  * @param b The <strong>File[]</strong> to append.
-	  * @return A <strong>File[]</strong> containing a followed by b.
-	  */
-	 static public File[] add(File a[], File b[]) {
-		  File[] c = null;
-		  if(a != null && b != null) {
-				c = new File[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-
-	 /** This method efficiently appends a new FileNode onto the end of a FileNode array.
-	  * @param a The initial <strong>FileNode[]</strong>.
-	  * @param b The new <strong>FileNode</strong>.
-	  * @return An <strong>FileNode[]</strong> containing a followed by b.
-	  */
-	 static public FileNode[] add(FileNode a[], FileNode b) {
-		  FileNode[] c = null;
-		  if(a != null && b != null) {
-				c = new FileNode[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new FileNode[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficently appends one FileNode array onto the end of another FileNode array.
-	  * @param a The initial <strong>FileNode[]</strong>.
-	  * @param b The <strong>FileNode[]</strong> to append.
-	  * @return An <strong>FileNode[]</strong> containing a followed by b.
-	  */
-	 static final public FileNode[] add(FileNode a[], FileNode b[]) {
-		  FileNode[] c = null;
-		  if(a != null && b != null) {
-				c = new FileNode[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** Determine if a certain FileNode is present in an array of FileNodes.
-	  * @param array The <strong>FileNode[]</strong>.
-	  * @param a The <strong>FileNode</strong> we are searching for.
-	  * @return <i>true</i> if the FileNode is in the array, <i>false</i> otherwise.
-	  */
-	 static final public boolean contains(FileNode array[], FileNode a) {
-		  for(int i = 0; i < array.length; i++) {
-				if(array[i].equals(a)) {
-					 return true;
-				}
-		  }
-		  return false;
-	 }
-	 /** Remove the first element from an array, and return the remaining 'tail'.
-	  * @param a A <strong>FileNode[]</strong>.
-	  * @return The same <strong>FileNode[]</strong> sans its head element.
-	  */
-	 static final public FileNode[] tail(FileNode a[]) {
-		  if(a.length == 1) {
-				return null;
-		  }
-		  FileNode b[] = new FileNode[a.length - 1];
-		  System.arraycopy(a, 1, b, 0, b.length);
-		  return b;
-	 }
+    /** Append a Component onto an array of Components. */
+    static public Component[] add(Component[] a, Component b) {
+	Component[] c = null;
+	if(a != null && b != null) {
+	    c = new Component[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new Component[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficiently appends a new element onto the end of a element array.
+     * @param a The initial <strong>Element[]</strong>.
+     * @param b The new <strong>Element</strong>.
+     * @return An <strong>Element[]</strong> containing a followed by b.
+     */
+    static public Element[] add(Element a[], Element b) {
+	Element[] c = null;
+	if(a != null && b != null) {
+	    c = new Element[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new Element[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficently appends one element array onto the end of another.
+     * @param a The initial <strong>Element[]</strong>.
+     * @param b The <strong>Element[]</strong> to append.
+     * @return An <strong>Element[]</strong> containing a followed by b.
+     */
+    static public Element[] add(Element a[], Element b[]) {
+	Element[] c = null;
+	if(a != null && b != null) {
+	    c = new Element[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficiently appends a new file onto the end of a file array.
+     * @param a The initial <strong>File[]</strong>.
+     * @param b The new <strong>File</strong>.
+     * @return A <strong>File[]</strong> containing a followed by b.
+     */
+    static public File[] add(File a[], File b) {
+	File[] c = null;
+	if(a != null && b != null) {
+	    c = new File[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new File[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficently appends one file array onto the end of another.
+     * @param a The initial <strong>File[]</strong>.
+     * @param b The <strong>File[]</strong> to append.
+     * @return A <strong>File[]</strong> containing a followed by b.
+     */
+    static public File[] add(File a[], File b[]) {
+	File[] c = null;
+	if(a != null && b != null) {
+	    c = new File[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+
+    /** This method efficiently appends a new FileNode onto the end of a FileNode array.
+     * @param a The initial <strong>FileNode[]</strong>.
+     * @param b The new <strong>FileNode</strong>.
+     * @return An <strong>FileNode[]</strong> containing a followed by b.
+     */
+    static public FileNode[] add(FileNode a[], FileNode b) {
+	FileNode[] c = null;
+	if(a != null && b != null) {
+	    c = new FileNode[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new FileNode[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficently appends one FileNode array onto the end of another FileNode array.
+     * @param a The initial <strong>FileNode[]</strong>.
+     * @param b The <strong>FileNode[]</strong> to append.
+     * @return An <strong>FileNode[]</strong> containing a followed by b.
+     */
+    static final public FileNode[] add(FileNode a[], FileNode b[]) {
+	FileNode[] c = null;
+	if(a != null && b != null) {
+	    c = new FileNode[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** Determine if a certain FileNode is present in an array of FileNodes.
+     * @param array The <strong>FileNode[]</strong>.
+     * @param a The <strong>FileNode</strong> we are searching for.
+     * @return <i>true</i> if the FileNode is in the array, <i>false</i> otherwise.
+     */
+    static final public boolean contains(FileNode array[], FileNode a) {
+	for(int i = 0; i < array.length; i++) {
+	    if(array[i].equals(a)) {
+		return true;
+	    }
+	}
+	return false;
+    }
+    /** Remove the first element from an array, and return the remaining 'tail'.
+     * @param a A <strong>FileNode[]</strong>.
+     * @return The same <strong>FileNode[]</strong> sans its head element.
+     */
+    static final public FileNode[] tail(FileNode a[]) {
+	if(a.length == 1) {
+	    return null;
+	}
+	FileNode b[] = new FileNode[a.length - 1];
+	System.arraycopy(a, 1, b, 0, b.length);
+	return b;
+    }
 	 
-	 /** Append a Metadata object onto an array of Metadata objects. */
-	 static public Metadata[] add(Metadata[] a, Metadata b) {
-		  Metadata[] c = null;
-		  if(a != null && b != null) {
-				c = new Metadata[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new Metadata[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** Append a Metadata object array onto an array of Metadata objects. */
-	 static public Metadata[] add(Metadata[] a, Metadata[] b) {
-		  Metadata[] c = null;
-		  if(a != null && b != null) {
-				c = new Metadata[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** Remove the Metadata object at the given index form the specified Metadata object array. */
-	 static public Metadata[] remove(Metadata[] a, int index) {
-		  Metadata[] c = null;
-		  if(a != null) {
-				c = new Metadata[a.length - 1];
-				System.arraycopy(a, 0, c, 0, index);
-				System.arraycopy(a, index + 1, c, index, c.length - index);
-		  }
-		  return c;
-	 }
-
-	 /** This method efficiently appends a new node onto the end of a node array.
-	  * @param a The initial <strong>Node[]</strong>.
-	  * @param b The new <strong>Node</strong>.
-	  * @return A <strong>Node[]</strong> containing a followed by b.
-	  */
-	 static public Node[] add(Node a[], Node b) {
-		  Node[] c = null;
-		  if(a != null && b != null) {
-				c = new Node[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new Node[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficently appends one node array onto the end of another.
-	  * @param a The initial <strong>Node[]</strong>.
-	  * @param b The <strong>Node[]</strong> to append.
-	  * @return A <strong>Node[]</strong> containing a followed by b.
-	  */
-	 static public Node[] add(Node a[], Node b[]) {
-		  Node[] c = null;
-		  if(a != null && b != null) {
-				c = new Node[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
+    /** Append a Metadata object onto an array of Metadata objects. */
+    static public Metadata[] add(Metadata[] a, Metadata b) {
+	Metadata[] c = null;
+	if(a != null && b != null) {
+	    c = new Metadata[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new Metadata[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** Append a Metadata object array onto an array of Metadata objects. */
+    static public Metadata[] add(Metadata[] a, Metadata[] b) {
+	Metadata[] c = null;
+	if(a != null && b != null) {
+	    c = new Metadata[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** Remove the Metadata object at the given index form the specified Metadata object array. */
+    static public Metadata[] remove(Metadata[] a, int index) {
+	Metadata[] c = null;
+	if(a != null) {
+	    c = new Metadata[a.length - 1];
+	    System.arraycopy(a, 0, c, 0, index);
+	    System.arraycopy(a, index + 1, c, index, c.length - index);
+	}
+	return c;
+    }
+
+    /** This method efficiently appends a new node onto the end of a node array.
+     * @param a The initial <strong>Node[]</strong>.
+     * @param b The new <strong>Node</strong>.
+     * @return A <strong>Node[]</strong> containing a followed by b.
+     */
+    static public Node[] add(Node a[], Node b) {
+	Node[] c = null;
+	if(a != null && b != null) {
+	    c = new Node[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new Node[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficently appends one node array onto the end of another.
+     * @param a The initial <strong>Node[]</strong>.
+     * @param b The <strong>Node[]</strong> to append.
+     * @return A <strong>Node[]</strong> containing a followed by b.
+     */
+    static public Node[] add(Node a[], Node b[]) {
+	Node[] c = null;
+	if(a != null && b != null) {
+	    c = new Node[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
 	 
-	 /** This method efficiently appends a new string onto the end of a string array.
-	  * @param a The initial <strong>String[]</strong>.
-	  * @param b The new <strong>String</strong>.
-	  * @return A <strong>String[]</strong> containing a followed by b.
-	  */
-	 static public String[] add(String a[], String b) {
-		  String[] c = null;
-		  if(a != null && b != null) {
-				c = new String[a.length + 1];
-				System.arraycopy(a, 0, c, 0, a.length);
-				c[c.length - 1] = b;
-		  }
-		  else if(a == null && b != null) {
-				c = new String[1];
-				c[0] = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method efficently appends one string array onto the end of another.
-	  * @param a The initial <strong>String[]</strong>.
-	  * @param b The <strong>String[]</strong> to append.
-	  * @return A <strong>String[]</strong> containing a followed by b.
-	  */
-	 static public String[] add(String a[], String b[]) {
-		  String[] c = null;
-		  if(a != null && b != null) {
-				c = new String[a.length + b.length];
-				System.arraycopy(a, 0, c, 0, a.length);
-				System.arraycopy(b, 0, c, a.length, b.length);
-		  }
-		  else if(a == null && b != null) {
-				c = b;
-		  }
-		  else if(a != null && b == null) {
-				c = a;
-		  }
-		  return c;
-	 }
-	 /** This method takes an array list and creates a string array.
-	  * @param a An <strong>ArrayList</strong> containing hopefully <strong>String</strong> or else this will fail.
-	  * @return A <strong>String[]</strong> or <i>null</i> if the array could not be created.
-	  */
-	 static public String[] arrayListToStringArray(ArrayList a) {
-		  String array[] = new String[a.size()];
-		  for(int i = 0; i < array.length; i++) {
-				array[i] = (String)a.get(i);
-		  }
-		  return array;
-	 }
-
-	 static public File[] filter(File[] files, String pattern, boolean exclude) {
-		  int write_ptr = 0;
-		  ///ystem.err.println("Filtering by '" + pattern + "', Exclude? " + exclude + " :");
-		  for(int read_ptr = 0; read_ptr < files.length; read_ptr++) {
-				File current = files[read_ptr];
-				files[write_ptr] = current;
+    /** This method efficiently appends a new string onto the end of a string array.
+     * @param a The initial <strong>String[]</strong>.
+     * @param b The new <strong>String</strong>.
+     * @return A <strong>String[]</strong> containing a followed by b.
+     */
+    static public String[] add(String a[], String b) {
+	String[] c = null;
+	if(a != null && b != null) {
+	    c = new String[a.length + 1];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    c[c.length - 1] = b;
+	}
+	else if(a == null && b != null) {
+	    c = new String[1];
+	    c[0] = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method efficently appends one string array onto the end of another.
+     * @param a The initial <strong>String[]</strong>.
+     * @param b The <strong>String[]</strong> to append.
+     * @return A <strong>String[]</strong> containing a followed by b.
+     */
+    static public String[] add(String a[], String b[]) {
+	String[] c = null;
+	if(a != null && b != null) {
+	    c = new String[a.length + b.length];
+	    System.arraycopy(a, 0, c, 0, a.length);
+	    System.arraycopy(b, 0, c, a.length, b.length);
+	}
+	else if(a == null && b != null) {
+	    c = b;
+	}
+	else if(a != null && b == null) {
+	    c = a;
+	}
+	return c;
+    }
+    /** This method takes an array list and creates a string array.
+     * @param a An <strong>ArrayList</strong> containing hopefully <strong>String</strong> or else this will fail.
+     * @return A <strong>String[]</strong> or <i>null</i> if the array could not be created.
+     */
+    static public String[] arrayListToStringArray(ArrayList a) {
+	String array[] = new String[a.size()];
+	for(int i = 0; i < array.length; i++) {
+	    array[i] = (String)a.get(i);
+	}
+	return array;
+    }
+
+    static public File[] filter(File[] files, String pattern, boolean exclude) {
+	int write_ptr = 0;
+	///ystem.err.println("Filtering by '" + pattern + "', Exclude? " + exclude + " :");
+	for(int read_ptr = 0; read_ptr < files.length; read_ptr++) {
+	    File current = files[read_ptr];
+	    files[write_ptr] = current;
 				///ystem.err.print("Testing " + current.getName() + " -> ");
 				// Determine whether we move the write pointer or not.
-				if(current.getName().toLowerCase().matches(pattern)) {
-					 if(!exclude) {
-						  ///ystem.err.println("Match, Exclude.");
-						  write_ptr++;  
-					 }
-					 else {
-						  ///ystem.err.println("Match, Include.");
-					 }
-				}
-				else {
-					 // You can't exclude folders with an inclusion filter!
-					 if(exclude || current.isDirectory()) {
-						  ///ystem.err.println("Nonmatch, Exclude.");
-						  write_ptr++;
-					 }
-					 else {
-						  ///ystem.err.println("Nonmatch, Include.");
-					 }
-				}
-		  }
-		  File[] result = new File[write_ptr];
-		  System.arraycopy(files, 0, result, 0, result.length);
-		  pattern = null;
-		  files = null;
-		  return result;
-	 }
-
-	 /** Method to convert a list of nodes from the DOM model into a node array. This is quite useful as using a <strong>NodeList</strong> is problematic if you wish to traverse the tree while editing. Because they are 'live' representations of the model you are editing, it is easy to drop out of for loops early or run off the ends of arrays.
-	  * @param node_list A <strong>NodeList</strong> representing the <strong>Node</strong>s we want to iterate through.
-	  * @return A brand new <strong>Node[]</strong> with the first <strong>Node</strong> in the <strong>NodeList</strong> at the head.
-	  * @see org.w3c.dom.Node
-	  * @see org.w3c.dom.NodeList
-	  */
-	 static public Node[] nodeListToNodeArray(NodeList node_list) {
-		  Node nodes[] = null;
-		  for(int i = 0; i < node_list.getLength(); i++) {
-				nodes = add(nodes, node_list.item(i));
-		  }
-		  return nodes;
-	 }
-	 /** Transforms an Object array into a single string of the form '[o1,o2,...,on]'.
-	  * @param objects An <strong>Object[]</strong>. Note that the objects in this array must support toString() reasonably.
-	  * @return A <strong>String</strong> representing the given array.
-	  */
-	 static public String objectArrayToString(Object objects[]) {
-		  StringBuffer result = new StringBuffer("[");
-		  for(int i = 0; i < objects.length; i++) {
-				result.append(objects[i].toString());
-				if(i < objects.length - 1) {
-					 result.append(",");
-				}
-		  }
-		  result.append("]");
-		  return result.toString();
-	 }
-
-	 /** Sorts an array of files. */
-	 static public void sort(File[] files) {
-		  sort(files, false);
-	 }
-
-	 /** Sorts an array of files. Can also to instructed to list directories first. Case insensitive.
-	  * @param files The File[] to be sorted.
-	  * @param directories_first true if you want to directories to be listed first.
-	  */
-	 static public void sort(File[] files, boolean directories_first) {
-		  if(files != null && files.length > 1) {
-				FileComparator comparator = new FileComparator(directories_first);
-				Arrays.sort(files, comparator);
-		  }		  
-	 }
-
-	 /** Comparator used to order files. */
-	 static private class FileComparator
-	 implements Comparator {
-		  private boolean directories_first = false;
-
-		  public FileComparator(boolean directories_first) {
-				this.directories_first = directories_first;
-		  }
-		  /** Compare two files in terms of ordering of their paths.
-			* @param o1 The <strong>Object</strong> that represents the first file.
-			* @param o2 The other <strong>Object</strong>, also a file.
-			* @return An <i>int</i> which is <1, 0 or >1 if o1 is < o2, = o2 or > o2 respectively.
-			*/
-		  public int compare(Object o1, Object o2) {
-				int result = 0;
-				File f1 = (File) o1;
-				String n1 = f1.getName().toLowerCase();
-				File f2 = (File) o2;
-				String n2 = f2.getName().toLowerCase();
+	    if(current.getName().toLowerCase().matches(pattern)) {
+		if(!exclude) {
+		    ///ystem.err.println("Match, Exclude.");
+		    write_ptr++;  
+		}
+		else {
+		    ///ystem.err.println("Match, Include.");
+		}
+	    }
+	    else {
+		// You can't exclude folders with an inclusion filter!
+		if(exclude || current.isDirectory()) {
+		    ///ystem.err.println("Nonmatch, Exclude.");
+		    write_ptr++;
+		}
+		else {
+		    ///ystem.err.println("Nonmatch, Include.");
+		}
+	    }
+	}
+	File[] result = new File[write_ptr];
+	System.arraycopy(files, 0, result, 0, result.length);
+	pattern = null;
+	files = null;
+	return result;
+    }
+
+    /** Method to convert a list of nodes from the DOM model into a node array. This is quite useful as using a <strong>NodeList</strong> is problematic if you wish to traverse the tree while editing. Because they are 'live' representations of the model you are editing, it is easy to drop out of for loops early or run off the ends of arrays.
+     * @param node_list A <strong>NodeList</strong> representing the <strong>Node</strong>s we want to iterate through.
+     * @return A brand new <strong>Node[]</strong> with the first <strong>Node</strong> in the <strong>NodeList</strong> at the head.
+     * @see org.w3c.dom.Node
+     * @see org.w3c.dom.NodeList
+     */
+    static public Node[] nodeListToNodeArray(NodeList node_list) {
+	Node nodes[] = null;
+	for(int i = 0; i < node_list.getLength(); i++) {
+	    nodes = add(nodes, node_list.item(i));
+	}
+	return nodes;
+    }
+    /** Transforms an Object array into a single string of the form '[o1,o2,...,on]'.
+     * @param objects An <strong>Object[]</strong>. Note that the objects in this array must support toString() reasonably.
+     * @return A <strong>String</strong> representing the given array.
+     */
+    static public String objectArrayToString(Object objects[]) {
+	StringBuffer result = new StringBuffer("[");
+	for(int i = 0; i < objects.length; i++) {
+	    result.append(objects[i].toString());
+	    if(i < objects.length - 1) {
+		result.append(",");
+	    }
+	}
+	result.append("]");
+	return result.toString();
+    }
+
+    /** Sorts an array of files. */
+    static public void sort(File[] files) {
+	sort(files, false);
+    }
+
+    /** Sorts an array of files. Can also to instructed to list directories first. Case insensitive.
+     * @param files The File[] to be sorted.
+     * @param directories_first true if you want to directories to be listed first.
+     */
+    static public void sort(File[] files, boolean directories_first) {
+	if(files != null && files.length > 1) {
+	    FileComparator comparator = new FileComparator(directories_first);
+	    Arrays.sort(files, comparator);
+	}		  
+    }
+
+    /** Comparator used to order files. */
+    static private class FileComparator
+	implements Comparator {
+	private boolean directories_first = false;
+
+	public FileComparator(boolean directories_first) {
+	    this.directories_first = directories_first;
+	}
+	/** Compare two files in terms of ordering of their paths.
+	 * @param o1 The <strong>Object</strong> that represents the first file.
+	 * @param o2 The other <strong>Object</strong>, also a file.
+	 * @return An <i>int</i> which is <1, 0 or >1 if o1 is < o2, = o2 or > o2 respectively.
+	 */
+	public int compare(Object o1, Object o2) {
+	    int result = 0;
+	    File f1 = (File) o1;
+	    String n1 = f1.getName().toLowerCase();
+	    File f2 = (File) o2;
+	    String n2 = f2.getName().toLowerCase();
 				// Special checks for system roots as these include removable media drives and thus we should never attempt to call isDirectory until the user explicitly attempts to map drive.
-				boolean f1_system_root = FileSystemView.getFileSystemView().isFileSystemRoot(f1);
-				boolean f2_system_root = FileSystemView.getFileSystemView().isFileSystemRoot(f2);
-				if((f1_system_root && f2_system_root) || (!f2_system_root && f1_system_root && f2.isDirectory()) || (!f1_system_root && f1.isDirectory() && f2_system_root) || (f1.isDirectory() && f2.isDirectory()) || (f1.isFile() && f2.isFile())) {
-					 result = n1.compareTo(n2);
-				}
-				else if(f1_system_root || f1.isDirectory()) {
-					 result = -1;
-				}
-				else {
-					 result = 1;
-				}
-				return result;
-		  }
-		  /** Compare two files for equality, in terms of their file paths.
-			* @param object The <strong>Object</strong> representing the file we are about to compare ourselves to.
-			* @return <i>true</i> if we equal the given file, <i>false</i> otherwise.
-			*/
-		  public boolean equals(Object obj) {
-				return (compare(this, obj) == 0);
-		  }
-	 }
+	    boolean f1_system_root = FileSystemView.getFileSystemView().isFileSystemRoot(f1);
+	    boolean f2_system_root = FileSystemView.getFileSystemView().isFileSystemRoot(f2);
+	    if((f1_system_root && f2_system_root) || (!f2_system_root && f1_system_root && f2.isDirectory()) || (!f1_system_root && f1.isDirectory() && f2_system_root) || (f1.isDirectory() && f2.isDirectory()) || (f1.isFile() && f2.isFile())) {
+		result = n1.compareTo(n2);
+	    }
+	    else if(f1_system_root || f1.isDirectory()) {
+		result = -1;
+	    }
+	    else {
+		result = 1;
+	    }
+	    return result;
+	}
+	/** Compare two files for equality, in terms of their file paths.
+	 * @param object The <strong>Object</strong> representing the file we are about to compare ourselves to.
+	 * @return <i>true</i> if we equal the given file, <i>false</i> otherwise.
+	 */
+	public boolean equals(Object obj) {
+	    return (compare(this, obj) == 0);
+	}
+    }
 }
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/DecodeHTMLReader.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/DecodeHTMLReader.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/DecodeHTMLReader.java	(revision 4364)
@@ -5,120 +5,120 @@
 
 public class DecodeHTMLReader
-	 extends PushbackReader {
+    extends PushbackReader {
 
-	 public DecodeHTMLReader(Reader source) {
-		  super(source, 4);
-	 }
+    public DecodeHTMLReader(Reader source) {
+	super(source, 4);
+    }
 
-	 /** Read a single character. */
-	 public int read() {
-		  return decode();
-	 }
+    /** Read a single character. */
+    public int read() {
+	return decode();
+    }
  
-	 /** Read characters into a portion of an array. */
-	 public int read(char[] cbuf, int off, int len) {
-		  int count = 0;
-		  for(int i = off; i < len && ready(); i++) {
-				cbuf[i] = (char)decode();
-				count++;
-		  }
-		  return count;
-	 }
+    /** Read characters into a portion of an array. */
+    public int read(char[] cbuf, int off, int len) {
+	int count = 0;
+	for(int i = off; i < len && ready(); i++) {
+	    cbuf[i] = (char)decode();
+	    count++;
+	}
+	return count;
+    }
 
-	 public boolean ready() {
-		  try {
-				return super.ready();
-		  }
-		  catch (Exception error) {
-		  }
-		  return false;
-	 }
+    public boolean ready() {
+	try {
+	    return super.ready();
+	}
+	catch (Exception error) {
+	}
+	return false;
+    }
 
-	 /** Retrieve the next character off the stream. Unfortunately I have to do this a character at a time (which is slow). I also have to keep in mind that if a suspect encoded character turns out not to be I have to replace the extra characters. */
-	 private int decode() {
-		  int character;
-		  try {
-				character = super.read();
-		  }
-		  catch (Exception error) {
-				character = ' ';
-		  }
-		  try {
-				if(character == '&') {
-					 int amp = super.read();
-					 switch(amp) {
-					 case 'a':
-					 case 'A':
-						  int ampap = super.read();
-						  int ampapo = super.read();
-						  int ampapos = super.read();
-						  int ampapossemi = super.read();
-						  if((ampap == 'p' || ampap == 'P') && (ampapo == 'o' || ampapo == 'O') && (ampapos == 's' || ampapos == 'S') && ampapossemi == ';') {
-								// Read an &apos; so return an apostrophy
-								return '\'';
-						  }
-						  // Not a apos. Return the characters removed in the correct order.
-						  super.unread(ampapossemi);
-						  super.unread(ampapos);
-						  super.unread(ampapo);
-						  super.unread(ampap);
-						  break;
-					 case 'g':
-					 case 'G':
-						  int ampgt = super.read();
-						  int ampgtsemi = super.read();
-						  if((ampgt == 't' || ampgt == 'T') && ampgtsemi == ';') {
-								return '>';
-						  }
-						  super.unread(ampgtsemi);
-						  super.unread(ampgt);
-						  break;
-					 case 'l':
-					 case 'L':
-						  int amplt = super.read();
-						  int ampltsemi = super.read();
-						  if((amplt == 't' || amplt == 'T') && ampltsemi == ';') {
-								return '<';
-						  }
-						  super.unread(ampltsemi);
-						  super.unread(amplt);
-						  break;
-					 case 'q':
-					 case 'Q':
-						  int ampqu = super.read();
-						  int ampquo = super.read();
-						  int ampquot = super.read();
-						  int ampquotsemi = super.read();
-						  if((ampqu == 'u' || ampqu == 'U') && (ampquo == 'o' || ampquo == 'O') && (ampquot == 't' || ampquot == 'T') && ampquotsemi == ';') {
-								return '\"';
-						  }
-						  super.unread(ampquotsemi);
-						  super.unread(ampquot);
-						  super.unread(ampquo);
-						  super.unread(ampqu);
-						  break;
-					 case '#':
-						  int amphash = super.read();
-						  int amphash3 = super.read();
-						  int amphash39 = super.read();
-						  int amphash39semi = super.read();
-						  if(amphash == '#' && amphash3 == '3' && amphash39 == '9' && amphash39semi == ';') {
-								return '\'';
-						  }
-						  super.unread(amphash39semi);
-						  super.unread(amphash39);
-						  super.unread(amphash3);
-						  super.unread(amphash);
-						  break;
-					 }
-					 // Not a suspect. Return the character removed.
-					 super.unread(amp);
-				}
-		  }
-		  catch (Exception error) {
-				Gatherer.printStackTrace(error);
-		  }
-		  // Nothing special. Simply return the character extracted.
-		  return character;
-	 }
+    /** Retrieve the next character off the stream. Unfortunately I have to do this a character at a time (which is slow). I also have to keep in mind that if a suspect encoded character turns out not to be I have to replace the extra characters. */
+    private int decode() {
+	int character;
+	try {
+	    character = super.read();
+	}
+	catch (Exception error) {
+	    character = ' ';
+	}
+	try {
+	    if(character == '&') {
+		int amp = super.read();
+		switch(amp) {
+		case 'a':
+		case 'A':
+		    int ampap = super.read();
+		    int ampapo = super.read();
+		    int ampapos = super.read();
+		    int ampapossemi = super.read();
+		    if((ampap == 'p' || ampap == 'P') && (ampapo == 'o' || ampapo == 'O') && (ampapos == 's' || ampapos == 'S') && ampapossemi == ';') {
+			// Read an &apos; so return an apostrophy
+			return '\'';
+		    }
+		    // Not a apos. Return the characters removed in the correct order.
+		    super.unread(ampapossemi);
+		    super.unread(ampapos);
+		    super.unread(ampapo);
+		    super.unread(ampap);
+		    break;
+		case 'g':
+		case 'G':
+		    int ampgt = super.read();
+		    int ampgtsemi = super.read();
+		    if((ampgt == 't' || ampgt == 'T') && ampgtsemi == ';') {
+			return '>';
+		    }
+		    super.unread(ampgtsemi);
+		    super.unread(ampgt);
+		    break;
+		case 'l':
+		case 'L':
+		    int amplt = super.read();
+		    int ampltsemi = super.read();
+		    if((amplt == 't' || amplt == 'T') && ampltsemi == ';') {
+			return '<';
+		    }
+		    super.unread(ampltsemi);
+		    super.unread(amplt);
+		    break;
+		case 'q':
+		case 'Q':
+		    int ampqu = super.read();
+		    int ampquo = super.read();
+		    int ampquot = super.read();
+		    int ampquotsemi = super.read();
+		    if((ampqu == 'u' || ampqu == 'U') && (ampquo == 'o' || ampquo == 'O') && (ampquot == 't' || ampquot == 'T') && ampquotsemi == ';') {
+			return '\"';
+		    }
+		    super.unread(ampquotsemi);
+		    super.unread(ampquot);
+		    super.unread(ampquo);
+		    super.unread(ampqu);
+		    break;
+		case '#':
+		    int amphash = super.read();
+		    int amphash3 = super.read();
+		    int amphash39 = super.read();
+		    int amphash39semi = super.read();
+		    if(amphash == '#' && amphash3 == '3' && amphash39 == '9' && amphash39semi == ';') {
+			return '\'';
+		    }
+		    super.unread(amphash39semi);
+		    super.unread(amphash39);
+		    super.unread(amphash3);
+		    super.unread(amphash);
+		    break;
+		}
+		// Not a suspect. Return the character removed.
+		super.unread(amp);
+	    }
+	}
+	catch (Exception error) {
+	    Gatherer.printStackTrace(error);
+	}
+	// Nothing special. Simply return the character extracted.
+	return character;
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/DictionaryTreeNode.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/DictionaryTreeNode.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/DictionaryTreeNode.java	(revision 4364)
@@ -4,11 +4,11 @@
 
 public interface DictionaryTreeNode {
-	 /** Returns the child at the specified index in this node's child array. */
-	 public TreeNode getChildAt(int index);
-	 /** Returns the number of children of this node. */   
-	 public int getChildCount();
-	 /** This method should always return the same key, regardless of the value of toString(). */
-	 public String getKey();
-	 /** Set the text shown by toString() to the given value. */
-	 public void setText(String value);
+    /** Returns the child at the specified index in this node's child array. */
+    public TreeNode getChildAt(int index);
+    /** Returns the number of children of this node. */   
+    public int getChildCount();
+    /** This method should always return the same key, regardless of the value of toString(). */
+    public String getKey();
+    /** Set the text shown by toString() to the given value. */
+    public void setText(String value);
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/DragComponent.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/DragComponent.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/DragComponent.java	(revision 4364)
@@ -52,14 +52,14 @@
 /** Any component that wants to act a GTree like drag'n'drop component must implement this. */
 public interface DragComponent {
-	 public void addFocusListener(FocusListener listener);
-	 /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the Group to tell this component to repair the 'spoilt' region left by its ghost. */
-	 public void clearGhost();
-	 /** Used to notify this component that it has gained focus by some method other that mouse focus. */
-	 public void gainFocus();	 
-	 /** Retrieve the model associated with this component. */
-	 public FileSystemModel getTreeModel();
-	 /** This method is used to inform this component when it loses focus by means other than a drag mouse event, and should indicate this somehow. */
-	 public void loseFocus();
-	 /** Set the components group. */
-	 public void setGroup(DragGroup group);
+    public void addFocusListener(FocusListener listener);
+    /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the Group to tell this component to repair the 'spoilt' region left by its ghost. */
+    public void clearGhost();
+    /** Used to notify this component that it has gained focus by some method other that mouse focus. */
+    public void gainFocus();	 
+    /** Retrieve the model associated with this component. */
+    public FileSystemModel getTreeModel();
+    /** This method is used to inform this component when it loses focus by means other than a drag mouse event, and should indicate this somehow. */
+    public void loseFocus();
+    /** Set the components group. */
+    public void setGroup(DragGroup group);
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/DragGroup.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/DragGroup.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/DragGroup.java	(revision 4364)
@@ -61,95 +61,92 @@
 /** This class acts as a linker between all the various drag and drop enabled DragComponent. It provides methods for ensuring only one component is the drop target, showing extra user feedback, and maintains a single point of focus. Moreover it provides a storage space for necessary shared variables such as mouse_offset of initial drag etc. */
 public class DragGroup 
-	 implements FocusListener {
-	 /** The image used for the ghost icon when dragging. */
-	 public BufferedImage image_ghost = null;
-	 /** The initial offset of the mouse from the selection within a DragComponent. Then as you drag the mouse, the ghost appears in a visually consistant place, and remains at the same offset throughout the dragging process. */
-	 public Point mouse_offset = null;
-	 /** The component currently in charge of drawing the ghost icon. Note that this isn't necessarily the only component that needs to repaint 'spoilt' screen real-estate as a side effect of the ghost. */
-	 private DragComponent ghost_owner = null;
-	 /** The component where the drag began, so that extra information necessary for the drag can be garnered by the drop target, or so that focus can return to the source component if the drop is rejected. 
-	  * @see org.greenstone.gatherer.tree.DragComponent#gainFocus
-	  * @see org.greenstone.gatherer.tree.DragComponent#loseFocus
-	  */
-	 private DragTree drag_source = null;
-	 /** The selected nodes, as determined when the drag begun. */
-	 private TreePath[] selection = null;
-	 /** A list of DragComponents registered as being part of this group. */
-	 private Vector registered = new Vector();
+    implements FocusListener {
+    /** The image used for the ghost icon when dragging. */
+    public BufferedImage image_ghost = null;
+    /** The initial offset of the mouse from the selection within a DragComponent. Then as you drag the mouse, the ghost appears in a visually consistant place, and remains at the same offset throughout the dragging process. */
+    public Point mouse_offset = null;
+    /** The component currently in charge of drawing the ghost icon. Note that this isn't necessarily the only component that needs to repaint 'spoilt' screen real-estate as a side effect of the ghost. */
+    private DragComponent ghost_owner = null;
+    /** The component where the drag began, so that extra information necessary for the drag can be garnered by the drop target, or so that focus can return to the source component if the drop is rejected. 
+     * @see org.greenstone.gatherer.tree.DragComponent#gainFocus
+     * @see org.greenstone.gatherer.tree.DragComponent#loseFocus
+     */
+    private DragTree drag_source = null;
+    /** The selected nodes, as determined when the drag begun. */
+    private TreePath[] selection = null;
+    /** A list of DragComponents registered as being part of this group. */
+    private Vector registered = new Vector();
 
-	 /** Register a DragComponent as begin part of this group.
+    /** Register a DragComponent as begin part of this group.
 	  * @param component The DragComponent to add.
 	  */
-	 public void add(DragComponent component) {
-		  if(!registered.contains(component)) {
-				registered.add(component);
-				component.addFocusListener(this);
-				component.setGroup(this);
-		  }
-	 }
+    public void add(DragComponent component) {
+	if(!registered.contains(component)) {
+	    registered.add(component);
+	    component.addFocusListener(this);
+	    component.setGroup(this);
+	}
+    }
 
-	 /** Invoked when a component gains the keyboard focus. */
-	 public void focusGained(FocusEvent event) {
-		  ((DragComponent)event.getComponent()).gainFocus();
-	 }
+    /** Invoked when a component gains the keyboard focus. */
+    public void focusGained(FocusEvent event) {
+	((DragComponent)event.getComponent()).gainFocus();
+    }
           
-	 /** Invoked when a component loses the keyboard focus. */
-	 public void focusLost(FocusEvent event) {
-		  ((DragComponent)event.getComponent()).loseFocus();
-	 }          
-	 /** Determines the current 'active' component, ie that component with focus.
+    /** Invoked when a component loses the keyboard focus. */
+    public void focusLost(FocusEvent event) {
+	((DragComponent)event.getComponent()).loseFocus();
+    }          
+    /** Determines the current 'active' component, ie that component with focus.
 	  * @return The DragComponent which is currently responsible for drawing the ghost, and thus is active.
 	  */
-	 public DragComponent getActive() {
-		  return ghost_owner;
-	 }
-	 /** Retrieve the nodes selected at the beginning of this drag operation.
+    public DragComponent getActive() {
+	return ghost_owner;
+    }
+    /** Retrieve the nodes selected at the beginning of this drag operation.
 	  * @return A TreePath[].
 	  */
-	 public TreePath[] getSelection() {
-		  return selection;
-	 }
-	 /** Retrieve the component which served as the source of this drag action.
+    public TreePath[] getSelection() {
+	return selection;
+    }
+    /** Retrieve the component which served as the source of this drag action.
 	  * @return The DragComponent in question.
 	  */
-	 public DragTree getSource() {
-		  return drag_source;
-	 }
-	 /** When called this method asserts that one of the registered GComponents just became the proud owner of focus, and is henceforth responsible for drawing the ghost, and acting like a component thats in focus.
+    public DragTree getSource() {
+	return drag_source;
+    }
+    /** When called this method asserts that one of the registered GComponents just became the proud owner of focus, and is henceforth responsible for drawing the ghost, and acting like a component thats in focus.
 	  * @param new_owner The DragComponent that has gained focus during a drag action.
 	  */
-	 public void grabFocus(DragComponent new_owner) {
-		  // Tell the previous owner of ghost to clear itself.
-		  if(ghost_owner != null) {
-				ghost_owner.clearGhost();
-		  }
-		  // Assign new owner
-		  ghost_owner = new_owner;
-		  // Then tell all other GComponents to indicate they aren't in focus in whatever way they do that (ie selected items of a GTree go gray). Of course the new owners told it is in focus.
-		  for(int i = 0; i < registered.size(); i++) {
-				DragComponent comp = (DragComponent)registered.get(i);
-				if(comp == new_owner) {
-					 comp.gainFocus();
-				}
-				else {
-					 comp.loseFocus();
-				}
-		  }
-	 }
-	 /** Sets the value of selected_nodes.
+    public void grabFocus(DragComponent new_owner) {
+	// Tell the previous owner of ghost to clear itself.
+	if(ghost_owner != null) {
+	    ghost_owner.clearGhost();
+	}
+	// Assign new owner
+	ghost_owner = new_owner;
+	// Then tell all other GComponents to indicate they aren't in focus in whatever way they do that (ie selected items of a GTree go gray). Of course the new owners told it is in focus.
+	for(int i = 0; i < registered.size(); i++) {
+	    DragComponent comp = (DragComponent)registered.get(i);
+	    if(comp == new_owner) {
+		comp.gainFocus();
+	    }
+	    else {
+		comp.loseFocus();
+	    }
+	}
+    }
+    /** Sets the value of selected_nodes.
 	  * @param selection The new value for selected_nodes as a TreePath[].
 	  */
-	 public void setSelection(TreePath[] selection) {
-		  this.selection = selection;
-	 }
-	 /** Sets the value of drag_source.
+    public void setSelection(TreePath[] selection) {
+	this.selection = selection;
+    }
+    /** Sets the value of drag_source.
 	  * @param drag_source The new value for drag_source as a DragTree.
 	  */
-	 public void setSource(DragTree drag_source) {
-		  this.drag_source = drag_source;
-		  System.err.println("The drag source is now " + drag_source);
-	 }
+    public void setSource(DragTree drag_source) {
+	this.drag_source = drag_source;
+	System.err.println("The drag source is now " + drag_source);
+    }
 }
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/DragTreeSelectionModel.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/DragTreeSelectionModel.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/DragTreeSelectionModel.java	(revision 4364)
@@ -45,248 +45,247 @@
 
 public class DragTreeSelectionModel
-	 extends DefaultTreeSelectionModel
-	 implements MouseListener {
-
-	 boolean immediate = false;
-
-	 private int type               = -1;
-
-	 private String suffix = null;
-
-	 private TreePath temp_path     = null;
-	 private TreePath temp_paths[]  = null;
-
-	 static private final int NONE  = -1;
-	 static private final int ADD   =  0;
-	 static private final int SET   =  1;
-
-	 public DragTreeSelectionModel(DragTree tree) {
-		  super();
-		  setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
-		  tree.addMouseListener(this);
-	 }
-	 
-	 public void addSelectionPath(TreePath path) {
-		  if(!immediate) {
-				this.temp_path = path;
-				this.type = this.ADD;
-		  }
-		  else if(isAppropriate(path)) {
-				temp_path = null;
-				super.addSelectionPath(path);
-				suffix = null;		
-		  }
-	 }
-	 
-	 public void addSelectionPaths(TreePath paths[]) {
-		  if(!immediate) {
-				this.temp_paths = paths;
-				this.type = this.ADD;
-		  }
-		  else if(isAppropriate(paths, true)) {
-				temp_paths = null;
-				super.setSelectionPaths(paths);
-				suffix = null;
-		  }		  
-	 }
-	 
-	 public String getDetails() {
-		  if(suffix == null) {
-				int file_count = 0;
-				int folder_count = 0;
-				for(int i = 0; selection != null && i < selection.length; i++) {
-					 TreeNode node = (TreeNode) selection[i].getLastPathComponent();
-					 if(node.isLeaf()) {
-						  file_count++;
-					 }
-					 else {
-						  folder_count++;
-					 }
-				}
+    extends DefaultTreeSelectionModel
+    implements MouseListener {
+
+    boolean immediate = false;
+
+    private int type               = -1;
+
+    private String suffix = null;
+
+    private TreePath temp_path     = null;
+    private TreePath temp_paths[]  = null;
+
+    static private final int NONE  = -1;
+    static private final int ADD   =  0;
+    static private final int SET   =  1;
+
+    public DragTreeSelectionModel(DragTree tree) {
+	super();
+	setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
+	tree.addMouseListener(this);
+    }
+	 
+    public void addSelectionPath(TreePath path) {
+	if(!immediate) {
+	    this.temp_path = path;
+	    this.type = this.ADD;
+	}
+	else if(isAppropriate(path)) {
+	    temp_path = null;
+	    super.addSelectionPath(path);
+	    suffix = null;		
+	}
+    }
+	 
+    public void addSelectionPaths(TreePath paths[]) {
+	if(!immediate) {
+	    this.temp_paths = paths;
+	    this.type = this.ADD;
+	}
+	else if(isAppropriate(paths, true)) {
+	    temp_paths = null;
+	    super.setSelectionPaths(paths);
+	    suffix = null;
+	}		  
+    }
+	 
+    public String getDetails() {
+	if(suffix == null) {
+	    int file_count = 0;
+	    int folder_count = 0;
+	    for(int i = 0; selection != null && i < selection.length; i++) {
+		TreeNode node = (TreeNode) selection[i].getLastPathComponent();
+		if(node.isLeaf()) {
+		    file_count++;
+		}
+		else {
+		    folder_count++;
+		}
+	    }
 				// Update the metaaudit_suffix
-				String args[] = null;
-				if(file_count > 0 && folder_count > 0) {
-					 if(file_count > 1 && folder_count > 1) {
-						  args = new String[2];
-						  args[0] = String.valueOf(file_count);
-						  args[1] = String.valueOf(folder_count);
-						  suffix = Gatherer.dictionary.get("FileActions.Selected", args);
-					 }
-					 else if(file_count > 1) {
-						  args = new String[1];
-						  args[0] = String.valueOf(file_count);
-						  suffix = Gatherer.dictionary.get("FileActions.Files_And_Directory_Selected", args);
-					 }
-					 else if(folder_count > 1) {
-						  args = new String[1];
-						  args[0] = String.valueOf(folder_count);
-						  suffix = Gatherer.dictionary.get("FileActions.File_And_Directories_Selected", args);
-					 }
-					 else {
-						  suffix = Gatherer.dictionary.get("FileActions.File_And_Directory_Selected");
-					 }
-				}
-				else if(file_count > 0) {
-					 if(file_count > 1) {
-						  args = new String[1];
-						  args[0] = String.valueOf(file_count);
-						  suffix = Gatherer.dictionary.get("FileActions.Files_Selected", args);
-					 }
-					 else if(file_count == 1) {
-						  suffix = Gatherer.dictionary.get("FileActions.File_Selected");
-					 }
-				}
-				else if(folder_count > 0) {
-					 if(folder_count > 1) {
-						  args = new String[1];
-						  args[0] = String.valueOf(folder_count);
-						  suffix = Gatherer.dictionary.get("FileActions.Directories_Selected", args);
-					 }
-					 else {
-						  suffix = Gatherer.dictionary.get("FileActions.Directory_Selected");
-					 }
-				}
-				else {
-					 Gatherer.dictionary.get("FileActions.No_Selection"); 
-				}
-				args = null;
-		  }
-		  return suffix;
-	 }
-	 
-	 /** Any implementation of MouseListener must include this method so
-	  * we can be informed when the mouse is clicked.
-	  * @param event A MouseEvent containing all the information about
-	  * this mouse click.
-	  */
-	 public void mouseClicked(MouseEvent event) {
-	 }
-	 
-	 /** Any implementation of MouseListener must include this method so
-	  * we can be informed when the mouse enters a component.
-	  * @param event A MouseEvent containing all the information about
-	  * this mouse action.
-	  */
-	 public void mouseEntered(MouseEvent event) {
-	 }
-	 
-	 /** Any implementation of MouseListener must include this method so
-	  * we can be informed when the mouse exits a component.
-	  * @param event A MouseEvent containing all the information about
-	  * this mouse action.
-	  */
-	 public void mouseExited(MouseEvent event) {
-	 }
-	 
-	 /** Any implementation of MouseListener must include this method so
-	  * we can be informed when the mouse is pressed (start of click).
-	  * @param event A MouseEvent containing all the information about
-	  * this mouse action.
-	  */
-	 public void mousePressed(MouseEvent event) {
-	 }
-
-	 /** Any implementation of MouseListener must include this method so
-	  * we can be informed when the mouse is released (end of click).
-	  * @param event A MouseEvent containing all the information about
-	  * this mouse action.
-	  */
-	 public void mouseReleased(MouseEvent event) {
-		  switch(this.type) {
-		  case 0: // this.ADD
-				if(this.temp_path != null && isAppropriate(temp_path)) {
-					 super.addSelectionPath(this.temp_path);
-					 this.temp_path = null;
-				}
-				if(this.temp_paths != null && isAppropriate(temp_paths, true)) {
-					 super.addSelectionPaths(this.temp_paths);
-					 this.temp_paths = null;
-				}
-				this.type = this.NONE;
-				suffix = null;
-				break;
-		  case 1: // this.SET
-				if(this.temp_path != null) {
-					 super.setSelectionPath(this.temp_path);
-					 this.temp_path = null;
-				}
-				if(this.temp_paths != null && isAppropriate(temp_paths, false)) {
-					 super.setSelectionPaths(this.temp_paths);
-					 this.temp_paths = null;
-				}
-				this.type = this.NONE;
-				suffix = null;
-				break;
-		  }
-	 }
-
-	 public void setImmediate(boolean value) {
-		  immediate = value;
-	 }
-	 
-	 public void setSelectionPath(TreePath path) {
-		  // Since this is only a single path we don't need to check whether its an appropriate selection.
-		  if(!immediate) {
-				this.temp_path = path;
-				this.type = this.SET;
-		  }
-		  else {
-				temp_path = null;
-				super.setSelectionPath(path);
-				suffix = null;
-		  }
-	 }
-	 
-	 public void setSelectionPaths(TreePath paths[]) {
-		  if(!immediate) {
-				this.temp_paths = paths;
-				this.type = this.SET;
-		  }
-		  else if(isAppropriate(paths, false)) {
-				temp_paths = null;
-				super.setSelectionPaths(paths);
-				suffix = null;
-		  }
-	 }
-
-	 /** Ensure that the given path is appropriate to add to the current selection, preventing mixed selections of files and folder. We also must check that no path is a ancestor/descendant of another. There is also a slight optimization in that if the current selection contains only files (which if the rules are followed, and the first 'test' node is a file, then it must) there is no point in checking the remaining files in the selection. Its a different story for folders however as we have to ensure no folder is in the lineage of another, even if they are all folders! */
-	 private boolean isAppropriate(TreePath path) {
-		  boolean appropriate = true;
-		  TreeNode new_node = (TreeNode) path.getLastPathComponent();
-		  if(selection != null && selection.length > 0) {
-				TreeNode test_node = (TreeNode) selection[0].getLastPathComponent();
-				appropriate = appropriate && (new_node.isLeaf() == test_node.isLeaf());
-				if(!test_node.isLeaf()) {
-					 appropriate = appropriate && !path.isDescendant(selection[0]) && !selection[0].isDescendant(path);
-					 for(int i = 1; appropriate && selection != null && i < selection.length; i++) {
-						  TreeNode current_node = (TreeNode) selection[i].getLastPathComponent();
-						  appropriate = appropriate && (new_node.isLeaf() == current_node.isLeaf());				
-						  appropriate = appropriate && !path.isDescendant(selection[i]) && !selection[i].isDescendant(path);
-					 }
-				}
-		  }
-		  return appropriate;
-	 }
-	 /** Ensure that the given paths are appropriate to add to the current selection, preventing mixed selections of files and folder. We also must check that no path is a ancestor/descendant of another. One last detail to keep in mind is that adding selections depends upon the current selection, whereas set the selection paths doesn't (replaces them instead) and thus no check of the current paths is needed. */
-	 private boolean isAppropriate(TreePath[] paths, boolean check_current) {
-		  boolean appropriate = true;
-		  if(paths != null) {
-				if(paths.length >= 2) {
-					 // Prevent folders being added to a previous selection of files and vice-versa
-					 // First check that the new selection are all of the same type
-					 for(int i = 0; appropriate && paths != null && i < paths.length - 1; i++) {
-						  TreeNode one_node = (TreeNode) paths[i].getLastPathComponent();
-						  TreeNode other_node = (TreeNode) paths[i+1].getLastPathComponent();
-						  appropriate = appropriate && (one_node.isLeaf() == other_node.isLeaf());
-						  appropriate = appropriate && !paths[i].isDescendant(paths[i+1]) && !paths[i+1].isDescendant(paths[i]);
-					 }
-				}
+	    String args[] = null;
+	    if(file_count > 0 && folder_count > 0) {
+		if(file_count > 1 && folder_count > 1) {
+		    args = new String[2];
+		    args[0] = String.valueOf(file_count);
+		    args[1] = String.valueOf(folder_count);
+		    suffix = Gatherer.dictionary.get("FileActions.Selected", args);
+		}
+		else if(file_count > 1) {
+		    args = new String[1];
+		    args[0] = String.valueOf(file_count);
+		    suffix = Gatherer.dictionary.get("FileActions.Files_And_Directory_Selected", args);
+		}
+		else if(folder_count > 1) {
+		    args = new String[1];
+		    args[0] = String.valueOf(folder_count);
+		    suffix = Gatherer.dictionary.get("FileActions.File_And_Directories_Selected", args);
+		}
+		else {
+		    suffix = Gatherer.dictionary.get("FileActions.File_And_Directory_Selected");
+		}
+	    }
+	    else if(file_count > 0) {
+		if(file_count > 1) {
+		    args = new String[1];
+		    args[0] = String.valueOf(file_count);
+		    suffix = Gatherer.dictionary.get("FileActions.Files_Selected", args);
+		}
+		else if(file_count == 1) {
+		    suffix = Gatherer.dictionary.get("FileActions.File_Selected");
+		}
+	    }
+	    else if(folder_count > 0) {
+		if(folder_count > 1) {
+		    args = new String[1];
+		    args[0] = String.valueOf(folder_count);
+		    suffix = Gatherer.dictionary.get("FileActions.Directories_Selected", args);
+		}
+		else {
+		    suffix = Gatherer.dictionary.get("FileActions.Directory_Selected");
+		}
+	    }
+	    else {
+		Gatherer.dictionary.get("FileActions.No_Selection"); 
+	    }
+	    args = null;
+	}
+	return suffix;
+    }
+	 
+    /** Any implementation of MouseListener must include this method so
+     * we can be informed when the mouse is clicked.
+     * @param event A MouseEvent containing all the information about
+     * this mouse click.
+     */
+    public void mouseClicked(MouseEvent event) {
+    }
+	 
+    /** Any implementation of MouseListener must include this method so
+     * we can be informed when the mouse enters a component.
+     * @param event A MouseEvent containing all the information about
+     * this mouse action.
+     */
+    public void mouseEntered(MouseEvent event) {
+    }
+	 
+    /** Any implementation of MouseListener must include this method so
+     * we can be informed when the mouse exits a component.
+     * @param event A MouseEvent containing all the information about
+     * this mouse action.
+     */
+    public void mouseExited(MouseEvent event) {
+    }
+	 
+    /** Any implementation of MouseListener must include this method so
+     * we can be informed when the mouse is pressed (start of click).
+     * @param event A MouseEvent containing all the information about
+     * this mouse action.
+     */
+    public void mousePressed(MouseEvent event) {
+    }
+
+    /** Any implementation of MouseListener must include this method so
+     * we can be informed when the mouse is released (end of click).
+     * @param event A MouseEvent containing all the information about
+     * this mouse action.
+     */
+    public void mouseReleased(MouseEvent event) {
+	switch(this.type) {
+	case 0: // this.ADD
+	    if(this.temp_path != null && isAppropriate(temp_path)) {
+		super.addSelectionPath(this.temp_path);
+		this.temp_path = null;
+	    }
+	    if(this.temp_paths != null && isAppropriate(temp_paths, true)) {
+		super.addSelectionPaths(this.temp_paths);
+		this.temp_paths = null;
+	    }
+	    this.type = this.NONE;
+	    suffix = null;
+	    break;
+	case 1: // this.SET
+	    if(this.temp_path != null) {
+		super.setSelectionPath(this.temp_path);
+		this.temp_path = null;
+	    }
+	    if(this.temp_paths != null && isAppropriate(temp_paths, false)) {
+		super.setSelectionPaths(this.temp_paths);
+		this.temp_paths = null;
+	    }
+	    this.type = this.NONE;
+	    suffix = null;
+	    break;
+	}
+    }
+
+    public void setImmediate(boolean value) {
+	immediate = value;
+    }
+	 
+    public void setSelectionPath(TreePath path) {
+	// Since this is only a single path we don't need to check whether its an appropriate selection.
+	if(!immediate) {
+	    this.temp_path = path;
+	    this.type = this.SET;
+	}
+	else {
+	    temp_path = null;
+	    super.setSelectionPath(path);
+	    suffix = null;
+	}
+    }
+	 
+    public void setSelectionPaths(TreePath paths[]) {
+	if(!immediate) {
+	    this.temp_paths = paths;
+	    this.type = this.SET;
+	}
+	else if(isAppropriate(paths, false)) {
+	    temp_paths = null;
+	    super.setSelectionPaths(paths);
+	    suffix = null;
+	}
+    }
+
+    /** Ensure that the given path is appropriate to add to the current selection, preventing mixed selections of files and folder. We also must check that no path is a ancestor/descendant of another. There is also a slight optimization in that if the current selection contains only files (which if the rules are followed, and the first 'test' node is a file, then it must) there is no point in checking the remaining files in the selection. Its a different story for folders however as we have to ensure no folder is in the lineage of another, even if they are all folders! */
+    private boolean isAppropriate(TreePath path) {
+	boolean appropriate = true;
+	TreeNode new_node = (TreeNode) path.getLastPathComponent();
+	if(selection != null && selection.length > 0) {
+	    TreeNode test_node = (TreeNode) selection[0].getLastPathComponent();
+	    appropriate = appropriate && (new_node.isLeaf() == test_node.isLeaf());
+	    if(!test_node.isLeaf()) {
+		appropriate = appropriate && !path.isDescendant(selection[0]) && !selection[0].isDescendant(path);
+		for(int i = 1; appropriate && selection != null && i < selection.length; i++) {
+		    TreeNode current_node = (TreeNode) selection[i].getLastPathComponent();
+		    appropriate = appropriate && (new_node.isLeaf() == current_node.isLeaf());				
+		    appropriate = appropriate && !path.isDescendant(selection[i]) && !selection[i].isDescendant(path);
+		}
+	    }
+	}
+	return appropriate;
+    }
+    /** Ensure that the given paths are appropriate to add to the current selection, preventing mixed selections of files and folder. We also must check that no path is a ancestor/descendant of another. One last detail to keep in mind is that adding selections depends upon the current selection, whereas set the selection paths doesn't (replaces them instead) and thus no check of the current paths is needed. */
+    private boolean isAppropriate(TreePath[] paths, boolean check_current) {
+	boolean appropriate = true;
+	if(paths != null) {
+	    if(paths.length >= 2) {
+		// Prevent folders being added to a previous selection of files and vice-versa
+		// First check that the new selection are all of the same type
+		for(int i = 0; appropriate && paths != null && i < paths.length - 1; i++) {
+		    TreeNode one_node = (TreeNode) paths[i].getLastPathComponent();
+		    TreeNode other_node = (TreeNode) paths[i+1].getLastPathComponent();
+		    appropriate = appropriate && (one_node.isLeaf() == other_node.isLeaf());
+		    appropriate = appropriate && !paths[i].isDescendant(paths[i+1]) && !paths[i+1].isDescendant(paths[i]);
+		}
+	    }
 				// Now we check the current selection against the first node in our new selection
-				if(appropriate && check_current) {
-					 appropriate = isAppropriate(paths[0]);
-				}
-		  }
-		  return appropriate;
-	 }
+	    if(appropriate && check_current) {
+		appropriate = isAppropriate(paths[0]);
+	    }
+	}
+	return appropriate;
+    }
 }
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/EmailAddress.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/EmailAddress.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/EmailAddress.java	(revision 4364)
@@ -66,188 +66,183 @@
  */
 public class EmailAddress {
-	 /** The controls for editing this email. */
-	 private Control controls = null;
-	 /** A reference to the Gatherer. */
-	 private Gatherer gatherer = null;
-	 /** The type of this email, in collection configuration terms. */
-	 private int type = -1;
-	 /** The host part of the email (after @). */
-	 public String host = null;
-	 /** The user part of the email (before @). */
-	 public String user = null;
-	 /** The name given to or associated with this particular email (maybe the persons name, or it this email belongs to a certain variable i.e Collection Author). */
-	 public String name = null;
-	 /** Enumeration of important email address types - in this case the creator of a collection. */
-	 static final public int CREATOR = 0;
-	 /** Enumeration of important email address types - in this case the maintainer of a collection. */
-	 static final public int MAINTAINER = 1;
-	 /** Enumeration of important email address types - in this case any other email address other than the two noted above. */
-	 static final public int OTHER = 2;
-	 /** Constructor.
-	  * @param gatherer A reference to the <strong>Gatherer</strong> for messaging purposes.
-	  */
-	 public EmailAddress(Gatherer gatherer) {
-		  this.gatherer = gatherer;
-	 }
-	 /** Constructor.
-	  * @param gatherer A reference to the <strong>Gatherer</strong> for messaging purposes.
-	  * @param raw A <strong>String</strong> containing both the name and email separated by whitespace.
-	  */
-	 public EmailAddress(Gatherer gatherer, String raw) {
-		  this(gatherer);
-		  StringTokenizer tokenizer = new StringTokenizer(raw);
-		  if(tokenizer.countTokens() >= 2) {
-				this.name = tokenizer.nextToken();
-				if(name.equals("creator")) {
-					 type = CREATOR;
-				}
-				else if(name.equals("maintainer")) {
-					 type = MAINTAINER;
-				}
-				else {
-					 type = OTHER;
-				}
-				String email = tokenizer.nextToken();
-				if(email.indexOf("@") != -1) {
-					 this.user = email.substring(0, email.indexOf("@"));
-					 this.host = email.substring(email.indexOf("@") + 1);
-				}
-		  }
-		  else {
-				this.name = "Invalid Email";
-				this.user = "";
-				this.host = "";
-		  }
-	 }
-	 /** Retrieve the controls associated with editing this email address.
-	  * @return A <strong>JPanel</strong> containing those controls.
-	  */
-	 public JPanel getControls() {
-		  if(controls == null) {
-				controls = new Control(name);
-		  }
-		  return controls;
-	 }
-	 /** Retrieve the controls associated with editing this email address, but ensure they have a certain title label.
-	  * @param label The <strong>String</strong> to use as the label.
-	  * @return A <strong>JPanel</strong> containing those controls.
-	  */
-	 public JPanel getControls(String label) {
-		  if(controls == null) {
-				controls = new Control(label);
-		  }
-		  return controls;
-	 }
-	 /** Method to produce this email as a string.
-	  * @return A <strong>String</string> representing this email.
-	  */
-	 public String toString() {
-		  return name + " " + user + "@" + host;
-	 }
-	 /** Determines if this is a valid email. 
-	  * @return <i>true</i> if it is current valid, <i>false</i> otherwise. 
-	  */
-	 public boolean isValid() {
-		  return (user != null && user.length() > 0) && (host != null && host.length() > 0);
-	 }
-	 /** Overloaded to call get with both a key and an empty argument array.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  */
-	 private String get(String key) {
-		  return get(key, null);
-	 }
-	 /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
-	  * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatically populated with formatting Strings of with argument String provided in the get call.
-	  * @see org.greenstone.gatherer.Dictionary
-	  * @see org.greenstone.gatherer.Gatherer
-	  */
-	 private String get(String key, String args[]) {
-		  if(key.indexOf('.') == -1) {
-				key = "CDM.General.Email." + key;
-		  }
-		  return gatherer.dictionary.get(key, args);
-	 }
-	 /** The controls used for editing an email address. Basically two text fields with an '@' label in the middle. You also have a title label for this control, which is known for the major email address types, or can be supplied for any other purpose. */
-	 private class Control 
-		  extends JPanel {
-		  /** The at symbol label. */
-		  private JLabel at = null;
-		  /** The title label. */
-		  private JLabel label = null;
-		  /** The first text field for the user part of an address. */
-		  private JTextField field1 = null;
-		  /** The second text field for the host part of an address. */
-		  private JTextField field2 = null;
-		  /** The panel on to which the text fields and the at label are placed. */
-		  private JPanel middle = null;
-		  /** Constructor.
-			* @param name The <strong>String</strong> to use for the title label.
-			* @see org.greenstone.gatherer.util.EmailAddress.Control.HostListener
-			* @see org.greenstone.gatherer.util.EmailAddress.Control.UserListener
-			*/
-		  public Control(String name) {
-				super();
-				switch(type) {
-				case CREATOR:
-					 label = new JLabel(get("Creator"));
-					 break;
-				case MAINTAINER:
-					 label = new JLabel(get("Maintainer"));
-					 break;					 
-				default:
-					 label = new JLabel(name);
-				}
-				middle = new JPanel();
-				field1 = new JTextField(user);
-				at = new JLabel("@");
-				field2 = new JTextField(host);
+    /** The controls for editing this email. */
+    private Control controls = null;
+    /** A reference to the Gatherer. */
+    private Gatherer gatherer = null;
+    /** The type of this email, in collection configuration terms. */
+    private int type = -1;
+    /** The host part of the email (after @). */
+    public String host = null;
+    /** The user part of the email (before @). */
+    public String user = null;
+    /** The name given to or associated with this particular email (maybe the persons name, or it this email belongs to a certain variable i.e Collection Author). */
+    public String name = null;
+    /** Enumeration of important email address types - in this case the creator of a collection. */
+    static final public int CREATOR = 0;
+    /** Enumeration of important email address types - in this case the maintainer of a collection. */
+    static final public int MAINTAINER = 1;
+    /** Enumeration of important email address types - in this case any other email address other than the two noted above. */
+    static final public int OTHER = 2;
+    /** Constructor.
+     * @param gatherer A reference to the <strong>Gatherer</strong> for messaging purposes.
+     */
+    public EmailAddress(Gatherer gatherer) {
+	this.gatherer = gatherer;
+    }
+    /** Constructor.
+     * @param gatherer A reference to the <strong>Gatherer</strong> for messaging purposes.
+     * @param raw A <strong>String</strong> containing both the name and email separated by whitespace.
+     */
+    public EmailAddress(Gatherer gatherer, String raw) {
+	this(gatherer);
+	StringTokenizer tokenizer = new StringTokenizer(raw);
+	if(tokenizer.countTokens() >= 2) {
+	    this.name = tokenizer.nextToken();
+	    if(name.equals("creator")) {
+		type = CREATOR;
+	    }
+	    else if(name.equals("maintainer")) {
+		type = MAINTAINER;
+	    }
+	    else {
+		type = OTHER;
+	    }
+	    String email = tokenizer.nextToken();
+	    if(email.indexOf("@") != -1) {
+		this.user = email.substring(0, email.indexOf("@"));
+		this.host = email.substring(email.indexOf("@") + 1);
+	    }
+	}
+	else {
+	    this.name = "Invalid Email";
+	    this.user = "";
+	    this.host = "";
+	}
+    }
+    /** Retrieve the controls associated with editing this email address.
+     * @return A <strong>JPanel</strong> containing those controls.
+     */
+    public JPanel getControls() {
+	if(controls == null) {
+	    controls = new Control(name);
+	}
+	return controls;
+    }
+    /** Retrieve the controls associated with editing this email address, but ensure they have a certain title label.
+     * @param label The <strong>String</strong> to use as the label.
+     * @return A <strong>JPanel</strong> containing those controls.
+     */
+    public JPanel getControls(String label) {
+	if(controls == null) {
+	    controls = new Control(label);
+	}
+	return controls;
+    }
+    /** Method to produce this email as a string.
+     * @return A <strong>String</string> representing this email.
+     */
+    public String toString() {
+	return name + " " + user + "@" + host;
+    }
+    /** Determines if this is a valid email. 
+     * @return <i>true</i> if it is current valid, <i>false</i> otherwise. 
+     */
+    public boolean isValid() {
+	return (user != null && user.length() > 0) && (host != null && host.length() > 0);
+    }
+    /** Overloaded to call get with both a key and an empty argument array.
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+     */
+    private String get(String key) {
+	return get(key, null);
+    }
+    /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
+     * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatically populated with formatting Strings of with argument String provided in the get call.
+     * @see org.greenstone.gatherer.Dictionary
+     * @see org.greenstone.gatherer.Gatherer
+     */
+    private String get(String key, String args[]) {
+	if(key.indexOf('.') == -1) {
+	    key = "CDM.General.Email." + key;
+	}
+	return gatherer.dictionary.get(key, args);
+    }
+    /** The controls used for editing an email address. Basically two text fields with an '@' label in the middle. You also have a title label for this control, which is known for the major email address types, or can be supplied for any other purpose. */
+    private class Control 
+	extends JPanel {
+	/** The at symbol label. */
+	private JLabel at = null;
+	/** The title label. */
+	private JLabel label = null;
+	/** The first text field for the user part of an address. */
+	private JTextField field1 = null;
+	/** The second text field for the host part of an address. */
+	private JTextField field2 = null;
+	/** The panel on to which the text fields and the at label are placed. */
+	private JPanel middle = null;
+	/** Constructor.
+	 * @param name The <strong>String</strong> to use for the title label.
+	 * @see org.greenstone.gatherer.util.EmailAddress.Control.HostListener
+	 * @see org.greenstone.gatherer.util.EmailAddress.Control.UserListener
+	 */
+	public Control(String name) {
+	    super();
+	    switch(type) {
+	    case CREATOR:
+		label = new JLabel(get("Creator"));
+		break;
+	    case MAINTAINER:
+		label = new JLabel(get("Maintainer"));
+		break;					 
+	    default:
+		label = new JLabel(name);
+	    }
+	    middle = new JPanel();
+	    field1 = new JTextField(user);
+	    at = new JLabel("@");
+	    field2 = new JTextField(host);
 				// Add listeners
-				field1.addKeyListener(new UserListener());
-				field2.addKeyListener(new HostListener());
+	    field1.addKeyListener(new UserListener());
+	    field2.addKeyListener(new HostListener());
 				// Layout
-				label.setBorder(BorderFactory.createEmptyBorder(2,5,2,10));
-
-				at.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
-
-				middle.setLayout(new BorderLayout());
-				middle.add(field1, BorderLayout.CENTER);
-				middle.add(at, BorderLayout.EAST);
-
-				setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-				setLayout(new GridLayout(1,3));
-				add(label);
-				add(middle);
-				add(field2);
-		  }
-		  /** Listens for changes of the host part of the email address and updates the email object accordingly. */	 
-		  private class HostListener
-				extends KeyAdapter {
+	    label.setBorder(BorderFactory.createEmptyBorder(2,5,2,10));
+
+	    at.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+	    middle.setLayout(new BorderLayout());
+	    middle.add(field1, BorderLayout.CENTER);
+	    middle.add(at, BorderLayout.EAST);
+
+	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	    setLayout(new GridLayout(1,3));
+	    add(label);
+	    add(middle);
+	    add(field2);
+	}
+	/** Listens for changes of the host part of the email address and updates the email object accordingly. */	 
+	private class HostListener
+	    extends KeyAdapter {
 				/** Whenever a key is released (ie after a key typed event has been fired) when typing in the host field, this method updates the host field of the email object.
 				 * @param event A <strong>KeyEvent</strong> containing extra information about the key release performed.
 				 */
-				public void keyReleased(KeyEvent event) {
-					 host = field2.getText();
-				}
-		  }
-		  /** Listens for changes of the user part of the email address and updates the email object accordingly. */
-		  private class UserListener
-				extends KeyAdapter {
+	    public void keyReleased(KeyEvent event) {
+		host = field2.getText();
+	    }
+	}
+	/** Listens for changes of the user part of the email address and updates the email object accordingly. */
+	private class UserListener
+	    extends KeyAdapter {
 				/** Whenever a key is released (ie after a key typed event has been fired) when typing in the user field, this method updates the user field of the email object.
 				 * @param event A <strong>KeyEvent</strong> containing extra information about the key release performed.
 				 */
-				public void keyReleased(KeyEvent event) {
-					 user = field1.getText();
-				}
-		  }
+	    public void keyReleased(KeyEvent event) {
+		user = field1.getText();
+	    }
+	}
 		  
-	 }
+    }
 }
-
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/EnumeratedVector.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/EnumeratedVector.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/EnumeratedVector.java	(revision 4364)
@@ -50,24 +50,24 @@
  */
 public class EnumeratedVector
-	 extends Vector 
-	 implements Enumeration {
-	 /** The current index into our enumerated vector. */
-	 private int index = 0;
-	 /** Can we call nextElement() without running out of entries in the vector.
-	  * @return <i>true</i> if there are elements remaining, <i>false</i> otherwise.
+    extends Vector 
+    implements Enumeration {
+    /** The current index into our enumerated vector. */
+    private int index = 0;
+    /** Can we call nextElement() without running out of entries in the vector.
+     * @return <i>true</i> if there are elements remaining, <i>false</i> otherwise.
 	  */
-	 public boolean hasMoreElements() {
-		  return (index < size());
-	 }
-	 /** Retrieve the next element indicated by the current index.
+    public boolean hasMoreElements() {
+	return (index < size());
+    }
+    /** Retrieve the next element indicated by the current index.
 	  * @return The next element as an <strong>Object</strong>.
 	  */
-	 public Object nextElement() {
-		  Object object = null;
-		  if(index < size()) {
-				object = get(index);
-				index++;
-		  }
-		  return object;
-	 }  
+    public Object nextElement() {
+	Object object = null;
+	if(index < size()) {
+	    object = get(index);
+	    index++;
+	}
+	return object;
+    }  
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/ExclusiveListSelectionListener.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/ExclusiveListSelectionListener.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/ExclusiveListSelectionListener.java	(revision 4364)
@@ -58,39 +58,39 @@
  */
 public class ExclusiveListSelectionListener 
-	 implements ListSelectionListener {
-	 /** A flag set to show that we are already dealing with a list selection change, and to ignore further ones until the flag becomes unset again. */
-	 private boolean ignore = false;
-	 /** An array of JList components, containing each JList thats registered with this listener. */
-	 private JList lists[] = null;
-	 /** Method to register a new JList with this listener.
-	  * @param list The new <strong>JList</strong> to register.
+    implements ListSelectionListener {
+    /** A flag set to show that we are already dealing with a list selection change, and to ignore further ones until the flag becomes unset again. */
+    private boolean ignore = false;
+    /** An array of JList components, containing each JList thats registered with this listener. */
+    private JList lists[] = null;
+    /** Method to register a new JList with this listener.
+     * @param list The new <strong>JList</strong> to register.
 	  */
-	 public void add(JList list) {
-		  list.addListSelectionListener(this);
-		  if(lists == null) {
-				lists = new JList[1];
-				lists[0] = list;
-		  }
-		  else {
-				JList temp[] = new JList[lists.length + 1];
-				System.arraycopy(lists, 0, temp, 0, lists.length);
-				temp[lists.length] = list;
-				lists = temp;
-		  }
-	 }
-	 /** Any implementation of ListSelectionListener must include this method so we can be informed when the selection changes. In this case we want to clear the selections of all other registered JLists other than the caller.
+    public void add(JList list) {
+	list.addListSelectionListener(this);
+	if(lists == null) {
+	    lists = new JList[1];
+	    lists[0] = list;
+	}
+	else {
+	    JList temp[] = new JList[lists.length + 1];
+	    System.arraycopy(lists, 0, temp, 0, lists.length);
+	    temp[lists.length] = list;
+	    lists = temp;
+	}
+    }
+    /** Any implementation of ListSelectionListener must include this method so we can be informed when the selection changes. In this case we want to clear the selections of all other registered JLists other than the caller.
 	  * @param event A <strong>ListSelectionEvent</strong> containing informaiton about the event.
 	  */
-	 public void valueChanged(ListSelectionEvent event) {
-		  if(!ignore) {
-				ignore = true;
-				JList source = (JList)event.getSource();
-				for(int i = 0; i < lists.length; i++) {
-					 if(lists[i] != source) {
-						  lists[i].clearSelection();
-					 }
-				}
-				ignore = false;
-		  }
-	 }
+    public void valueChanged(ListSelectionEvent event) {
+	if(!ignore) {
+	    ignore = true;
+	    JList source = (JList)event.getSource();
+	    for(int i = 0; i < lists.length; i++) {
+		if(lists[i] != source) {
+		    lists[i].clearSelection();
+		}
+	    }
+	    ignore = false;
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/GSDLSiteConfig.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/GSDLSiteConfig.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/GSDLSiteConfig.java	(revision 4364)
@@ -5,146 +5,141 @@
 
 public class GSDLSiteConfig
-	 extends LinkedHashMap {
-	 private File gsdlsite_cfg;
-	 private String autoenter_initial;
-	 private String start_browser_initial;
+    extends LinkedHashMap {
+    private File gsdlsite_cfg;
+    private String autoenter_initial;
+    private String start_browser_initial;
 
-	 static final public String ADD_COMMAND = "a=config&cmd=add-collection&c=";
-	 static final public String RELEASE_COMMAND = "a=config&cmd=release-collection&c=";
-	 static final public String QUIT_COMMAND = "a=";
+    static final public String ADD_COMMAND = "a=config&cmd=add-collection&c=";
+    static final public String RELEASE_COMMAND = "a=config&cmd=release-collection&c=";
+    static final public String QUIT_COMMAND = "a=";
 
-	 static final private String AUTOENTER = "autoenter";
-	 static final private String COLON = ":";
-	 static final private String ENTERLIB = "enterlib";
-	 static final private String FALSE = "0";
-	 static final private String GSDL = "gsdl";
-	 static final private String GSDLSITE_CFG = "gsdlsite.cfg";
-	 static final private String PORTNUMBER = "portnumber";
-	 static final private String SEPARATOR = "/";
-	 static final private String STARTBROWSER = "start_browser";
-	 static final private String TRUE = "1";
-	 static final private String URL = "url";
+    static final private String AUTOENTER = "autoenter";
+    static final private String COLON = ":";
+    static final private String ENTERLIB = "enterlib";
+    static final private String FALSE = "0";
+    static final private String GSDL = "gsdl";
+    static final private String GSDLSITE_CFG = "gsdlsite.cfg";
+    static final private String PORTNUMBER = "portnumber";
+    static final private String SEPARATOR = "/";
+    static final private String STARTBROWSER = "start_browser";
+    static final private String TRUE = "1";
+    static final private String URL = "url";
 
-	 public GSDLSiteConfig(File server_exe) {
-		  debug("New GSDLSiteConfig for: " + server_exe.getAbsolutePath());
-		  gsdlsite_cfg = new File(server_exe.getParentFile(), GSDLSITE_CFG);
+    public GSDLSiteConfig(File server_exe) {
+	debug("New GSDLSiteConfig for: " + server_exe.getAbsolutePath());
+	gsdlsite_cfg = new File(server_exe.getParentFile(), GSDLSITE_CFG);
 		  
-		  autoenter_initial = null;
-		  start_browser_initial = null;
-		  load();
-	 }
+	autoenter_initial = null;
+	start_browser_initial = null;
+	load();
+    }
 
-	 public boolean exists() {
-		  return gsdlsite_cfg.exists();
-	 }
+    public boolean exists() {
+	return gsdlsite_cfg.exists();
+    }
 
-	 public String getURL() {
-		  // URL is made from url and portnumber
-		  String url = (String) get(URL);
-		  if(url != null) {
-				StringBuffer temp = new StringBuffer(url);
-				temp.append(COLON);
-				temp.append((String)get(PORTNUMBER));
-				String enterlib = (String)get(ENTERLIB);
-				if(enterlib == null || enterlib.length() == 0) {
-					// Use the default /gsdl and hope for the best.
-					temp.append(SEPARATOR);
-					temp.append(GSDL);
-				}
-				else {
-					if(!enterlib.startsWith(SEPARATOR)) {
-				  		temp.append(SEPARATOR);
-					}
-					temp.append(enterlib);
-				}
-				enterlib = null;
-				url = temp.toString();
-		  }
-		  debug("Found Local Library Address: " + url);
-		  return url;
-	 }
+    public String getURL() {
+	// URL is made from url and portnumber
+	String url = (String) get(URL);
+	if(url != null) {
+	    StringBuffer temp = new StringBuffer(url);
+	    temp.append(COLON);
+	    temp.append((String)get(PORTNUMBER));
+	    String enterlib = (String)get(ENTERLIB);
+	    if(enterlib == null || enterlib.length() == 0) {
+		// Use the default /gsdl and hope for the best.
+		temp.append(SEPARATOR);
+		temp.append(GSDL);
+	    }
+	    else {
+		if(!enterlib.startsWith(SEPARATOR)) {
+		    temp.append(SEPARATOR);
+		}
+		temp.append(enterlib);
+	    }
+	    enterlib = null;
+	    url = temp.toString();
+	}
+	debug("Found Local Library Address: " + url);
+	return url;
+    }
 
-	 public void load() {
-		  if(gsdlsite_cfg.exists()) {
-				debug("Load: " + gsdlsite_cfg.getAbsolutePath());
-				clear();
-				try {
-					 BufferedReader in = new BufferedReader(new FileReader(gsdlsite_cfg));
-					 String line = null;
-					 while((line = in.readLine()) != null) {
-						  String key = null;
-						  String value = null;
-						  int index = -1;
-						  if((index = line.indexOf("=")) != -1 && line.length() >= index + 1) {
-								key = line.substring(0, index);
-								value = line.substring(index + 1);
-						  }
-						  else {
-								key = line;
-						  }
-						  put(key, value);
-					 }
-				}
-				catch (Exception error) {
-					 error.printStackTrace();
-				}
-		  }
-		  else {
-				debug("No GSDLsite.cfg file can be found!");
-		  }
-	 }
+    public void load() {
+	if(gsdlsite_cfg.exists()) {
+	    debug("Load: " + gsdlsite_cfg.getAbsolutePath());
+	    clear();
+	    try {
+		BufferedReader in = new BufferedReader(new FileReader(gsdlsite_cfg));
+		String line = null;
+		while((line = in.readLine()) != null) {
+		    String key = null;
+		    String value = null;
+		    int index = -1;
+		    if((index = line.indexOf("=")) != -1 && line.length() >= index + 1) {
+			key = line.substring(0, index);
+			value = line.substring(index + 1);
+		    }
+		    else {
+			key = line;
+		    }
+		    put(key, value);
+		}
+	    }
+	    catch (Exception error) {
+		error.printStackTrace();
+	    }
+	}
+	else {
+	    debug("No GSDLsite.cfg file can be found!");
+	}
+    }
 
-	 /** Restore the autoenter value to its initial value, and remove url if present. */
-	 public void restore() {
-		 debug("Restore Initial Settings");
-		 put(AUTOENTER, autoenter_initial);
-		 put(STARTBROWSER, start_browser_initial);
-		 remove(URL);
-		 save();
-	 }
+    /** Restore the autoenter value to its initial value, and remove url if present. */
+    public void restore() {
+	debug("Restore Initial Settings");
+	put(AUTOENTER, autoenter_initial);
+	put(STARTBROWSER, start_browser_initial);
+	remove(URL);
+	save();
+    }
 
-	 public void set() {
-		  debug("Set Session Settings");
-		  if(autoenter_initial == null) {
-				autoenter_initial = (String) get(AUTOENTER);
-				debug("Remember autoenter was: " + autoenter_initial);
-		  }
-		  put(AUTOENTER, TRUE);
-		  if(start_browser_initial == null) {
-				start_browser_initial = (String) get(STARTBROWSER);
-				debug("Remember start_browser was: " + start_browser_initial);
-		  }
-		  save();
-	 }
+    public void set() {
+	debug("Set Session Settings");
+	if(autoenter_initial == null) {
+	    autoenter_initial = (String) get(AUTOENTER);
+	    debug("Remember autoenter was: " + autoenter_initial);
+	}
+	put(AUTOENTER, TRUE);
+	if(start_browser_initial == null) {
+	    start_browser_initial = (String) get(STARTBROWSER);
+	    debug("Remember start_browser was: " + start_browser_initial);
+	}
+	save();
+    }
 
-	 private void debug(String message) {
-		  System.err.println(message);
-	 }
+    private void debug(String message) {
+	System.err.println(message);
+    }
 
-	 private void save() {
-		  debug("Save: " + gsdlsite_cfg.getAbsolutePath());
-		  try {
-				BufferedWriter out = new BufferedWriter(new FileWriter(gsdlsite_cfg, false));
-				for(Iterator keys = keySet().iterator(); keys.hasNext(); ) {
-					 String key = (String) keys.next();
-					 String value = (String) get(key);
-					 out.write(key, 0, key.length());
-					 if(value != null) {
-						  out.write('=');
-						  out.write(value, 0, value.length());
-					 }
-					 out.newLine();
-				}
-				out.flush();
-				out.close();
-		  }
-		  catch (Exception error) {
-				error.printStackTrace();
-		  }
-	 }
+    private void save() {
+	debug("Save: " + gsdlsite_cfg.getAbsolutePath());
+	try {
+	    BufferedWriter out = new BufferedWriter(new FileWriter(gsdlsite_cfg, false));
+	    for(Iterator keys = keySet().iterator(); keys.hasNext(); ) {
+		String key = (String) keys.next();
+		String value = (String) get(key);
+		out.write(key, 0, key.length());
+		if(value != null) {
+		    out.write('=');
+		    out.write(value, 0, value.length());
+		}
+		out.newLine();
+	    }
+	    out.flush();
+	    out.close();
+	}
+	catch (Exception error) {
+	    error.printStackTrace();
+	}
+    }
 }
-
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/GURL.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/GURL.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/GURL.java	(revision 4364)
@@ -60,134 +60,133 @@
  */
 public class GURL
-	 implements Cloneable, Serializable {
+    implements Cloneable, Serializable {
 
-	 private Integer type;
+    private Integer type;
 
-	 private URL url;
+    private URL url;
 
-	 private String file;
-	 private String host;
-	 private String path;
-	 private String protocol;
-	 private String title = "empty";
+    private String file;
+    private String host;
+    private String path;
+    private String protocol;
+    private String title = "empty";
 
-	 /** This Vector maintains a record of the url links from within
-	  * this resource, and their type (EXTERNAL vs INTERNAL). Internal
-	  * links can be 'hidden' from the user if desired, or a user can
-	  * opt to have all internal links automatically moved into a
-	  * collection if the page that depends upon them is moved. <BR>
-	  * Each entry in the vector is of the form.
-	  * links[i] = String url
-	  * links[i+1] = int type
-	  */
-	 private Vector links;
+    /** This Vector maintains a record of the url links from within
+     * this resource, and their type (EXTERNAL vs INTERNAL). Internal
+     * links can be 'hidden' from the user if desired, or a user can
+     * opt to have all internal links automatically moved into a
+     * collection if the page that depends upon them is moved. <BR>
+     * Each entry in the vector is of the form.
+     * links[i] = String url
+     * links[i+1] = int type
+     */
+    private Vector links;
 
-	 /* Static Integer identifiers for ease of use. */
-	 static public Integer INTERNAL = new Integer(0);
-	 static public Integer EXTERNAL = new Integer(1);
+    /* Static Integer identifiers for ease of use. */
+    static public Integer INTERNAL = new Integer(0);
+    static public Integer EXTERNAL = new Integer(1);
 
-	 public GURL(String url_str) {
-		  this(url_str, GURL.EXTERNAL);
-	 }
+    public GURL(String url_str) {
+	this(url_str, GURL.EXTERNAL);
+    }
 
-	 public GURL(URL url) {
-		  this(url, GURL.EXTERNAL);
-	 }
+    public GURL(URL url) {
+	this(url, GURL.EXTERNAL);
+    }
 
-	 public GURL(URL url, Integer type) {
-		  this.type = type;
-		  this.url = url;
-	 }
+    public GURL(URL url, Integer type) {
+	this.type = type;
+	this.url = url;
+    }
 
-	 public GURL(String url_str, Integer type) {
-		  this.type = type;
+    public GURL(String url_str, Integer type) {
+	this.type = type;
 
-		  try {
-				if(!url_str.startsWith("file:") && url_str.indexOf("://") == -1) {
-					 url_str = "http://" + url_str;
-				}
-				url = new URL(url_str);
-		  }
-		  catch (MalformedURLException e) {
+	try {
+	    if(!url_str.startsWith("file:") && url_str.indexOf("://") == -1) {
+		url_str = "http://" + url_str;
+	    }
+	    url = new URL(url_str);
+	}
+	catch (MalformedURLException e) {
 				// Fix it
-		  }
-	 }
+	}
+    }
 
-	 public void addLink(String url) {
-		  addLink(url, GURL.EXTERNAL);
-	 }
+    public void addLink(String url) {
+	addLink(url, GURL.EXTERNAL);
+    }
 
-	 public void addLink(String url, Integer type) {
-		  if(links == null) {
-				links = new Vector();
-		  }
-		  links.add(url);
-		  links.add(type);
-	 }
+    public void addLink(String url, Integer type) {
+	if(links == null) {
+	    links = new Vector();
+	}
+	links.add(url);
+	links.add(type);
+    }
 
-	 /** Used by tree renderer to display name. So we'll only return the
-	  * filename.
-	  */
-	 public String getFile() {
-		  return parseFile(url.getFile());
-	 }
+    /** Used by tree renderer to display name. So we'll only return the
+     * filename.
+     */
+    public String getFile() {
+	return parseFile(url.getFile());
+    }
 
-	 public String getHost() {
-		  return url.getHost();
-	 }
+    public String getHost() {
+	return url.getHost();
+    }
 
-	 public String getLocalFile() {
-		  String local_file = getPath() + File.separator + getFile();
-		  // Strip msdos roots if they're still there.
-			while(local_file.indexOf(":") != -1) {
-				local_file = local_file.substring(0, local_file.indexOf(":")) + local_file.substring(local_file.indexOf(":") + 1);
-			}
-		  return local_file;
-	 }
+    public String getLocalFile() {
+	String local_file = getPath() + File.separator + getFile();
+	// Strip msdos roots if they're still there.
+	while(local_file.indexOf(":") != -1) {
+	    local_file = local_file.substring(0, local_file.indexOf(":")) + local_file.substring(local_file.indexOf(":") + 1);
+	}
+	return local_file;
+    }
 
-	 public String getPath() {
-		  return getHost() + parsePath(url.getFile());
-	 }
+    public String getPath() {
+	return getHost() + parsePath(url.getFile());
+    }
 
-	 public String getProtocol() {
-		  String protocol = url.getProtocol();
-		  if(protocol.equals("file")) {
-				return protocol + ":";
-		  }
-		  return protocol + "://";
-	 }
+    public String getProtocol() {
+	String protocol = url.getProtocol();
+	if(protocol.equals("file")) {
+	    return protocol + ":";
+	}
+	return protocol + "://";
+    }
 
-	 public URL getURL() {
-		  return url;
-	 }
+    public URL getURL() {
+	return url;
+    }
 
-	 public String toString() {
-		  return url.toString();
-	 }
+    public String toString() {
+	return url.toString();
+    }
 
-	 public boolean valid() {
-		  return (url != null);
-	 }
+    public boolean valid() {
+	return (url != null);
+    }
 
-	 private String parseFile(String raw) {
-		  if(raw != null && !raw.equals("")) {
+    private String parseFile(String raw) {
+	if(raw != null && !raw.equals("")) {
 				// Remove everything upto and including the last '/'
-				if(raw.indexOf("/") != -1) {
-					 return raw.substring(raw.lastIndexOf("/") + 1);
-				}
-		  }
-		  return raw;
-	 }
+	    if(raw.indexOf("/") != -1) {
+		return raw.substring(raw.lastIndexOf("/") + 1);
+	    }
+	}
+	return raw;
+    }
 
-	 private String parsePath(String raw) {
-		  if(raw != null && !raw.equals("")) {
+    private String parsePath(String raw) {
+	if(raw != null && !raw.equals("")) {
 				// Remove everything after the last "/"
-				if(raw.indexOf("/") != -1) {
-					 return raw.substring(0, raw.lastIndexOf("/"));
-				}
-		  }
-		  return raw;
-	 }
+	    if(raw.indexOf("/") != -1) {
+		return raw.substring(0, raw.lastIndexOf("/"));
+	    }
+	}
+	return raw;
+    }
 
 }
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/Generator.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/Generator.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/Generator.java	(revision 4364)
@@ -57,394 +57,394 @@
 public class Generator {
 
-	private FileOutputStream file_output_stream = null;
-
-	private TemplateManager template_manager = null;
-
-	public Generator(File test_file, File template_file) {
-		template_manager = new TemplateManager(template_file);
-		File files[] = null;
-		// If test_file is a file, goody.
-		if(test_file.isFile()) {
-			files = new File[1];
-			files[0] = test_file;
-		}
-		// Otherwise we use all of test_files children.
-		else {
-			files = test_file.listFiles();
-		}
-		// Its unfortunate we test this twice for the basic case, but we need to ensure that source managers are only contructed for non-directory files.
-		for(int i = 0; i < files.length; i++) {
-			if(files[i].isFile()) {
-				SourceManager source_manager = new SourceManager(files[i]);
-				source_manager = null;
-			}
-		}
-		// All done.
-	}
-
-	public void exit() {
-		if(file_output_stream != null) {
-			try {
-				file_output_stream.close();
-				file_output_stream = null;
-			}
-			catch(Exception error) {
-				error.printStackTrace();
-			}
-		}
-		System.exit(0);
-	}
-
-	public void out(String msg) {
-		System.err.println(msg);
-		try {
-			if(file_output_stream == null) {
-				file_output_stream = new FileOutputStream("output.txt");
-			}
-			file_output_stream.write((msg + "\n").getBytes());
-		}
-		catch(Exception error) {
-			error.printStackTrace();
-			System.err.println("An error has occured. View output.txt for more information.");
-			exit();
-		}
-	}
-
-	static public void main(String args[]) {
-		System.err.println("Rapid repeated HTML generation tool.");
-		System.err.println("  written by John Thompson, 2002\n");
-		boolean usage = false;
-		// Attempt to generate html from text
-		if(args.length == 2) {
-			File text_file = new File(args[0]);
-			File template_file = new File(args[1]);
-			if(!text_file.exists() || !template_file.exists() || !template_file.isFile()) {
-				usage = true;
-			}
+    private FileOutputStream file_output_stream = null;
+
+    private TemplateManager template_manager = null;
+
+    public Generator(File test_file, File template_file) {
+	template_manager = new TemplateManager(template_file);
+	File files[] = null;
+	// If test_file is a file, goody.
+	if(test_file.isFile()) {
+	    files = new File[1];
+	    files[0] = test_file;
+	}
+	// Otherwise we use all of test_files children.
+	else {
+	    files = test_file.listFiles();
+	}
+	// Its unfortunate we test this twice for the basic case, but we need to ensure that source managers are only contructed for non-directory files.
+	for(int i = 0; i < files.length; i++) {
+	    if(files[i].isFile()) {
+		SourceManager source_manager = new SourceManager(files[i]);
+		source_manager = null;
+	    }
+	}
+	// All done.
+    }
+
+    public void exit() {
+	if(file_output_stream != null) {
+	    try {
+		file_output_stream.close();
+		file_output_stream = null;
+	    }
+	    catch(Exception error) {
+		error.printStackTrace();
+	    }
+	}
+	System.exit(0);
+    }
+
+    public void out(String msg) {
+	System.err.println(msg);
+	try {
+	    if(file_output_stream == null) {
+		file_output_stream = new FileOutputStream("output.txt");
+	    }
+	    file_output_stream.write((msg + "\n").getBytes());
+	}
+	catch(Exception error) {
+	    error.printStackTrace();
+	    System.err.println("An error has occured. View output.txt for more information.");
+	    exit();
+	}
+    }
+
+    static public void main(String args[]) {
+	System.err.println("Rapid repeated HTML generation tool.");
+	System.err.println("  written by John Thompson, 2002\n");
+	boolean usage = false;
+	// Attempt to generate html from text
+	if(args.length == 2) {
+	    File text_file = new File(args[0]);
+	    File template_file = new File(args[1]);
+	    if(!text_file.exists() || !template_file.exists() || !template_file.isFile()) {
+		usage = true;
+	    }
+	    else {
+		Generator generator = new Generator(text_file, template_file);
+		generator.exit();
+	    }
+	}
+	else {
+	    usage = true;
+	}
+	// Print usage message
+	if(usage) {
+	    System.err.println("Usage: GenHelp <text_file> <template_file>");
+	    System.err.println("text_file     - A file containing text marked up using special tags.");
+	    System.err.println("template_file - An HTML file using tags to specify where to insert text.");
+	}
+    }
+
+    /** An HTML block object is essentially a String buffer containing several places where it demands further text be appended. To this end you can ask if an HTML block is ready, and if not provide it with a further text fragment and try again. */
+    private class HTMLBlock {
+	/** Prevent the original HTML block being changed. */
+	private boolean original;
+
+	private int insertion_position = -1;
+	/** The name of this HTML block as taken from the template file. */
+	private String name = null;
+	private String paragraph_tag = null;
+	/** The HTML block itself. */
+	private StringBuffer data = null;
+	/** Constructor takes the HTML blocks name as extracted from the template file.
+	 * @param name The name of this HTML block as a <strong>String</strong>.
+	 */
+	public HTMLBlock(String name) {
+	    this.data = new StringBuffer("");
+	    this.name = name;
+	    this.original = true;
+	}
+	/** Copy constructor takes the HTML block and its name as extracted from the template file.
+	 * @param name The name of this HTML block as a <strong>String</strong>.
+	 * @param raw The raw HTML code as a <strong>String</strong>.
+	 */
+	public HTMLBlock(String name, String raw) {
+	    this.data = new StringBuffer(raw);
+	    this.name = name;
+	    this.original = false;
+	}
+	/** Append this text to the end of the current block.
+	 * @param text The <strong>String</strong> to add.
+	 */
+	public void addLine(String text) {
+	    if(original) {
+		data.append(text);
+		data.append("\n");
+	    }
+	}
+	/** Retrieve the name of this HTML block.
+	 * @return A <strong>String</strong> representing the name.
+	 */
+	public String getName() {
+	    return name;
+	}
+	/** Insert a text fragment into this HTML block.
+	 * @param fragment The <strong>String</strong> to insert.
+	 * @return <i>true</i> if an insertion occured, <i>false</i> otherwise.
+	 */
+	public boolean insert(String fragment) {
+	    if(!original) {
+				// Find the start of an insertion tag.
+		int start = data.indexOf("<$");
+		if(start != -1) {
+		    // Find the end of an insertion tag.
+		    int end = data.indexOf(">", start) + 1;
+		    // Determine the tag, and search for any paragraphing information.
+		    String tag = data.substring(start, end);
+		    if(tag.length() > 7) {
+			paragraph_tag = "<p " + tag.substring(7) + "\n";
+			fragment = paragraph_tag + fragment + "\n</p>\n";
+		    }
+		    // Substitue the fragment for the tag.
+		    data.replace(start, end, fragment);
+		    // Now set insertion position
+		    insertion_position = start + fragment.length();
+		    return true;
+		}
+				// Its possible that we've already got enough text to output this block. Essentially this new fragment is meant to be another paragraph. Such fragments are always added immediately after the last fragment inserted.
+		else if(insertion_position != -1) {
+		    if(paragraph_tag != null) {
+			fragment = paragraph_tag + fragment + "\n</p>\n";
+		    }
+		    data.insert(insertion_position, fragment);
+		    insertion_position = insertion_position + fragment.length();
+		}
+	    }
+	    return false;
+	}
+	/** Is this block ready to written out.
+	 * @return <i>true</i> if we can write this block out, <i>false</i> if text insertion points still exist.
+	 */
+	public boolean ready() {
+	    return data.indexOf("<$") == -1;
+	}
+	/** Produce a copy of this block with all text insertions intact.
+	 * @return A new <strong>HTMLBlock</strong>.
+	 */
+	public HTMLBlock spawn() {
+	    return new HTMLBlock(name, data.toString());
+	}
+	/** Return the HTML block within this object.
+	 * @return A <strong>String</strong> containing the HTML code.
+	 */
+	public String toString() {
+	    return data.toString();
+	}
+    }
+    /** Reads in the given text file, outputing HTML by utilizing HTML blocks stored in the TemplateManager. The manager understands one extra tag <$FILE name=<filename>> which is used to indicate the presence of several output files within one input file. If this tag is not found the default name for the output file is "<text_file_name>.htm". */
+    private class SourceManager {
+	private File text_file = null;
+
+	private FileOutputStream output_file = null;
+
+	public SourceManager(File text_file) {
+	    out("Building Source Manager:");
+	    this.text_file = text_file;
+	    try {
+		out("  reading from: " + text_file.getName());
+				// Create input stream from the text file which we read a line at a time.
+		FileReader file_reader = new FileReader(text_file);
+		BufferedReader buffered_reader = new BufferedReader(file_reader);
+		HTMLBlock block = null;
+		String line = null;
+		StringBuffer paragraph = null;
+		while((line = buffered_reader.readLine()) != null) {
+		    boolean done = false;
+		    // What we do here depends on what state we are in and what tag or text we are reading.
+		    // The first case to consider is a file open command tag. This tells us the next block of html should be written to a certain file.
+		    if(line.startsWith("<$FILE")) {
+			// Close any existing output file.
+			if(output_file != null) {
+			    // Write any existing block
+			    if(block != null) {
+				if(paragraph != null) {
+				    block.insert(paragraph.toString());
+				    paragraph = null;
+				}
+				out("  write: " + block.getName());
+				output_file.write((block.toString()).getBytes());
+				block = null;
+			    }
+			    output_file.close();
+			    output_file = null;
+			}
+			// Determine the new file name.
+			String name = line.substring(12, line.lastIndexOf(">"));
+			// Create new file.
+			output_file = new FileOutputStream(name);
+			// Dealt with
+			out("  writing to: " + name);
+			name = null;
+			done = true;
+		    }
+		    else if(line.startsWith("<$")) {
+			// If we have a block open its time to write it out.
+			if(block != null) {
+			    if(paragraph != null) {
+				block.insert(paragraph.toString());
+				paragraph = null;
+			    }
+			    // Check we have somewhere to write to.
+			    if(output_file == null) {
+				output_file = new FileOutputStream(text_file.getName() + ".htm");
+			    }
+			    out("  write: " + block.getName());
+			    output_file.write((block.toString()).getBytes());
+			    block = null;
+			}
+			// Now determine the new blocks name.
+			String name = line.substring(2, line.lastIndexOf(">"));
+			// Retrieve that block
+			block = template_manager.getHTMLBlock(name);
+			if(block == null) {
+			    // Somethings gone wrong, most likely an unidentified block name.
+			    out("  no such block: " + name);
+			}
+			name = null;
+			done = true;
+		    }
+		    // Its just text. However we want text in paragraph blocks. A paragraph is delimited by a "\n\n" or the start of a tag.
+		    if(!done) {
+			// If the line read is "" it means we've encountered a "\n\n".
+			if(line.equals("")) {
+			    // Add paragraph to open block.
+			    if(block != null && paragraph != null) {
+				block.insert(paragraph.toString());
+				paragraph = null;
+			    }
+			}
+			// Any other case just add to paragraph. Ensure that there is at least one space between the two fragments
 			else {
-				Generator generator = new Generator(text_file, template_file);
-				generator.exit();
-			}
-		}
-		else {
-			usage = true;
-		}
-		// Print usage message
-		if(usage) {
-			System.err.println("Usage: GenHelp <text_file> <template_file>");
-			System.err.println("text_file     - A file containing text marked up using special tags.");
-			System.err.println("template_file - An HTML file using tags to specify where to insert text.");
-		}
-	}
-
-	/** An HTML block object is essentially a String buffer containing several places where it demands further text be appended. To this end you can ask if an HTML block is ready, and if not provide it with a further text fragment and try again. */
-	private class HTMLBlock {
-		/** Prevent the original HTML block being changed. */
-		private boolean original;
-
-		private int insertion_position = -1;
-		/** The name of this HTML block as taken from the template file. */
-		private String name = null;
-		private String paragraph_tag = null;
-		/** The HTML block itself. */
-		private StringBuffer data = null;
-		/** Constructor takes the HTML blocks name as extracted from the template file.
-		* @param name The name of this HTML block as a <strong>String</strong>.
-		*/
-		public HTMLBlock(String name) {
-			this.data = new StringBuffer("");
-			this.name = name;
-			this.original = true;
-		}
-		/** Copy constructor takes the HTML block and its name as extracted from the template file.
-		* @param name The name of this HTML block as a <strong>String</strong>.
-		* @param raw The raw HTML code as a <strong>String</strong>.
-		*/
-		public HTMLBlock(String name, String raw) {
-			this.data = new StringBuffer(raw);
-			this.name = name;
-			this.original = false;
-		}
-		/** Append this text to the end of the current block.
-		* @param text The <strong>String</strong> to add.
-		*/
-		public void addLine(String text) {
-			if(original) {
-				data.append(text);
-				data.append("\n");
-			}
-		}
-		/** Retrieve the name of this HTML block.
-		* @return A <strong>String</strong> representing the name.
-		*/
-		public String getName() {
-			return name;
-		}
-		/** Insert a text fragment into this HTML block.
-		* @param fragment The <strong>String</strong> to insert.
-		* @return <i>true</i> if an insertion occured, <i>false</i> otherwise.
-		*/
-		public boolean insert(String fragment) {
-			if(!original) {
-				// Find the start of an insertion tag.
-				int start = data.indexOf("<$");
-				if(start != -1) {
-					// Find the end of an insertion tag.
-					int end = data.indexOf(">", start) + 1;
-					// Determine the tag, and search for any paragraphing information.
-					String tag = data.substring(start, end);
-					if(tag.length() > 7) {
-						paragraph_tag = "<p " + tag.substring(7) + "\n";
-						fragment = paragraph_tag + fragment + "\n</p>\n";
-					}
-					// Substitue the fragment for the tag.
-					data.replace(start, end, fragment);
-					// Now set insertion position
-					insertion_position = start + fragment.length();
-					return true;
+			    if(paragraph == null) {
+				paragraph = new StringBuffer(line);
+			    }
+			    else {
+				if(paragraph.charAt(paragraph.length() - 1) != ' ' && line.charAt(0) != ' ') {
+				    paragraph.append(" ");
 				}
-				// Its possible that we've already got enough text to output this block. Essentially this new fragment is meant to be another paragraph. Such fragments are always added immediately after the last fragment inserted.
-				else if(insertion_position != -1) {
-					if(paragraph_tag != null) {
-						fragment = paragraph_tag + fragment + "\n</p>\n";
-					}
-				  data.insert(insertion_position, fragment);
-					insertion_position = insertion_position + fragment.length();
-				}
-			}
-			return false;
-		}
-		/** Is this block ready to written out.
-		* @return <i>true</i> if we can write this block out, <i>false</i> if text insertion points still exist.
-		*/
-		public boolean ready() {
-			return data.indexOf("<$") == -1;
-		}
-		/** Produce a copy of this block with all text insertions intact.
-		* @return A new <strong>HTMLBlock</strong>.
-		*/
-		public HTMLBlock spawn() {
-			return new HTMLBlock(name, data.toString());
-		}
-		/** Return the HTML block within this object.
-		* @return A <strong>String</strong> containing the HTML code.
-		*/
-		public String toString() {
-			return data.toString();
-		}
-	}
-	/** Reads in the given text file, outputing HTML by utilizing HTML blocks stored in the TemplateManager. The manager understands one extra tag <$FILE name=<filename>> which is used to indicate the presence of several output files within one input file. If this tag is not found the default name for the output file is "<text_file_name>.htm". */
-	private class SourceManager {
-		private File text_file = null;
-
-		private FileOutputStream output_file = null;
-
-		public SourceManager(File text_file) {
-			out("Building Source Manager:");
-			this.text_file = text_file;
-			try {
-				out("  reading from: " + text_file.getName());
-				// Create input stream from the text file which we read a line at a time.
-				FileReader file_reader = new FileReader(text_file);
-				BufferedReader buffered_reader = new BufferedReader(file_reader);
-				HTMLBlock block = null;
-				String line = null;
-				StringBuffer paragraph = null;
-				while((line = buffered_reader.readLine()) != null) {
-					boolean done = false;
-					// What we do here depends on what state we are in and what tag or text we are reading.
-					// The first case to consider is a file open command tag. This tells us the next block of html should be written to a certain file.
-					if(line.startsWith("<$FILE")) {
-						// Close any existing output file.
-						if(output_file != null) {
-  						// Write any existing block
-  						if(block != null) {
-  							if(paragraph != null) {
-  								block.insert(paragraph.toString());
-  								paragraph = null;
-  							}
-  							out("  write: " + block.getName());
-	  						output_file.write((block.toString()).getBytes());
-	  						block = null;
-	  					}
-							output_file.close();
-							output_file = null;
-						}
-						// Determine the new file name.
-						String name = line.substring(12, line.lastIndexOf(">"));
-						// Create new file.
-						output_file = new FileOutputStream(name);
-						// Dealt with
-						out("  writing to: " + name);
-						name = null;
-						done = true;
-					}
-					else if(line.startsWith("<$")) {
-						// If we have a block open its time to write it out.
-						if(block != null) {
-							if(paragraph != null) {
-								block.insert(paragraph.toString());
-								paragraph = null;
-							}
-							// Check we have somewhere to write to.
-							if(output_file == null) {
-								output_file = new FileOutputStream(text_file.getName() + ".htm");
-							}
-							out("  write: " + block.getName());
-							output_file.write((block.toString()).getBytes());
-							block = null;
-						}
-						// Now determine the new blocks name.
-						String name = line.substring(2, line.lastIndexOf(">"));
-						// Retrieve that block
-						block = template_manager.getHTMLBlock(name);
-						if(block == null) {
-							// Somethings gone wrong, most likely an unidentified block name.
-							out("  no such block: " + name);
-						}
-						name = null;
-						done = true;
-					}
-					// Its just text. However we want text in paragraph blocks. A paragraph is delimited by a "\n\n" or the start of a tag.
-					if(!done) {
-						// If the line read is "" it means we've encountered a "\n\n".
-						if(line.equals("")) {
-							// Add paragraph to open block.
-							if(block != null && paragraph != null) {
-								block.insert(paragraph.toString());
-								paragraph = null;
-							}
-						}
-						// Any other case just add to paragraph. Ensure that there is at least one space between the two fragments
-						else {
-							if(paragraph == null) {
-								paragraph = new StringBuffer(line);
-							}
-							else {
-								if(paragraph.charAt(paragraph.length() - 1) != ' ' && line.charAt(0) != ' ') {
-									paragraph.append(" ");
-								}
-								paragraph.append(line);
-							}
-						}
-					}
-				}
+				paragraph.append(line);
+			    }
+			}
+		    }
+		}
 				// Write out any outstanding blocks.
-				if(block != null) {
-					if(paragraph != null) {
-						block.insert(paragraph.toString());
-						paragraph = null;
-					}
-					// Check we have somewhere to write to.
-					if(output_file == null) {
-						output_file = new FileOutputStream(text_file.getName() + ".htm");
-					}
-					out("  write: " + block.getName());
-					output_file.write((block.toString()).getBytes());
-				}
+		if(block != null) {
+		    if(paragraph != null) {
+			block.insert(paragraph.toString());
+			paragraph = null;
+		    }
+		    // Check we have somewhere to write to.
+		    if(output_file == null) {
+			output_file = new FileOutputStream(text_file.getName() + ".htm");
+		    }
+		    out("  write: " + block.getName());
+		    output_file.write((block.toString()).getBytes());
+		}
+		block = null;
+		paragraph = null;
+		line = null;
+		output_file.close();
+		output_file = null;
+	    }
+	    catch (Exception error) {
+		error.printStackTrace();
+		System.err.println("An error has occured. View output.txt for more information.");
+		exit();
+	    }
+	}
+    }
+    /** Reads in a template file, extracting HTMLBlocks and storing them. It then allows the program to retrieve a block by its name and process it accordingly. */
+    private class TemplateManager
+	extends HashMap {
+	/** Constructor.
+	 * @param template_file The <strong>File</strong> template information will be extracted from.
+	 */
+	public TemplateManager(File template_file) {
+	    out("Building Template Manager:");
+	    try {
+		FileReader file_reader = new FileReader(template_file);
+		BufferedReader buffered_reader = new BufferedReader(file_reader);
+		HTMLBlock block = null;
+		String line = null;
+		while((line = buffered_reader.readLine()) != null) {
+		    boolean done = false;
+		    // Detect a template command.
+		    if(line.startsWith("<$")) {
+			// See if this is an open block tag, text insertion tag, or a block closing tag and react appropriately.
+			if(line.startsWith("<$TEXT")) {
+			    // Text tag. deal with it below.
+			}
+			else if(line.startsWith("<$/")) {
+			    // Block closing tag. If we have an open block close it.
+			    if(block != null) {
+				out("  parsed: " + block.getName());
+				// Add to our hashmapping of known blocks.
+				put(block.getName(), block);
 				block = null;
-				paragraph = null;
-				line = null;
-				output_file.close();
-				output_file = null;
-			}
-			catch (Exception error) {
-				error.printStackTrace();
-  			System.err.println("An error has occured. View output.txt for more information.");
-				exit();
-			}
-		}
-	}
-	/** Reads in a template file, extracting HTMLBlocks and storing them. It then allows the program to retrieve a block by its name and process it accordingly. */
-	private class TemplateManager
-		extends HashMap {
-		/** Constructor.
-		* @param template_file The <strong>File</strong> template information will be extracted from.
-		*/
-		public TemplateManager(File template_file) {
-			out("Building Template Manager:");
-			try {
-				FileReader file_reader = new FileReader(template_file);
-				BufferedReader buffered_reader = new BufferedReader(file_reader);
-				HTMLBlock block = null;
-				String line = null;
-				while((line = buffered_reader.readLine()) != null) {
-					boolean done = false;
-					// Detect a template command.
-					if(line.startsWith("<$")) {
-						// See if this is an open block tag, text insertion tag, or a block closing tag and react appropriately.
-						if(line.startsWith("<$TEXT")) {
-							// Text tag. deal with it below.
-						}
-						else if(line.startsWith("<$/")) {
-							// Block closing tag. If we have an open block close it.
-							if(block != null) {
-								out("  parsed: " + block.getName());
-								// Add to our hashmapping of known blocks.
-								put(block.getName(), block);
-								block = null;
-								done = true;
-							}
-							// Otherwise deal with it below.
-						}
-						// The last option is an open block tag.
-						else {
-							// If have a block open, close it.
-							if(block != null) {
-								out("  parsed: " + block.getName());
-								put(block.getName(), block);
-								block = null;
-							}
-							// Now create a new block with the appropriate name.
-							String name = line.substring(2, line.indexOf(">"));
-							block = new HTMLBlock(name);
-							done = true;
-						}
-					}
-					// Otherwise its just HTML.
-					if(!done) {
-						// If we are building a block append this line.
-						if(block != null) {
-							block.addLine(line);
-						}
-						// Otherwise we just ignore it.
-						else {
-							if(line.length() > 0) {
-								out("  can't parse: " + line);
-							}
-							// Otherwise we're just chewing up blank space.
-						}
-					}
-				}
+				done = true;
+			    }
+			    // Otherwise deal with it below.
+			}
+			// The last option is an open block tag.
+			else {
+			    // If have a block open, close it.
+			    if(block != null) {
+				out("  parsed: " + block.getName());
+				put(block.getName(), block);
+				block = null;
+			    }
+			    // Now create a new block with the appropriate name.
+			    String name = line.substring(2, line.indexOf(">"));
+			    block = new HTMLBlock(name);
+			    done = true;
+			}
+		    }
+		    // Otherwise its just HTML.
+		    if(!done) {
+			// If we are building a block append this line.
+			if(block != null) {
+			    block.addLine(line);
+			}
+			// Otherwise we just ignore it.
+			else {
+			    if(line.length() > 0) {
+				out("  can't parse: " + line);
+			    }
+			    // Otherwise we're just chewing up blank space.
+			}
+		    }
+		}
 				// If we have a block open close it.
-				if(block != null) {
-					out("  parsed: " + block.getName());
-					put(block.getName(), block);
-				}
-				line = null;
-				block = null;
-				buffered_reader.close();
-				buffered_reader = null;
-				file_reader.close();
-				file_reader = null;
-			}
-			catch (Exception error) {
-				error.printStackTrace();
-  			System.err.println("An error has occured. View output.txt for more information.");
-				exit();
-			}
-		}
-		/** Retrieve the block indicated by the given name.
-		* @param name The name of the block as a <strong>String</strong>.
-		* @return The <strong>HTMLBlock</strong> asked for or <i>null</i> if no such block.
-		*/
-		public HTMLBlock getHTMLBlock(String name) {
-			HTMLBlock block = (HTMLBlock) get(name);
-			// Don't return the original, but a copy.
-			if(block != null) {
-				block = block.spawn();
-			}
-			return block;
-		}
-	}
+		if(block != null) {
+		    out("  parsed: " + block.getName());
+		    put(block.getName(), block);
+		}
+		line = null;
+		block = null;
+		buffered_reader.close();
+		buffered_reader = null;
+		file_reader.close();
+		file_reader = null;
+	    }
+	    catch (Exception error) {
+		error.printStackTrace();
+		System.err.println("An error has occured. View output.txt for more information.");
+		exit();
+	    }
+	}
+	/** Retrieve the block indicated by the given name.
+	 * @param name The name of the block as a <strong>String</strong>.
+	 * @return The <strong>HTMLBlock</strong> asked for or <i>null</i> if no such block.
+	 */
+	public HTMLBlock getHTMLBlock(String name) {
+	    HTMLBlock block = (HTMLBlock) get(name);
+	    // Don't return the original, but a copy.
+	    if(block != null) {
+		block = block.spawn();
+	    }
+	    return block;
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/HTMLStringTokenizer.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/HTMLStringTokenizer.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/HTMLStringTokenizer.java	(revision 4364)
@@ -54,111 +54,111 @@
 /** This class functions much like a <strong>StringTokenizer</strong> in that it tokenizes a long string into tokens, however this tokenizer cleverly notices HTML formatting tags. */
 public class HTMLStringTokenizer {
-	 /** The current position in the source string. */
-	 private int pos = 0;
-	 /** The current token, usually created by the last nextToken call. */
-	 private String current = null;
-	 /** The previous token. */
-	 private String previous = null;
-	 /** The string to be tokenized, including any HTML markup. */
-	 private String source = null;
-	 /** Constructor.
-	  * @param source The source <strong>String</strong> to be tokenized.
+    /** The current position in the source string. */
+    private int pos = 0;
+    /** The current token, usually created by the last nextToken call. */
+    private String current = null;
+    /** The previous token. */
+    private String previous = null;
+    /** The string to be tokenized, including any HTML markup. */
+    private String source = null;
+    /** Constructor.
+     * @param source The source <strong>String</strong> to be tokenized.
 	  */
-	 public HTMLStringTokenizer(String source) {
-		  this.source = source;
-		  // Parse the first token.
-		  parseToken();
-	 }
-	 /** Determines if there are still tokens remaining unparsed in the source.
+    public HTMLStringTokenizer(String source) {
+	this.source = source;
+	// Parse the first token.
+	parseToken();
+    }
+    /** Determines if there are still tokens remaining unparsed in the source.
 	  * @return A <strong>boolean</strong> which is <i>true</i> if there are more tokens.
 	  */ 
-	 public boolean hasMoreTokens() {
-		  if(current != null && current.length() > 0) {
-				return true;
-		  }
-		  return false;
-	 } 
-	 /** Determines if the tag currently being returned by sameToken is a tag.
+    public boolean hasMoreTokens() {
+	if(current != null && current.length() > 0) {
+	    return true;
+	}
+	return false;
+    } 
+    /** Determines if the tag currently being returned by sameToken is a tag.
 	  * @return A <strong>boolean</strong> indicating if the token is a tag.
 	  */
-	 public boolean isTag() {
-		  if(previous.startsWith("<") && previous.endsWith(">")) {
-				return true;
-		  }
-		  return false;
-	 }
-	 /** Retrieves the next token. 
+    public boolean isTag() {
+	if(previous.startsWith("<") && previous.endsWith(">")) {
+	    return true;
+	}
+	return false;
+    }
+    /** Retrieves the next token. 
 	  * @return A <strong>String</strong> representing the token.
 	  */
-	 public String nextToken() {
-		  previous = current;
-		  // Get the next token.
-		  parseToken();
-		  // Return previous.
-		  return previous;
-	 }
-	 /** Repeats the result of the last <i>nextToken()</i>.
+    public String nextToken() {
+	previous = current;
+	// Get the next token.
+	parseToken();
+	// Return previous.
+	return previous;
+    }
+    /** Repeats the result of the last <i>nextToken()</i>.
 	  * @return A <strong>String</strong> representing the token.
 	  */
-	 public String sameToken() {
-		  return previous;
-	 }
-	 /** Parses the next token and stores it in current.
+    public String sameToken() {
+	return previous;
+    }
+    /** Parses the next token and stores it in current.
 	  */
-	 private void parseToken() {
-		  boolean found = false;
-		  boolean tag = false;
-		  boolean text = false;
-		  // Reset current
-		  current = "";
-		  // Parse away
-		  dumpWhiteSpace();
-		  while(pos < source.length() && !found) {
-				char c = (char)source.charAt(pos);
-				if(!tag && !text) {
-					 if(c == '<') {
-						  tag = true;
-					 }
-					 else {
-						  text = true;
-					 }
-					 current = current + c;
-				}
+    private void parseToken() {
+	boolean found = false;
+	boolean tag = false;
+	boolean text = false;
+	// Reset current
+	current = "";
+	// Parse away
+	dumpWhiteSpace();
+	while(pos < source.length() && !found) {
+	    char c = (char)source.charAt(pos);
+	    if(!tag && !text) {
+		if(c == '<') {
+		    tag = true;
+		}
+		else {
+		    text = true;
+		}
+		current = current + c;
+	    }
 				// Reading a tag. Watch only for '>'.
-				else if(tag) {
-					 if(c == '>') {
-						  found = true;
-					 }
-					 current = current + c;
-				}
+	    else if(tag) {
+		if(c == '>') {
+		    found = true;
+		}
+		current = current + c;
+	    }
 				// Reading text. Watch for ' ' and '<'. Rollback '<'.
-				else if(text) {
-					 if(c == ' ') {
-						  found = true;
-					 }
-					 else if(c == '<') {
-						  found = true;
-						  pos--;
-					 }
-					 else {
-						  current = current + c;
-					 }
-				}
-				pos++;
-		  }
-	 }
-	 /** Method to ignore whitespace in the source.
+	    else if(text) {
+		if(c == ' ') {
+		    found = true;
+		}
+		else if(c == '<') {
+		    found = true;
+		    pos--;
+		}
+		else {
+		    current = current + c;
+		}
+	    }
+	    pos++;
+	}
+    }
+    /** Method to ignore whitespace in the source.
 	  */
-	 private void dumpWhiteSpace() {
-		  while(pos < source.length() && source.charAt(pos) == ' ') {
-				pos++;
-		  }
-	 }
+    private void dumpWhiteSpace() {
+	while(pos < source.length() && source.charAt(pos) == ' ') {
+	    pos++;
+	}
+    }
 
-	 static public void main(String args[]) {
-		  String init = "<HTML>Where material to be imported is found. Defaults to <i>GSDLHOME/collection/col_name/gimport</i></HTML>";
-		  ///ystem.err.println("Before: " + init);
-		  String result = Utility.formatHTMLWidth(init, 40);
-		  ///ystem.err.println("After: " + result);
-	 }
+    static public void main(String args[]) {
+	String init = "<HTML>Where material to be imported is found. Defaults to <i>GSDLHOME/collection/col_name/gimport</i></HTML>";
+	///ystem.err.println("Before: " + init);
+	String result = Utility.formatHTMLWidth(init, 40);
+	///ystem.err.println("After: " + result);
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/HashMap3D.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/HashMap3D.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/HashMap3D.java	(revision 4364)
@@ -57,122 +57,122 @@
 /** Provides a HashMap implementation that indexes by two keys. Perfect for the storage of metadata references based on their metadata element and assigned value. */
 public class HashMap3D
-	 extends HashMap {
+    extends HashMap {
 
-	 private int capacity = 4;
+    private int capacity = 4;
 
-	 private Object last_key_one = null;
-	 private Object last_key_two = null;
-	 private Object last_value = null;
+    private Object last_key_one = null;
+    private Object last_key_two = null;
+    private Object last_value = null;
 
-	 /** Default constructor.
-	  */
-	 public HashMap3D() {
-		  super();
-	 }
-	 /** Constructor with a specific initial capacity, as we should already have a good idea based on the number of Metadata Elements.
-	  * @param capacity The initial capacity as an <i>int</i>.
-	  */
-	 public HashMap3D(int capacity) {
-		  super(capacity);
-		  this.capacity = capacity;
-	 }
-	 /** Completely remove the contents of this HashMap and its child HashMaps. */
-	 public void clear() {
-		  Iterator iterator = values().iterator();
-		  while(iterator.hasNext()) {
-				HashMap inner_mapping = (HashMap) iterator.next();
-				inner_mapping.clear();
-				inner_mapping = null;
-		  }
-		  iterator = null;
-		  super.clear();
-	 }
-	 /** Determine if this hash map contains an entry for the given keys. Also cache this entry because theres a good chance the next get call will ask for this entry.
-	  * @param key_one The first key as an <strong>Object</strong>.
-	  * @param key_two The second key as an <strong>Object</strong>.
-	  * @return <i>true</i> if such an entry exists, <i>false</i> otherwise.
-	  */
-	 public boolean contains(Object key_one, Object key_two) {
-		  boolean result = false;
-		  // Retrieve the hash mapping at key_one.
-		  HashMap map = (HashMap) get(key_one);
-		  // If there is such a map then retrieve the value at key_two.
-		  if(map != null) {
-				last_value = map.get(key_two);
-				if(last_value != null) {
-					 last_key_one = key_one;
-					 last_key_two = key_two;
-					 result = true;
-				}
-		  }
-		  return result;
-	 }
+    /** Default constructor.
+     */
+    public HashMap3D() {
+	super();
+    }
+    /** Constructor with a specific initial capacity, as we should already have a good idea based on the number of Metadata Elements.
+     * @param capacity The initial capacity as an <i>int</i>.
+     */
+    public HashMap3D(int capacity) {
+	super(capacity);
+	this.capacity = capacity;
+    }
+    /** Completely remove the contents of this HashMap and its child HashMaps. */
+    public void clear() {
+	Iterator iterator = values().iterator();
+	while(iterator.hasNext()) {
+	    HashMap inner_mapping = (HashMap) iterator.next();
+	    inner_mapping.clear();
+	    inner_mapping = null;
+	}
+	iterator = null;
+	super.clear();
+    }
+    /** Determine if this hash map contains an entry for the given keys. Also cache this entry because theres a good chance the next get call will ask for this entry.
+     * @param key_one The first key as an <strong>Object</strong>.
+     * @param key_two The second key as an <strong>Object</strong>.
+     * @return <i>true</i> if such an entry exists, <i>false</i> otherwise.
+     */
+    public boolean contains(Object key_one, Object key_two) {
+	boolean result = false;
+	// Retrieve the hash mapping at key_one.
+	HashMap map = (HashMap) get(key_one);
+	// If there is such a map then retrieve the value at key_two.
+	if(map != null) {
+	    last_value = map.get(key_two);
+	    if(last_value != null) {
+		last_key_one = key_one;
+		last_key_two = key_two;
+		result = true;
+	    }
+	}
+	return result;
+    }
 
-	 /** Retrieve an entry from this three dimensional hash mapping.
-	  * @param key_one The first key as an <strong>Object</strong>.
-	  * @param key_two The second key as an <strong>Object</strong>.
-	  * @return The value <strong>Object</strong> located at (key_one, key_two), or <i>null</i> if no such value.
-	  */
-	 public Object get(Object key_one, Object key_two) {
-		  Object result = null;
-		  if(key_one.equals(last_key_one) && key_two.equals(last_key_two)) {
-				result = last_value;
-		  }
-		  else {
+    /** Retrieve an entry from this three dimensional hash mapping.
+     * @param key_one The first key as an <strong>Object</strong>.
+     * @param key_two The second key as an <strong>Object</strong>.
+     * @return The value <strong>Object</strong> located at (key_one, key_two), or <i>null</i> if no such value.
+     */
+    public Object get(Object key_one, Object key_two) {
+	Object result = null;
+	if(key_one.equals(last_key_one) && key_two.equals(last_key_two)) {
+	    result = last_value;
+	}
+	else {
 				// Retrieve the hash mapping at key_one.
-				HashMap map = (HashMap) get(key_one);
+	    HashMap map = (HashMap) get(key_one);
 				// If there is such a map then retrieve the value at key_two.
-				if(map != null) {
-					 result = map.get(key_two);
-					 if(result != null) {
-						  last_key_one = key_one;
-						  last_key_two = key_two;
-						  last_value = result;
-					 }
-				}
-		  }
-		  return result;
-	 }
-	 /** -SPECIAL - Attempts to retrieve a previous instance of metadata with the same parameters as the given one. If such a metadata is found, it is returned, otherwise the given metadata is added then returned as being the first unique instance.
-	  * @param metadata The <strong>Metadata</strong> for whom we are trying to find the first unique instance.
-	  * @return The first unique instance of the target <strong>Metadata</strong> which may in fact be the same metadata given as a paramater.
-	  */
-	 public Metadata locate(Metadata metadata) {
-		  Metadata result = null;
-		  if(metadata != null) {
+	    if(map != null) {
+		result = map.get(key_two);
+		if(result != null) {
+		    last_key_one = key_one;
+		    last_key_two = key_two;
+		    last_value = result;
+		}
+	    }
+	}
+	return result;
+    }
+    /** -SPECIAL - Attempts to retrieve a previous instance of metadata with the same parameters as the given one. If such a metadata is found, it is returned, otherwise the given metadata is added then returned as being the first unique instance.
+     * @param metadata The <strong>Metadata</strong> for whom we are trying to find the first unique instance.
+     * @return The first unique instance of the target <strong>Metadata</strong> which may in fact be the same metadata given as a paramater.
+     */
+    public Metadata locate(Metadata metadata) {
+	Metadata result = null;
+	if(metadata != null) {
 				// Locate the appropriate value->metadata hashmap.
-				String element_name = metadata.getElement().toString();
-				HashMap inner_mapping = (HashMap) get(element_name);
-				if(inner_mapping == null) {
-					 inner_mapping = new HashMap(4); // Small initial capacity.
-					 put(element_name, inner_mapping);
-				}
-				element_name = null;
+	    String element_name = metadata.getElement().toString();
+	    HashMap inner_mapping = (HashMap) get(element_name);
+	    if(inner_mapping == null) {
+		inner_mapping = new HashMap(4); // Small initial capacity.
+		put(element_name, inner_mapping);
+	    }
+	    element_name = null;
 				// Locate the appropriate metadata
-				String value_name = metadata.getValueNode().getFullPath();
-				result = (Metadata) inner_mapping.get(value_name);
-				if(result == null) {
-					 result = metadata;
-					 inner_mapping.put(value_name, metadata);
-				}
-				value_name = null;
-				inner_mapping = null;
-		  }
-		  return result;
-	 }
-	 /** Put an entry into this three dimensional hash mapping.
-	  * @param key_one The first key, to store this value under, as an <strong>Object</strong>.
-	  * @param key_two The second key, to store this value under, as an <strong>Object</strong>.
-	  * @param value The value <strong>Object</strong> itself.
-	  */
-	 public void put(Object key_one, Object key_two, Object value) {
-		  // Retrieve the hash mapping at key_one, or if none exists create one.
-		  HashMap map = (HashMap) get(key_one);
-		  if(map == null) {
-				map = new HashMap(capacity);
-				put(key_one, map);
-		  }
-		  // Now add the value to this mapping.
-		  map.put(key_two, value);
-	 }
+	    String value_name = metadata.getValueNode().getFullPath();
+	    result = (Metadata) inner_mapping.get(value_name);
+	    if(result == null) {
+		result = metadata;
+		inner_mapping.put(value_name, metadata);
+	    }
+	    value_name = null;
+	    inner_mapping = null;
+	}
+	return result;
+    }
+    /** Put an entry into this three dimensional hash mapping.
+     * @param key_one The first key, to store this value under, as an <strong>Object</strong>.
+     * @param key_two The second key, to store this value under, as an <strong>Object</strong>.
+     * @param value The value <strong>Object</strong> itself.
+     */
+    public void put(Object key_one, Object key_two, Object value) {
+	// Retrieve the hash mapping at key_one, or if none exists create one.
+	HashMap map = (HashMap) get(key_one);
+	if(map == null) {
+	    map = new HashMap(capacity);
+	    put(key_one, map);
+	}
+	// Now add the value to this mapping.
+	map.put(key_two, value);
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/MED.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/MED.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/MED.java	(revision 4364)
@@ -57,83 +57,83 @@
  */
 public class MED {
-	 //****************************
-	 // Get minimum of three values
-	 //****************************
-	 static private int Minimum (int a, int b, int c) {
-		  int mi = a;
-		  if (b < mi) {
-				mi = b;
-		  }
-		  if (c < mi) {
-				mi = c;
-		  }
-		  return mi;
-	 }
-	 //*****************************
-	 // Compute Levenshtein distance
-	 //*****************************
-	 static public int LD (String s, String t) {
-		  int d[][]; // matrix
-		  int n; // length of s
-		  int m; // length of t
-		  int i; // iterates through s
-		  int j; // iterates through t
-		  char s_i; // ith character of s
-		  char t_j; // jth character of t
-		  int cost; // cost
-		  // Step 1
-		  // Set n to be the length of s.
-		  // Set m to be the length of t.
-		  // If n = 0, return m and exit.
-		  // If m = 0, return n and exit.
-		  // Construct a matrix containing 0..m rows and 0..n columns.
-		  n = s.length ();
-		  m = t.length ();
-		  if (n == 0) {
-				return m;
-		  }
-		  if (m == 0) {
-				return n;
-		  }
-		  d = new int[n+1][m+1];
-		  // Step 2
-		  // Initialize the first row to 0..n.
-		  // Initialize the first column to 0..m.
-		  for (i = 0; i <= n; i++) {
-				d[i][0] = i;
-		  }
-		  for (j = 0; j <= m; j++) {
-				d[0][j] = j;
-		  }
-		  // Step 3
-		  // Examine each character of s (i from 1 to n).
-		  for (i = 1; i <= n; i++) {
-				s_i = s.charAt (i - 1);
+    //****************************
+    // Get minimum of three values
+    //****************************
+    static private int Minimum (int a, int b, int c) {
+	int mi = a;
+	if (b < mi) {
+	    mi = b;
+	}
+	if (c < mi) {
+	    mi = c;
+	}
+	return mi;
+    }
+    //*****************************
+    // Compute Levenshtein distance
+    //*****************************
+    static public int LD (String s, String t) {
+	int d[][]; // matrix
+	int n; // length of s
+	int m; // length of t
+	int i; // iterates through s
+	int j; // iterates through t
+	char s_i; // ith character of s
+	char t_j; // jth character of t
+	int cost; // cost
+	// Step 1
+	// Set n to be the length of s.
+	// Set m to be the length of t.
+	// If n = 0, return m and exit.
+	// If m = 0, return n and exit.
+	// Construct a matrix containing 0..m rows and 0..n columns.
+	n = s.length ();
+	m = t.length ();
+	if (n == 0) {
+	    return m;
+	}
+	if (m == 0) {
+	    return n;
+	}
+	d = new int[n+1][m+1];
+	// Step 2
+	// Initialize the first row to 0..n.
+	// Initialize the first column to 0..m.
+	for (i = 0; i <= n; i++) {
+	    d[i][0] = i;
+	}
+	for (j = 0; j <= m; j++) {
+	    d[0][j] = j;
+	}
+	// Step 3
+	// Examine each character of s (i from 1 to n).
+	for (i = 1; i <= n; i++) {
+	    s_i = s.charAt (i - 1);
 				// Step 4
 				// Examine each character of t (j from 1 to m).
-				for (j = 1; j <= m; j++) {
-					 t_j = t.charAt (j - 1);
-					 // Step 5
-					 // If s[i] equals t[j], the cost is 0.
-					 // If s[i] doesn't equal t[j], the cost is 1.
-					 if (s_i == t_j) {
-						  cost = 0;
-					 }
-					 else {
-						  cost = 1;
-					 }
-					 // Step 6
-					 // Set cell d[i,j] of the matrix equal to the minimum of:
-					 // a. The cell immediately above plus 1: d[i-1,j] + 1.
-					 // b. The cell immediately to the left plus 1: d[i,j-1] + 1.
-					 // c. The cell diagonally above and to the left plus the cost: d[i-1,j-1] + cost.
-					 d[i][j] = Minimum (d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost);
-				}
-		  }
-		  // Step 7
-		  // After the iteration steps (3, 4, 5, 6) are complete, the distance is found in cell d[n,m].
-		  int result = d[n][m];
-		  d = null;
-		  return result;
-	 }
+	    for (j = 1; j <= m; j++) {
+		t_j = t.charAt (j - 1);
+		// Step 5
+		// If s[i] equals t[j], the cost is 0.
+		// If s[i] doesn't equal t[j], the cost is 1.
+		if (s_i == t_j) {
+		    cost = 0;
+		}
+		else {
+		    cost = 1;
+		}
+		// Step 6
+		// Set cell d[i,j] of the matrix equal to the minimum of:
+		// a. The cell immediately above plus 1: d[i-1,j] + 1.
+		// b. The cell immediately to the left plus 1: d[i,j-1] + 1.
+		// c. The cell diagonally above and to the left plus the cost: d[i-1,j-1] + cost.
+		d[i][j] = Minimum (d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost);
+	    }
+	}
+	// Step 7
+	// After the iteration steps (3, 4, 5, 6) are complete, the distance is found in cell d[n,m].
+	int result = d[n][m];
+	d = null;
+	return result;
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/MetadataXML.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/MetadataXML.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/MetadataXML.java	(revision 4364)
@@ -57,130 +57,128 @@
 
 public class MetadataXML {
-	 //private boolean can_wait = true;
-	 private Gatherer gatherer = null;
-	 private Hashtable known_indexes = null;
-	 //private int spare_processes = 25;
-	 //private MetadataXML mummy = null;
-	 private SaveProgressDialog spd = null;
-	 //private Vector complete = null;
-	 //private Vector processes = null;
+    //private boolean can_wait = true;
+    private Gatherer gatherer = null;
+    private Hashtable known_indexes = null;
+    //private int spare_processes = 25;
+    //private MetadataXML mummy = null;
+    private SaveProgressDialog spd = null;
+    //private Vector complete = null;
+    //private Vector processes = null;
 
-	 static public String METADATA_FILE = "metadata.xml";
+    static public String METADATA_FILE = "metadata.xml";
 
     // not actually used anywhere
     /*	 static public IXMLElement read(String filename) {
-		  IXMLElement xml = null;
-		  IXMLParser parser = null;
-		  IXMLReader reader = null;
-		  try {
-				parser = XMLParserFactory.createDefaultXMLParser();
-				File in_file = new File(filename);
-				FileReader in_reader = new FileReader(in_file);
-				BufferedReader in = new BufferedReader(in_reader, Utility.BUFFER_SIZE);
-				String content = "";
-				String next_line = null;
-				while((next_line = in.readLine()) != null) {
-					 // Throw away the document type, as I don't want to deal
-					 // with dtd's. If someone has tampered with these files to
-					 // the point that this parser can't read them then too bad.
-					 if(!next_line.startsWith("<!DOCTYPE")) {
-						  content = content + next_line;
-					 }
-				}
-				in.close();
-				reader = StdXMLReader.stringReader(content);
-				parser.setReader(reader);
-			   xml = (IXMLElement) parser.parse();
-		  }
-		  catch (Exception e) {
-		  }
-		  return xml;
+	 IXMLElement xml = null;
+	 IXMLParser parser = null;
+	 IXMLReader reader = null;
+	 try {
+	 parser = XMLParserFactory.createDefaultXMLParser();
+	 File in_file = new File(filename);
+	 FileReader in_reader = new FileReader(in_file);
+	 BufferedReader in = new BufferedReader(in_reader, Utility.BUFFER_SIZE);
+	 String content = "";
+	 String next_line = null;
+	 while((next_line = in.readLine()) != null) {
+	 // Throw away the document type, as I don't want to deal
+	 // with dtd's. If someone has tampered with these files to
+	 // the point that this parser can't read them then too bad.
+	 if(!next_line.startsWith("<!DOCTYPE")) {
+	 content = content + next_line;
+	 }
+	 }
+	 in.close();
+	 reader = StdXMLReader.stringReader(content);
+	 parser.setReader(reader);
+	 xml = (IXMLElement) parser.parse();
+	 }
+	 catch (Exception e) {
+	 }
+	 return xml;
 	 }
     */
-	 public static void write(Gatherer gatherer, String etc_dir) {
-		  if(gatherer != null && gatherer.c_man != null && gatherer.c_man.getCollection() != null && gatherer.c_man.getCollection().msm != null) {
-				Vector elements = gatherer.c_man.getCollection().msm.getElements(true);
-				for(int i = 0; i < elements.size(); i++) {
-					 ElementWrapper element = (ElementWrapper)elements.get(i);
-					 ///ystem.err.print("Checking " + element + " for HFile: ");
-					 GValueModel model = gatherer.c_man.getCollection().msm.getValueTree(element);
-					 if(model != null && (element.getNamespace().equals(MetadataSetManager.HIDDEN) || model.isHierarchy())) {
-						  ///ystem.err.println("Found. Writing file.");
-						  write(model, gatherer.c_man.getCollection().msm, etc_dir);
-					 }
-					 else {
-						  ///ystem.err.println("No file found.");
-					 }
-				}
-		  }
-	 }
+    public static void write(Gatherer gatherer, String etc_dir) {
+	if(gatherer != null && gatherer.c_man != null && gatherer.c_man.getCollection() != null && gatherer.c_man.getCollection().msm != null) {
+	    Vector elements = gatherer.c_man.getCollection().msm.getElements(true);
+	    for(int i = 0; i < elements.size(); i++) {
+		ElementWrapper element = (ElementWrapper)elements.get(i);
+		///ystem.err.print("Checking " + element + " for HFile: ");
+		GValueModel model = gatherer.c_man.getCollection().msm.getValueTree(element);
+		if(model != null && (element.getNamespace().equals(MetadataSetManager.HIDDEN) || model.isHierarchy())) {
+		    ///ystem.err.println("Found. Writing file.");
+		    write(model, gatherer.c_man.getCollection().msm, etc_dir);
+		}
+		else {
+		    ///ystem.err.println("No file found.");
+		}
+	    }
+	}
+    }
 
-	 private String getHIndex(Gatherer gatherer, GValueModel model, String value) {
-		  String index = null;
-		  index = (String) known_indexes.get(value);
-		  if(index == null) {
-				index = model.getHIndex(value);
+    private String getHIndex(Gatherer gatherer, GValueModel model, String value) {
+	String index = null;
+	index = (String) known_indexes.get(value);
+	if(index == null) {
+	    index = model.getHIndex(value);
 				///ystem.err.println("Adding to known indexes: " + value + " -> " + index);
-				known_indexes.put(value, index);
-		  }
-		  return index;
-	 }
+	    known_indexes.put(value, index);
+	}
+	return index;
+    }
 
-	 static private String safe(String unsafe) {
-		  String safe_str = "";
-		  for(int i = 0; i < unsafe.length(); i++) {
-				char c = unsafe.charAt(i);
-				if(c != ' ') {
-					 safe_str = safe_str + c;
-				}
-		  }
-		  return safe_str;
-	 }
+    static private String safe(String unsafe) {
+	String safe_str = "";
+	for(int i = 0; i < unsafe.length(); i++) {
+	    char c = unsafe.charAt(i);
+	    if(c != ' ') {
+		safe_str = safe_str + c;
+	    }
+	}
+	return safe_str;
+    }
 
-	 static private void write(Writer w, String text)
-		  throws Exception {
-		  text = text + "\r\n";
-		  char buffer[] = text.toCharArray();
-		  w.write(buffer, 0, buffer.length);
-	 }
+    static private void write(Writer w, String text)
+	throws Exception {
+	text = text + "\r\n";
+	char buffer[] = text.toCharArray();
+	w.write(buffer, 0, buffer.length);
+    }
 
-	 static public void write(GValueModel model, MetadataSetManager msm, String etc_dir) {
-		  try {
-				File out_file = new File(etc_dir + model.toString() + ".txt");
-				FileOutputStream fos = new FileOutputStream(out_file);
-				OutputStreamWriter osw = new OutputStreamWriter(fos);
-				BufferedWriter bw = new BufferedWriter(osw, Utility.BUFFER_SIZE);
-				Vector all_values = model.traverseTree();
-				for(int i = 0; i < all_values.size(); i++) {
-					 GValueNode node = (GValueNode)all_values.get(i);
-					 TreePath path = new TreePath(node.getPath());
-					 String value = GValueTree.formatPath(null, path, true);
-					 String index = model.getHIndex(value);
-					 String alias = node.getAlias(index);
-					 if(value.indexOf("\\") != -1) {
-						  value = value.substring(value.lastIndexOf("\\") + 1);
-					 }
-					 write(bw, index + "\t\"" + alias + "\"\t\"" + Utility.stripNL(value) + "\"");
-				}
+    static public void write(GValueModel model, MetadataSetManager msm, String etc_dir) {
+	try {
+	    File out_file = new File(etc_dir + model.toString() + ".txt");
+	    FileOutputStream fos = new FileOutputStream(out_file);
+	    OutputStreamWriter osw = new OutputStreamWriter(fos);
+	    BufferedWriter bw = new BufferedWriter(osw, Utility.BUFFER_SIZE);
+	    Vector all_values = model.traverseTree();
+	    for(int i = 0; i < all_values.size(); i++) {
+		GValueNode node = (GValueNode)all_values.get(i);
+		TreePath path = new TreePath(node.getPath());
+		String value = GValueTree.formatPath(null, path, true);
+		String index = model.getHIndex(value);
+		String alias = node.getAlias(index);
+		if(value.indexOf("\\") != -1) {
+		    value = value.substring(value.lastIndexOf("\\") + 1);
+		}
+		write(bw, index + "\t\"" + alias + "\"\t\"" + Utility.stripNL(value) + "\"");
+	    }
 				// Very important we do this, or else buffer may not have 
 				// flushed.
-				bw.flush();
-				bw.close();
-		  }
-		  catch(Exception error) {
-				error.printStackTrace();
-		  }
-	 }
+	    bw.flush();
+	    bw.close();
+	}
+	catch(Exception error) {
+	    error.printStackTrace();
+	}
+    }
 
-	 static final private long showTime(String message, long time) {
-		  if(time == -1) {
+    static final private long showTime(String message, long time) {
+	if(time == -1) {
 				///ystem.err.println(message + System.currentTimeMillis());
-		  }
-		  else {
+	}
+	else {
 				///ystem.err.println(message + (System.currentTimeMillis() - time));
-		  }
-		  return System.currentTimeMillis();
-	 }
+	}
+	return System.currentTimeMillis();
+    }
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/MutableComboBoxEntry.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/MutableComboBoxEntry.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/MutableComboBoxEntry.java	(revision 4364)
@@ -2,4 +2,4 @@
 
 public interface MutableComboBoxEntry {
-	 public void setText(String text);
+    public void setText(String text);
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/SwingWorker.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/SwingWorker.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/SwingWorker.java	(revision 4364)
@@ -101,19 +101,19 @@
     public SwingWorker() {
         final Runnable doFinished = new Runnable() {
-           public void run() { finished(); }
-        };
+		public void run() { finished(); }
+	    };
 
         Runnable doConstruct = new Runnable() { 
-            public void run() {
-                try {
-                    setValue(construct());
-                }
-                finally {
-                    threadVar.clear();
-                }
+		public void run() {
+		    try {
+			setValue(construct());
+		    }
+		    finally {
+			threadVar.clear();
+		    }
 
-                SwingUtilities.invokeLater(doFinished);
-            }
-        };
+		    SwingUtilities.invokeLater(doFinished);
+		}
+	    };
 
         Thread t = new Thread(doConstruct);
Index: /trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java	(revision 4364)
@@ -35,103 +35,103 @@
  */
 public class SynchronizedTreeModelTools {
-	 /** Adds an insertNodeInto model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above. */
-	 static final public Runnable insertNodeInto(final DefaultTreeModel model, final MutableTreeNode parent, final MutableTreeNode target_node) {
-		  final Runnable doInsertNodeInto = new Runnable() {
-					 public void run() {
-						  int index = -1;
-						  int pos = 0;
-						  while(index == -1 && pos < parent.getChildCount()) {
-								TreeNode node = parent.getChildAt(pos);
-								int result = 0;
-								///ystem.err.println("Compare " + target_node + " to " + node);
-								if((target_node.isLeaf() && node.isLeaf()) || (!target_node.isLeaf() && !node.isLeaf())) {
-									 result = target_node.toString().toLowerCase().compareTo(node.toString().toLowerCase());
-								}
-								else if(target_node.isLeaf()) {
-									 result = -1;
-								}
-								else {
-									 result = 1;
-								}
-								if(result > 0) {
-									 ///ystem.err.println("Keep searching...");
-									 pos++;
-								}
-								else {
-									 ///ystem.err.println("Found!");
-									 index = pos;
-								}
-						  }
-						  if(index == -1) {
-								index = parent.getChildCount();
-						  }
-						  model.insertNodeInto(target_node, parent, index);
-					 }
-				};
-		  try {
+    /** Adds an insertNodeInto model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above. */
+    static final public Runnable insertNodeInto(final DefaultTreeModel model, final MutableTreeNode parent, final MutableTreeNode target_node) {
+	final Runnable doInsertNodeInto = new Runnable() {
+		public void run() {
+		    int index = -1;
+		    int pos = 0;
+		    while(index == -1 && pos < parent.getChildCount()) {
+			TreeNode node = parent.getChildAt(pos);
+			int result = 0;
+			///ystem.err.println("Compare " + target_node + " to " + node);
+			if((target_node.isLeaf() && node.isLeaf()) || (!target_node.isLeaf() && !node.isLeaf())) {
+			    result = target_node.toString().toLowerCase().compareTo(node.toString().toLowerCase());
+			}
+			else if(target_node.isLeaf()) {
+			    result = -1;
+			}
+			else {
+			    result = 1;
+			}
+			if(result > 0) {
+			    ///ystem.err.println("Keep searching...");
+			    pos++;
+			}
+			else {
+			    ///ystem.err.println("Found!");
+			    index = pos;
+			}
+		    }
+		    if(index == -1) {
+			index = parent.getChildCount();
+		    }
+		    model.insertNodeInto(target_node, parent, index);
+		}
+	    };
+	try {
 				//SwingUtilities.invokeLater(doInsertNodeInto);
-				SwingUtilities.invokeAndWait(doInsertNodeInto);
-		  }
-		  catch (Exception e) {
-				e.printStackTrace();
-		  }
-		  // If (and only if) we've thrown an error because we tried to invoke the runnable task and wait, when we are in the AWTEvent thread already, then try agin but with an invoke later.
-		  catch (java.lang.Error error) {
-				if(error.toString().equals("java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread")) {
-					 SwingUtilities.invokeLater(doInsertNodeInto);
-				}
-		  }
-		  return doInsertNodeInto;
-	 }
-	 /** Adds a removeNodeFromParent model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above.
-	  * @param model The <strong>GTreeModel</strong> we want to remove the node from.
-	  * @param target_node The <strong>GTreeNode</strong> to remove.
-	  */
-	 static final public void removeNodeFromParent(final DefaultTreeModel model, final MutableTreeNode target_node) {
-		  ///ystem.err.println("Remove " + target_node + " from parent in model " + model);
-		  final Runnable doRemoveNodeFromParent = new Runnable() {
-					 public void run() {
-						  model.removeNodeFromParent(target_node);
-					 }
-				};
-		  try {
+	    SwingUtilities.invokeAndWait(doInsertNodeInto);
+	}
+	catch (Exception e) {
+	    e.printStackTrace();
+	}
+	// If (and only if) we've thrown an error because we tried to invoke the runnable task and wait, when we are in the AWTEvent thread already, then try agin but with an invoke later.
+	catch (java.lang.Error error) {
+	    if(error.toString().equals("java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread")) {
+		SwingUtilities.invokeLater(doInsertNodeInto);
+	    }
+	}
+	return doInsertNodeInto;
+    }
+    /** Adds a removeNodeFromParent model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above.
+     * @param model The <strong>GTreeModel</strong> we want to remove the node from.
+     * @param target_node The <strong>GTreeNode</strong> to remove.
+     */
+    static final public void removeNodeFromParent(final DefaultTreeModel model, final MutableTreeNode target_node) {
+	///ystem.err.println("Remove " + target_node + " from parent in model " + model);
+	final Runnable doRemoveNodeFromParent = new Runnable() {
+		public void run() {
+		    model.removeNodeFromParent(target_node);
+		}
+	    };
+	try {
 				//SwingUtilities.invokeLater(doRemoveNodeFromParent);
-				SwingUtilities.invokeAndWait(doRemoveNodeFromParent);
-		  }
-		  catch (Exception e) {
-				e.printStackTrace();
+	    SwingUtilities.invokeAndWait(doRemoveNodeFromParent);
+	}
+	catch (Exception e) {
+	    e.printStackTrace();
 				///ystem.err.println(e);
-		  }
-		  // If we've thrown an error because we tried to invoke the runnable task and wait, when we are in the AWTEvent thread already, then try agin but with an invoke later.
-		  catch (java.lang.Error error) {
-				if(error.toString().equals("java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread")) {
-					 SwingUtilities.invokeLater(doRemoveNodeFromParent);
-				}
-		  }
-	 }
-	 /** Adds a replaceNode model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above.
-	  * @param model The <strong>GTreeModel</strong> we want to change.
-	  * @param old_node The <strong>GTreeNode</strong> to replace.
-	  * @param new_node The <strong>GTreeNode</strong> to replace it with.
-	  */
-	 static final public void replaceNode(final DefaultTreeModel model, final MutableTreeNode old_node, final MutableTreeNode new_node) {
-		  final Runnable doReplaceNode = new Runnable() {
-					 public void run() {
-						  MutableTreeNode parent = (MutableTreeNode) old_node.getParent();
-						  if(parent != null) {
-								int index = parent.getIndex(old_node);
-								model.removeNodeFromParent(old_node);
-								model.insertNodeInto(new_node, parent, index);
-						  }
-						  parent = null;
-					 }
-				};
-		  try {
+	}
+	// If we've thrown an error because we tried to invoke the runnable task and wait, when we are in the AWTEvent thread already, then try agin but with an invoke later.
+	catch (java.lang.Error error) {
+	    if(error.toString().equals("java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread")) {
+		SwingUtilities.invokeLater(doRemoveNodeFromParent);
+	    }
+	}
+    }
+    /** Adds a replaceNode model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above.
+     * @param model The <strong>GTreeModel</strong> we want to change.
+     * @param old_node The <strong>GTreeNode</strong> to replace.
+     * @param new_node The <strong>GTreeNode</strong> to replace it with.
+     */
+    static final public void replaceNode(final DefaultTreeModel model, final MutableTreeNode old_node, final MutableTreeNode new_node) {
+	final Runnable doReplaceNode = new Runnable() {
+		public void run() {
+		    MutableTreeNode parent = (MutableTreeNode) old_node.getParent();
+		    if(parent != null) {
+			int index = parent.getIndex(old_node);
+			model.removeNodeFromParent(old_node);
+			model.insertNodeInto(new_node, parent, index);
+		    }
+		    parent = null;
+		}
+	    };
+	try {
 				//SwingUtilities.invokeLater(doReplaceNode);
-				SwingUtilities.invokeAndWait(doReplaceNode);
-		  }
-		  catch (Exception e) {
-				e.printStackTrace();
-		  }
-	 }
+	    SwingUtilities.invokeAndWait(doReplaceNode);
+	}
+	catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/TreeSynchronizer.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/TreeSynchronizer.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/TreeSynchronizer.java	(revision 4364)
@@ -40,98 +40,98 @@
  */
 final public class TreeSynchronizer 
-	 extends Vector 
-	 implements TreeExpansionListener, TreeSelectionListener {
-	 /** <i>true</i> if we should temporarily ignore further events, most likely because we know our actions are causing them. */ 
-	 private boolean ignore;
-	 /** A list of tree selection listeners. */
-	 private Vector selection_listeners = new Vector();
-	 /** Add a new tree to the synchronization list of trees to be synchronized.
-	  * @param tree The lastest victim, a <strong>JTree</strong>.
+    extends Vector 
+    implements TreeExpansionListener, TreeSelectionListener {
+    /** <i>true</i> if we should temporarily ignore further events, most likely because we know our actions are causing them. */ 
+    private boolean ignore;
+    /** A list of tree selection listeners. */
+    private Vector selection_listeners = new Vector();
+    /** Add a new tree to the synchronization list of trees to be synchronized.
+     * @param tree The lastest victim, a <strong>JTree</strong>.
 	  */
-	 public void add(JTree tree) {
-		  super.add(tree);
-		  tree.addTreeExpansionListener(this);
-		  //tree.addTreeSelectionListener(this);
-	 }
+    public void add(JTree tree) {
+	super.add(tree);
+	tree.addTreeExpansionListener(this);
+	//tree.addTreeSelectionListener(this);
+    }
 
-	 /** We allow the Gatherer to add tree listeners to this class, as it persists between collection changes transparently. Thus there is no need to reattach listeners everytime the collection changes. */
-	 public void addTreeSelectionListener(TreeSelectionListener listener) {
-		  if(!selection_listeners.contains(listener)) {
-				selection_listeners.add(listener);
-		  }
-	 }
+    /** We allow the Gatherer to add tree listeners to this class, as it persists between collection changes transparently. Thus there is no need to reattach listeners everytime the collection changes. */
+    public void addTreeSelectionListener(TreeSelectionListener listener) {
+	if(!selection_listeners.contains(listener)) {
+	    selection_listeners.add(listener);
+	}
+    }
 
-	 /** Called whenever an item in the tree has been collapsed.
+    /** Called whenever an item in the tree has been collapsed.
 	  * @param event A <strong>TreeExpansionEvent</strong> containing information about the event.
 	  */
-	 public void treeCollapsed(TreeExpansionEvent event) {
-		  if(!ignore) {
-				ignore = true;
+    public void treeCollapsed(TreeExpansionEvent event) {
+	if(!ignore) {
+	    ignore = true;
 				// Collapse that path in all registered trees.
-				JTree tree = (JTree)event.getSource();
-				TreePath path = event.getPath();
-				for(int i = size(); i != 0; i--) {
-					 JTree sibling = (JTree) get(i - 1);
-					 if(!sibling.equals(tree)) {
-						  sibling.collapsePath(path);
-					 }
-				}
-				ignore = false;
-		  }
-	 }
-	 /** Called whenever an item in the tree has been expanded.
+	    JTree tree = (JTree)event.getSource();
+	    TreePath path = event.getPath();
+	    for(int i = size(); i != 0; i--) {
+		JTree sibling = (JTree) get(i - 1);
+		if(!sibling.equals(tree)) {
+		    sibling.collapsePath(path);
+		}
+	    }
+	    ignore = false;
+	}
+    }
+    /** Called whenever an item in the tree has been expanded.
 	  * @param event A <strong>TreeExpansionEvent</strong> containing information about the event.
 	  */
-	 public void treeExpanded(TreeExpansionEvent event) {
-		  if(!ignore) {
-				ignore = true;
+    public void treeExpanded(TreeExpansionEvent event) {
+	if(!ignore) {
+	    ignore = true;
 				// Expand that path in all registered trees.
-				JTree tree = (JTree)event.getSource();
-				TreePath path = event.getPath();
-				for(int i = size(); i != 0; i--) {
-					 JTree sibling = (JTree) get(i - 1);
-					 if(!sibling.equals(tree)) {
-						  sibling.expandPath(path);
-					 }
-				}
-				ignore = false;
-		  }
-	 }
-	 /** Called whenever the one of the trees selection changes.
+	    JTree tree = (JTree)event.getSource();
+	    TreePath path = event.getPath();
+	    for(int i = size(); i != 0; i--) {
+		JTree sibling = (JTree) get(i - 1);
+		if(!sibling.equals(tree)) {
+		    sibling.expandPath(path);
+		}
+	    }
+	    ignore = false;
+	}
+    }
+    /** Called whenever the one of the trees selection changes.
 	  * @param event A <strong>TreeSelectionEvent</strong> containing information about the event.
 	  */
-	 public void valueChanged(TreeSelectionEvent event) {
-		  if(!ignore) {
-				ignore = true;
+    public void valueChanged(TreeSelectionEvent event) {
+	if(!ignore) {
+	    ignore = true;
 				// Recover the tree that is the source.
-				JTree tree = (JTree) event.getSource();
+	    JTree tree = (JTree) event.getSource();
 				// Brute Force approach.
 				// Extract the currently selected paths.
-				TreePath paths[] = tree.getSelectionPaths();
+	    TreePath paths[] = tree.getSelectionPaths();
 				// Then for every registered tree, that isn't this one, ensure those paths are selected.
-				for(int i = size(); paths != null && i != 0; i--) {
-					 JTree sibling = (JTree) get(i - 1);
-					 if(!sibling.equals(tree)) {
-						  // One last thing to do. If this is actually a DragTree we are dealing with we have to tell it to set the selection values immediately, not wait for the clear until it is sure it is no the pre-cursor to a drag.
-						  if(sibling instanceof DragTree) {
-								DragTree gtree = (DragTree)sibling;
-								gtree.setImmediate(true);
-								gtree.setSelectionPaths(paths);
-								// I'm going to ensure that the last selected path is visible.
-								gtree.scrollPathToVisible(paths[paths.length - 1]);
-								gtree.setImmediate(false);
-						  }
-						  else {
-								sibling.setSelectionPaths(paths);
-								sibling.scrollPathToVisible(paths[paths.length - 1]);
-						  }
-					 }
-				}
+	    for(int i = size(); paths != null && i != 0; i--) {
+		JTree sibling = (JTree) get(i - 1);
+		if(!sibling.equals(tree)) {
+		    // One last thing to do. If this is actually a DragTree we are dealing with we have to tell it to set the selection values immediately, not wait for the clear until it is sure it is no the pre-cursor to a drag.
+		    if(sibling instanceof DragTree) {
+			DragTree gtree = (DragTree)sibling;
+			gtree.setImmediate(true);
+			gtree.setSelectionPaths(paths);
+			// I'm going to ensure that the last selected path is visible.
+			gtree.scrollPathToVisible(paths[paths.length - 1]);
+			gtree.setImmediate(false);
+		    }
+		    else {
+			sibling.setSelectionPaths(paths);
+			sibling.scrollPathToVisible(paths[paths.length - 1]);
+		    }
+		}
+	    }
 				// Pass on message to all listeners.
-				for(int i = 0; i < selection_listeners.size(); i++) {
-					 ((TreeSelectionListener) selection_listeners.get(i)).valueChanged(event);
-				}
-				ignore = false;
-		  }
-	 }
+	    for(int i = 0; i < selection_listeners.size(); i++) {
+		((TreeSelectionListener) selection_listeners.get(i)).valueChanged(event);
+	    }
+	    ignore = false;
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/Utility.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/Utility.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/Utility.java	(revision 4364)
@@ -53,864 +53,861 @@
  */
 public class Utility {
-	 /** The default size of a gatherer progress bar, in either the download view or the build view. */
-	 static final public Dimension PROGRESS_BAR_SIZE = new Dimension(580,65);
-	 /** The number of kilobytes to use as a io buffer. */
-	 static final public int FACTOR = 1;
-	 /** The size of the io buffer, calculated as FACTOR * 1024. */
-	 static final public int BUFFER_SIZE = FACTOR * 1024;
-	 /** Definition of an important directory name, in this case the archive directory for the collection. */
-	 static final public String ARCHIVE_DIR = "archives" + File.separator;
-	 /** Definition of an important directory name, in this case the base dir, or the working directory of the Gatherer. */
-	 static final public String BASE_DIR  = System.getProperty("user.dir") + File.separator;
-	 /** Definition of an important directory name, in this case the building directory for the collection. */
-	 static final public String BUILD_DIR = "building" + File.separator;
-	 /** Definition of an important directory name, in this case the public web cache for the Gatherer. */
-	 static final public String CACHE_DIR = BASE_DIR + "cache" + File.separator;
-	 static final public String CFG_COLLECTIONMETA_COLLECTIONNAME = "collectionmeta collectionname";
-	 static final public String CFG_COLLECTIONMETA_COLLECTIONEXTRA = "collectionmeta collectionextra";
-	 static final public String CFG_COLLECTIONMETA_ICONCOLLECTION = "collectionmeta iconcollection";
-	 static final public String CFG_CLASSIFY = "classify";
-	 static final public String CFG_CLASSIFY_BUTTONNAME = "-buttonname";
-	 static final public String CFG_CLASSIFY_HFILE = "-hfile";
-	 static final public String CFG_CLASSIFY_METADATA = "-metadata";
-	 static final public String CFG_CLASSIFY_SORT = "-sort";
-	 static final public String CFG_CREATOR = "creator";
-	 static final public String CFG_FORMAT = "format";
-	 static final public String CFG_MAINTAINER = "maintainer";
-	 /** Definition of an important directory name, in this case the parent directory of all the collections in the gsdl. */
-	 static final public String COL_DIR = "collect" + File.separator;
-	 static final public String COLLECTION_DEMO = "greenstone demo";
-	 static final public String COLLECTION_DEMO_DIRECTORY = "demo" + File.separator;
-	 static final public String COLLECTION_DLS = "Development Library Subset";
-	 static final public String COLLECTION_DLS_DIRECTORY = "dls" + File.separator;
-	 static final public String COLLECTION_TREE = "Collection";
-	 /** Definition of an important directory name, in this case the file the collection configuration is expect to be in. */
-	 static final public String CONFIG_DIR = "etc" + File.separator + "collect.cfg";
-	 /** The default file name for the urls missing any file. */
-	 static final public String DEFAULT_FILE = "index.html";
-	 static final public String DEFAULT_NAMESPACE = "gsp";
-	 /** The default protocol header for those urls missing any protocol. */
-	 static final public String DEFAULT_PROTOCOL = "http://";
-	 /** The default dictionary to load. */
-	 static final public String DICTIONARY = "dictionary";
-	 static final public String DLS_MDS = "dls.mds";
-	 static final public String ENGLISH_VALUE = "en";
-	 /** Definition of an important directory name, in this case the etc (or extra information) directory for the collection. */
-	 static final public String ETC_DIR = "etc" + File.separator;
-	 static final public String EXTRACTED_METADATA_NAMESPACE = "ex";
-	 /** The location of the default greenstone metadata file. */
-	 static final public String GREENSTONEDIRECTORYMETADATA_TEMPLATE = "xml/metadata.xml";
-	 /** Definition of an important directory name, in this case the private web cache directory for the collection. */
-	 static final public String GCACHE_DIR = "gcache" + File.separator;
-/** Definition of an important directory name, in this case the location of help documentation. */
-	 static final public String HELP_DIR = BASE_DIR + "help" + File.separator;
-	 /** Definition of an important directory name, in this case the import directory for the collection. */
-	 static final public String IMPORT_DIR = "gimport" + File.separator;
-	 /** Definition of an important directory name, in this case the index directory for the collection. */
-	 static final public String INDEX_DIR = "index" + File.separator;
-	 static final public String LANGUAGE_ATTRIBUTE = "language";
-	 /** Definition of an important directory name, in this case the log directory for the collection. */
-	 static final public String LOG_DIR = "log" + File.separator;
-	 /** Definition of an important directory name, in this case the location of the expected collection metadata sets.. */
-	 static final public String META_DIR = "metadata" + File.separator; // Col. Copy
-	 /** Definition of an important directory name, in this case the location of the default metadata sets. */
-	 static final public String METADATA_DIR = BASE_DIR + "metadata" + File.separator;
-	 static final public String METADATA_EXTRACTED = "extracted.mds";
-	 /** The location the gatherer expects to find metadata set information. */
-	 static final public String METADATA_SET_TEMPLATE = "xml" + File.separator + "template.mds";
-	 static final public String METADATA_VALUE_TEMPLATE = "xml" + File.separator + "template.mdv";
-	 static final public String METADATA_XML = "metadata.xml";
-	 static final public String NAME_ELEMENT = "Name";
-	 /** Definition of an important directory name, in this case the import directory for the collection. */
-	 static final public String OLD_IMPORT_DIR = "import" + File.separator;
-	 /** The default name of the perl executable under unix. */
-	 static final public String PERL_EXECUTABLE_UNIX = "perl";
-	 /** The default name of the perl executable under windows. */
-	 static final public String PERL_EXECUTABLE_WINDOWS = "Perl.exe";
-	 /** The name of the Gatherer. */
-	 static final public String PROGRAM_NAME = "The Librarian Interface";
-	 /** The current version of the Gatherer. */
-	 static final public String PROGRAM_VERSION = "ver 2.0";
-	 /** Definition of an important directory name, in this case the location of the recycled files location. */
-	 static final public String RECYCLE = BASE_DIR + "recycle" + File.separator;
-	 /** Definition of an important directory name, in this case the location of image and other resources. */
-	 static final public String RES_DIR = BASE_DIR + "resource" + File.separator;
-	 static final public String SERVER_EXE = "server.exe";
-	 /** Definition of an important directory name, in this case the location of opening (or welcome) screen html. */
-	 static final public String WELCOME_DIR = BASE_DIR + "welcome" + File.separator;
-	 static final public String WORKSPACE_TREE = "Workspace";
-	 // These are out of alphabetic order to avoid forward reference error.
-	 /** The default icon to produce a 'help-icon' sized blank space before a menu entry. */
-	 static final public ImageIcon BLANK_ICON = new ImageIcon(ClassLoader.getSystemResource("images/blank.gif"));
-	 /** The default error icon image. */
-	 static final public ImageIcon ERROR_ICON = new ImageIcon(ClassLoader.getSystemResource("images/error.gif"));
-	 static final public ImageIcon HELP_ICON = new ImageIcon(ClassLoader.getSystemResource("images/help.gif"));
-	 /** The image for a toggle button whose state is 'on'. */
-	 static final public ImageIcon ON_ICON = new ImageIcon(ClassLoader.getSystemResource("images/check.gif"));
-	 /** The image for a toggle button whose state is 'off'. */
-	 static final public ImageIcon OFF_ICON = new ImageIcon(ClassLoader.getSystemResource("images/cross.gif"));
-	 /** Method to turn a file from with the system file tree into a tree path for insertion into a tree.
-	  * @param file The <strong>File</strong> whose tree path you are attempting to discover.
-	  * @param in_col A <i>boolean</i> indicating whether we are looking for a file within a collection of not. If <i>true</i> then the tree paths head in the collection name, and no element in the path refers to the import directory. Otherwise the paths head will be one of the system roots and all traversed file locations will exist in the path.
-	  * @return A <strong>TreePath</strong> which traverses the file system tree to the specified file.
-	  */
-	 public static TreePath createTreePath(File file, boolean in_col) {
-		  TreePath path = null;
-		  // Get the absolute path of the file.
-		  String abs_path = file.getAbsolutePath();
-		  while(file != null) {
+    /** The default size of a gatherer progress bar, in either the download view or the build view. */
+    static final public Dimension PROGRESS_BAR_SIZE = new Dimension(580,65);
+    /** The number of kilobytes to use as a io buffer. */
+    static final public int FACTOR = 1;
+    /** The size of the io buffer, calculated as FACTOR * 1024. */
+    static final public int BUFFER_SIZE = FACTOR * 1024;
+    /** Definition of an important directory name, in this case the archive directory for the collection. */
+    static final public String ARCHIVE_DIR = "archives" + File.separator;
+    /** Definition of an important directory name, in this case the base dir, or the working directory of the Gatherer. */
+    static final public String BASE_DIR  = System.getProperty("user.dir") + File.separator;
+    /** Definition of an important directory name, in this case the building directory for the collection. */
+    static final public String BUILD_DIR = "building" + File.separator;
+    /** Definition of an important directory name, in this case the public web cache for the Gatherer. */
+    static final public String CACHE_DIR = BASE_DIR + "cache" + File.separator;
+    static final public String CFG_COLLECTIONMETA_COLLECTIONNAME = "collectionmeta collectionname";
+    static final public String CFG_COLLECTIONMETA_COLLECTIONEXTRA = "collectionmeta collectionextra";
+    static final public String CFG_COLLECTIONMETA_ICONCOLLECTION = "collectionmeta iconcollection";
+    static final public String CFG_CLASSIFY = "classify";
+    static final public String CFG_CLASSIFY_BUTTONNAME = "-buttonname";
+    static final public String CFG_CLASSIFY_HFILE = "-hfile";
+    static final public String CFG_CLASSIFY_METADATA = "-metadata";
+    static final public String CFG_CLASSIFY_SORT = "-sort";
+    static final public String CFG_CREATOR = "creator";
+    static final public String CFG_FORMAT = "format";
+    static final public String CFG_MAINTAINER = "maintainer";
+    /** Definition of an important directory name, in this case the parent directory of all the collections in the gsdl. */
+    static final public String COL_DIR = "collect" + File.separator;
+    static final public String COLLECTION_DEMO = "greenstone demo";
+    static final public String COLLECTION_DEMO_DIRECTORY = "demo" + File.separator;
+    static final public String COLLECTION_DLS = "Development Library Subset";
+    static final public String COLLECTION_DLS_DIRECTORY = "dls" + File.separator;
+    static final public String COLLECTION_TREE = "Collection";
+    /** Definition of an important directory name, in this case the file the collection configuration is expect to be in. */
+    static final public String CONFIG_DIR = "etc" + File.separator + "collect.cfg";
+    /** The default file name for the urls missing any file. */
+    static final public String DEFAULT_FILE = "index.html";
+    static final public String DEFAULT_NAMESPACE = "gsp";
+    /** The default protocol header for those urls missing any protocol. */
+    static final public String DEFAULT_PROTOCOL = "http://";
+    /** The default dictionary to load. */
+    static final public String DICTIONARY = "dictionary";
+    static final public String DLS_MDS = "dls.mds";
+    static final public String ENGLISH_VALUE = "en";
+    /** Definition of an important directory name, in this case the etc (or extra information) directory for the collection. */
+    static final public String ETC_DIR = "etc" + File.separator;
+    static final public String EXTRACTED_METADATA_NAMESPACE = "ex";
+    /** The location of the default greenstone metadata file. */
+    static final public String GREENSTONEDIRECTORYMETADATA_TEMPLATE = "xml/metadata.xml";
+    /** Definition of an important directory name, in this case the private web cache directory for the collection. */
+    static final public String GCACHE_DIR = "gcache" + File.separator;
+    /** Definition of an important directory name, in this case the location of help documentation. */
+    static final public String HELP_DIR = BASE_DIR + "help" + File.separator;
+    /** Definition of an important directory name, in this case the import directory for the collection. */
+    static final public String IMPORT_DIR = "gimport" + File.separator;
+    /** Definition of an important directory name, in this case the index directory for the collection. */
+    static final public String INDEX_DIR = "index" + File.separator;
+    static final public String LANGUAGE_ATTRIBUTE = "language";
+    /** Definition of an important directory name, in this case the log directory for the collection. */
+    static final public String LOG_DIR = "log" + File.separator;
+    /** Definition of an important directory name, in this case the location of the expected collection metadata sets.. */
+    static final public String META_DIR = "metadata" + File.separator; // Col. Copy
+    /** Definition of an important directory name, in this case the location of the default metadata sets. */
+    static final public String METADATA_DIR = BASE_DIR + "metadata" + File.separator;
+    static final public String METADATA_EXTRACTED = "extracted.mds";
+    /** The location the gatherer expects to find metadata set information. */
+    static final public String METADATA_SET_TEMPLATE = "xml" + File.separator + "template.mds";
+    static final public String METADATA_VALUE_TEMPLATE = "xml" + File.separator + "template.mdv";
+    static final public String METADATA_XML = "metadata.xml";
+    static final public String NAME_ELEMENT = "Name";
+    /** Definition of an important directory name, in this case the import directory for the collection. */
+    static final public String OLD_IMPORT_DIR = "import" + File.separator;
+    /** The default name of the perl executable under unix. */
+    static final public String PERL_EXECUTABLE_UNIX = "perl";
+    /** The default name of the perl executable under windows. */
+    static final public String PERL_EXECUTABLE_WINDOWS = "Perl.exe";
+    /** The name of the Gatherer. */
+    static final public String PROGRAM_NAME = "The Librarian Interface";
+    /** The current version of the Gatherer. */
+    static final public String PROGRAM_VERSION = "ver 2.0";
+    /** Definition of an important directory name, in this case the location of the recycled files location. */
+    static final public String RECYCLE = BASE_DIR + "recycle" + File.separator;
+    /** Definition of an important directory name, in this case the location of image and other resources. */
+    static final public String RES_DIR = BASE_DIR + "resource" + File.separator;
+    static final public String SERVER_EXE = "server.exe";
+    /** Definition of an important directory name, in this case the location of opening (or welcome) screen html. */
+    static final public String WELCOME_DIR = BASE_DIR + "welcome" + File.separator;
+    static final public String WORKSPACE_TREE = "Workspace";
+    // These are out of alphabetic order to avoid forward reference error.
+    /** The default icon to produce a 'help-icon' sized blank space before a menu entry. */
+    static final public ImageIcon BLANK_ICON = new ImageIcon(ClassLoader.getSystemResource("images/blank.gif"));
+    /** The default error icon image. */
+    static final public ImageIcon ERROR_ICON = new ImageIcon(ClassLoader.getSystemResource("images/error.gif"));
+    static final public ImageIcon HELP_ICON = new ImageIcon(ClassLoader.getSystemResource("images/help.gif"));
+    /** The image for a toggle button whose state is 'on'. */
+    static final public ImageIcon ON_ICON = new ImageIcon(ClassLoader.getSystemResource("images/check.gif"));
+    /** The image for a toggle button whose state is 'off'. */
+    static final public ImageIcon OFF_ICON = new ImageIcon(ClassLoader.getSystemResource("images/cross.gif"));
+    /** Method to turn a file from with the system file tree into a tree path for insertion into a tree.
+     * @param file The <strong>File</strong> whose tree path you are attempting to discover.
+     * @param in_col A <i>boolean</i> indicating whether we are looking for a file within a collection of not. If <i>true</i> then the tree paths head in the collection name, and no element in the path refers to the import directory. Otherwise the paths head will be one of the system roots and all traversed file locations will exist in the path.
+     * @return A <strong>TreePath</strong> which traverses the file system tree to the specified file.
+     */
+    public static TreePath createTreePath(File file, boolean in_col) {
+	TreePath path = null;
+	// Get the absolute path of the file.
+	String abs_path = file.getAbsolutePath();
+	while(file != null) {
 				// If we are looking for a node within our collection, we expect
 				// its path from root to be <col_name>/... without any higher
 				// details and without gimport. So if we encounter a gimport we
 				// skip to its parent, add that, then return.
-				if(in_col && file.getName().equals("gimport")) {
-					 file = file.getParentFile();
-					 if(path == null) {
-						  path = new TreePath(file.getName());
-					 }
-					 else {
-						  path = path.pathByAddingChild(file.getName());
-					 }
-					 file = null;
-				}
-				else {
-					 if(path == null) {
-						  path = new TreePath(file.getName());
-					 }
-					 else {
-						  path = path.pathByAddingChild(file.getName());
-					 }
-					 file = file.getParentFile();
-				}
-		  }
-		  // Unfortunately we've created the path in reverse order so we have to
-		  // reverse it.
-		  Object temp[] = new Object[path.getPathCount()];
-		  for(int i = 0; i < temp.length; i++) {
-				temp[(temp.length - 1) - i] = path.getPathComponent(i);
-		  }
-		  return new TreePath(temp);
-	 }
-	 /** Decodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces "\n" with a newline.
-	  * @param raw The <strong>String</strong> before decoding, read from the configuration file..
-	  * @return A <strong>String</strong> ready to be placed in a component.
-	  */
-	 static public String decodeGreenstone(String raw) {
-		  raw = raw.replaceAll("&apos;", "\'");
-		  raw = raw.replaceAll("&gt;", ">");
-		  raw = raw.replaceAll("&lt;", "<");
-		  raw = raw.replaceAll("&quot;", "\"");
-		  raw = raw.replaceAll("&#39;", "\'");
-		  raw = raw.replaceAll("\\\\n", "\n");
-		  return raw;
-	 }
-	 /** Takes a rfc2616 'safe' String and translates it back into its 'unsafe' form. Basically the native c wget decode_string() function, but without pointer stuff. If searches through the String looking for the pattern %xy where x and y are hexidecimal digits and where xy maps to a character.<BR> If x or y are not hexidecimal or % is followed by a \0 then the pattern is left as is.
-	  * @param encoded The url-safe <strong>String</strong> to be decoded.
-	  * @return The decoded <strong>String</strong>.
-	  */
-	 public static String decodeString(String encoded) {
-		  String decoded = "";
-		  for(int i = 0; i < encoded.length(); i++) {
-				if(encoded.charAt(i) == '%') {
-					 if(hexidecimal(encoded.charAt(i+1)) != -1
-						 && hexidecimal(encoded.charAt(i+2)) != -1) {
-						  char unsafe_chr = (char)
-								((hexidecimal(encoded.charAt(i+1)) * 16) +
-								 hexidecimal(encoded.charAt(i+2)));
-						  decoded = decoded + unsafe_chr;
-						  i = i + 2;
-					 }
-				}
-				else {
-					 decoded = decoded + encoded.charAt(i);
-				}
-		  }
-		  return decoded;
-	 }
-	 /** It turns out that in Java you have to make sure a directory is empty before you delete it (much like unix I suppose), and so just like unix I'll have to set up a recursive delete function.
-	  * @param file The <strong>File</strong> you want to delete.
-	  * @return A <i>boolean</i> which is <i>true</i> if the file specified was successfully deleted, <i>false</i> otherwise.
-	  */
-	 static public boolean delete(File file) {
-		  boolean result = true;
-		  // If files a directory, delete files children.
-		  if(file.isDirectory()) {
-				File files[] = file.listFiles();
-				for(int i = 0; files != null && result && i < files.length; i++) {
-					 result = delete(files[i]);
-				}
-		  }
-		  if(result) {
+	    if(in_col && file.getName().equals("gimport")) {
+		file = file.getParentFile();
+		if(path == null) {
+		    path = new TreePath(file.getName());
+		}
+		else {
+		    path = path.pathByAddingChild(file.getName());
+		}
+		file = null;
+	    }
+	    else {
+		if(path == null) {
+		    path = new TreePath(file.getName());
+		}
+		else {
+		    path = path.pathByAddingChild(file.getName());
+		}
+		file = file.getParentFile();
+	    }
+	}
+	// Unfortunately we've created the path in reverse order so we have to
+	// reverse it.
+	Object temp[] = new Object[path.getPathCount()];
+	for(int i = 0; i < temp.length; i++) {
+	    temp[(temp.length - 1) - i] = path.getPathComponent(i);
+	}
+	return new TreePath(temp);
+    }
+    /** Decodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces "\n" with a newline.
+     * @param raw The <strong>String</strong> before decoding, read from the configuration file..
+     * @return A <strong>String</strong> ready to be placed in a component.
+     */
+    static public String decodeGreenstone(String raw) {
+	raw = raw.replaceAll("&apos;", "\'");
+	raw = raw.replaceAll("&gt;", ">");
+	raw = raw.replaceAll("&lt;", "<");
+	raw = raw.replaceAll("&quot;", "\"");
+	raw = raw.replaceAll("&#39;", "\'");
+	raw = raw.replaceAll("\\\\n", "\n");
+	return raw;
+    }
+    /** Takes a rfc2616 'safe' String and translates it back into its 'unsafe' form. Basically the native c wget decode_string() function, but without pointer stuff. If searches through the String looking for the pattern %xy where x and y are hexidecimal digits and where xy maps to a character.<BR> If x or y are not hexidecimal or % is followed by a \0 then the pattern is left as is.
+     * @param encoded The url-safe <strong>String</strong> to be decoded.
+     * @return The decoded <strong>String</strong>.
+     */
+    public static String decodeString(String encoded) {
+	String decoded = "";
+	for(int i = 0; i < encoded.length(); i++) {
+	    if(encoded.charAt(i) == '%') {
+		if(hexidecimal(encoded.charAt(i+1)) != -1
+		   && hexidecimal(encoded.charAt(i+2)) != -1) {
+		    char unsafe_chr = (char)
+			((hexidecimal(encoded.charAt(i+1)) * 16) +
+			 hexidecimal(encoded.charAt(i+2)));
+		    decoded = decoded + unsafe_chr;
+		    i = i + 2;
+		}
+	    }
+	    else {
+		decoded = decoded + encoded.charAt(i);
+	    }
+	}
+	return decoded;
+    }
+    /** It turns out that in Java you have to make sure a directory is empty before you delete it (much like unix I suppose), and so just like unix I'll have to set up a recursive delete function.
+     * @param file The <strong>File</strong> you want to delete.
+     * @return A <i>boolean</i> which is <i>true</i> if the file specified was successfully deleted, <i>false</i> otherwise.
+     */
+    static public boolean delete(File file) {
+	boolean result = true;
+	// If files a directory, delete files children.
+	if(file.isDirectory()) {
+	    File files[] = file.listFiles();
+	    for(int i = 0; files != null && result && i < files.length; i++) {
+		result = delete(files[i]);
+	    }
+	}
+	if(result) {
 				// Delete file.
-				return file.delete();
-		  }
-		  return result;
-	 }
-	 /** Generate a depth first enumeration of a tree. */
-	 static public EnumeratedVector depthFirstEnumeration(TreeNode node, EnumeratedVector result) {
-		  result.add(node);
-		  for(int i = 0; i < node.getChildCount(); i++) {
-				depthFirstEnumeration(node.getChildAt(i), result);
-		  }
-		  return result;
-	 }
-	 /** Encodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces newlines with their escaped form.
-	  * @param raw The <strong>String</strong> before encoding.
-	  * @return A <strong>String</strong> which is safe to write to the configuration file.
-	  */
-	 static public String encodeGreenstone(String raw) {
-		  raw = raw.replaceAll("<", "&lt;");
-		  raw = raw.replaceAll(">", "&gt;");
-		  return raw.replaceAll("\n", "\\\\n");
-	 }
-
-	 /** Using this method we can request that a certain document be written, as valid XML, to a certain output stream. This makes use of the Xerces Serialization suite, which should in no way be confused with the usual method of Serialization used by Java. */
-	 static public boolean export(Document document, String filename) {
-		  return export(document, new File(filename));
-	 }
-
-	 static public boolean export(Document document, File file) {
-		  try {
-				OutputStream os = new FileOutputStream(file);
+	    return file.delete();
+	}
+	return result;
+    }
+    /** Generate a depth first enumeration of a tree. */
+    static public EnumeratedVector depthFirstEnumeration(TreeNode node, EnumeratedVector result) {
+	result.add(node);
+	for(int i = 0; i < node.getChildCount(); i++) {
+	    depthFirstEnumeration(node.getChildAt(i), result);
+	}
+	return result;
+    }
+    /** Encodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces newlines with their escaped form.
+     * @param raw The <strong>String</strong> before encoding.
+     * @return A <strong>String</strong> which is safe to write to the configuration file.
+     */
+    static public String encodeGreenstone(String raw) {
+	raw = raw.replaceAll("<", "&lt;");
+	raw = raw.replaceAll(">", "&gt;");
+	return raw.replaceAll("\n", "\\\\n");
+    }
+
+    /** Using this method we can request that a certain document be written, as valid XML, to a certain output stream. This makes use of the Xerces Serialization suite, which should in no way be confused with the usual method of Serialization used by Java. */
+    static public boolean export(Document document, String filename) {
+	return export(document, new File(filename));
+    }
+
+    static public boolean export(Document document, File file) {
+	try {
+	    OutputStream os = new FileOutputStream(file);
 				// Create an output format for our document.
-				OutputFormat f = new OutputFormat(document);
-				f.setIndenting(true);
-				f.setLineWidth(256);
-				f.setPreserveSpace(false);
+	    OutputFormat f = new OutputFormat(document);
+	    f.setIndenting(true);
+	    f.setLineWidth(256);
+	    f.setPreserveSpace(false);
 				// Create the necessary writer stream for serialization.
-				OutputStreamWriter osw = new OutputStreamWriter(os);
-				Writer w               = new BufferedWriter(osw);
+	    OutputStreamWriter osw = new OutputStreamWriter(os);
+	    Writer w               = new BufferedWriter(osw);
 				// Generate a new serializer from the above.
-				XMLSerializer s        = new XMLSerializer(w, f);
-				s.asDOMSerializer();
+	    XMLSerializer s        = new XMLSerializer(w, f);
+	    s.asDOMSerializer();
 				// Finally serialize the document to file.
-				s.serialize(document);
+	    s.serialize(document);
 				// And close.
-				os.close();
-				return true;
-		  }
-		  // A file not found exception is most likely thrown because the directory the metadata.xml file is attempting to be written to no longer has any files in it. I'll add a test in GDMDocument to test for this, but if it still happens ignore it (a non-existant directory can't really have metadata added to it any way.
-		  catch (FileNotFoundException fnf_exception) {
-				if(!file.getName().endsWith(METADATA_XML)) {
-					 fnf_exception.printStackTrace();
-					 return false;
-				}
-				return true;
-		  }
-		  catch (IOException ioe) {
-				ioe.printStackTrace();
-				return false;
-		  }
-	 }
-
-	 /** Given a starting directory, searches for the collect.cfg file and returns it if found.
-	  * @return The collect.cfg File or null if not found.
-	  */
-	 static final public File findConfigFile(File start) {
-		  if(start == null) {
-				return null;
-		  }
-		  // See if the collect.cfg files here.
-		  File collect_cfg = new File(start, "collect.cfg");
-		  if(collect_cfg.exists()) {
-				return collect_cfg;
-		  }
-		  // Search for the existance of collect.cfg in a etc directory.
-		  File etc_dir = new File(start, "etc" + File.separator + "collect.cfg");
-		  if(etc_dir.exists()) {
-				return etc_dir;
-		  }
-		  // Otherwise search this directories parent if its not null.
-		  return findConfigFile(start.getParentFile());
-	 }
-
-	 /** Convert a long, detailing the length of a file in bytes, into a nice human readable string using b, kb, Mb and Gb. */
-	 static final public String BYTE_SUFFIX = " b";
-	 static final public long GIGABYTE = 1024000000l;
-	 static final public String GIGABYTE_SUFFIX = " Gb";
-	 static final public long KILOBYTE =       1024l;
-	 static final public String KILOBYTE_SUFFIX = " kb";
-	 static final public long MEGABYTE =    1024000l;
-	 static final public String MEGABYTE_SUFFIX = " mb";
-	 static final public String formatFileLength(long length) {
-		  StringBuffer result = new StringBuffer("");
-		  float number = 0f;
-		  String suffix = null;
-		  // Determine the floating point number and the suffix (radix) used.
-		  if(length >= GIGABYTE) {
-				number = (float) length / (float) GIGABYTE;
-				suffix = GIGABYTE_SUFFIX;
-		  }
-		  else if(length >= MEGABYTE) {
-				number = (float) length / (float) MEGABYTE;
-				suffix = MEGABYTE_SUFFIX;
-		  }
-		  else if(length >= KILOBYTE) {
-				number = (float) length / (float) KILOBYTE;
-				suffix = KILOBYTE_SUFFIX;
-		  }
-		  else {
-				number = (float) length;
-				suffix = BYTE_SUFFIX;
-		  }
-		  // Create the formatted string remembering to round the number to 2.d.p. To do this copy everything in the number string from the start to the first occurance of '.' then copy two more digits. Finally search for and print anything that appears after (and including) the optional 'E' delimter.
-		  String number_str = Float.toString(number);
-		  char number_char[] = number_str.toCharArray();
-		  int pos = 0;
-		  // Print the characters up to the '.'
-		  while(number_char != null && pos < number_char.length && number_char[pos] != '.') {
-				result.append(number_char[pos]);
-				pos++;
-		  }
-		  if(pos < number_char.length) {
+	    os.close();
+	    return true;
+	}
+	// A file not found exception is most likely thrown because the directory the metadata.xml file is attempting to be written to no longer has any files in it. I'll add a test in GDMDocument to test for this, but if it still happens ignore it (a non-existant directory can't really have metadata added to it any way.
+	catch (FileNotFoundException fnf_exception) {
+	    if(!file.getName().endsWith(METADATA_XML)) {
+		fnf_exception.printStackTrace();
+		return false;
+	    }
+	    return true;
+	}
+	catch (IOException ioe) {
+	    ioe.printStackTrace();
+	    return false;
+	}
+    }
+
+    /** Given a starting directory, searches for the collect.cfg file and returns it if found.
+     * @return The collect.cfg File or null if not found.
+     */
+    static final public File findConfigFile(File start) {
+	if(start == null) {
+	    return null;
+	}
+	// See if the collect.cfg files here.
+	File collect_cfg = new File(start, "collect.cfg");
+	if(collect_cfg.exists()) {
+	    return collect_cfg;
+	}
+	// Search for the existance of collect.cfg in a etc directory.
+	File etc_dir = new File(start, "etc" + File.separator + "collect.cfg");
+	if(etc_dir.exists()) {
+	    return etc_dir;
+	}
+	// Otherwise search this directories parent if its not null.
+	return findConfigFile(start.getParentFile());
+    }
+
+    /** Convert a long, detailing the length of a file in bytes, into a nice human readable string using b, kb, Mb and Gb. */
+    static final public String BYTE_SUFFIX = " b";
+    static final public long GIGABYTE = 1024000000l;
+    static final public String GIGABYTE_SUFFIX = " Gb";
+    static final public long KILOBYTE =       1024l;
+    static final public String KILOBYTE_SUFFIX = " kb";
+    static final public long MEGABYTE =    1024000l;
+    static final public String MEGABYTE_SUFFIX = " mb";
+    static final public String formatFileLength(long length) {
+	StringBuffer result = new StringBuffer("");
+	float number = 0f;
+	String suffix = null;
+	// Determine the floating point number and the suffix (radix) used.
+	if(length >= GIGABYTE) {
+	    number = (float) length / (float) GIGABYTE;
+	    suffix = GIGABYTE_SUFFIX;
+	}
+	else if(length >= MEGABYTE) {
+	    number = (float) length / (float) MEGABYTE;
+	    suffix = MEGABYTE_SUFFIX;
+	}
+	else if(length >= KILOBYTE) {
+	    number = (float) length / (float) KILOBYTE;
+	    suffix = KILOBYTE_SUFFIX;
+	}
+	else {
+	    number = (float) length;
+	    suffix = BYTE_SUFFIX;
+	}
+	// Create the formatted string remembering to round the number to 2.d.p. To do this copy everything in the number string from the start to the first occurance of '.' then copy two more digits. Finally search for and print anything that appears after (and including) the optional 'E' delimter.
+	String number_str = Float.toString(number);
+	char number_char[] = number_str.toCharArray();
+	int pos = 0;
+	// Print the characters up to the '.'
+	while(number_char != null && pos < number_char.length && number_char[pos] != '.') {
+	    result.append(number_char[pos]);
+	    pos++;
+	}
+	if(pos < number_char.length) {
 				// Print the '.' and at most two characters after it
-				result.append(number_char[pos]);
-				pos++;
-				for(int i = 0; i < 2 && pos < number_char.length; i++, pos++) {
-					 result.append(number_char[pos]);
-				}
+	    result.append(number_char[pos]);
+	    pos++;
+	    for(int i = 0; i < 2 && pos < number_char.length; i++, pos++) {
+		result.append(number_char[pos]);
+	    }
 				// Search through the remaining string for 'E'
-				while(pos < number_char.length && number_char[pos] != 'E') {
-					 pos++;
-				}
+	    while(pos < number_char.length && number_char[pos] != 'E') {
+		pos++;
+	    }
 				// If we still have string then we found an E. Copy the remaining string.
-				while(pos < number_char.length) {
-					 result.append(number_char[pos]);
-					 pos++;
-				}
-		  }
-		  // Add suffix
-		  result.append(suffix);
-		  // Done
-		  return result.toString();
-	 }
-
-	 /** This method formats a given string, using HTML markup, so its width does not exceed the given width and its appearance if justified.
-	  * @param text The <strong>String</strong> requiring formatting.
-	  * @param width The maximum width per line as an <i>int</i>.
-	  * @return A <strong>String</strong> formatted so as to have no line longer than the specified width.
-	  * TODO Currently HTML formatting tags are simply removed from the text, as the effects of spreading HTML tags over a break are undetermined. To solve this we need to associate tags with a certain text token so if it gets broken on to the next line the tags go with it, or if the tags cover a sequence of words that are broken we need to close then reopen the tags. However all this is a major task and well beyond anything I have time to 'muck-round' on.
-	  */
-	 static public String formatHTMLWidth(String text, int width) {
-		  HTMLStringTokenizer html = new HTMLStringTokenizer(text);
-		  int current_width = 0;
-		  int threshold = width / 2;
-		  Stack lines = new Stack();
-		  String line = "";
-		  while(html.hasMoreTokens()) {
-				String token = html.nextToken();
-				while(token != null) {
-					 if(html.isTag()) {
-						  // Insert smart HTML tag code here.
-						  token = null;
-					 }
-					 else {
-						  // If the token is bigger than two thirds width, before we've even started break it down.
-						  if(current_width + 1 + token.length() > width && token.length() > threshold) {
-								String prefix = token.substring(0, width - 1 - current_width);
-								token = token.substring(prefix.length());
-								if(current_width == 0) {
-									 line = line + prefix;
-								}
-								else {
-									 line = line + " " + prefix;
-								}
-								lines.push(line);
-								line = "";
-								current_width = 0;
-						  }
-						  // If adding the next token would push us over the maximum line width.
-						  else if(current_width + 1 + token.length() > width) {
-								line = space(line, width, current_width);
-								lines.push(line);
-							   line = token;
-								current_width = token.length();
-								token = null;
-						  }
-						  // Otherwise we should be able to just add the token, give or take.
-						  else {
-								if(current_width == 0) {
-									 line = line + token;
-									 current_width = token.length();
-								}
-								else {
-									 // Special case for standard punctuation which may exist after a tag like so:
-									 // My name is <scratchy>Slim Shady</scratchy>.   <-- Annoying punctuation.
-									 if(token.equals(".") || token.equals(",") || token.equals("!") || token.equals("?")) {
-										  line = line + token;
-										  current_width = current_width + 1;
-									 }
-									 else {
-										  line = line + " " + token;
-										  current_width = current_width + 1 + token.length();
-									 }
-								}
-								token = null;
-						  }
-					 }
-				}
-		  }
-		  String result = line;
-		  while(!lines.empty()) {
-				result = (String)lines.pop() + "<BR>" + result;
-		  }
-		  // Replace ' ' with "&nbsp;"
-		  boolean tag = false;
-		  int pos = 0;
-		  while(pos < result.length()) {
-				if(result.charAt(pos) == '<') {
-					 tag = true;
-				}
-				else if(result.charAt(pos) == '>') {
-					 tag = false;
-				}
-				else if(result.charAt(pos) == ' ' && !tag) {
-					 String prefix = result.substring(0, pos);
-					 String suffix = result.substring(pos + 1);
-					 result = prefix + "&nbsp;" + suffix;
-				}
-				pos++;
-		  }
-		  result = "<HTML>" + result + "</HTML>";
-		  return result;
-	 }
-	 /** Format the given filename path string so that it is no longer than the given width. If it is wider replace starting directories with ...
-	  * @param key The key <strong>String</Strong> used to retrieve a phrase from the dictionary for this item.
-	  * @param raw The raw filename path <strong>String</strong>.
-	  * @param width The maximum width as an <i>int</i>.
-	  * @return A path <strong>String</strong> no longer than width.
-	  */
-	 static public String formatPath(String key, String raw, int width) {
-		  JLabel label = new JLabel(Gatherer.dictionary.get(key, raw));
-		  int position = -1;
-		  while(label.getPreferredSize().width > width && (position = raw.indexOf(File.separator)) != -1) {
-				raw = "..." + raw.substring(position + 1);
-				label.setText(Gatherer.dictionary.get(key, raw));
-		  }
-		  if(raw.indexOf(File.separator) == -1 && raw.startsWith("...")) {
-				raw = raw.substring(3);
-		  }
-		  return raw;
-	 }
-
-	 /** Method which constructs the archive directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections archive directory, also as a <strong>String</strong>.
-	  */
-	 static public String getArchiveDir(String gsdl_path, String col_name) {
-		  return gsdl_path + File.separator + COL_DIR + col_name + File.separator + ARCHIVE_DIR;
-	 }
-	 /** Method which constructs the build directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections build directory, also as a <strong>String</strong>.
-	  */
-	 static public String getBuildDir(String col_dir) {
-		  if(col_dir == null) {
-				return BASE_DIR + BUILD_DIR;
-		  }
-		  return col_dir + BUILD_DIR;
-	 }
-	 /** Builds the private cache dir by appending col_dir and 'cache'.
-	  * @param col_dir A String representing the directory path of the current collection.
-	  * @return A String representing the path to the private file cache within the current collection.
-	  */
-	 public static String getCacheDir(String col_dir) {
-		  return col_dir + GCACHE_DIR;
-	 }
-	 /** Method which constructs the collection directory for Greenstone.
-	  * @param gsdl_path The location of the gsdl installation directory as a <strong>String</strong>.
-	  * @return The location of the collection directory, also as a <strong>String</strong>.
-	  */
-	 public static String getCollectionDir(String gsdl_path) {
-		  return gsdl_path + COL_DIR;
-	 }
-	 /** Method which constructs the configuration file given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections configuration file, also as a <strong>String</strong>.
-	  */
-	 static public String getConfigDir(String col_dir) {
-		  return col_dir + CONFIG_DIR;
-	 }
-
-	 static public String getDateString() {
-		  Calendar current = Calendar.getInstance();
-		  String day_name = null;
-		  switch(current.get(Calendar.DAY_OF_WEEK)) {
-		  case Calendar.MONDAY: day_name = "Mon"; break;
-		  case Calendar.TUESDAY: day_name = "Tue"; break;
-		  case Calendar.WEDNESDAY: day_name = "Wed"; break;
-		  case Calendar.THURSDAY: day_name = "Thu"; break;
-		  case Calendar.FRIDAY: day_name = "Fri"; break;
-		  case Calendar.SATURDAY: day_name = "Sat"; break;
-		  case Calendar.SUNDAY: day_name = "Sun"; break;
-		  default: day_name = "";
-		  }
-		  String month_name = null;
-		  switch(current.get(Calendar.MONTH)) {
-		  case Calendar.JANUARY: month_name = "Jan"; break;
-		  case Calendar.FEBRUARY: month_name = "Feb"; break;
-		  case Calendar.MARCH: month_name = "Mar"; break;
-		  case Calendar.APRIL: month_name = "Apr"; break;
-		  case Calendar.MAY: month_name = "May"; break;
-		  case Calendar.JUNE: month_name = "Jun"; break;
-		  case Calendar.JULY: month_name = "Jul"; break;
-		  case Calendar.AUGUST: month_name = "Aug"; break;
-		  case Calendar.SEPTEMBER: month_name = "Sep"; break;
-		  case Calendar.OCTOBER: month_name = "Oct"; break;
-		  case Calendar.NOVEMBER: month_name = "Nov"; break;
-		  case Calendar.DECEMBER: month_name = "Dec"; break;
-		  default: month_name = "";
-		  }
-		  int day = current.get(Calendar.DAY_OF_MONTH);
-		  int hour = current.get(Calendar.HOUR_OF_DAY);
-		  int minute = current.get(Calendar.MINUTE);
-		  int second = current.get(Calendar.SECOND);
-		  int year = current.get(Calendar.YEAR);
-
-		  return day_name + " " + month_name + " " + day + " " + year + " " + Utility.pad(String.valueOf(hour), 2, '0', true) + ":" + Utility.pad(String.valueOf(minute), 2, '0', true) + ":" + Utility.pad(String.valueOf(second), 2, '0', true);
-	 }
-
-	 /** Retrieves and formats the depth field of the config file to four characters.
-	  * @param length The length of the desired string as an <i>int</i>.
-	  * @return A <strong>String</strong> representation of the mirroring depth padded to length characters.
-	  */
-	 public static String getDepthString(int length) {
-		  return pad("" + Gatherer.self.config.getInt("mirroring.depth", false), length);
-	 }
-	 /** Method which constructs the etc directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections etc directory, also as a <strong>String</strong>.
-	  */
-	 public static String getEtcDir(String col_dir) {
-		  return col_dir + ETC_DIR;
-	 }
-	 /** Method to retrieve an image icon with the given filename found in classpath or the resouces directory.
-	  *  @return The specified <strong>ImageIcon</strong>, or an error image replacement if no such images exists.
-	  */
-	 static public ImageIcon getImage(String filename) {
-		  ImageIcon image = null;
-		  try {
-				image = new ImageIcon(ClassLoader.getSystemResource("images/" + Gatherer.dictionary.get("Version") + File.separator + filename));
-		  }
-		  catch(Exception error_one) {
-				try {
-					 image = new ImageIcon(ClassLoader.getSystemResource("images/" + filename));
-				}
-				catch (Exception error_two) {
-					 if(Gatherer.dictionary != null) {
-						  File locale_image_file = new File(RES_DIR + Gatherer.dictionary.get("Version") + File.separator + filename);
-						  if(locale_image_file.exists()) {
-								image = new ImageIcon(locale_image_file.toString());
-						  }
-					 }
-					 if(image == null) {
-						  File image_file = new File(RES_DIR + filename);
-						  if(image_file.exists()) {
-								image = new ImageIcon(image_file.toString());
-						  }
-					 }
-				}
-		  }
-		  if(image == null) {
-				image = ERROR_ICON;
-		  }
-		  return image;
-	 }
-
-	 /** Method which constructs the import directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections import directory, also as a <strong>String</strong>.
-	  */
-	 public static String getImportDir(String col_dir) {
-		  return col_dir + IMPORT_DIR;
-	 }
-	 /** Method which constructs the index directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections index directory, also as a <strong>String</strong>.
-	  */
-	 static public String getIndexDir(String col_dir) {
-		  return col_dir + INDEX_DIR;
-	 }
-	 /** Method which constructs the log directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections log directory, also as a <strong>String</strong>.
-	  */
-	 public static String getLogDir(String col_dir) {
-		  return col_dir + LOG_DIR;
-	 }
-	 /** Determine this machines name.
-	  * @return The name as a <strong>String</strong>.
-	  */
+	    while(pos < number_char.length) {
+		result.append(number_char[pos]);
+		pos++;
+	    }
+	}
+	// Add suffix
+	result.append(suffix);
+	// Done
+	return result.toString();
+    }
+
+    /** This method formats a given string, using HTML markup, so its width does not exceed the given width and its appearance if justified.
+     * @param text The <strong>String</strong> requiring formatting.
+     * @param width The maximum width per line as an <i>int</i>.
+     * @return A <strong>String</strong> formatted so as to have no line longer than the specified width.
+     * TODO Currently HTML formatting tags are simply removed from the text, as the effects of spreading HTML tags over a break are undetermined. To solve this we need to associate tags with a certain text token so if it gets broken on to the next line the tags go with it, or if the tags cover a sequence of words that are broken we need to close then reopen the tags. However all this is a major task and well beyond anything I have time to 'muck-round' on.
+     */
+    static public String formatHTMLWidth(String text, int width) {
+	HTMLStringTokenizer html = new HTMLStringTokenizer(text);
+	int current_width = 0;
+	int threshold = width / 2;
+	Stack lines = new Stack();
+	String line = "";
+	while(html.hasMoreTokens()) {
+	    String token = html.nextToken();
+	    while(token != null) {
+		if(html.isTag()) {
+		    // Insert smart HTML tag code here.
+		    token = null;
+		}
+		else {
+		    // If the token is bigger than two thirds width, before we've even started break it down.
+		    if(current_width + 1 + token.length() > width && token.length() > threshold) {
+			String prefix = token.substring(0, width - 1 - current_width);
+			token = token.substring(prefix.length());
+			if(current_width == 0) {
+			    line = line + prefix;
+			}
+			else {
+			    line = line + " " + prefix;
+			}
+			lines.push(line);
+			line = "";
+			current_width = 0;
+		    }
+		    // If adding the next token would push us over the maximum line width.
+		    else if(current_width + 1 + token.length() > width) {
+			line = space(line, width, current_width);
+			lines.push(line);
+			line = token;
+			current_width = token.length();
+			token = null;
+		    }
+		    // Otherwise we should be able to just add the token, give or take.
+		    else {
+			if(current_width == 0) {
+			    line = line + token;
+			    current_width = token.length();
+			}
+			else {
+			    // Special case for standard punctuation which may exist after a tag like so:
+			    // My name is <scratchy>Slim Shady</scratchy>.   <-- Annoying punctuation.
+			    if(token.equals(".") || token.equals(",") || token.equals("!") || token.equals("?")) {
+				line = line + token;
+				current_width = current_width + 1;
+			    }
+			    else {
+				line = line + " " + token;
+				current_width = current_width + 1 + token.length();
+			    }
+			}
+			token = null;
+		    }
+		}
+	    }
+	}
+	String result = line;
+	while(!lines.empty()) {
+	    result = (String)lines.pop() + "<BR>" + result;
+	}
+	// Replace ' ' with "&nbsp;"
+	boolean tag = false;
+	int pos = 0;
+	while(pos < result.length()) {
+	    if(result.charAt(pos) == '<') {
+		tag = true;
+	    }
+	    else if(result.charAt(pos) == '>') {
+		tag = false;
+	    }
+	    else if(result.charAt(pos) == ' ' && !tag) {
+		String prefix = result.substring(0, pos);
+		String suffix = result.substring(pos + 1);
+		result = prefix + "&nbsp;" + suffix;
+	    }
+	    pos++;
+	}
+	result = "<HTML>" + result + "</HTML>";
+	return result;
+    }
+    /** Format the given filename path string so that it is no longer than the given width. If it is wider replace starting directories with ...
+     * @param key The key <strong>String</Strong> used to retrieve a phrase from the dictionary for this item.
+     * @param raw The raw filename path <strong>String</strong>.
+     * @param width The maximum width as an <i>int</i>.
+     * @return A path <strong>String</strong> no longer than width.
+     */
+    static public String formatPath(String key, String raw, int width) {
+	JLabel label = new JLabel(Gatherer.dictionary.get(key, raw));
+	int position = -1;
+	while(label.getPreferredSize().width > width && (position = raw.indexOf(File.separator)) != -1) {
+	    raw = "..." + raw.substring(position + 1);
+	    label.setText(Gatherer.dictionary.get(key, raw));
+	}
+	if(raw.indexOf(File.separator) == -1 && raw.startsWith("...")) {
+	    raw = raw.substring(3);
+	}
+	return raw;
+    }
+
+    /** Method which constructs the archive directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections archive directory, also as a <strong>String</strong>.
+     */
+    static public String getArchiveDir(String gsdl_path, String col_name) {
+	return gsdl_path + File.separator + COL_DIR + col_name + File.separator + ARCHIVE_DIR;
+    }
+    /** Method which constructs the build directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections build directory, also as a <strong>String</strong>.
+     */
+    static public String getBuildDir(String col_dir) {
+	if(col_dir == null) {
+	    return BASE_DIR + BUILD_DIR;
+	}
+	return col_dir + BUILD_DIR;
+    }
+    /** Builds the private cache dir by appending col_dir and 'cache'.
+     * @param col_dir A String representing the directory path of the current collection.
+     * @return A String representing the path to the private file cache within the current collection.
+     */
+    public static String getCacheDir(String col_dir) {
+	return col_dir + GCACHE_DIR;
+    }
+    /** Method which constructs the collection directory for Greenstone.
+     * @param gsdl_path The location of the gsdl installation directory as a <strong>String</strong>.
+     * @return The location of the collection directory, also as a <strong>String</strong>.
+     */
+    public static String getCollectionDir(String gsdl_path) {
+	return gsdl_path + COL_DIR;
+    }
+    /** Method which constructs the configuration file given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections configuration file, also as a <strong>String</strong>.
+     */
+    static public String getConfigDir(String col_dir) {
+	return col_dir + CONFIG_DIR;
+    }
+
+    static public String getDateString() {
+	Calendar current = Calendar.getInstance();
+	String day_name = null;
+	switch(current.get(Calendar.DAY_OF_WEEK)) {
+	case Calendar.MONDAY: day_name = "Mon"; break;
+	case Calendar.TUESDAY: day_name = "Tue"; break;
+	case Calendar.WEDNESDAY: day_name = "Wed"; break;
+	case Calendar.THURSDAY: day_name = "Thu"; break;
+	case Calendar.FRIDAY: day_name = "Fri"; break;
+	case Calendar.SATURDAY: day_name = "Sat"; break;
+	case Calendar.SUNDAY: day_name = "Sun"; break;
+	default: day_name = "";
+	}
+	String month_name = null;
+	switch(current.get(Calendar.MONTH)) {
+	case Calendar.JANUARY: month_name = "Jan"; break;
+	case Calendar.FEBRUARY: month_name = "Feb"; break;
+	case Calendar.MARCH: month_name = "Mar"; break;
+	case Calendar.APRIL: month_name = "Apr"; break;
+	case Calendar.MAY: month_name = "May"; break;
+	case Calendar.JUNE: month_name = "Jun"; break;
+	case Calendar.JULY: month_name = "Jul"; break;
+	case Calendar.AUGUST: month_name = "Aug"; break;
+	case Calendar.SEPTEMBER: month_name = "Sep"; break;
+	case Calendar.OCTOBER: month_name = "Oct"; break;
+	case Calendar.NOVEMBER: month_name = "Nov"; break;
+	case Calendar.DECEMBER: month_name = "Dec"; break;
+	default: month_name = "";
+	}
+	int day = current.get(Calendar.DAY_OF_MONTH);
+	int hour = current.get(Calendar.HOUR_OF_DAY);
+	int minute = current.get(Calendar.MINUTE);
+	int second = current.get(Calendar.SECOND);
+	int year = current.get(Calendar.YEAR);
+
+	return day_name + " " + month_name + " " + day + " " + year + " " + Utility.pad(String.valueOf(hour), 2, '0', true) + ":" + Utility.pad(String.valueOf(minute), 2, '0', true) + ":" + Utility.pad(String.valueOf(second), 2, '0', true);
+    }
+
+    /** Retrieves and formats the depth field of the config file to four characters.
+     * @param length The length of the desired string as an <i>int</i>.
+     * @return A <strong>String</strong> representation of the mirroring depth padded to length characters.
+     */
+    public static String getDepthString(int length) {
+	return pad("" + Gatherer.self.config.getInt("mirroring.depth", false), length);
+    }
+    /** Method which constructs the etc directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections etc directory, also as a <strong>String</strong>.
+     */
+    public static String getEtcDir(String col_dir) {
+	return col_dir + ETC_DIR;
+    }
+    /** Method to retrieve an image icon with the given filename found in classpath or the resouces directory.
+     *  @return The specified <strong>ImageIcon</strong>, or an error image replacement if no such images exists.
+     */
+    static public ImageIcon getImage(String filename) {
+	ImageIcon image = null;
+	try {
+	    image = new ImageIcon(ClassLoader.getSystemResource("images/" + Gatherer.dictionary.get("Version") + File.separator + filename));
+	}
+	catch(Exception error_one) {
+	    try {
+		image = new ImageIcon(ClassLoader.getSystemResource("images/" + filename));
+	    }
+	    catch (Exception error_two) {
+		if(Gatherer.dictionary != null) {
+		    File locale_image_file = new File(RES_DIR + Gatherer.dictionary.get("Version") + File.separator + filename);
+		    if(locale_image_file.exists()) {
+			image = new ImageIcon(locale_image_file.toString());
+		    }
+		}
+		if(image == null) {
+		    File image_file = new File(RES_DIR + filename);
+		    if(image_file.exists()) {
+			image = new ImageIcon(image_file.toString());
+		    }
+		}
+	    }
+	}
+	if(image == null) {
+	    image = ERROR_ICON;
+	}
+	return image;
+    }
+
+    /** Method which constructs the import directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections import directory, also as a <strong>String</strong>.
+     */
+    public static String getImportDir(String col_dir) {
+	return col_dir + IMPORT_DIR;
+    }
+    /** Method which constructs the index directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections index directory, also as a <strong>String</strong>.
+     */
+    static public String getIndexDir(String col_dir) {
+	return col_dir + INDEX_DIR;
+    }
+    /** Method which constructs the log directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections log directory, also as a <strong>String</strong>.
+     */
+    public static String getLogDir(String col_dir) {
+	return col_dir + LOG_DIR;
+    }
+    /** Determine this machines name.
+     * @return The name as a <strong>String</strong>.
+     */
     static public String getMachineName() {
-		  try {
-				return InetAddress.getLocalHost().getHostName();
-		  }
-		  catch(UnknownHostException ex) {
-		  }
-		  return "Unknown Machine";
-	 }
-	 /** Method which constructs the metadata directory given a certain collection.
-	  * @param col_dir The location of the collection directory as a <strong>String</strong>.
-	  * @return The location of the given collections metadata directory, also as a <strong>String</strong>.
-	  */
-	 static public String getMetadataDir(String col_dir) {
-		  return col_dir + META_DIR;
-	 }
-
-
-	 static public File getRecycleDirectory() {
-		  return new File(RECYCLE);
-	 }
-
-	 /** Determine whether a character is a hexidecimal one.
-	  * @param chr The <i>char</i> in question.
-	  * @return An <i>int</i> representing the value of the hexidecimal character or -1 if not a hexidecimal.
-	  */
-	 public static int hexidecimal(char chr) {
-		  switch(chr) {
-		  case '0':
-				return 0;
-		  case '1':
-				return 1;
-		  case '2':
-				return 2;
-		  case '3':
-				return 3;
-		  case '4':
-				return 4;
-		  case '5':
-				return 5;
-		  case '6':
-				return 6;
-		  case '7':
-				return 7;
-		  case '8':
-				return 8;
-		  case '9':
-				return 9;
-		  case 'A':
-				return 10;
-		  case 'B':
-				return 11;
-		  case 'C':
-				return 12;
-		  case 'D':
-				return 13;
-		  case 'E':
-				return 14;
-		  case 'F':
-				return 15;
-		  default:
-				return -1;
-		  }
-	 }
-
-	 /** A string is a valid hierarchy index if it matches '[0-9](\.[0-9])*' */
-	 static public boolean isIndex(String raw) {
-		  boolean result = true;
-		  for(int i = 0; result && i < raw.length(); i++) {
-				char c = raw.charAt(i);
-				if(Character.isDigit(c) || (c == '.' && (i != 0 || i != raw.length() - 1))) {
-					 // Valid index
-				}
-				else {
-					 result = false;
-				}
-		  }
-		  return result;
-	 }
-
-	 /** Method to determine if the host system is Microsoft Windows based.
-	  * @return A <i>boolean</i> which is <i>true</i> if the platform is Windows, <i>false</i> otherwise.
-	  */
-	 public static boolean isWindows() {
-		  Properties props = System.getProperties();
-		  String os_name = props.getProperty("os.name","");
-		  if(os_name.startsWith("Windows")) {
-				return true;
-		  }
-		  return false;
-	 }
-	 /** Takes a string and a desired length and pads out the string to the length by adding spaces to the left.
-	  * @param str The target <strong>String</strong> that needs to be padded.
-	  * @param length The desired length of the string as an <i>int</i>.
-	  * @return A <strong>String</strong> made from appending space characters with the string until it has a length equal to length.
-	  */
-	 public static String pad(String str, int length) {
-		  return pad(str, length, ' ', true);
-	 }
-	 public static String pad(String str_raw, int length, char fill, boolean end) {
-		  StringBuffer str = new StringBuffer(str_raw);
-		  while(str.length() < length) {
-				if(end) {
-					 str.insert(0, fill);
-				}
-				else {
-					 str.append(fill);
-				}
-		  }
-		  return str.toString();
-	 }
-
-	 /** Parse in a xml document from a given filename. Note that this filename may need to be resolved by the class loader, especially for template files within a jar. */
-	 static public Document parse(String filename, boolean use_classloader) {
-		  File file = null;
-		  if(use_classloader) {
-				try {
-					 URL url = ClassLoader.getSystemResource(filename);
-					 file = new File(URLDecoder.decode(url.getFile()));
-					 url = null;
-				}
-				catch (Exception error) {
-					 // Most likely file name.
-					 file = new File("classes" + File.separator + filename);
-					 //Gatherer.printStackTrace(error);
-				}
-		  }
-		  if(file == null) {
-				file = new File(filename);
-		  }
-		  return parse(file, true);
-	 }
-	 /** Parse in a xml document from a given file. */
-	 static public Document parse(File file) {
-		  return parse(file, true);
-	 }
-	 /** Parse in a xml document from a given file. */
-	 static public Document parse(File file, boolean noisey) {
-		  Document document = null;
-		  try {
-				FileInputStream fis   = new FileInputStream(file);
-				InputStreamReader isr = new InputStreamReader(fis);
-				Reader r              = new BufferedReader(isr);
-				InputSource isc       = new InputSource(r);
-				DOMParser parser      = new DOMParser();
-				parser.setFeature("http://xml.org/sax/features/validation", false);
-				parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+	try {
+	    return InetAddress.getLocalHost().getHostName();
+	}
+	catch(UnknownHostException ex) {
+	}
+	return "Unknown Machine";
+    }
+    /** Method which constructs the metadata directory given a certain collection.
+     * @param col_dir The location of the collection directory as a <strong>String</strong>.
+     * @return The location of the given collections metadata directory, also as a <strong>String</strong>.
+     */
+    static public String getMetadataDir(String col_dir) {
+	return col_dir + META_DIR;
+    }
+
+
+    static public File getRecycleDirectory() {
+	return new File(RECYCLE);
+    }
+
+    /** Determine whether a character is a hexidecimal one.
+     * @param chr The <i>char</i> in question.
+     * @return An <i>int</i> representing the value of the hexidecimal character or -1 if not a hexidecimal.
+     */
+    public static int hexidecimal(char chr) {
+	switch(chr) {
+	case '0':
+	    return 0;
+	case '1':
+	    return 1;
+	case '2':
+	    return 2;
+	case '3':
+	    return 3;
+	case '4':
+	    return 4;
+	case '5':
+	    return 5;
+	case '6':
+	    return 6;
+	case '7':
+	    return 7;
+	case '8':
+	    return 8;
+	case '9':
+	    return 9;
+	case 'A':
+	    return 10;
+	case 'B':
+	    return 11;
+	case 'C':
+	    return 12;
+	case 'D':
+	    return 13;
+	case 'E':
+	    return 14;
+	case 'F':
+	    return 15;
+	default:
+	    return -1;
+	}
+    }
+
+    /** A string is a valid hierarchy index if it matches '[0-9](\.[0-9])*' */
+    static public boolean isIndex(String raw) {
+	boolean result = true;
+	for(int i = 0; result && i < raw.length(); i++) {
+	    char c = raw.charAt(i);
+	    if(Character.isDigit(c) || (c == '.' && (i != 0 || i != raw.length() - 1))) {
+		// Valid index
+	    }
+	    else {
+		result = false;
+	    }
+	}
+	return result;
+    }
+
+    /** Method to determine if the host system is Microsoft Windows based.
+     * @return A <i>boolean</i> which is <i>true</i> if the platform is Windows, <i>false</i> otherwise.
+     */
+    public static boolean isWindows() {
+	Properties props = System.getProperties();
+	String os_name = props.getProperty("os.name","");
+	if(os_name.startsWith("Windows")) {
+	    return true;
+	}
+	return false;
+    }
+    /** Takes a string and a desired length and pads out the string to the length by adding spaces to the left.
+     * @param str The target <strong>String</strong> that needs to be padded.
+     * @param length The desired length of the string as an <i>int</i>.
+     * @return A <strong>String</strong> made from appending space characters with the string until it has a length equal to length.
+     */
+    public static String pad(String str, int length) {
+	return pad(str, length, ' ', true);
+    }
+    public static String pad(String str_raw, int length, char fill, boolean end) {
+	StringBuffer str = new StringBuffer(str_raw);
+	while(str.length() < length) {
+	    if(end) {
+		str.insert(0, fill);
+	    }
+	    else {
+		str.append(fill);
+	    }
+	}
+	return str.toString();
+    }
+
+    /** Parse in a xml document from a given filename. Note that this filename may need to be resolved by the class loader, especially for template files within a jar. */
+    static public Document parse(String filename, boolean use_classloader) {
+	File file = null;
+	if(use_classloader) {
+	    try {
+		URL url = ClassLoader.getSystemResource(filename);
+		file = new File(URLDecoder.decode(url.getFile()));
+		url = null;
+	    }
+	    catch (Exception error) {
+		// Most likely file name.
+		file = new File("classes" + File.separator + filename);
+		//Gatherer.printStackTrace(error);
+	    }
+	}
+	if(file == null) {
+	    file = new File(filename);
+	}
+	return parse(file, true);
+    }
+    /** Parse in a xml document from a given file. */
+    static public Document parse(File file) {
+	return parse(file, true);
+    }
+    /** Parse in a xml document from a given file. */
+    static public Document parse(File file, boolean noisey) {
+	Document document = null;
+	try {
+	    FileInputStream fis   = new FileInputStream(file);
+	    InputStreamReader isr = new InputStreamReader(fis);
+	    Reader r              = new BufferedReader(isr);
+	    InputSource isc       = new InputSource(r);
+	    DOMParser parser      = new DOMParser();
+	    parser.setFeature("http://xml.org/sax/features/validation", false);
+	    parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
 				// May or may not be ignored, the documentation for Xerces is contradictory. If it works then parsing -should- be faster.
-				parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true);
-				parser.setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false);
-				parser.parse(isc);
-				document = parser.getDocument();
-				isr.close();
-				fis.close();
-				parser = null;
-				isc = null;
-				r = null;
-				isr = null;
-				fis = null;
-				file = null;
-		  }
-		  catch (Exception error) {
-				if(noisey) {
-					 Gatherer.printStackTrace(error);
-				}
-		  }
-		  return document;
-	 }
-
-	 /** Method to spread out a line of text so that is is justified to the given width, by attempting to widen white-spacing in a balanced way.
-	  * @param original The <strong>String</strong> to justify.
-	  * @param width The desired width as an <i>int</i>.
-	  * @param current_width An <i>int</i> representing the current width of the string, which takes into account special characters.
-	  * @return The newly justified <strong>String</strong>.
-	  */
-	 static public String space(String original, int width, int current_width) {
-		  // Strip trailing whitespace.
-		  while(original.charAt(original.length() - 1) == ' ') {
-				original = original.substring(0, original.length() - 2);
-		  }
-		  int diff = width - current_width;
-		  // Now add diff spaces, one at each existing space.
-		  int pos = 0;
-		  while(diff > 0) {
-				if(pos == original.length()) {
-					 pos = 0;
-				}
-				if(original.charAt(pos) == ' ') {
-					 // Insert a space.
-					 String prefix = original.substring(0, pos);
-					 String suffix = original.substring(pos);
-					 original = prefix + " " + suffix;
-					 pos = pos + 2;
-					 diff--;
-				}
-				pos++;
-		  }
-		  return original;
-	 }
-	 /** Method to strip new lines and extra spaces from a string. Used to restore text that has been mangled into width formatted blocks by the DOM parser.
-	  * @param raw The <strong>Strong</strong> containing the mangled text.
-	  * @return A <strong>String</strong> with new lines and extra spaces removed.
-	  */
-	 static public String stripNL(String raw_str) {
-		  byte raw[] = raw_str.getBytes();
-		  byte formatted[] = new byte[raw.length];
-		  byte previous = '\0';
-		  int j = 0;
-		  for(int i = 0; i < raw.length; i++) {
-				if(raw[i] == '\n') {
-					 // Skip new lines.
-				}
-				else if(raw[i] == '\t') {
-					 // Skip tabs.
-				}
-				else if(raw[i] == ' ' && raw[i] == previous) {
-					 // Skip erroneous whitespace.
-				}
-				else {
-					 formatted[j] = raw[i];
-					 j++;
-				}
-				previous = raw[i];
-		  }
-		  byte finish[] = new byte[j];
-		  System.arraycopy(formatted, 0, finish, 0, j);
-		  return new String(finish);
-	 }
-	 /** Trims the string text to the length specified removing end characters and adding if necessary.
-	  * @param text A <strong>String</strong> which you wish to ensure is shorter than length.
-	  * @param length An <i>int</i> specifying the strings maximum length after which its trimmed.
-	  * @return The trimmed <strong>String</strong>.
-	  */
-	 public static String trim(String text, int length) {
-		  if(text.length() > length) {
-				text = text.substring(0, length);
-				text = text + "...";
-		  }
-		  return text;
-	 }
-
-	 static public String trimCenter(String text, int length) {
-		  if(text.length() > length) {
-				int half = (length - 3) / 2;
-				StringBuffer temp = new StringBuffer(text.substring(0, half));
-				temp.append("...");
-				temp.append(text.substring(text.length() - half));
-				text = temp.toString();
-		  }
-		  return text;
-	 }
-	 /** This method checks to see what registered file system root directorys are mounted, and returns only accessible ones. The exception is removable media drives (in particular floppy-disk drives) which will throw all sorts of error if we test them here. Instead they are assumed to be always accessible, but a test is conducted at the time you attempt to map them to test for actual accessibility (then at least the errors are thrown after the user tries to initiate the mapping of the drive which has no disk in it).
-	  * @param roots A <strong>File[]</strong> containing all of the file system roots registered on this system.
-	  * @return A filtered <strong>File[]</strong> containing only those drives that are accessible and/or are floppy-disk media drives.
-	  */
-	 public static File[] validateDrives(File roots[]) {
-		  Vector valid = new Vector();
-		  for(int i = 0; i < roots.length; i++) {
-				String name = roots[i].getAbsolutePath();
-				name = name.toLowerCase();
-				if(!name.startsWith("a:") && !name.startsWith("b:")) {
-					 valid.add(roots[i]);
-				}
-		  }
-		  roots = new File[valid.size()];
-		  for(int i = 0; i < roots.length; i++) {
-				roots[i] = (File)valid.get(i);
-		  }
-		  return roots;
-	 }
+	    parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true);
+	    parser.setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false);
+	    parser.parse(isc);
+	    document = parser.getDocument();
+	    isr.close();
+	    fis.close();
+	    parser = null;
+	    isc = null;
+	    r = null;
+	    isr = null;
+	    fis = null;
+	    file = null;
+	}
+	catch (Exception error) {
+	    if(noisey) {
+		Gatherer.printStackTrace(error);
+	    }
+	}
+	return document;
+    }
+
+    /** Method to spread out a line of text so that is is justified to the given width, by attempting to widen white-spacing in a balanced way.
+     * @param original The <strong>String</strong> to justify.
+     * @param width The desired width as an <i>int</i>.
+     * @param current_width An <i>int</i> representing the current width of the string, which takes into account special characters.
+     * @return The newly justified <strong>String</strong>.
+     */
+    static public String space(String original, int width, int current_width) {
+	// Strip trailing whitespace.
+	while(original.charAt(original.length() - 1) == ' ') {
+	    original = original.substring(0, original.length() - 2);
+	}
+	int diff = width - current_width;
+	// Now add diff spaces, one at each existing space.
+	int pos = 0;
+	while(diff > 0) {
+	    if(pos == original.length()) {
+		pos = 0;
+	    }
+	    if(original.charAt(pos) == ' ') {
+		// Insert a space.
+		String prefix = original.substring(0, pos);
+		String suffix = original.substring(pos);
+		original = prefix + " " + suffix;
+		pos = pos + 2;
+		diff--;
+	    }
+	    pos++;
+	}
+	return original;
+    }
+    /** Method to strip new lines and extra spaces from a string. Used to restore text that has been mangled into width formatted blocks by the DOM parser.
+     * @param raw The <strong>Strong</strong> containing the mangled text.
+     * @return A <strong>String</strong> with new lines and extra spaces removed.
+     */
+    static public String stripNL(String raw_str) {
+	byte raw[] = raw_str.getBytes();
+	byte formatted[] = new byte[raw.length];
+	byte previous = '\0';
+	int j = 0;
+	for(int i = 0; i < raw.length; i++) {
+	    if(raw[i] == '\n') {
+		// Skip new lines.
+	    }
+	    else if(raw[i] == '\t') {
+		// Skip tabs.
+	    }
+	    else if(raw[i] == ' ' && raw[i] == previous) {
+		// Skip erroneous whitespace.
+	    }
+	    else {
+		formatted[j] = raw[i];
+		j++;
+	    }
+	    previous = raw[i];
+	}
+	byte finish[] = new byte[j];
+	System.arraycopy(formatted, 0, finish, 0, j);
+	return new String(finish);
+    }
+    /** Trims the string text to the length specified removing end characters and adding if necessary.
+     * @param text A <strong>String</strong> which you wish to ensure is shorter than length.
+     * @param length An <i>int</i> specifying the strings maximum length after which its trimmed.
+     * @return The trimmed <strong>String</strong>.
+     */
+    public static String trim(String text, int length) {
+	if(text.length() > length) {
+	    text = text.substring(0, length);
+	    text = text + "...";
+	}
+	return text;
+    }
+
+    static public String trimCenter(String text, int length) {
+	if(text.length() > length) {
+	    int half = (length - 3) / 2;
+	    StringBuffer temp = new StringBuffer(text.substring(0, half));
+	    temp.append("...");
+	    temp.append(text.substring(text.length() - half));
+	    text = temp.toString();
+	}
+	return text;
+    }
+    /** This method checks to see what registered file system root directorys are mounted, and returns only accessible ones. The exception is removable media drives (in particular floppy-disk drives) which will throw all sorts of error if we test them here. Instead they are assumed to be always accessible, but a test is conducted at the time you attempt to map them to test for actual accessibility (then at least the errors are thrown after the user tries to initiate the mapping of the drive which has no disk in it).
+     * @param roots A <strong>File[]</strong> containing all of the file system roots registered on this system.
+     * @return A filtered <strong>File[]</strong> containing only those drives that are accessible and/or are floppy-disk media drives.
+     */
+    public static File[] validateDrives(File roots[]) {
+	Vector valid = new Vector();
+	for(int i = 0; i < roots.length; i++) {
+	    String name = roots[i].getAbsolutePath();
+	    name = name.toLowerCase();
+	    if(!name.startsWith("a:") && !name.startsWith("b:")) {
+		valid.add(roots[i]);
+	    }
+	}
+	roots = new File[valid.size()];
+	for(int i = 0; i < roots.length; i++) {
+	    roots[i] = (File)valid.get(i);
+	}
+	return roots;
+    }
 }
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/util/WinRegistry.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/WinRegistry.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/WinRegistry.java	(revision 4364)
@@ -59,5 +59,5 @@
     /** A constant used to interpret the return of a native function as successful. */
     private final static int NATIVE_HANDLE = 0;
-	 /** A constant used to interpret the return of a native function as an error. */
+    /** A constant used to interpret the return of a native function as an error. */
     private final static int ERROR_CODE = 1;
     /** Windows error codes. */
@@ -65,184 +65,184 @@
     /** HKEY_LOCAL_MACHINE address. */
     private final static int HKEY_LOCAL_MACHINE = 0x80000002;
-	 /** Called to build a command string that will launch the given file in its associated program according to the windows registry.
-	  * @param filename The name of a file, including the required extension, as a <strong>String</strong>.
-	  * @return Another <strong>String</strong> containing a command line which, when run in a command shell, will launch an external application to view this file.
-	  */
+    /** Called to build a command string that will launch the given file in its associated program according to the windows registry.
+     * @param filename The name of a file, including the required extension, as a <strong>String</strong>.
+     * @return Another <strong>String</strong> containing a command line which, when run in a command shell, will launch an external application to view this file.
+     */
     public static String openCommand(String filename) {
-		  if(filename.indexOf(".") != -1) {
-				String extension = filename.substring(filename.indexOf("."));
-				try {
-					 // First find the name of the file type that is associated with this extension.
-					 String file_type = getValue("Software\\CLASSES\\" + extension);
-					 // Then if we got a type, get the open command from its shell reference.
-					 ///ystem.err.println("Searching for file-type = " + file_type);
-					 if(file_type != null) {
-						  String raw = getValue("Software\\CLASSES\\" + file_type + "\\shell\\open\\command");
-						  //Replace "%1" with "filename"
-						  if(raw.indexOf("%1") != -1) {
-								String prefix = raw.substring(0, raw.indexOf("%1"));
-								if(!prefix.endsWith("\"")) {
-									 prefix = prefix + "\"";
-								}
-								String suffix = raw.substring(raw.indexOf("%1") + 2);
-								if(!suffix.endsWith("\"")) {
-									 suffix = suffix + "\"";
-								}
-								return prefix + filename + suffix;
-						  }
-						  // Arg. There is a windows flag called /dde, which if it exists in a command tells it to use the filename (hence the meaningful name dde eh?)
-						  else if(raw.indexOf("/dde") != -1) {
-								String prefix = raw.substring(0, raw.indexOf("/dde"));
-								if(!prefix.endsWith("\"")) {
-									 prefix = prefix + "\"";
-								}
-								String suffix = raw.substring(raw.indexOf("/dde") + 4);
-								if(!suffix.endsWith("\"")) {
-									 suffix = suffix + "\"";
-								}
-								return prefix + filename + suffix;
-						  }
-						  // No file parameter. Append the file to the command and hope for the best.
-						  return raw + " \"" + filename + "\"";
-					 }
-				}
-				catch (Exception error) {
-					 ///ystem.err.println("No such value.");
-					 //error.printStackTrace();
-				}
-		  }
-		  return null;
-	 }
-	 /** Attempts to retrieve the value at the location given from the registry using Reflection.
-	  * @param path The path to the desired key as a <strong>String</strong>.
-	  * @return The value found at the indicated key as a <strong>String</strong>.
-	  */ 
-	 private static String getValue(String path)
-		  throws ClassNotFoundException, Exception, NoSuchMethodException {
-		  ///ystem.err.println("Get value " + path);
-		  byte[] WINDOWS_ROOT_PATH = stringToByteArray(path);
-		  Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
-		  int[] result = openKey(HKEY_LOCAL_MACHINE, WINDOWS_ROOT_PATH, KEY_QUERY_VALUE);
-		  if (result[ERROR_CODE] != ERROR_SUCCESS) {
-				throw new Exception("Path not found!");
-		  }
-		  int native_handle = result[NATIVE_HANDLE];
-		  Method m = theClass.getDeclaredMethod("WindowsRegQueryValueEx", new Class[]{int.class, byte[].class});
-		  m.setAccessible(true);
-		  byte[] windows_name = toWindowsName("");
-		  Object value = m.invoke(null, new Object[]{new Integer(native_handle), windows_name});
-		  WindowsRegCloseKey(native_handle);
-		  if (value == null) {
-				throw new Exception("Path found. Key not found.");
-		  }
-		  byte[] origBuffer = (byte[]) value;
-		  byte[] destBuffer = new byte[origBuffer.length - 1];
-		  System.arraycopy(origBuffer, 0, destBuffer, 0, origBuffer.length - 1);
-		  return new String(destBuffer);
-	 }
-	 /** Closes the reference (and hence the instantiated objects) to a certain entry within the registry. Do this or suffer massive memory leaks.
-	  * @param nativeHandle A reference to the piece of memory containing an inspected windows key-value pair.
-	  * @return The status code returned from closing the key, as an <i>int</i>.
-	  */
-	 public static int WindowsRegCloseKey(int nativeHandle)
-		  throws Exception {
-		  Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
-		  Method m = theClass.getDeclaredMethod("WindowsRegCloseKey", new Class[]{int.class});
-		  m.setAccessible(true);
-		  Object ret = m.invoke(null, new Object[]{new Integer(nativeHandle)});
-		  return ((Integer) ret).intValue();
-	 }
-	 /** Attempts to open a certain key-value pair from the windows registry, from within a certain region (or HKEY). 
-	  * @param hkey An <i>int</i> which is the offset for a certain group within the registry.
-	  * @param windowsAbsolutePath A <i>byte[]</i> containing the path to the desired key, relative to the given HKEY.
-	  * @param securityMask A mask needed to read values from the registry, as an <i>int</i>.
-	  * @return An <i>int[]</i> pair, the first of which is the reference to the indicated key if no error occured, and the second of which reports the end error state of the registry open attempt.
-	  */
-	 public static int[] openKey(int hkey, byte[] windowsAbsolutePath, int securityMask)
-		  throws Exception {
-		  Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
-		  Method m = theClass.getDeclaredMethod("WindowsRegOpenKey", new Class[]{int.class, byte[].class, int.class});
-		  m.setAccessible(true);
-		  Object ret = m.invoke(null, new Object[]{new Integer(hkey), windowsAbsolutePath, new Integer(securityMask)});
-		  return (int[]) ret;
-	 }
-	 /** Converts a string to a byte array. Duh!
-	  * @param str The <strong>String</strong> to be converted.
-	  * @return A <i>byte[]</i> containing orderer characters from the String.
-	  */
-	 private static byte[] stringToByteArray(String str) {
-		  byte[] result = new byte[str.length() + 1];
-		  for (int i = 0; i < str.length(); i++) {
-				result[i] = (byte) str.charAt(i);
-		  }
-		  result[str.length()] = 0;
-		  return result;
-	 }
+	if(filename.indexOf(".") != -1) {
+	    String extension = filename.substring(filename.indexOf("."));
+	    try {
+		// First find the name of the file type that is associated with this extension.
+		String file_type = getValue("Software\\CLASSES\\" + extension);
+		// Then if we got a type, get the open command from its shell reference.
+		///ystem.err.println("Searching for file-type = " + file_type);
+		if(file_type != null) {
+		    String raw = getValue("Software\\CLASSES\\" + file_type + "\\shell\\open\\command");
+		    //Replace "%1" with "filename"
+		    if(raw.indexOf("%1") != -1) {
+			String prefix = raw.substring(0, raw.indexOf("%1"));
+			if(!prefix.endsWith("\"")) {
+			    prefix = prefix + "\"";
+			}
+			String suffix = raw.substring(raw.indexOf("%1") + 2);
+			if(!suffix.endsWith("\"")) {
+			    suffix = suffix + "\"";
+			}
+			return prefix + filename + suffix;
+		    }
+		    // Arg. There is a windows flag called /dde, which if it exists in a command tells it to use the filename (hence the meaningful name dde eh?)
+		    else if(raw.indexOf("/dde") != -1) {
+			String prefix = raw.substring(0, raw.indexOf("/dde"));
+			if(!prefix.endsWith("\"")) {
+			    prefix = prefix + "\"";
+			}
+			String suffix = raw.substring(raw.indexOf("/dde") + 4);
+			if(!suffix.endsWith("\"")) {
+			    suffix = suffix + "\"";
+			}
+			return prefix + filename + suffix;
+		    }
+		    // No file parameter. Append the file to the command and hope for the best.
+		    return raw + " \"" + filename + "\"";
+		}
+	    }
+	    catch (Exception error) {
+		///ystem.err.println("No such value.");
+		//error.printStackTrace();
+	    }
+	}
+	return null;
+    }
+    /** Attempts to retrieve the value at the location given from the registry using Reflection.
+     * @param path The path to the desired key as a <strong>String</strong>.
+     * @return The value found at the indicated key as a <strong>String</strong>.
+     */ 
+    private static String getValue(String path)
+	throws ClassNotFoundException, Exception, NoSuchMethodException {
+	///ystem.err.println("Get value " + path);
+	byte[] WINDOWS_ROOT_PATH = stringToByteArray(path);
+	Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
+	int[] result = openKey(HKEY_LOCAL_MACHINE, WINDOWS_ROOT_PATH, KEY_QUERY_VALUE);
+	if (result[ERROR_CODE] != ERROR_SUCCESS) {
+	    throw new Exception("Path not found!");
+	}
+	int native_handle = result[NATIVE_HANDLE];
+	Method m = theClass.getDeclaredMethod("WindowsRegQueryValueEx", new Class[]{int.class, byte[].class});
+	m.setAccessible(true);
+	byte[] windows_name = toWindowsName("");
+	Object value = m.invoke(null, new Object[]{new Integer(native_handle), windows_name});
+	WindowsRegCloseKey(native_handle);
+	if (value == null) {
+	    throw new Exception("Path found. Key not found.");
+	}
+	byte[] origBuffer = (byte[]) value;
+	byte[] destBuffer = new byte[origBuffer.length - 1];
+	System.arraycopy(origBuffer, 0, destBuffer, 0, origBuffer.length - 1);
+	return new String(destBuffer);
+    }
+    /** Closes the reference (and hence the instantiated objects) to a certain entry within the registry. Do this or suffer massive memory leaks.
+     * @param nativeHandle A reference to the piece of memory containing an inspected windows key-value pair.
+     * @return The status code returned from closing the key, as an <i>int</i>.
+     */
+    public static int WindowsRegCloseKey(int nativeHandle)
+	throws Exception {
+	Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
+	Method m = theClass.getDeclaredMethod("WindowsRegCloseKey", new Class[]{int.class});
+	m.setAccessible(true);
+	Object ret = m.invoke(null, new Object[]{new Integer(nativeHandle)});
+	return ((Integer) ret).intValue();
+    }
+    /** Attempts to open a certain key-value pair from the windows registry, from within a certain region (or HKEY). 
+     * @param hkey An <i>int</i> which is the offset for a certain group within the registry.
+     * @param windowsAbsolutePath A <i>byte[]</i> containing the path to the desired key, relative to the given HKEY.
+     * @param securityMask A mask needed to read values from the registry, as an <i>int</i>.
+     * @return An <i>int[]</i> pair, the first of which is the reference to the indicated key if no error occured, and the second of which reports the end error state of the registry open attempt.
+     */
+    public static int[] openKey(int hkey, byte[] windowsAbsolutePath, int securityMask)
+	throws Exception {
+	Class theClass = Class.forName("java.util.prefs.WindowsPreferences");
+	Method m = theClass.getDeclaredMethod("WindowsRegOpenKey", new Class[]{int.class, byte[].class, int.class});
+	m.setAccessible(true);
+	Object ret = m.invoke(null, new Object[]{new Integer(hkey), windowsAbsolutePath, new Integer(securityMask)});
+	return (int[]) ret;
+    }
+    /** Converts a string to a byte array. Duh!
+     * @param str The <strong>String</strong> to be converted.
+     * @return A <i>byte[]</i> containing orderer characters from the String.
+     */
+    private static byte[] stringToByteArray(String str) {
+	byte[] result = new byte[str.length() + 1];
+	for (int i = 0; i < str.length(); i++) {
+	    result[i] = (byte) str.charAt(i);
+	}
+	result[str.length()] = 0;
+	return result;
+    }
 	 
-	 private static String toJavaValueString(byte[] windowsNameArray) {
-		  // Use modified native2ascii algorithm
-		  String windowsName = byteArrayToString(windowsNameArray);
-		  StringBuffer javaName = new StringBuffer();
-		  char ch;
-		  for (int i = 0; i < windowsName.length(); i++) {
-				if ((ch = windowsName.charAt(i)) == '/') {
-					 char next = ' ';
+    private static String toJavaValueString(byte[] windowsNameArray) {
+	// Use modified native2ascii algorithm
+	String windowsName = byteArrayToString(windowsNameArray);
+	StringBuffer javaName = new StringBuffer();
+	char ch;
+	for (int i = 0; i < windowsName.length(); i++) {
+	    if ((ch = windowsName.charAt(i)) == '/') {
+		char next = ' ';
 					 
-					 if (windowsName.length() > i + 1 &&
-						  (next = windowsName.charAt(i + 1)) == 'u') {
-						  if (windowsName.length() < i + 6) {
-								break;
-						  } else {
-								ch = (char) Integer.parseInt
-									 (windowsName.substring(i + 2, i + 6), 16);
-								i += 5;
-						  }
-					 } else
-						  if ((windowsName.length() > i + 1) &&
-								((windowsName.charAt(i + 1)) >= 'A') && (next <= 'Z')) {
-								ch = next;
-								i++;
-						  } else if ((windowsName.length() > i + 1) &&
-										 (next == '/')) {
-								ch = '\\';
-								i++;
-						  }
-				} else if (ch == '\\') {
-					 ch = '/';
-				}
-				javaName.append(ch);
-		  }
-		  return javaName.toString();
-	 }
-	 /** */
-	 private static String byteArrayToString(byte[] array) {
-		  StringBuffer result = new StringBuffer();
-		  for (int i = 0; i < array.length - 1; i++) {
-				result.append((char) array[i]);
-		  }
-		  return result.toString();
-	 }
-	 /** Magics the name of a certain path component from a Java String to the unique string windows requires.
-	  * @param javaName The name of a path component as a <strong>String</strong>.
-	  * @return A <i>byte[]</i> containing the translated string.
-	  */
-	 private static byte[] toWindowsName(String javaName) {
-		  StringBuffer windowsName = new StringBuffer();
-		  for (int i = 0; i < javaName.length(); i++) {
-				char ch = javaName.charAt(i);
-				if ((ch < 0x0020) || (ch > 0x007f)) {
-					 throw new RuntimeException("Unable to convert to Windows name");
-				}
-				if (ch == '\\') {
-					 windowsName.append("//");
-				} else if (ch == '/') {
-					 windowsName.append('\\');
-				} else if ((ch >= 'A') && (ch <= 'Z')) {
-					 windowsName.append("/" + ch);
-				} else {
-					 windowsName.append(ch);
-				}
-		  }
-		  return stringToByteArray(windowsName.toString());
-	 } 
+		if (windowsName.length() > i + 1 &&
+		    (next = windowsName.charAt(i + 1)) == 'u') {
+		    if (windowsName.length() < i + 6) {
+			break;
+		    } else {
+			ch = (char) Integer.parseInt
+			    (windowsName.substring(i + 2, i + 6), 16);
+			i += 5;
+		    }
+		} else
+		    if ((windowsName.length() > i + 1) &&
+			((windowsName.charAt(i + 1)) >= 'A') && (next <= 'Z')) {
+			ch = next;
+			i++;
+		    } else if ((windowsName.length() > i + 1) &&
+			       (next == '/')) {
+			ch = '\\';
+			i++;
+		    }
+	    } else if (ch == '\\') {
+		ch = '/';
+	    }
+	    javaName.append(ch);
+	}
+	return javaName.toString();
+    }
+    /** */
+    private static String byteArrayToString(byte[] array) {
+	StringBuffer result = new StringBuffer();
+	for (int i = 0; i < array.length - 1; i++) {
+	    result.append((char) array[i]);
+	}
+	return result.toString();
+    }
+    /** Magics the name of a certain path component from a Java String to the unique string windows requires.
+     * @param javaName The name of a path component as a <strong>String</strong>.
+     * @return A <i>byte[]</i> containing the translated string.
+     */
+    private static byte[] toWindowsName(String javaName) {
+	StringBuffer windowsName = new StringBuffer();
+	for (int i = 0; i < javaName.length(); i++) {
+	    char ch = javaName.charAt(i);
+	    if ((ch < 0x0020) || (ch > 0x007f)) {
+		throw new RuntimeException("Unable to convert to Windows name");
+	    }
+	    if (ch == '\\') {
+		windowsName.append("//");
+	    } else if (ch == '/') {
+		windowsName.append('\\');
+	    } else if ((ch >= 'A') && (ch <= 'Z')) {
+		windowsName.append("/" + ch);
+	    } else {
+		windowsName.append(ch);
+	    }
+	}
+	return stringToByteArray(windowsName.toString());
+    } 
 }
Index: /trunk/gli/src/org/greenstone/gatherer/util/XORToggleButtonGroup.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/util/XORToggleButtonGroup.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/util/XORToggleButtonGroup.java	(revision 4364)
@@ -4,47 +4,47 @@
 /** Just like any other button group, this object encapsulates several checkbox listeners, but instead of recording which are selected, or indeed what buttons it has assigned to it, this group has but one purpose: to ensure that at least one check button is always selected. */
 public class XORToggleButtonGroup
-	 implements ActionListener {
-	 /** true if we are to ignore any further change events we recieve. */
-	 private boolean ignore = false;
-	 /** The current number of checkboxes that are selected. Never < 1. */
-	 private int selected_count = 0;
+    implements ActionListener {
+    /** true if we are to ignore any further change events we recieve. */
+    private boolean ignore = false;
+    /** The current number of checkboxes that are selected. Never < 1. */
+    private int selected_count = 0;
 
-	 private JToggleButton last_button;
+    private JToggleButton last_button;
 
-	 /** Any implementation of ActionListener includes this method so that we can be informed when an action has occured on any registered checkboxes, allowing us to roll back the action if this is the only selected checkbox remaining.
+    /** Any implementation of ActionListener includes this method so that we can be informed when an action has occured on any registered checkboxes, allowing us to roll back the action if this is the only selected checkbox remaining.
 	  * @param event An <strong>ActionEvent</strong> containing information about the event.
 	  */
-	 public void actionPerformed(ActionEvent event) {
-		  if(!ignore) {
-				ignore = true;
-				JToggleButton toggle_button = (JToggleButton) event.getSource();
-				if(toggle_button.isSelected()) { 
-					 selected_count++;
-				}
-				else {
-					 if(selected_count > 1) {
-						  selected_count--;
-					 }
-					 else {
-						  toggle_button.setSelected(true);
-					 }
-				}
-				ignore = false;
-		  }
-	 }
-	 public void add(JToggleButton toggle_button) {
-		  last_button = toggle_button;
-		  // If the checkbox is selected, increase our count.
-		  if(toggle_button.isSelected()) {
-				selected_count++;
-		  }
-		  toggle_button.addActionListener(this);
-	 }
+    public void actionPerformed(ActionEvent event) {
+	if(!ignore) {
+	    ignore = true;
+	    JToggleButton toggle_button = (JToggleButton) event.getSource();
+	    if(toggle_button.isSelected()) { 
+		selected_count++;
+	    }
+	    else {
+		if(selected_count > 1) {
+		    selected_count--;
+		}
+		else {
+		    toggle_button.setSelected(true);
+		}
+	    }
+	    ignore = false;
+	}
+    }
+    public void add(JToggleButton toggle_button) {
+	last_button = toggle_button;
+	// If the checkbox is selected, increase our count.
+	if(toggle_button.isSelected()) {
+	    selected_count++;
+	}
+	toggle_button.addActionListener(this);
+    }
 
-	 /** Ensures that at least one option is selected. */
-	 public void establish() {
-		  if(selected_count == 0 && last_button != null) {
-				last_button.setSelected(true);
-		  }
-	 }
+    /** Ensures that at least one option is selected. */
+    public void establish() {
+	if(selected_count == 0 && last_button != null) {
+	    last_button.setSelected(true);
+	}
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/valuetree/GNode.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/valuetree/GNode.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/valuetree/GNode.java	(revision 4364)
@@ -48,14 +48,14 @@
 
 public class GNode
-	extends DefaultMutableTreeNode {
+    extends DefaultMutableTreeNode {
 
-	private String name     = null;
+    private String name     = null;
 
-	public GNode(String name) {
-		this.name = name;
-	}
+    public GNode(String name) {
+	this.name = name;
+    }
 
-	public String toString() {
-		return name;
-	}
+    public String toString() {
+	return name;
+    }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueModel.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueModel.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueModel.java	(revision 4364)
@@ -50,277 +50,275 @@
  */
 public class GValueModel 
-	 extends DefaultTreeModel {
-
-	 private ElementWrapper element;
-
-	 public GValueModel() {
-		  super(new DefaultMutableTreeNode("Temp"));
-	 }
-
-	 public GValueModel(String root) {
-		  super(new DefaultMutableTreeNode(root));
-	 }
-
-	 public GValueModel(ElementWrapper e) {
-	     super(new DefaultMutableTreeNode("Temp"));
-	     this.element = e;
-		  // Load the template value tree document.
-		  Document document = MSMUtils.getValueTreeTemplate();
-		  Element new_root = document.getDocumentElement();
-		  new_root.setAttribute("element", e.getName());
-		  root = new GValueNode(new_root);
-	 }
-
-	 public GValueModel(ElementWrapper e, Document document) {
-		  super(new DefaultMutableTreeNode("Temp"));
-		  this.element = e;
-		  Element new_root = document.getDocumentElement();
-		  new_root.setAttribute("element", e.getName());
-		  root = new GValueNode(new_root);
-	 }
-
-	 /** Value may include path ie news/newssw */
-	 public GValueNode addValue(String value) {
-		  ///ystem.err.println("Adding value to valuetree: " + value);
-		  StringTokenizer tokenizer = new StringTokenizer(value, "\\");
-		  GValueNode subject = (GValueNode) root;
-		  while(tokenizer.hasMoreTokens() && subject != null) {
-				String token = tokenizer.nextToken();
-				subject = addValue(token, subject, null);
+    extends DefaultTreeModel {
+
+    private ElementWrapper element;
+
+    public GValueModel() {
+	super(new DefaultMutableTreeNode("Temp"));
+    }
+
+    public GValueModel(String root) {
+	super(new DefaultMutableTreeNode(root));
+    }
+
+    public GValueModel(ElementWrapper e) {
+	super(new DefaultMutableTreeNode("Temp"));
+	this.element = e;
+	// Load the template value tree document.
+	Document document = MSMUtils.getValueTreeTemplate();
+	Element new_root = document.getDocumentElement();
+	new_root.setAttribute("element", e.getName());
+	root = new GValueNode(new_root);
+    }
+
+    public GValueModel(ElementWrapper e, Document document) {
+	super(new DefaultMutableTreeNode("Temp"));
+	this.element = e;
+	Element new_root = document.getDocumentElement();
+	new_root.setAttribute("element", e.getName());
+	root = new GValueNode(new_root);
+    }
+
+    /** Value may include path ie news/newssw */
+    public GValueNode addValue(String value) {
+	///ystem.err.println("Adding value to valuetree: " + value);
+	StringTokenizer tokenizer = new StringTokenizer(value, "\\");
+	GValueNode subject = (GValueNode) root;
+	while(tokenizer.hasMoreTokens() && subject != null) {
+	    String token = tokenizer.nextToken();
+	    subject = addValue(token, subject, null);
 				///ystem.err.println("Found or created a node " + token);
-		  }
-		  return subject;
-	 }
-
-	 public GValueNode addValue(String value, GValueNode subject, String alias) {
-		  // To add a value we must first ensure it isn't already present in -this- nodes children. The bummer is that the nice getElements functions search the whole tree so...
-		  GValueNode value_node = subject.getValue(Utility.stripNL(value));
-		  if(value_node == null) {
+	}
+	return subject;
+    }
+
+    public GValueNode addValue(String value, GValueNode subject, String alias) {
+	// To add a value we must first ensure it isn't already present in -this- nodes children. The bummer is that the nice getElements functions search the whole tree so...
+	GValueNode value_node = subject.getValue(Utility.stripNL(value));
+	if(value_node == null) {
 				// Now add the new value.
-				Document document = subject.getElement().getOwnerDocument();
+	    Document document = subject.getElement().getOwnerDocument();
 				// Now we create a new subject and add it subject
-				Element new_subject = document.createElementNS("","Subject");
-				Element new_value = document.createElementNS("","Value");
-				new_subject.appendChild(new_value);
-				Text new_text = document.createTextNode(value);
-				new_value.appendChild(new_text);
-				if(alias != null && alias.length() > 0) {
-					 Element new_alias = document.createElementNS("","Alias");
-					 new_subject.appendChild(new_alias);
-					 Text new_alias_text = document.createTextNode(alias);
-					 new_alias.appendChild(new_alias_text);					
-				} 
-				value_node = new GValueNode(new_subject);
+	    Element new_subject = document.createElementNS("","Subject");
+	    Element new_value = document.createElementNS("","Value");
+	    new_subject.appendChild(new_value);
+	    Text new_text = document.createTextNode(value);
+	    new_value.appendChild(new_text);
+	    if(alias != null && alias.length() > 0) {
+		Element new_alias = document.createElementNS("","Alias");
+		new_subject.appendChild(new_alias);
+		Text new_alias_text = document.createTextNode(alias);
+		new_alias.appendChild(new_alias_text);					
+	    } 
+	    value_node = new GValueNode(new_subject);
 				// Figure out where this node will be inserted in subjects 
 				// children.
-				int position = -1;
-				for(int i = 0; position == -1 && i < subject.getChildCount(); i++) {
-					 Object sibling = subject.getChildAt(i);
-					 int rel_pos = value.compareTo(sibling.toString());
-					 ///ystem.err.println("'"+value+"'.compareTo('"+sibling+"') = " + rel_pos);
-					 if(rel_pos <= 0) {
-						  position = i;
-					 }
-				}
-				if(position == -1) {
-					 position = subject.getChildCount();
-				}
+	    int position = -1;
+	    for(int i = 0; position == -1 && i < subject.getChildCount(); i++) {
+		Object sibling = subject.getChildAt(i);
+		int rel_pos = value.compareTo(sibling.toString());
+		///ystem.err.println("'"+value+"'.compareTo('"+sibling+"') = " + rel_pos);
+		if(rel_pos <= 0) {
+		    position = i;
+		}
+	    }
+	    if(position == -1) {
+		position = subject.getChildCount();
+	    }
 				// Insert it. If position is still -1, append it to the end of subjects children.
 				///ystem.err.println("Inserting '" + value + "' at position " + position);
-				insertNodeInto(value_node, subject, position);
+	    insertNodeInto(value_node, subject, position);
 				// Inform listeners that we've changed.
-				Gatherer.c_man.getCollection().msm.fireValueChanged(element, null, this);
-		  }
-		  return value_node;
-	 }
-
-	 public Document getDocument() {
-		  return ((GValueNode)root).getElement().getOwnerDocument();
-	 }
-
-	 public ElementWrapper getElement() {
-		  return element;
-	 }
-
-	 /** Retrieve the hindex for a certain value within the value tree.
-	  * @param value The value whose index you wish to determine as a <strong>String</strong>.
-	  * @return A <strong>String</strong> containing an index such as "1", "2.1" or "18.2.5".
-	  */ 
-	 public String getHIndex(String value) {
-		  ///ystem.err.println("getHIndex(" + value + ")");
-		  return getHIndex((GValueNode) root, value, null);
-	 }
-
-	 /** Retrieve a value node given its hierarchical reference or value.
-	  * @param index The hierarchy index or value as a <strong>String</strong>.
-	  */
-	 public GValueNode getValue(String index_str) {
-		  ///ystem.err.println("Retrieve the value for: " + index_str);
-		  GValueNode result = null;
-		  if(isHierarchy() && Utility.isIndex(index_str)) {
+	    Gatherer.c_man.getCollection().msm.fireValueChanged(element, null, this);
+	}
+	return value_node;
+    }
+
+    public Document getDocument() {
+	return ((GValueNode)root).getElement().getOwnerDocument();
+    }
+
+    public ElementWrapper getElement() {
+	return element;
+    }
+
+    /** Retrieve the hindex for a certain value within the value tree.
+     * @param value The value whose index you wish to determine as a <strong>String</strong>.
+     * @return A <strong>String</strong> containing an index such as "1", "2.1" or "18.2.5".
+     */ 
+    public String getHIndex(String value) {
+	///ystem.err.println("getHIndex(" + value + ")");
+	return getHIndex((GValueNode) root, value, null);
+    }
+
+    /** Retrieve a value node given its hierarchical reference or value.
+     * @param index The hierarchy index or value as a <strong>String</strong>.
+     */
+    public GValueNode getValue(String index_str) {
+	///ystem.err.println("Retrieve the value for: " + index_str);
+	GValueNode result = null;
+	if(isHierarchy() && Utility.isIndex(index_str)) {
 				// StringTokenize the index
-				StringTokenizer tokenizer = new StringTokenizer(index_str, ".");
-				result = (GValueNode) root;
+	    StringTokenizer tokenizer = new StringTokenizer(index_str, ".");
+	    result = (GValueNode) root;
 				// Using the index numbers retrieve the appropriate node.
-				try {
-					 while(result != null && tokenizer.hasMoreTokens()) {
-						  int index = Integer.parseInt(tokenizer.nextToken()) - 1;
-						  // Retrieve the index'th child of the current result node
-						  if(0 <= index && index < result.getChildCount()) {
-								result = (GValueNode) result.getChildAt(index);
-						  }
-						  // Otherwise we're broken.
-						  else {
-								///ystem.err.println("There is no " + index + "th childnode of " + result);
-								result = null;
-						  }
-					 }
-				}
+	    try {
+		while(result != null && tokenizer.hasMoreTokens()) {
+		    int index = Integer.parseInt(tokenizer.nextToken()) - 1;
+		    // Retrieve the index'th child of the current result node
+		    if(0 <= index && index < result.getChildCount()) {
+			result = (GValueNode) result.getChildAt(index);
+		    }
+		    // Otherwise we're broken.
+		    else {
+			///ystem.err.println("There is no " + index + "th childnode of " + result);
+			result = null;
+		    }
+		}
+	    }
 				// Most likely caused by parseInt throwing a wobbly.
-				catch (Exception error) {
-					 result = null;
-				}
-				if(result != null) {
-					 // Ensure result is enabled
-					 result.setEnabled(true);
-				}
-		  }
-		  if(result == null) {
+	    catch (Exception error) {
+		result = null;
+	    }
+	    if(result != null) {
+		// Ensure result is enabled
+		result.setEnabled(true);
+	    }
+	}
+	if(result == null) {
 				///ystem.err.println("No existing value. Adding " + index_str);
-				result = addValue(index_str);
-		  }
-		  return result;
-	 }
-
-	 public boolean isHierarchy() {
-		  boolean result = false;
-		  // We are a hierarchy if our element says so....
-		  if(element.isHierarchy()) {
-				return true;
-		  }
-		  // Or if our children are actually a hierarchy.
-		  for(int i = 0; i < root.getChildCount() && !result; i++) {
-				GValueNode node = (GValueNode) root.getChildAt(i);
-				if(node != null && node.getChildCount() > 0) {
-					 result = true;
-				}
-		  }
-		  return result;
-	 }
-
-	 public void removeValue(GValueNode child) {
-		  child.setEnabled(false); // Doesn't appear anymore.
-		  SynchronizedTreeModelTools.removeNodeFromParent(this, child);
-		  Gatherer.c_man.getCollection().msm.fireValueChanged(new ElementWrapper(child.getElement()), null, this);
-	 }
-
-	 public void removeValue(String value) {
-		  // Retrieve the node to be removed.
-		  GValueNode node = getValue(value);
-		  // Now remove it
-		  removeNodeFromParent(node);
-	 }
-
-	 /** Called to remove a certain value of metadata within a specific
-	  * subject within a sbject hierarchy. 
-	  * Note that this method currently doesn't do anything.
-	  * @param value A String representing the value to be removed.
-	  * @param subject An Element representing the subject you wish to remove 
-	  * this value from.
-	  */ 
-	 public void removeValue(String value, GValueNode subject) {
-	 }
-
-	 public int size() {
-		  return size(root);
-	 }
-
-	 private int size(TreeNode current) {
-		  int size = 1;
-		  for(int i = 0; i < current.getChildCount(); i++) {
-				size = size + size(current.getChildAt(i));
-		  }
-		  return size;
-	 }
-
-	 public String toString() {
-		  return element.toString();
-	 }
-
-	 public Vector traverseTree() {
-		  Vector contents = new Vector();
-		  contents.addAll(traverseTree((GValueNode) root));
-		  contents.remove((GValueNode) root);
-		  return contents;
-	 }
-
-	 public void updateValue(String new_value, String old_value) {
-		  ///ystem.err.println("Updating '" + old_value + "' to '" + new_value + "'"); 
-		  StringTokenizer tokenizer = new StringTokenizer(new_value, "\\");
-		  GValueNode subject = (GValueNode) root;
-		  while(tokenizer.hasMoreTokens()) {
-				String token = tokenizer.nextToken();
-				subject = updateValue(token, old_value, subject);
-		  }
-	 }
-
-	 /** Called to update a certain value of metadata within a specific
-	  * subject within a sbject hierarchy. 
-	  * Note that this simply in turn calls removeValue() to deal with the
-	  * old value, then addValue() to account for the new. 
-	  * @param new_value A String representing the updated value.
-	  * @param old_value A String representing the old value.
-	  * @param subject An Element representing the subject you wish to update 
-	  * this value in.
-	  */ 
-	 public GValueNode updateValue(String new_value, String old_value, GValueNode subject) {
-		  return addValue(new_value, subject, null);
-	 }
-
-	 private GValueModel copy() {
-		  Element document_element = ((GValueNode) root).getElement();
-		  Document document_copy = MSMUtils.getValueTreeTemplate();
-		  Element document_copy_element = document_copy.getDocumentElement();
-		  document_copy_element.setAttribute("element", element.getName());
-		  for(Node node = document_element.getFirstChild(); node != null; node = node.getNextSibling()) {
-				if(node.getNodeName().equals("Subject")) {
-					 Node node_copy = document_copy.importNode(node, true);
-					 document_copy_element.appendChild(node_copy);
-				}
-		  }
-		  return new GValueModel(element, document_copy); 				
-	 }
-
-	 private String getHIndex(GValueNode node, String value, String index) {
-		  for(int i = node.size(); i != 0; i--) {
-				GValueNode next = (GValueNode)node.get(i - 1);
-				String next_str = next.toString();
-				if(value.startsWith(next_str)) {
-					 if(index == null) {
-						  index = String.valueOf(i);
-					 }
-					 else {
-						  index = index + "." + i;
-					 }
-					 value = value.substring(next.toString().length());
-					 if(value.startsWith("\\")) {
-						  value = value.substring(1);
-						  index = getHIndex(next, value, index);
-					 }
-					 return index;
-				}
-		  }
-		  return index;
-	 }
-
-	 private Vector traverseTree(GValueNode node) {
-		  Vector contents = new Vector();
-		  contents.add(node);
-		  for(int i = 0; i < node.getChildCount(); i++) {
-				contents.addAll(traverseTree((GValueNode)node.getChildAt(i)));
-		  }
-		  return contents;
-	 }
+	    result = addValue(index_str);
+	}
+	return result;
+    }
+
+    public boolean isHierarchy() {
+	boolean result = false;
+	// We are a hierarchy if our element says so....
+	if(element.isHierarchy()) {
+	    return true;
+	}
+	// Or if our children are actually a hierarchy.
+	for(int i = 0; i < root.getChildCount() && !result; i++) {
+	    GValueNode node = (GValueNode) root.getChildAt(i);
+	    if(node != null && node.getChildCount() > 0) {
+		result = true;
+	    }
+	}
+	return result;
+    }
+
+    public void removeValue(GValueNode child) {
+	child.setEnabled(false); // Doesn't appear anymore.
+	SynchronizedTreeModelTools.removeNodeFromParent(this, child);
+	Gatherer.c_man.getCollection().msm.fireValueChanged(new ElementWrapper(child.getElement()), null, this);
+    }
+
+    public void removeValue(String value) {
+	// Retrieve the node to be removed.
+	GValueNode node = getValue(value);
+	// Now remove it
+	removeNodeFromParent(node);
+    }
+
+    /** Called to remove a certain value of metadata within a specific
+     * subject within a sbject hierarchy. 
+     * Note that this method currently doesn't do anything.
+     * @param value A String representing the value to be removed.
+     * @param subject An Element representing the subject you wish to remove 
+     * this value from.
+     */ 
+    public void removeValue(String value, GValueNode subject) {
+    }
+
+    public int size() {
+	return size(root);
+    }
+
+    private int size(TreeNode current) {
+	int size = 1;
+	for(int i = 0; i < current.getChildCount(); i++) {
+	    size = size + size(current.getChildAt(i));
+	}
+	return size;
+    }
+
+    public String toString() {
+	return element.toString();
+    }
+
+    public Vector traverseTree() {
+	Vector contents = new Vector();
+	contents.addAll(traverseTree((GValueNode) root));
+	contents.remove((GValueNode) root);
+	return contents;
+    }
+
+    public void updateValue(String new_value, String old_value) {
+	///ystem.err.println("Updating '" + old_value + "' to '" + new_value + "'"); 
+	StringTokenizer tokenizer = new StringTokenizer(new_value, "\\");
+	GValueNode subject = (GValueNode) root;
+	while(tokenizer.hasMoreTokens()) {
+	    String token = tokenizer.nextToken();
+	    subject = updateValue(token, old_value, subject);
+	}
+    }
+
+    /** Called to update a certain value of metadata within a specific
+     * subject within a sbject hierarchy. 
+     * Note that this simply in turn calls removeValue() to deal with the
+     * old value, then addValue() to account for the new. 
+     * @param new_value A String representing the updated value.
+     * @param old_value A String representing the old value.
+     * @param subject An Element representing the subject you wish to update 
+     * this value in.
+     */ 
+    public GValueNode updateValue(String new_value, String old_value, GValueNode subject) {
+	return addValue(new_value, subject, null);
+    }
+
+    private GValueModel copy() {
+	Element document_element = ((GValueNode) root).getElement();
+	Document document_copy = MSMUtils.getValueTreeTemplate();
+	Element document_copy_element = document_copy.getDocumentElement();
+	document_copy_element.setAttribute("element", element.getName());
+	for(Node node = document_element.getFirstChild(); node != null; node = node.getNextSibling()) {
+	    if(node.getNodeName().equals("Subject")) {
+		Node node_copy = document_copy.importNode(node, true);
+		document_copy_element.appendChild(node_copy);
+	    }
+	}
+	return new GValueModel(element, document_copy); 				
+    }
+
+    private String getHIndex(GValueNode node, String value, String index) {
+	for(int i = node.size(); i != 0; i--) {
+	    GValueNode next = (GValueNode)node.get(i - 1);
+	    String next_str = next.toString();
+	    if(value.startsWith(next_str)) {
+		if(index == null) {
+		    index = String.valueOf(i);
+		}
+		else {
+		    index = index + "." + i;
+		}
+		value = value.substring(next.toString().length());
+		if(value.startsWith("\\")) {
+		    value = value.substring(1);
+		    index = getHIndex(next, value, index);
+		}
+		return index;
+	    }
+	}
+	return index;
+    }
+
+    private Vector traverseTree(GValueNode node) {
+	Vector contents = new Vector();
+	contents.add(node);
+	for(int i = 0; i < node.getChildCount(); i++) {
+	    contents.addAll(traverseTree((GValueNode)node.getChildAt(i)));
+	}
+	return contents;
+    }
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueNode.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueNode.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueNode.java	(revision 4364)
@@ -72,371 +72,367 @@
  */
 public class GValueNode 
-	 extends DefaultMutableTreeNode {
-	 private String element_name = null;
-	 private String default_value = null;
-	 public GValueNode(Element element) {
-		  this.children = null;
-		  this.userObject = element;
-		  this.default_value = null;
-	 }
-
-	 public GValueNode(String element_name, String default_value) {
-		  this.element_name = element_name;
-		  this.default_value = default_value;
-	 }
-	 /** Compares two GValueNodes for ordering by using the String.compareTo method.
-	  * @param sibling The <strong>Object</strong> representing the GValueNode we are comparing ourselves to.
-	  * @return An <i>int</i> indicating the ordering. The value is less than zero if we are before the parameter object, zero if we are equal and greater than zero if we preceed sibling.
-	  */
-	 public int compareTo(Object sibling) {
-		  return toString().compareTo(sibling.toString());
-	 }
-	 /** Determine if this tree node contains a child with a matching value.
-	  * @param value The value we are attempting to match, as a <strong>String</strong>.
-	  * @return <i>true</i> if there is a matching child node, <i>false</i> otherwise.
-	  */
-	 public boolean containsValue(String value) {
-		  if(default_value != null) {
-				return false;
-		  }
-		  return getValue(value) != null;
-	 }
-	 /** Returns an enumeration of the child nodes.
-	  * @return An <strong>Enumeration</strong> containing the child nodes.
-	  */
-	 public Enumeration children() {
-		  if(default_value != null) {
-				return null;
-		  }
-		  if(children == null) {
-				map();
-		  }
-		  return children.elements();
-	 }
-	 public boolean equals(Object other) {
-		  return compareTo(other) == 0;
-	 }
-
-	 public GValueNode get(int index) {
-		  GValueNode result = null;
-		  if(children != null) {
-				result = (GValueNode) children.get(index);
-		  }
-		  return result;
-	 }
-
-	 public String getAlias() {
-		  // Attempt to retrieve a child node named Alias
-		  Element element = (Element) userObject;
-		  for(Node pos_node = element.getFirstChild(); pos_node != null; pos_node = pos_node.getNextSibling()) {
+    extends DefaultMutableTreeNode {
+    private String element_name = null;
+    private String default_value = null;
+    public GValueNode(Element element) {
+	this.children = null;
+	this.userObject = element;
+	this.default_value = null;
+    }
+
+    public GValueNode(String element_name, String default_value) {
+	this.element_name = element_name;
+	this.default_value = default_value;
+    }
+    /** Compares two GValueNodes for ordering by using the String.compareTo method.
+     * @param sibling The <strong>Object</strong> representing the GValueNode we are comparing ourselves to.
+     * @return An <i>int</i> indicating the ordering. The value is less than zero if we are before the parameter object, zero if we are equal and greater than zero if we preceed sibling.
+     */
+    public int compareTo(Object sibling) {
+	return toString().compareTo(sibling.toString());
+    }
+    /** Determine if this tree node contains a child with a matching value.
+     * @param value The value we are attempting to match, as a <strong>String</strong>.
+     * @return <i>true</i> if there is a matching child node, <i>false</i> otherwise.
+     */
+    public boolean containsValue(String value) {
+	if(default_value != null) {
+	    return false;
+	}
+	return getValue(value) != null;
+    }
+    /** Returns an enumeration of the child nodes.
+     * @return An <strong>Enumeration</strong> containing the child nodes.
+     */
+    public Enumeration children() {
+	if(default_value != null) {
+	    return null;
+	}
+	if(children == null) {
+	    map();
+	}
+	return children.elements();
+    }
+    public boolean equals(Object other) {
+	return compareTo(other) == 0;
+    }
+
+    public GValueNode get(int index) {
+	GValueNode result = null;
+	if(children != null) {
+	    result = (GValueNode) children.get(index);
+	}
+	return result;
+    }
+
+    public String getAlias() {
+	// Attempt to retrieve a child node named Alias
+	Element element = (Element) userObject;
+	for(Node pos_node = element.getFirstChild(); pos_node != null; pos_node = pos_node.getNextSibling()) {
 				// And if we find such a node 
-				if(pos_node.getNodeName().equals("Alias")) {
-					 // Retrieve its text node
-					 for(Node pos_text = pos_node.getFirstChild(); pos_text != null; pos_text = pos_text.getNextSibling()) {
-						  // When we find the node return its value
-						  if(pos_text.getNodeName().equals("#text")) {
-								return pos_text.getNodeValue();
-						  }
-					 }
-				}
-		  }		  
-		  return "";
-	 }
-
-	 public String getAlias(String index) {
-		  return index;
-	 }
-
-	 /** Returns <I>true</I> if the reciever allows children. */
-	 public boolean getAllowsChildren() {
-		  return true;
-	 }
-
-	 /** Returns the child <I>TreeNode</I> at index <I>childIndex</I>.*/
-	 public TreeNode getChildAt(int index) {
-		  GValueNode result = null;
-		  if(default_value != null) {
-				return this;
-		  }
-		  else {
-				if(children == null) {
-					 map();
-				}
-				for(int i = 0; index >= 0 && i < children.size(); i++) {
-					 GValueNode child = (GValueNode) children.get(i);
-					 if(child.getEnabled()) {
-						  result = child;
-						  index--;
-					 }
-				}
-		  }
-		  return result;
-	 }
-	 /** Returns the number of children <I>TreeNode</I>s the reciever contains. */
-	 public int getChildCount() {
-		  int size = 0;
-		  if(default_value != null) {
-				size = 0;
-		  }
-		  else {
-				if(children == null) {
-					 map();
-				}
-				for(int i = 0; i < children.size(); i++) {
-					 GValueNode child = (GValueNode) children.get(i);
-					 if(child.getEnabled()) {
-						  size++;
-					 }
-				}
-		  }
-		  return size;
-	 }
-	 public Element getElement() {
-		  return (Element) userObject;
-	 }
-	 public boolean getEnabled() {
-		  return !(((Element)userObject).getAttribute("enabled").equals("false"));
-	 }
-
-	 /** Return the path to this node within the value tree model.
-	  * @return A <strong>String</strong> representing the path, ie "Titles\Modern\The Green Mile"
-	  */
-	 public String getFullPath() {
-		  if(default_value != null) {
-				return default_value;
-		  }
-		  StringBuffer path = new StringBuffer(toString());
-		  GValueNode node = (GValueNode) getParent();
-		  while(node != null && !node.getElement().getNodeName().equalsIgnoreCase("AssignedValues")) {
-				path.insert(0, "\\");
-				path.insert(0, node.toString());
-				node = (GValueNode) node.getParent();
-		  }
-		  return path.toString();
-	 }
-
-	 /** Returns the index of <I>node</I> in the recievers children. If the
-	  * reciever does not contain <I>node</I>, <I>-1</I> will be returned. */
-	 public int getIndex(TreeNode node) {
-		  if(default_value != null) {
-				return 0;
-		  }
-		  if(children == null) {
-				map();
-		  }
-		  return children.indexOf(node);
-	 }
-
-	 public String getMetadataElementName() {
-		  if(default_value != null) {
-				return element_name;
-		  }
-		  GValueNode node = this;
-		  while(node != null && !node.getElement().getNodeName().equalsIgnoreCase("AssignedValues")) {
-				node = (GValueNode)node.getParent();
-		  }
-		  if(node != null) {
-				return node.getElement().getAttribute("element");
-		  }
-		  return null;
-	 }
-
-	 public GValueNode getValue(String value) {
-		  if(default_value != null) {
-				return this;
-		  }
-		  if(children == null) {
-				map();
-		  }
-		  GValueNode result = null;
-		  for(int i = 0; result == null && i < children.size(); i++) {
-				Object child = children.get(i);
-				if(child.toString().equals(value)) {
-					 result = (GValueNode) child;
-				}
-		  }
-		  return result;
-	 }
-
-	 /** Adds <I>child</I> to the receiever at <I>index</I>. <I>child</I> will
-	  * be messaged with setParent().
-	  */
-	 public void insert(MutableTreeNode child, int index) {
-		  if(default_value != null) {
-				return;
-		  }
-		  if(index >= children.size()) {
+	    if(pos_node.getNodeName().equals("Alias")) {
+		// Retrieve its text node
+		for(Node pos_text = pos_node.getFirstChild(); pos_text != null; pos_text = pos_text.getNextSibling()) {
+		    // When we find the node return its value
+		    if(pos_text.getNodeName().equals("#text")) {
+			return pos_text.getNodeValue();
+		    }
+		}
+	    }
+	}		  
+	return "";
+    }
+
+    public String getAlias(String index) {
+	return index;
+    }
+
+    /** Returns <I>true</I> if the reciever allows children. */
+    public boolean getAllowsChildren() {
+	return true;
+    }
+
+    /** Returns the child <I>TreeNode</I> at index <I>childIndex</I>.*/
+    public TreeNode getChildAt(int index) {
+	GValueNode result = null;
+	if(default_value != null) {
+	    return this;
+	}
+	else {
+	    if(children == null) {
+		map();
+	    }
+	    for(int i = 0; index >= 0 && i < children.size(); i++) {
+		GValueNode child = (GValueNode) children.get(i);
+		if(child.getEnabled()) {
+		    result = child;
+		    index--;
+		}
+	    }
+	}
+	return result;
+    }
+    /** Returns the number of children <I>TreeNode</I>s the reciever contains. */
+    public int getChildCount() {
+	int size = 0;
+	if(default_value != null) {
+	    size = 0;
+	}
+	else {
+	    if(children == null) {
+		map();
+	    }
+	    for(int i = 0; i < children.size(); i++) {
+		GValueNode child = (GValueNode) children.get(i);
+		if(child.getEnabled()) {
+		    size++;
+		}
+	    }
+	}
+	return size;
+    }
+    public Element getElement() {
+	return (Element) userObject;
+    }
+    public boolean getEnabled() {
+	return !(((Element)userObject).getAttribute("enabled").equals("false"));
+    }
+
+    /** Return the path to this node within the value tree model.
+     * @return A <strong>String</strong> representing the path, ie "Titles\Modern\The Green Mile"
+     */
+    public String getFullPath() {
+	if(default_value != null) {
+	    return default_value;
+	}
+	StringBuffer path = new StringBuffer(toString());
+	GValueNode node = (GValueNode) getParent();
+	while(node != null && !node.getElement().getNodeName().equalsIgnoreCase("AssignedValues")) {
+	    path.insert(0, "\\");
+	    path.insert(0, node.toString());
+	    node = (GValueNode) node.getParent();
+	}
+	return path.toString();
+    }
+
+    /** Returns the index of <I>node</I> in the recievers children. If the
+     * reciever does not contain <I>node</I>, <I>-1</I> will be returned. */
+    public int getIndex(TreeNode node) {
+	if(default_value != null) {
+	    return 0;
+	}
+	if(children == null) {
+	    map();
+	}
+	return children.indexOf(node);
+    }
+
+    public String getMetadataElementName() {
+	if(default_value != null) {
+	    return element_name;
+	}
+	GValueNode node = this;
+	while(node != null && !node.getElement().getNodeName().equalsIgnoreCase("AssignedValues")) {
+	    node = (GValueNode)node.getParent();
+	}
+	if(node != null) {
+	    return node.getElement().getAttribute("element");
+	}
+	return null;
+    }
+
+    public GValueNode getValue(String value) {
+	if(default_value != null) {
+	    return this;
+	}
+	if(children == null) {
+	    map();
+	}
+	GValueNode result = null;
+	for(int i = 0; result == null && i < children.size(); i++) {
+	    Object child = children.get(i);
+	    if(child.toString().equals(value)) {
+		result = (GValueNode) child;
+	    }
+	}
+	return result;
+    }
+
+    /** Adds <I>child</I> to the receiever at <I>index</I>. <I>child</I> will
+     * be messaged with setParent().
+     */
+    public void insert(MutableTreeNode child, int index) {
+	if(default_value != null) {
+	    return;
+	}
+	if(index >= children.size()) {
 				// Append to children.
-				children.add(child);
+	    children.add(child);
 				// And to document
-				getElement().appendChild(((GValueNode)child).getElement());
-		  }
-		  else {
-				GValueNode sibling = (GValueNode) children.get(index);
+	    getElement().appendChild(((GValueNode)child).getElement());
+	}
+	else {
+	    GValueNode sibling = (GValueNode) children.get(index);
 				// Insert in children
-				children.add(index, child);
+	    children.add(index, child);
 				// And in document
-				getElement().insertBefore(((GValueNode)child).getElement(), sibling.getElement());
-		  }
-		  child.setParent(this);
-	 }
-	 /** Returns <I>true</I> if the reciever is a leaf. */
-	 public boolean isLeaf() {
-		  if(default_value != null || getChildCount() > 0) {
-				return false;
-		  }
-		  return true;
-	 }
-
-	 /** Ensure that the children nodes of this tree are instantiated, and record the initial mappings for legacy sake. 
-	  * @param mapping A <strong>HashMap</strong> into which to write the initial mappings.
-	  * @param prefix The prefix to use for indexes, as a <strong>String</strong>.
-	  */
-	 public void map(HashMap mapping, String prefix) {
-		  if(default_value != null) {
-				return;
-		  }
-		  children = new Vector();
-		  if(prefix.length() > 0) {
-				prefix = prefix + ".";
-		  }
-		  int i = 1;
-		  for(Node node = getElement().getFirstChild(); node != null; node = node.getNextSibling()) {
-				if(node.getNodeName().equals("Subject")) {
-					 GValueNode child = new GValueNode((Element)node);
-					 child.setParent(this);
-					 children.add(child);
-					 String index = prefix + i;
-					 mapping.put(index, child);
-					 child.map(mapping, index);
-					 i++;
-				}
-		  }
-	 }
-
-	 /** Removes the child at <I>index</I> from the reciever. */
-	 public void remove(int index) {
-		  // Can't ever remove Elements from here. Have to use editor. */
-	 }
-
-	 /** Removes <I>node</I> from the reciever. <I>setParent</I> will be 
-	  * messaged on node. 
-	  */
-	 public void remove(MutableTreeNode node) {
-		  // Can't ever remove Elements from here. Have to use editor. */
-	 }
-
-	 /** Removes the reciever from its parent. */
-	 public void removeFromParent() {
-		  // Can't ever remove Elements from here. Have to use editor. */
-	 }
-
-	 public void setAlias(String alias) {
-		  setNodeValue("Alias", alias);
-	 }
-
-	 public void setEnabled(boolean state) {
-		  Element element = (Element) userObject;
-		  if(state) {
-				element.setAttribute("enabled", "true");
-		  }
-		  else {
-				element.setAttribute("enabled", "false");
-		  }
-	 }
-
-	 /** Resets the user object of the reciever to <I>object</I>. */
-	 public void setUserObject(Object object) {
-		  // Can't ever change Elements from here. Have to use editor. */		  
-	 }
-
-	 public void setValue(String value) {
-		  setNodeValue("Value", value);
-	 }
-
-	 public int size() {
-		  int size = 0;
-		  if(children != null) {
-				size = children.size();
-		  }
-		  return size;
-	 }
-
-	 /** Returns a <I>String</I> representation of the reciever. If this
-	  * happens to be the AssignedValues 'root' then we do something slightly
-	  * different, otherwise we return the value of the <I>#Text</I> child of 
-	  * the <I>Value</I> of this <I>Subject</I> node!
-	  */
-	 public String toString() {
-		  if(default_value != null) {
-				return default_value;
-		  }
-		  Element element = getElement();
-		  String name = element.getNodeName();
-		  String result = null;
-		  if(name.equals("Subject")) {
-				result = MSMUtils.getValue(element);
-		  }
-		  else if(name.equals("AssignedValues")) {
-				result = getMetadataElementName();
-		  }
-		  return Utility.stripNL(result);
-	 }
-
-	 public void unmap() {
-		  children = null;
-	 }
-
-	 private void map() {
-		  children = new Vector();
-		  for(Node node = getElement().getFirstChild(); node != null; node = node.getNextSibling()) {
-				if(node.getNodeName().equals("Subject")) {
-					 GValueNode child = new GValueNode((Element)node);
-					 child.setParent(this);
-					 children.add(child);
-				}
-		  }
-	 }
-
-	 private void setNodeValue(String name, String value) {
-		  boolean found = false;
-		  // Attempt to retrieve a child node named name
-		  Element element = (Element) userObject;
-		  for(Node pos_node = element.getFirstChild(); pos_node != null; pos_node = pos_node.getNextSibling()) {
+	    getElement().insertBefore(((GValueNode)child).getElement(), sibling.getElement());
+	}
+	child.setParent(this);
+    }
+    /** Returns <I>true</I> if the reciever is a leaf. */
+    public boolean isLeaf() {
+	if(default_value != null || getChildCount() > 0) {
+	    return false;
+	}
+	return true;
+    }
+
+    /** Ensure that the children nodes of this tree are instantiated, and record the initial mappings for legacy sake. 
+     * @param mapping A <strong>HashMap</strong> into which to write the initial mappings.
+     * @param prefix The prefix to use for indexes, as a <strong>String</strong>.
+     */
+    public void map(HashMap mapping, String prefix) {
+	if(default_value != null) {
+	    return;
+	}
+	children = new Vector();
+	if(prefix.length() > 0) {
+	    prefix = prefix + ".";
+	}
+	int i = 1;
+	for(Node node = getElement().getFirstChild(); node != null; node = node.getNextSibling()) {
+	    if(node.getNodeName().equals("Subject")) {
+		GValueNode child = new GValueNode((Element)node);
+		child.setParent(this);
+		children.add(child);
+		String index = prefix + i;
+		mapping.put(index, child);
+		child.map(mapping, index);
+		i++;
+	    }
+	}
+    }
+
+    /** Removes the child at <I>index</I> from the reciever. */
+    public void remove(int index) {
+	// Can't ever remove Elements from here. Have to use editor. */
+    }
+
+    /** Removes <I>node</I> from the reciever. <I>setParent</I> will be 
+     * messaged on node. 
+     */
+    public void remove(MutableTreeNode node) {
+	// Can't ever remove Elements from here. Have to use editor. */
+    }
+
+    /** Removes the reciever from its parent. */
+    public void removeFromParent() {
+	// Can't ever remove Elements from here. Have to use editor. */
+    }
+
+    public void setAlias(String alias) {
+	setNodeValue("Alias", alias);
+    }
+
+    public void setEnabled(boolean state) {
+	Element element = (Element) userObject;
+	if(state) {
+	    element.setAttribute("enabled", "true");
+	}
+	else {
+	    element.setAttribute("enabled", "false");
+	}
+    }
+
+    /** Resets the user object of the reciever to <I>object</I>. */
+    public void setUserObject(Object object) {
+	// Can't ever change Elements from here. Have to use editor. */		  
+    }
+
+    public void setValue(String value) {
+	setNodeValue("Value", value);
+    }
+
+    public int size() {
+	int size = 0;
+	if(children != null) {
+	    size = children.size();
+	}
+	return size;
+    }
+
+    /** Returns a <I>String</I> representation of the reciever. If this
+     * happens to be the AssignedValues 'root' then we do something slightly
+     * different, otherwise we return the value of the <I>#Text</I> child of 
+     * the <I>Value</I> of this <I>Subject</I> node!
+     */
+    public String toString() {
+	if(default_value != null) {
+	    return default_value;
+	}
+	Element element = getElement();
+	String name = element.getNodeName();
+	String result = null;
+	if(name.equals("Subject")) {
+	    result = MSMUtils.getValue(element);
+	}
+	else if(name.equals("AssignedValues")) {
+	    result = getMetadataElementName();
+	}
+	return Utility.stripNL(result);
+    }
+
+    public void unmap() {
+	children = null;
+    }
+
+    private void map() {
+	children = new Vector();
+	for(Node node = getElement().getFirstChild(); node != null; node = node.getNextSibling()) {
+	    if(node.getNodeName().equals("Subject")) {
+		GValueNode child = new GValueNode((Element)node);
+		child.setParent(this);
+		children.add(child);
+	    }
+	}
+    }
+
+    private void setNodeValue(String name, String value) {
+	boolean found = false;
+	// Attempt to retrieve a child node named name
+	Element element = (Element) userObject;
+	for(Node pos_node = element.getFirstChild(); pos_node != null; pos_node = pos_node.getNextSibling()) {
 				// And if we find such a node 
-				if(pos_node.getNodeName().equals(name)) {
-					 // And the new value is non-null, retrieve its text node and change it.
-					 if(value != null) {
-						  for(Node pos_text = pos_node.getFirstChild(); pos_text != null; pos_text = pos_text.getNextSibling()) {
-								// When we find the node...
-								if(pos_text.getNodeName().equals("#text")) {
-									 pos_text.setNodeValue(value);
-								}
-						  }
-					 }
-					 // Otherwise remove the node altogether
-					 else {
-						  element.removeChild(pos_node);
-					 }
-					 found = true;
-				}
-		  }
-		  // Otherwise if no such node exists add it.
-		  if(!found && value != null) {
-				Document document = element.getOwnerDocument();
-				Node new_node = document.createElementNS("", name);
-				element.appendChild(new_node);
-				Node new_text = document.createTextNode(value);
-				new_node.appendChild(new_text);
-				new_text = null;
-				new_node = null;
-		  }
-		  // Done.
-		  element = null;
-		  return;
-	 }
+	    if(pos_node.getNodeName().equals(name)) {
+		// And the new value is non-null, retrieve its text node and change it.
+		if(value != null) {
+		    for(Node pos_text = pos_node.getFirstChild(); pos_text != null; pos_text = pos_text.getNextSibling()) {
+			// When we find the node...
+			if(pos_text.getNodeName().equals("#text")) {
+			    pos_text.setNodeValue(value);
+			}
+		    }
+		}
+		// Otherwise remove the node altogether
+		else {
+		    element.removeChild(pos_node);
+		}
+		found = true;
+	    }
+	}
+	// Otherwise if no such node exists add it.
+	if(!found && value != null) {
+	    Document document = element.getOwnerDocument();
+	    Node new_node = document.createElementNS("", name);
+	    element.appendChild(new_node);
+	    Node new_text = document.createTextNode(value);
+	    new_node.appendChild(new_text);
+	    new_text = null;
+	    new_node = null;
+	}
+	// Done.
+	element = null;
+	return;
+    }
 }
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueTree.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueTree.java	(revision 4363)
+++ /trunk/gli/src/org/greenstone/gatherer/valuetree/GValueTree.java	(revision 4364)
@@ -61,287 +61,287 @@
  */
 public class GValueTree
-	 extends JPanel
-	 implements ActionListener, FocusListener, MSMListener, TreeSelectionListener {
-	 /** Is the metadata currently shown in the edit controls common to all records selected. */
-	 private boolean common = false;
-	 /** Certain actions are disabled if the metadata selected isn't at file level. */
-	 private boolean file_level = false;
-	 private boolean ignore = false;
-	 private boolean ignore_item_change = false;
-	 private CardLayout card_layout;
-	 private DefaultListModel list_model;
-	 /** The metadata element that is currently selected. */
-	 private ElementWrapper selected_metadata_element = null;
-	 //private GComboBox elements;
-	 private MetadataComboBoxModel em;
-	 private GValueModel vm;
-	 private Hashtable previous_values = new Hashtable();
-	 private JButton add;
-	 /** This button creates an editor dialog to allow for an expanded area to type in text. */
-	 private JButton expand = null;
-	 private JButton remove;
-	 private JButton restore;
-	 private JButton search;
-	 private JButton update;
-	 //private JLabel element_label;
-	 private JLabel value_label;
-	 private JList list;
-	 private JPanel auto_pane;
-	 private JPanel button_pane;
-	 private JPanel center_pane;
-	 //private JPanel list_pane;
-	 private JPanel north_pane;
-	 //private JPanel south_pane;
-	 private JPanel tree_pane;
-	 private JPanel value_pane;
-	 private JTextArea auto_message;
-	 private JTextField value;
-	 private JTree tree;
-	 private MetaEditPane metaedit;
-	 private String args[] = new String[1];
-	 private TreePath previous;
-	 private ValueListener value_listener;
-	 /** Stock standard size for labels. */
-	 static final private Dimension LABEL_SIZE = new Dimension(132, 26);
-
-	 //static final private String LIST = "List";
-	 static final private String NONE = "None";
-	 static final private String TREE = "Tree";
-
-
-	 public GValueTree(MetaEditPane metaedit, int width, int height, JButton add, JButton update, JButton remove) {
-		  this(metaedit, new Dimension(width, height), add, update, remove);
-	 }
-
-	 public GValueTree(MetaEditPane metaedit, Dimension size, JButton add, JButton update, JButton remove) {
-		 super();
-
-		 this.add = add;
-		 this.metaedit = metaedit;
-		 this.remove = remove;
-		 this.update = update;
-
-		 ///ystem.err.println("Created new ValueListener!");
-		 value_listener = new ValueListener();
-
-		 int height = size.height;
-		 int width = size.width;
-
-		 setFont(Gatherer.config.getFont("general.font", false));
-		 //setSize(size);
-		 setPreferredSize(size);
-
-		 //north_pane = new JPanel();
-
-		 //element_label = new JLabel(get("Element"));
-		 //element_label.setPreferredSize(LABEL_SIZE);
-
-		 //elements = new GComboBox();
-		 //elements.addItemListener(this);
-		 //elements.setEditable(false);
-		 //elements.setMaximumRowCount(5);
-		 //elements.setPreferredSize(new Dimension(413, 24));
-
-		 center_pane = new JPanel();
-
-		 card_layout = new CardLayout();
-
-		 auto_pane = new JPanel();
-		 args[0] = "Title";
-		 auto_message = new SmarterTextArea(get("AutoMessage", args), false);
-		 auto_message.setLineWrap(true);
-		 auto_message.setOpaque(false);
-		 auto_message.setWrapStyleWord(true);
-
-		 tree_pane = new JPanel();
-
-		 JLabel tree_label = new JLabel(get("Tree"));
-
-		 tree = new JTree(new GValueModel());
-		 tree.addTreeSelectionListener(this);
-		 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
-		 tree.setRootVisible(true);
-		 tree.putClientProperty("JTree.lineStyle", "Angled");
-		 //setTreeEnabled(false);
-
-		 //list_pane = new JPanel();
-		 //JLabel list_label = new JLabel(get("List"));
-		 //list_model = new DefaultListModel();
-		 //list = new JList(list_model);
-		 //list.addListSelectionListener(new ListSelectionListenerImpl());
-
-		 JPanel controls_pane = new JPanel();
-
-		 value_pane = new JPanel();
-		 value_label = new JLabel(get("Value"));
-		 value_label.setPreferredSize(LABEL_SIZE);
-		 JPanel edit_pane = new JPanel();
-		 expand = new JButton(get("General.Expand"));
-		 expand.setMnemonic(KeyEvent.VK_E);
-		 expand.setPreferredSize(MetaEditPane.BUTTON_SIZE);
-
-		 value = new JTextField();
-		 value.addActionListener(this);
-		 value.setBackground(Gatherer.config.getColor("coloring.editable", false));
-		 value.addFocusListener(this);
-		 //value.addKeyListener(this);
-		 value.setPreferredSize(new Dimension(413, 24));
-
-		 button_pane = new JPanel();
-
-		 JPanel inner_button_pane = new JPanel();
-
-		 //restore = new JButton(get("Restore"));
-		 //restore.setEnabled(false);
-		 //restore.setMnemonic(KeyEvent.VK_T);
-
-		 //search = new JButton(get("Search"));
-		 //search.setMnemonic(KeyEvent.VK_S);
-
-		 // Connection
-		 expand.addActionListener(this);
-		 //restore.addActionListener(this);
-		 //search.addActionListener(this);
-		 value.getDocument().addDocumentListener(new DocumentListenerImpl());
-
-		 // Layout
-		 //element_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
-		 //north_pane.setLayout(new BorderLayout());
-		 //north_pane.add(element_label, BorderLayout.CENTER);
-		 //north_pane.add(elements, BorderLayout.EAST);
-
-		 tree_pane.setLayout(new BorderLayout());
-		 tree_pane.add(tree_label, BorderLayout.NORTH);
-		 tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
-
-		 //list_pane.setLayout(new BorderLayout());
-		 //list_pane.add(list_label, BorderLayout.NORTH);
-		 //list_pane.add(new JScrollPane(list), BorderLayout.CENTER);
-
-		 auto_pane.setBorder(BorderFactory.createEmptyBorder(25,10,25,10));
-		 auto_pane.setLayout(new BorderLayout());
-		 auto_pane.add(auto_message, BorderLayout.CENTER);
-
-		 center_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
-		 center_pane.setLayout(card_layout);
-		 center_pane.add(tree_pane, TREE);
-		 //center_pane.add(list_pane, LIST);
-		 center_pane.add(auto_pane, NONE);
-
-		 value_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
-
-		 inner_button_pane.setLayout(new GridLayout(3,1,0,0));
-		 inner_button_pane.add(add);
-		 inner_button_pane.add(update);
-		 inner_button_pane.add(remove);
-
-		 edit_pane.setLayout(new BorderLayout());
-		 edit_pane.add(expand, BorderLayout.NORTH);
-
-		 button_pane.setLayout(new BorderLayout());
-		 button_pane.add(inner_button_pane, BorderLayout.WEST);
-		 button_pane.add(edit_pane, BorderLayout.EAST);
-
-		 value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
-		 value_pane.setLayout(new BorderLayout());
-		 value_pane.add(value_label, BorderLayout.CENTER);
-		 value_pane.add(value, BorderLayout.EAST);
-		 value_pane.add(button_pane, BorderLayout.SOUTH);
-
-		 controls_pane.setLayout(new BorderLayout());
-		 controls_pane.add(value_pane, BorderLayout.CENTER);
-		 //controls_pane.add(button_pane, BorderLayout.SOUTH);
-
-		 this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-		 this.setLayout(new BorderLayout());
-		 this.add(controls_pane, BorderLayout.NORTH);
-		 this.add(center_pane, BorderLayout.CENTER);
-		 //this.add(south_pane, BorderLayout.SOUTH);
-	 }
-
-	public void actionPerformed(ActionEvent event) {
-		 ignore = true;
-		 if(event.getSource() == value) {
-			  StringTokenizer tokenizer = new StringTokenizer(value.getText(), "\\");
-			  TreePath closest = getClosestPath(tokenizer);
-			  if(closest != null) {
-					TreePath path = tree.getSelectionPath();
-					GValueNode node = (GValueNode)path.getLastPathComponent();
-					///ystem.err.println("1. Set value: " + node.toString());
-					value.setText(Utility.stripNL(node.toString()));//formatPath(path, false)));
-					value.setCaretPosition(0);
-					// Now systematically add to selected record.
-					add.doClick();
-			  }
-			  else {
-					int response = JOptionPane.showConfirmDialog(this, "Do you wish to create a new entry?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
-					if(response == JOptionPane.YES_OPTION) {
-						 TreePath new_path = addNode(value.getText());
-						 tree.setSelectionPath(new_path);
-						 // tree.scrollPathToVisible(new_path);
-						 scrollTreePathToVisible(new_path);
-						 GValueNode node = (GValueNode)new_path.getLastPathComponent();
-						 ///ystem.err.println("2. Set value: " + node.toString());
-						 value.setText(Utility.stripNL(node.toString()));	
-						 value.setCaretPosition(0);
-						 // Now systematically add to selected record.
-						 add.doClick();
-					}
-			  }
-		 }
-		 //else if(event.getSource() == search) {
-		 //	  searchValues();
-		 //	  restore.setEnabled(true);
-		 //	  card_layout.show(center_pane, LIST);
-		 //}
-		 //else if(event.getSource() == restore) {
-		 //	  restore.setEnabled(false);
-		 //	  card_layout.show(center_pane, TREE);
-		 //}
-		 else if(event.getSource() == expand) {
-			  EditorDialog ed = new EditorDialog();
-			  String temp = ed.display(value.getText());
-			  if(temp != null) {
-					///ystem.err.println("3. Set value: " + temp);
-					value.setText(temp);
-					value.setCaretPosition(0);
-			  }
-			  //validateControls();
-		 }
-		 ignore = false;
-	}
-	 /** Called whenever an element within the metadata set module changes in some way.
+    extends JPanel
+    implements ActionListener, FocusListener, MSMListener, TreeSelectionListener {
+    /** Is the metadata currently shown in the edit controls common to all records selected. */
+    private boolean common = false;
+    /** Certain actions are disabled if the metadata selected isn't at file level. */
+    private boolean file_level = false;
+    private boolean ignore = false;
+    private boolean ignore_item_change = false;
+    private CardLayout card_layout;
+    private DefaultListModel list_model;
+    /** The metadata element that is currently selected. */
+    private ElementWrapper selected_metadata_element = null;
+    //private GComboBox elements;
+    private MetadataComboBoxModel em;
+    private GValueModel vm;
+    private Hashtable previous_values = new Hashtable();
+    private JButton add;
+    /** This button creates an editor dialog to allow for an expanded area to type in text. */
+    private JButton expand = null;
+    private JButton remove;
+    private JButton restore;
+    private JButton search;
+    private JButton update;
+    //private JLabel element_label;
+    private JLabel value_label;
+    private JList list;
+    private JPanel auto_pane;
+    private JPanel button_pane;
+    private JPanel center_pane;
+    //private JPanel list_pane;
+    private JPanel north_pane;
+    //private JPanel south_pane;
+    private JPanel tree_pane;
+    private JPanel value_pane;
+    private JTextArea auto_message;
+    private JTextField value;
+    private JTree tree;
+    private MetaEditPane metaedit;
+    private String args[] = new String[1];
+    private TreePath previous;
+    private ValueListener value_listener;
+    /** Stock standard size for labels. */
+    static final private Dimension LABEL_SIZE = new Dimension(132, 26);
+
+    //static final private String LIST = "List";
+    static final private String NONE = "None";
+    static final private String TREE = "Tree";
+
+
+    public GValueTree(MetaEditPane metaedit, int width, int height, JButton add, JButton update, JButton remove) {
+	this(metaedit, new Dimension(width, height), add, update, remove);
+    }
+
+    public GValueTree(MetaEditPane metaedit, Dimension size, JButton add, JButton update, JButton remove) {
+	super();
+
+	this.add = add;
+	this.metaedit = metaedit;
+	this.remove = remove;
+	this.update = update;
+
+	///ystem.err.println("Created new ValueListener!");
+	value_listener = new ValueListener();
+
+	int height = size.height;
+	int width = size.width;
+
+	setFont(Gatherer.config.getFont("general.font", false));
+	//setSize(size);
+	setPreferredSize(size);
+
+	//north_pane = new JPanel();
+
+	//element_label = new JLabel(get("Element"));
+	//element_label.setPreferredSize(LABEL_SIZE);
+
+	//elements = new GComboBox();
+	//elements.addItemListener(this);
+	//elements.setEditable(false);
+	//elements.setMaximumRowCount(5);
+	//elements.setPreferredSize(new Dimension(413, 24));
+
+	center_pane = new JPanel();
+
+	card_layout = new CardLayout();
+
+	auto_pane = new JPanel();
+	args[0] = "Title";
+	auto_message = new SmarterTextArea(get("AutoMessage", args), false);
+	auto_message.setLineWrap(true);
+	auto_message.setOpaque(false);
+	auto_message.setWrapStyleWord(true);
+
+	tree_pane = new JPanel();
+
+	JLabel tree_label = new JLabel(get("Tree"));
+
+	tree = new JTree(new GValueModel());
+	tree.addTreeSelectionListener(this);
+	tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+	tree.setRootVisible(true);
+	tree.putClientProperty("JTree.lineStyle", "Angled");
+	//setTreeEnabled(false);
+
+	//list_pane = new JPanel();
+	//JLabel list_label = new JLabel(get("List"));
+	//list_model = new DefaultListModel();
+	//list = new JList(list_model);
+	//list.addListSelectionListener(new ListSelectionListenerImpl());
+
+	JPanel controls_pane = new JPanel();
+
+	value_pane = new JPanel();
+	value_label = new JLabel(get("Value"));
+	value_label.setPreferredSize(LABEL_SIZE);
+	JPanel edit_pane = new JPanel();
+	expand = new JButton(get("General.Expand"));
+	expand.setMnemonic(KeyEvent.VK_E);
+	expand.setPreferredSize(MetaEditPane.BUTTON_SIZE);
+
+	value = new JTextField();
+	value.addActionListener(this);
+	value.setBackground(Gatherer.config.getColor("coloring.editable", false));
+	value.addFocusListener(this);
+	//value.addKeyListener(this);
+	value.setPreferredSize(new Dimension(413, 24));
+
+	button_pane = new JPanel();
+
+	JPanel inner_button_pane = new JPanel();
+
+	//restore = new JButton(get("Restore"));
+	//restore.setEnabled(false);
+	//restore.setMnemonic(KeyEvent.VK_T);
+
+	//search = new JButton(get("Search"));
+	//search.setMnemonic(KeyEvent.VK_S);
+
+	// Connection
+	expand.addActionListener(this);
+	//restore.addActionListener(this);
+	//search.addActionListener(this);
+	value.getDocument().addDocumentListener(new DocumentListenerImpl());
+
+	// Layout
+	//element_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
+	//north_pane.setLayout(new BorderLayout());
+	//north_pane.add(element_label, BorderLayout.CENTER);
+	//north_pane.add(elements, BorderLayout.EAST);
+
+	tree_pane.setLayout(new BorderLayout());
+	tree_pane.add(tree_label, BorderLayout.NORTH);
+	tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
+
+	//list_pane.setLayout(new BorderLayout());
+	//list_pane.add(list_label, BorderLayout.NORTH);
+	//list_pane.add(new JScrollPane(list), BorderLayout.CENTER);
+
+	auto_pane.setBorder(BorderFactory.createEmptyBorder(25,10,25,10));
+	auto_pane.setLayout(new BorderLayout());
+	auto_pane.add(auto_message, BorderLayout.CENTER);
+
+	center_pane.setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
+	center_pane.setLayout(card_layout);
+	center_pane.add(tree_pane, TREE);
+	//center_pane.add(list_pane, LIST);
+	center_pane.add(auto_pane, NONE);
+
+	value_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
+
+	inner_button_pane.setLayout(new GridLayout(3,1,0,0));
+	inner_button_pane.add(add);
+	inner_button_pane.add(update);
+	inner_button_pane.add(remove);
+
+	edit_pane.setLayout(new BorderLayout());
+	edit_pane.add(expand, BorderLayout.NORTH);
+
+	button_pane.setLayout(new BorderLayout());
+	button_pane.add(inner_button_pane, BorderLayout.WEST);
+	button_pane.add(edit_pane, BorderLayout.EAST);
+
+	value_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
+	value_pane.setLayout(new BorderLayout());
+	value_pane.add(value_label, BorderLayout.CENTER);
+	value_pane.add(value, BorderLayout.EAST);
+	value_pane.add(button_pane, BorderLayout.SOUTH);
+
+	controls_pane.setLayout(new BorderLayout());
+	controls_pane.add(value_pane, BorderLayout.CENTER);
+	//controls_pane.add(button_pane, BorderLayout.SOUTH);
+
+	this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	this.setLayout(new BorderLayout());
+	this.add(controls_pane, BorderLayout.NORTH);
+	this.add(center_pane, BorderLayout.CENTER);
+	//this.add(south_pane, BorderLayout.SOUTH);
+    }
+
+    public void actionPerformed(ActionEvent event) {
+	ignore = true;
+	if(event.getSource() == value) {
+	    StringTokenizer tokenizer = new StringTokenizer(value.getText(), "\\");
+	    TreePath closest = getClosestPath(tokenizer);
+	    if(closest != null) {
+		TreePath path = tree.getSelectionPath();
+		GValueNode node = (GValueNode)path.getLastPathComponent();
+		///ystem.err.println("1. Set value: " + node.toString());
+		value.setText(Utility.stripNL(node.toString()));//formatPath(path, false)));
+		value.setCaretPosition(0);
+		// Now systematically add to selected record.
+		add.doClick();
+	    }
+	    else {
+		int response = JOptionPane.showConfirmDialog(this, "Do you wish to create a new entry?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+		if(response == JOptionPane.YES_OPTION) {
+		    TreePath new_path = addNode(value.getText());
+		    tree.setSelectionPath(new_path);
+		    // tree.scrollPathToVisible(new_path);
+		    scrollTreePathToVisible(new_path);
+		    GValueNode node = (GValueNode)new_path.getLastPathComponent();
+		    ///ystem.err.println("2. Set value: " + node.toString());
+		    value.setText(Utility.stripNL(node.toString()));	
+		    value.setCaretPosition(0);
+		    // Now systematically add to selected record.
+		    add.doClick();
+		}
+	    }
+	}
+	//else if(event.getSource() == search) {
+	//	  searchValues();
+	//	  restore.setEnabled(true);
+	//	  card_layout.show(center_pane, LIST);
+	//}
+	//else if(event.getSource() == restore) {
+	//	  restore.setEnabled(false);
+	//	  card_layout.show(center_pane, TREE);
+	//}
+	else if(event.getSource() == expand) {
+	    EditorDialog ed = new EditorDialog();
+	    String temp = ed.display(value.getText());
+	    if(temp != null) {
+		///ystem.err.println("3. Set value: " + temp);
+		value.setText(temp);
+		value.setCaretPosition(0);
+	    }
+	    //validateControls();
+	}
+	ignore = false;
+    }
+    /** Called whenever an element within the metadata set module changes in some way.
 	  * @param event A <strong>MSMEvent</strong> which contains information about the change.
 	  */
-	 public void elementChanged(MSMEvent event) {
-		  /* @TODO - Not sure. Figure out. */
-	 }
-	 public void focusGained(FocusEvent event) {
-
-	 }
-
-	 public void focusLost(FocusEvent event) {
-
-	 }
-
-	 public boolean getCommon() {
-		  return common;
-	 }
-
-	 public ElementWrapper getSelectedMetadataElement() {
-		  return selected_metadata_element;
-	 }
-
-	 public String getSelectedValue() {
-		  String value_text = value.getText();
-		  previous_values.put(selected_metadata_element, value_text);
-		  return value_text;
-	 }
-
-	 public GValueModel getValueModel() {
-		  return (GValueModel) tree.getModel();
-	 }
-
-	 /** This method is required for any ItemListener so that it can be informed when the state of an item set changes (such as when a new selection is made in a combobox). Note that this update is subtly different from the validateControls() method as it also gathers new models for each combobox as required. The reason this isn't done as part of validateControls() is that this would continously reset whatever the user is attempting to type (ie value would be found to be enabled and a new model would be loaded erasing whatever the user just typed in value to fire the validateControls() in the first place!)
+    public void elementChanged(MSMEvent event) {
+	/* @TODO - Not sure. Figure out. */
+    }
+    public void focusGained(FocusEvent event) {
+
+    }
+
+    public void focusLost(FocusEvent event) {
+
+    }
+
+    public boolean getCommon() {
+	return common;
+    }
+
+    public ElementWrapper getSelectedMetadataElement() {
+	return selected_metadata_element;
+    }
+
+    public String getSelectedValue() {
+	String value_text = value.getText();
+	previous_values.put(selected_metadata_element, value_text);
+	return value_text;
+    }
+
+    public GValueModel getValueModel() {
+	return (GValueModel) tree.getModel();
+    }
+
+    /** This method is required for any ItemListener so that it can be informed when the state of an item set changes (such as when a new selection is made in a combobox). Note that this update is subtly different from the validateControls() method as it also gathers new models for each combobox as required. The reason this isn't done as part of validateControls() is that this would continously reset whatever the user is attempting to type (ie value would be found to be enabled and a new model would be loaded erasing whatever the user just typed in value to fire the validateControls() in the first place!)
 	  * @param event An ItemEvent encapsulating all the information about this event.
 	  */
@@ -384,294 +384,294 @@
 	  * @param event A <strong>MSMEvent</strong> which encapsulates relevant data about the change.
 	  */
-	 public void metadataChanged(MSMEvent event) {
-		  // Don't care.
-	 }
+    public void metadataChanged(MSMEvent event) {
+	// Don't care.
+    }
 	 
     public void newMetadataSetManager() {
-		  if(Gatherer.c_man != null && Gatherer.c_man.getCollection() != null && Gatherer.c_man.getCollection().msm != null) {
-				Gatherer.c_man.getCollection().msm.addMSMListener(this);
-		  }
-		  setChanged(null);
-    }
-
-	 public void refreshValueTree() {
-		  if(em != null && em.getSize() > 0) {
-				setSelectedMetadataElement((ElementWrapper)em.getSelectedItem());
-		  }
-	 }
+	if(Gatherer.c_man != null && Gatherer.c_man.getCollection() != null && Gatherer.c_man.getCollection().msm != null) {
+	    Gatherer.c_man.getCollection().msm.addMSMListener(this);
+	}
+	setChanged(null);
+    }
+
+    public void refreshValueTree() {
+	if(em != null && em.getSize() > 0) {
+	    setSelectedMetadataElement((ElementWrapper)em.getSelectedItem());
+	}
+    }
     
     public void registerModel(GValueModel model) {
-		  vm = model;
-		  vm.addTreeModelListener(value_listener);
-		  tree.setModel(model);
-		  tree.addTreeSelectionListener(this);
-    }
-	 /** Called whenever a set within the metadata set module changes in some way.
+	vm = model;
+	vm.addTreeModelListener(value_listener);
+	tree.setModel(model);
+	tree.addTreeSelectionListener(this);
+    }
+    /** Called whenever a set within the metadata set module changes in some way.
 	  * @param event A <strong>MSMEvent</strong> which contains information about the change.
 	  */
-	 public void setChanged(MSMEvent event) {
-		  // Update the element model.
-		  MetadataComboBoxModel model = Gatherer.c_man.getCollection().msm.getElementModel();
-		  if(model.getSize() > 0) {
-				setElementModel(model);
-				model.setSelectedItem(model.getElementAt(0));
-				vm = Gatherer.c_man.getCollection().msm.getValueTree((ElementWrapper)model.getSelectedItem());
-				setTreeEnabled(true);
-				if(vm == null) {
-					 vm = new GValueModel(get("Not_Applicable"));
-					 setTreeEnabled(false);
-				}
-		  }
-		  else {
-				vm = new GValueModel();
-				setTreeEnabled(true);
-		  }
-		  vm.addTreeModelListener(value_listener);
-		  tree.setModel(vm);
-		  tree.addTreeSelectionListener(this);
-	 } 
-
-	 public void setCommon(boolean common) {
-		  this.common = common;
-	 }
+    public void setChanged(MSMEvent event) {
+	// Update the element model.
+	MetadataComboBoxModel model = Gatherer.c_man.getCollection().msm.getElementModel();
+	if(model.getSize() > 0) {
+	    setElementModel(model);
+	    model.setSelectedItem(model.getElementAt(0));
+	    vm = Gatherer.c_man.getCollection().msm.getValueTree((ElementWrapper)model.getSelectedItem());
+	    setTreeEnabled(true);
+	    if(vm == null) {
+		vm = new GValueModel(get("Not_Applicable"));
+		setTreeEnabled(false);
+	    }
+	}
+	else {
+	    vm = new GValueModel();
+	    setTreeEnabled(true);
+	}
+	vm.addTreeModelListener(value_listener);
+	tree.setModel(vm);
+	tree.addTreeSelectionListener(this);
+    } 
+
+    public void setCommon(boolean common) {
+	this.common = common;
+    }
 
     public void setElementModel(MetadataComboBoxModel model) {
-		  ///ystem.err.println("Reset ElementModel.");
-		  setTreeEnabled(true);
-		  Gatherer.c_man.getCollection().msm.addMSMListener(this);
-		  if(model == null) {
-				Gatherer.println("model = null");
-				System.exit(0);
-		  }
-		  ignore_item_change = true;
-		  em = model;
-		  //elements.setModel(em);
-		  if(em.getSize() > 0) {
+	///ystem.err.println("Reset ElementModel.");
+	setTreeEnabled(true);
+	Gatherer.c_man.getCollection().msm.addMSMListener(this);
+	if(model == null) {
+	    Gatherer.println("model = null");
+	    System.exit(0);
+	}
+	ignore_item_change = true;
+	em = model;
+	//elements.setModel(em);
+	if(em.getSize() > 0) {
 				///ystem.err.println("em.size() > 0");
 				//elements.setEnabled(true);
 				//elements.setSelectedIndex(0);
-				selected_metadata_element = (ElementWrapper)em.getSelectedItem();
+	    selected_metadata_element = (ElementWrapper)em.getSelectedItem();
 				//elements.setToolTipText(selected_metadata_element.getToolTip());
 				// First we create and install a blank model, or else no updates ever work.
-				vm = new GValueModel(get("Not_Applicable"));
-				tree.setModel(vm);
+	    vm = new GValueModel(get("Not_Applicable"));
+	    tree.setModel(vm);
 				// Now we create a new model if applicable, disabling otherwise.
-				if(selected_metadata_element.getNamespace().length() > 0) {
-					 vm = Gatherer.c_man.getCollection().msm.getValueTree(selected_metadata_element);
-					 vm.addTreeModelListener(value_listener);
-					 ///ystem.err.println("Found existing model containing " + vm.size() + " entries");
-					 setTreeEnabled(true);
-					 tree.setModel(vm); // This doesn't seem to work
-				}
-				else {
-					 ///ystem.err.println("Creating new model.");
-					 setTreeEnabled(false);
-				}
+	    if(selected_metadata_element.getNamespace().length() > 0) {
+		vm = Gatherer.c_man.getCollection().msm.getValueTree(selected_metadata_element);
+		vm.addTreeModelListener(value_listener);
+		///ystem.err.println("Found existing model containing " + vm.size() + " entries");
+		setTreeEnabled(true);
+		tree.setModel(vm); // This doesn't seem to work
+	    }
+	    else {
+		///ystem.err.println("Creating new model.");
+		setTreeEnabled(false);
+	    }
 				///ystem.err.println("The value tree should have been updated by now.");
 				///ystem.err.println("6. Set value: " + getLastValue());
-				value.setText(Utility.stripNL(getLastValue()));
-				value.setCaretPosition(0);
-		  }
-		  else {
+	    value.setText(Utility.stripNL(getLastValue()));
+	    value.setCaretPosition(0);
+	}
+	else {
 				///ystem.err.println("No elements loaded.");
 				//elements.setEnabled(false);
 				//elements.setToolTipText(get("No_Description"));
 				//setTreeEnabled(false);
-				value.setText("");
-		  }
-		  previous = null;
-		  ignore_item_change = false;
-		  validateControls();
-    }
-
-	 public void setFileLevel(boolean file_level) {
-		  this.file_level = file_level;
-	 }
-
-	 public void setRootVisible(boolean val) {
-		  tree.setRootVisible(val);
-	 }
+	    value.setText("");
+	}
+	previous = null;
+	ignore_item_change = false;
+	validateControls();
+    }
+
+    public void setFileLevel(boolean file_level) {
+	this.file_level = file_level;
+    }
+
+    public void setRootVisible(boolean val) {
+	tree.setRootVisible(val);
+    }
 
     public void setSelectedMetadataElement(ElementWrapper element) {
-		  if(!ignore_item_change) {
-				ignore_item_change = true;
-				if(element != null) {
-					 selected_metadata_element = element;
-					 //elements.setSelectedItem(element);
-					 //elements.setToolTipText(element.getToolTip());
-					 vm = Gatherer.c_man.getCollection().msm.getValueTree(element);
-					 if(vm != null) {
-						  vm.addTreeModelListener(value_listener);
-						  setTreeEnabled(true);
-					 }
-					 else {
-						  vm = new GValueModel(get("Not_Applicable"));
-						  setTreeEnabled(false);
-					 }
-					 tree.setModel(vm);
-					 ///ystem.err.println("7. Set value: " + getLastValue());
-					 //value.setText(Utility.stripNL(getLastValue()));
-					 //value.setCaretPosition(0);
-				}
-				else {
-					 //elements.setToolTipText(get("No_Description"));
-					 setTreeEnabled(false);
-					 //value.setText("");
-				}
-				metaedit.validateControls(false);
-		  }
-		  previous = null;
-		  ignore_item_change = false;
-		  //validateControls();
-    }
-
-	 public void setSelectedValue(String val) {
-		  ignore = true;
-		  value.setText(Utility.stripNL(val));
-		  value.setCaretPosition(0);
-		  StringTokenizer tokenizer = new StringTokenizer(val, "\\");
-		  TreePath closest = getClosestPath(tokenizer);
-		  if(previous != null) {
-				tree.collapsePath(getParent(previous));
-				previous = null;
-		  }
-		  if(closest != null) {
-				tree.setSelectionPath(closest);
+	if(!ignore_item_change) {
+	    ignore_item_change = true;
+	    if(element != null) {
+		selected_metadata_element = element;
+		//elements.setSelectedItem(element);
+		//elements.setToolTipText(element.getToolTip());
+		vm = Gatherer.c_man.getCollection().msm.getValueTree(element);
+		if(vm != null) {
+		    vm.addTreeModelListener(value_listener);
+		    setTreeEnabled(true);
+		}
+		else {
+		    vm = new GValueModel(get("Not_Applicable"));
+		    setTreeEnabled(false);
+		}
+		tree.setModel(vm);
+		///ystem.err.println("7. Set value: " + getLastValue());
+		//value.setText(Utility.stripNL(getLastValue()));
+		//value.setCaretPosition(0);
+	    }
+	    else {
+		//elements.setToolTipText(get("No_Description"));
+		setTreeEnabled(false);
+		//value.setText("");
+	    }
+	    metaedit.validateControls(false);
+	}
+	previous = null;
+	ignore_item_change = false;
+	//validateControls();
+    }
+
+    public void setSelectedValue(String val) {
+	ignore = true;
+	value.setText(Utility.stripNL(val));
+	value.setCaretPosition(0);
+	StringTokenizer tokenizer = new StringTokenizer(val, "\\");
+	TreePath closest = getClosestPath(tokenizer);
+	if(previous != null) {
+	    tree.collapsePath(getParent(previous));
+	    previous = null;
+	}
+	if(closest != null) {
+	    tree.setSelectionPath(closest);
 				// tree.scrollPathToVisible(closest);
-				scrollTreePathToVisible(closest);
-		  }
-		  ignore = false;
-		  //validateControls();
-	 }
-
-	 /** This method enables or diables the various controls based upon the
+	    scrollTreePathToVisible(closest);
+	}
+	ignore = false;
+	//validateControls();
+    }
+
+    /** This method enables or diables the various controls based upon the
 	  * state of the system. This method should be called after every change
 	  * which could affect the GUI. <br>
 	  * Hack #1: For some reason this gets called 14 times for one selection. One day I'll figure out why, but for now I'll just ignore all but every fourteenth call.
 	  */
-	 public void validateControls() {
-		  Metadata selected_metadata = metaedit.getSelectedMetadata();
-		  if(metaedit.getSelectedNode() != null && selected_metadata != null) {
-				ElementWrapper element = selected_metadata.getElement();
-				if(element.getNamespace() != "") {
-					 GValueNode value_node = selected_metadata.getValueNode();
-					 ///ystem.err.println("Value node = " + value_node);
-					 // We may be dealing with an element that has no current value
-					 if(value_node == null) {
-						  String new_value = value.getText();
-						  if(new_value.length() > 0) {
-								add.setEnabled(true);
-						  }
-						  else {
-								add.setEnabled(false);
-						  }
-						  // Nothing to update
-						  update.setEnabled(false);
-						  // Nothing to remove
-						  remove.setEnabled(false);
-					 }
-					 // Or else we have some current value that may or may not match the one in our value field.
-					 else {
-						  String current_value = selected_metadata.getValueNode().getFullPath();
-						  String new_value = value.getText();
-						  ///ystem.err.println("Current value: '" + current_value + "'");
-						  ///ystem.err.println("New value:     '" + new_value + "'");
-						  if(new_value.length() > 0 && !current_value.equals(new_value)) {
-								add.setEnabled(true);
-								if(common) {
-									 update.setEnabled(true);
-								}
-								else {
-									 update.setEnabled(false);
-								}
-						  }
-						  else {
-								add.setEnabled(false);
-								update.setEnabled(false);
-						  }
-						  // If you have something selected, and we're at the file level, you can remove it.
-						  if(file_level) {
-								remove.setEnabled(true);
-						  }
-						  else {
-								remove.setEnabled(false);
-						  }
-					 }
-				}
-				else {
-					 add.setEnabled(false);
-					 update.setEnabled(false);
-					 remove.setEnabled(false);
-				}			
-		  }
-		  else {
-				add.setEnabled(false);
-				update.setEnabled(false);
-				remove.setEnabled(false);
-		  }
-	 }
-
-	 /** Called when the value tree of a certain element has changed significantly. If we are displaying the tree for the element noted in the event, then reload the value model.
+    public void validateControls() {
+	Metadata selected_metadata = metaedit.getSelectedMetadata();
+	if(metaedit.getSelectedNode() != null && selected_metadata != null) {
+	    ElementWrapper element = selected_metadata.getElement();
+	    if(element.getNamespace() != "") {
+		GValueNode value_node = selected_metadata.getValueNode();
+		///ystem.err.println("Value node = " + value_node);
+		// We may be dealing with an element that has no current value
+		if(value_node == null) {
+		    String new_value = value.getText();
+		    if(new_value.length() > 0) {
+			add.setEnabled(true);
+		    }
+		    else {
+			add.setEnabled(false);
+		    }
+		    // Nothing to update
+		    update.setEnabled(false);
+		    // Nothing to remove
+		    remove.setEnabled(false);
+		}
+		// Or else we have some current value that may or may not match the one in our value field.
+		else {
+		    String current_value = selected_metadata.getValueNode().getFullPath();
+		    String new_value = value.getText();
+		    ///ystem.err.println("Current value: '" + current_value + "'");
+		    ///ystem.err.println("New value:     '" + new_value + "'");
+		    if(new_value.length() > 0 && !current_value.equals(new_value)) {
+			add.setEnabled(true);
+			if(common) {
+			    update.setEnabled(true);
+			}
+			else {
+			    update.setEnabled(false);
+			}
+		    }
+		    else {
+			add.setEnabled(false);
+			update.setEnabled(false);
+		    }
+		    // If you have something selected, and we're at the file level, you can remove it.
+		    if(file_level) {
+			remove.setEnabled(true);
+		    }
+		    else {
+			remove.setEnabled(false);
+		    }
+		}
+	    }
+	    else {
+		add.setEnabled(false);
+		update.setEnabled(false);
+		remove.setEnabled(false);
+	    }			
+	}
+	else {
+	    add.setEnabled(false);
+	    update.setEnabled(false);
+	    remove.setEnabled(false);
+	}
+    }
+
+    /** Called when the value tree of a certain element has changed significantly. If we are displaying the tree for the element noted in the event, then reload the value model.
 	  * @param event A <strong>MSMEvent</strong> containing information about the event.
 	  */
-	 public void valueChanged(MSMEvent event) {
-		  ElementWrapper element = event.getElement();
-		  if(selected_metadata_element.equals(element)) {
-				GValueModel temp = Gatherer.c_man.getCollection().msm.getValueTree(element);
-				if(temp != null) {
-					 vm = temp;
-					 tree.setModel(vm);
-					 vm.removeTreeModelListener(value_listener);
-					 vm.addTreeModelListener(value_listener);
-					 setTreeEnabled(true);
-					 setSelectedValue(value.getText());
-				}
-				else {
-				    vm = new GValueModel(get("Not_Applicable"));
-				    tree.setModel(vm);
-				    setTreeEnabled(false);
-				}
-		  }
-	 }
+    public void valueChanged(MSMEvent event) {
+	ElementWrapper element = event.getElement();
+	if(selected_metadata_element.equals(element)) {
+	    GValueModel temp = Gatherer.c_man.getCollection().msm.getValueTree(element);
+	    if(temp != null) {
+		vm = temp;
+		tree.setModel(vm);
+		vm.removeTreeModelListener(value_listener);
+		vm.addTreeModelListener(value_listener);
+		setTreeEnabled(true);
+		setSelectedValue(value.getText());
+	    }
+	    else {
+		vm = new GValueModel(get("Not_Applicable"));
+		tree.setModel(vm);
+		setTreeEnabled(false);
+	    }
+	}
+    }
 
     public void valueChanged(TreeSelectionEvent event) {
-		  if(tree.getSelectionCount() != 0 && !ignore) {
-				TreePath path = tree.getSelectionPath();
-				GValueNode node = (GValueNode) path.getLastPathComponent();
-				value.setText(node.getFullPath());//formatPath(tree.getSelectionPath(), false)));
-				value.setCaretPosition(0);
-				validateControls();
-		  }
+	if(tree.getSelectionCount() != 0 && !ignore) {
+	    TreePath path = tree.getSelectionPath();
+	    GValueNode node = (GValueNode) path.getLastPathComponent();
+	    value.setText(node.getFullPath());//formatPath(tree.getSelectionPath(), false)));
+	    value.setCaretPosition(0);
+	    validateControls();
+	}
     }
     
     private TreePath addNode(String reference) {
-		  StringTokenizer tokenizer = new StringTokenizer(reference, "\\");
-		  GValueModel model = (GValueModel)tree.getModel();
-		  GValueNode current = (GValueNode)model.getRoot();
-		  while(tokenizer.hasMoreElements()) {
-				String token = tokenizer.nextToken();
-				GValueNode node = (GValueNode)getClosestNode(token, current);
-				if(node == null) {
-					 Gatherer.println("Before: '" + current.toString() + "'");
-					 for(int i = 0; i < current.getChildCount(); i++) {
-						  Gatherer.println(current.getChildAt(i).toString());
-					 }
-					 node = model.addValue(token, current, null);
-					 Gatherer.println("After: '" + current.toString() + "'");
-					 for(int i = 0; i < current.getChildCount(); i++) {
-						  Gatherer.println(current.getChildAt(i).toString());
-					 }
-				}
-				current = node;
-		  }
-		  tree.updateUI();
-		  if(current != null) {
-				return new TreePath(current.getPath());
-		  }
-		  else {
-				return new TreePath(((GValueNode)model.getRoot()).getPath());
-		  }
-    }
-	 /** Returns the given tree path as path formatted string (ie subject\subject\subject).
+	StringTokenizer tokenizer = new StringTokenizer(reference, "\\");
+	GValueModel model = (GValueModel)tree.getModel();
+	GValueNode current = (GValueNode)model.getRoot();
+	while(tokenizer.hasMoreElements()) {
+	    String token = tokenizer.nextToken();
+	    GValueNode node = (GValueNode)getClosestNode(token, current);
+	    if(node == null) {
+		Gatherer.println("Before: '" + current.toString() + "'");
+		for(int i = 0; i < current.getChildCount(); i++) {
+		    Gatherer.println(current.getChildAt(i).toString());
+		}
+		node = model.addValue(token, current, null);
+		Gatherer.println("After: '" + current.toString() + "'");
+		for(int i = 0; i < current.getChildCount(); i++) {
+		    Gatherer.println(current.getChildAt(i).toString());
+		}
+	    }
+	    current = node;
+	}
+	tree.updateUI();
+	if(current != null) {
+	    return new TreePath(current.getPath());
+	}
+	else {
+	    return new TreePath(((GValueNode)model.getRoot()).getPath());
+	}
+    }
+    /** Returns the given tree path as path formatted string (ie subject\subject\subject).
 	  * @param tree The <strong>JTree</strong> the TreePath came from. Used to determine if the root node should be encoded as well.
 	  * @param path A <strong>TreePath</strong> that you wish to encode to String.
@@ -681,129 +681,129 @@
 	  * @see javax.swing.tree.TreePath
 	  */
-	static public String formatPath(JTree tree, TreePath path, boolean full) {
-		String text = "";
-		int i = 0;
-		if(tree == null || (!tree.isRootVisible() && !full)) {
-			 i = 1;
-		}
-		for( ; i < path.getPathCount(); i++) {
-			GValueNode node = (GValueNode)path.getPathComponent(i);
-		  text = text + node.toString();
-			if(node.getChildCount() > 0) {
-				text = text + "\\";
-			}
-		}
-		if(full && text.endsWith("\\")) {
-			 return text.substring(0, text.length() - 1);
-		}
-		return text;
-	}
-
-	 private String get(String key) {
-		  return get(key, null);
-	 }
-
-	 private String get(String key, String args[]) {
-		  if(key.indexOf('.') == -1) {
-				key = "MetaEdit." + key;
-		  }
-		  return Gatherer.dictionary.get(key,args);
-	 }
-
-	private GValueNode getClosestNode(String target, GValueNode current) {
-		// We have struck it lucky.
-		if(startsWith(current.toString(), target)) {
-			return current;
-		}
-		GValueNode closest = null;
-		for(int i = 0; i < current.getChildCount() && closest == null; i++) {
-			closest = getClosestNode(target, (GValueNode)current.getChildAt(i));
-		}
-		return closest;
-	}
-
-	 private TreePath getClosestPath(StringTokenizer tokenizer) {
-		  if(tokenizer.countTokens() > 0) {
-				TreeNode tree_node = (TreeNode)tree.getModel().getRoot();
-				if(tree_node instanceof GValueNode) {
-					 GValueNode current = (GValueNode)tree_node;
-					 TreePath closest = null;
-					 while(tokenizer.hasMoreElements() && current != null) {
-						  current = getClosestNode(tokenizer.nextToken(), current);
-					 }
-					 if(current != null) {
-						  return new TreePath(current.getPath());
-					 }
-					 return null;
-				}
-		  }
-		  return null;
-	 }
-
-	 private String getLastValue() {
-		  if(previous_values.containsKey(selected_metadata_element)) {
-				return (String)previous_values.get(selected_metadata_element);
-		  }
-		  return "";
-	 }
-
-	private TreePath getParent(TreePath path) {
-		if(path.getPathCount() > 1) {
-			TreeNode nodes[] = new TreeNode[path.getPathCount() - 1];
-			for(int i = 0; i < path.getPathCount() - 1; i++) {
-				nodes[i] = (TreeNode)path.getPathComponent(i);
-			}
-			return new TreePath(nodes);
+    static public String formatPath(JTree tree, TreePath path, boolean full) {
+	String text = "";
+	int i = 0;
+	if(tree == null || (!tree.isRootVisible() && !full)) {
+	    i = 1;
+	}
+	for( ; i < path.getPathCount(); i++) {
+	    GValueNode node = (GValueNode)path.getPathComponent(i);
+	    text = text + node.toString();
+	    if(node.getChildCount() > 0) {
+		text = text + "\\";
+	    }
+	}
+	if(full && text.endsWith("\\")) {
+	    return text.substring(0, text.length() - 1);
+	}
+	return text;
+    }
+
+    private String get(String key) {
+	return get(key, null);
+    }
+
+    private String get(String key, String args[]) {
+	if(key.indexOf('.') == -1) {
+	    key = "MetaEdit." + key;
+	}
+	return Gatherer.dictionary.get(key,args);
+    }
+
+    private GValueNode getClosestNode(String target, GValueNode current) {
+	// We have struck it lucky.
+	if(startsWith(current.toString(), target)) {
+	    return current;
+	}
+	GValueNode closest = null;
+	for(int i = 0; i < current.getChildCount() && closest == null; i++) {
+	    closest = getClosestNode(target, (GValueNode)current.getChildAt(i));
+	}
+	return closest;
+    }
+
+    private TreePath getClosestPath(StringTokenizer tokenizer) {
+	if(tokenizer.countTokens() > 0) {
+	    TreeNode tree_node = (TreeNode)tree.getModel().getRoot();
+	    if(tree_node instanceof GValueNode) {
+		GValueNode current = (GValueNode)tree_node;
+		TreePath closest = null;
+		while(tokenizer.hasMoreElements() && current != null) {
+		    current = getClosestNode(tokenizer.nextToken(), current);
+		}
+		if(current != null) {
+		    return new TreePath(current.getPath());
 		}
 		return null;
-	}
-
-	private void searchValues() {
-		 // Clear any old search results.
-		 list_model.clear();
-		 String target = value.getText();
-		 GValueNode current = (GValueNode)tree.getModel().getRoot();
-		 Vector search_space = new Vector();
-		 search_space.add(current);
-		 for(int i = 0; i < search_space.size(); i++) {
-			  GValueNode node = (GValueNode)search_space.get(i);
-			  // Add this nodes children.
-			  for(int j = 0; j < node.getChildCount(); j++) {
-					search_space.add(node.getChildAt(j));
-			  }
-			  // And if this node matches add it to list, except for the very
-			  // first node as that is AssignedValues the root node!
-			  if(node.toString().indexOf(target) != -1 &&
-				  node != current) {
-					list_model.add(list_model.size(), node);
-			  }
-		 }
-	}
-
-	private void setTreeEnabled(boolean value) {
-		 tree.setRootVisible(!value);
-		 tree.setEnabled(value);
-		 if(value) {
-			  card_layout.show(center_pane, TREE);
-		 }
-		 else {
-			  Metadata selected_metadata = metaedit.getSelectedMetadata();
-			  if(selected_metadata != null && selected_metadata.getElement() != null) {
-					args[0] = selected_metadata.getElement().toString();
-			  }
-			  else {
-					args[0] = "";
-			  }
-			  auto_message.setText(get("AutoMessage", args));
-			  card_layout.show(center_pane, NONE);
-		 }
-	}
-
-	private boolean startsWith(String longer, String shorter) {
-		if(longer.length() > shorter.length()) {
-		  longer = longer.substring(0, shorter.length());
-		}
-		return longer.equalsIgnoreCase(shorter);
-	}
+	    }
+	}
+	return null;
+    }
+
+    private String getLastValue() {
+	if(previous_values.containsKey(selected_metadata_element)) {
+	    return (String)previous_values.get(selected_metadata_element);
+	}
+	return "";
+    }
+
+    private TreePath getParent(TreePath path) {
+	if(path.getPathCount() > 1) {
+	    TreeNode nodes[] = new TreeNode[path.getPathCount() - 1];
+	    for(int i = 0; i < path.getPathCount() - 1; i++) {
+		nodes[i] = (TreeNode)path.getPathComponent(i);
+	    }
+	    return new TreePath(nodes);
+	}
+	return null;
+    }
+
+    private void searchValues() {
+	// Clear any old search results.
+	list_model.clear();
+	String target = value.getText();
+	GValueNode current = (GValueNode)tree.getModel().getRoot();
+	Vector search_space = new Vector();
+	search_space.add(current);
+	for(int i = 0; i < search_space.size(); i++) {
+	    GValueNode node = (GValueNode)search_space.get(i);
+	    // Add this nodes children.
+	    for(int j = 0; j < node.getChildCount(); j++) {
+		search_space.add(node.getChildAt(j));
+	    }
+	    // And if this node matches add it to list, except for the very
+	    // first node as that is AssignedValues the root node!
+	    if(node.toString().indexOf(target) != -1 &&
+	       node != current) {
+		list_model.add(list_model.size(), node);
+	    }
+	}
+    }
+
+    private void setTreeEnabled(boolean value) {
+	tree.setRootVisible(!value);
+	tree.setEnabled(value);
+	if(value) {
+	    card_layout.show(center_pane, TREE);
+	}
+	else {
+	    Metadata selected_metadata = metaedit.getSelectedMetadata();
+	    if(selected_metadata != null && selected_metadata.getElement() != null) {
+		args[0] = selected_metadata.getElement().toString();
+	    }
+	    else {
+		args[0] = "";
+	    }
+	    auto_message.setText(get("AutoMessage", args));
+	    card_layout.show(center_pane, NONE);
+	}
+    }
+
+    private boolean startsWith(String longer, String shorter) {
+	if(longer.length() > shorter.length()) {
+	    longer = longer.substring(0, shorter.length());
+	}
+	return longer.equalsIgnoreCase(shorter);
+    }
 
     private void printTreePaths() {
@@ -811,12 +811,12 @@
 	Vector nodes = new Vector();
 	nodes.add(tree.getModel().getRoot());
-		  while(nodes.size() > 0) {
-		      GValueNode node = (GValueNode)nodes.get(0);
-		      nodes.remove(node);
-		      for(int i = 0; i < node.getChildCount(); i++) {
-			  nodes.add(node.getChildAt(i));
-		      }
+	while(nodes.size() > 0) {
+	    GValueNode node = (GValueNode)nodes.get(0);
+	    nodes.remove(node);
+	    for(int i = 0; i < node.getChildCount(); i++) {
+		nodes.add(node.getChildAt(i));
+	    }
 				///ystem.out.println("TreePath = " + node.getPath());
-		  }
+	}
     }
 
@@ -840,124 +840,119 @@
     }
 
-	 private class DocumentListenerImpl
-		  implements DocumentListener {
-		  /** Gives notification that an attribute or set of attributes changed. */
-		  public void changedUpdate(DocumentEvent e) {
-				validate();
-		  }
+    private class DocumentListenerImpl
+	implements DocumentListener {
+	/** Gives notification that an attribute or set of attributes changed. */
+	public void changedUpdate(DocumentEvent e) {
+	    validate();
+	}
           
-		  /** Gives notification that there was an insert into the document. */
-		  public void insertUpdate(DocumentEvent e) {
-				validate();
-		  }
+	/** Gives notification that there was an insert into the document. */
+	public void insertUpdate(DocumentEvent e) {
+	    validate();
+	}
           
-		  /** Gives notification that a portion of the document has been removed. */
-		  public void removeUpdate(DocumentEvent e) {
-				validate();
-		  }
-
-		  public void validate() {
-				ignore = true;
+	/** Gives notification that a portion of the document has been removed. */
+	public void removeUpdate(DocumentEvent e) {
+	    validate();
+	}
+
+	public void validate() {
+	    ignore = true;
 				///ystem.err.println("Key released.");
-				StringTokenizer tokenizer = new StringTokenizer(value.getText(), "\\");
-				TreePath closest = getClosestPath(tokenizer);
-				if(closest == previous) {
-					 validateControls();
-					 ignore = false;
-					 return;
-				}
-				else {
-					 // Clear previous
-					 if(previous != null) {
-						  // We have a brand new match close up the old one.
-						  if(closest != null) {
-								if(getParent(previous) != null &&
-									!getParent(previous).toString().equals("[AssignedValues]")) {
-									 tree.collapsePath(getParent(previous));
-								}
-								previous = null;
-						  }
-						  // We haven't got a match, but we did have a matching path to
-						  // this point. Move the selection to previous paths parent.
-						  else {
-								tree.setSelectionPath(getParent(previous));
-						  }
-					 }
+	    StringTokenizer tokenizer = new StringTokenizer(value.getText(), "\\");
+	    TreePath closest = getClosestPath(tokenizer);
+	    if(closest == previous) {
+		validateControls();
+		ignore = false;
+		return;
+	    }
+	    else {
+		// Clear previous
+		if(previous != null) {
+		    // We have a brand new match close up the old one.
+		    if(closest != null) {
+			if(getParent(previous) != null &&
+			   !getParent(previous).toString().equals("[AssignedValues]")) {
+			    tree.collapsePath(getParent(previous));
+			}
+			previous = null;
+		    }
+		    // We haven't got a match, but we did have a matching path to
+		    // this point. Move the selection to previous paths parent.
+		    else {
+			tree.setSelectionPath(getParent(previous));
+		    }
+		}
 					 
-					 if(closest != null) {
-						  tree.setSelectionPath(closest);
-						  // tree.scrollPathToVisible(closest);
-						  scrollTreePathToVisible(closest);
-						  previous = closest;
-					 }
-				}
-				ignore = false;
-				validateControls();
-		  }
-	 }
-
-	 private class ListSelectionListenerImpl
-		  implements ListSelectionListener {
-		  public void valueChanged(ListSelectionEvent event) {
-				GValueNode node = (GValueNode)list.getSelectedValue();
-				value.setText(Utility.stripNL(node.toString()));//formatPath(node.getPath(), false)));
-				value.setCaretPosition(0);
-		  }
-	 }
+		if(closest != null) {
+		    tree.setSelectionPath(closest);
+		    // tree.scrollPathToVisible(closest);
+		    scrollTreePathToVisible(closest);
+		    previous = closest;
+		}
+	    }
+	    ignore = false;
+	    validateControls();
+	}
+    }
+
+    private class ListSelectionListenerImpl
+	implements ListSelectionListener {
+	public void valueChanged(ListSelectionEvent event) {
+	    GValueNode node = (GValueNode)list.getSelectedValue();
+	    value.setText(Utility.stripNL(node.toString()));//formatPath(node.getPath(), false)));
+	    value.setCaretPosition(0);
+	}
+    }
 
     private class ValueListener
-		  implements TreeModelListener {
-		  /* Called when nodes have been modified, but the model hasn't changed.
+	implements TreeModelListener {
+	/* Called when nodes have been modified, but the model hasn't changed.
+	 * @param event Everything you ever wanted to know about model changes
+	 * in one tidy TreeModelEvent package.
+			*/
+	public void treeNodesChanged(TreeModelEvent event) {
+	    update();
+	}
+		  
+	/* Called when nodes have been added to the model.
 			* @param event Everything you ever wanted to know about model changes
 			* in one tidy TreeModelEvent package.
 			*/
-		  public void treeNodesChanged(TreeModelEvent event) {
-				update();
-		  }
+	public void treeNodesInserted(TreeModelEvent event) {
+	    update();
+	}
 		  
-		  /* Called when nodes have been added to the model.
+	/* Called when nodes have been removed from the model.
 			* @param event Everything you ever wanted to know about model changes
 			* in one tidy TreeModelEvent package.
 			*/
-		  public void treeNodesInserted(TreeModelEvent event) {
-				update();
-		  }
+	public void treeNodesRemoved(TreeModelEvent event) {
+	    update();
+	}
 		  
-		  /* Called when nodes have been removed from the model.
+	/** Called when nodes have been rearranged within the model.
 			* @param event Everything you ever wanted to know about model changes
 			* in one tidy TreeModelEvent package.
 			*/
-		  public void treeNodesRemoved(TreeModelEvent event) {
-				update();
-		  }
+	public void treeStructureChanged(TreeModelEvent event) {
+	    update();
+	}
 		  
-		  /** Called when nodes have been rearranged within the model.
-			* @param event Everything you ever wanted to know about model changes
-			* in one tidy TreeModelEvent package.
-			*/
-		  public void treeStructureChanged(TreeModelEvent event) {
-				update();
-		  }
-		  
-		  private void update() {
+	private void update() {
 				// Don't bother updating if the metaedit tab isn't selected.
-				if(Gatherer.g_man.getSelectedView() == Gatherer.g_man.metaedit_pane) {
-					 vm = null;
-					 vm = Gatherer.c_man.getCollection().msm.getValueTree((ElementWrapper)em.getSelectedItem());
-					 if(vm != null) {
-						  vm.addTreeModelListener(value_listener);
-					 }
-					 else {
-						  vm = new GValueModel(get("Not_Applicable"));
-					 }
-					 tree.setModel(vm);
-					 tree.repaint();
-				}
-		  }
+	    if(Gatherer.g_man.getSelectedView() == Gatherer.g_man.metaedit_pane) {
+		vm = null;
+		vm = Gatherer.c_man.getCollection().msm.getValueTree((ElementWrapper)em.getSelectedItem());
+		if(vm != null) {
+		    vm.addTreeModelListener(value_listener);
+		}
+		else {
+		    vm = new GValueModel(get("Not_Applicable"));
+		}
+		tree.setModel(vm);
+		tree.repaint();
+	    }
+	}
     }
 }
-
-
-
-
-
