/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * <BR><BR>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.gui;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.cdm.BuildTypeManager;
import org.greenstone.gatherer.cdm.CollectionDesignManager;
import org.greenstone.gatherer.collection.Collection;
import org.greenstone.gatherer.shell.GBuildProgressMonitor;
import org.greenstone.gatherer.shell.GImportProgressMonitor;
import org.greenstone.gatherer.shell.GScheduleProgressMonitor; 
import org.greenstone.gatherer.shell.GShell;
import org.greenstone.gatherer.shell.GShellEvent;
import org.greenstone.gatherer.shell.GShellListener;
import org.greenstone.gatherer.shell.GShellProgressMonitor;
import org.greenstone.gatherer.util.AppendLineOnlyFileDocument;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;

import org.greenstone.gatherer.collection.CollectionManager;
import org.greenstone.gatherer.util.GS3ServerThread;

/** This class provides a GUI view showing some statistics on your current collection, and options for building it. As the collection is built this initial view is replaced with one showing progress bars and a message log of the creation process. This log can be later accessed via the options tree located in the center of the initial pane. This class is also responsible for creating the GShellProgressMonitors that are then attatched to external shell processes, and calling the methods in the CollectionManager for actually importing and building the collection. <br><BR>
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class CreatePane
    extends JPanel
    implements GShellListener {

    static private Dimension ARGUMENT_SIZE = new Dimension(800,90);
    /** The threshold for when the simple view is replaced by the complex one. */
    static private final int THRESHOLD = Configuration.EXPERT_MODE;
    /** An identifier for the control panel within the card layout. */
    static private String CONTROL  = "Control";
    /** An identifier for the progress panel within the card layout. */
    static private String PROGRESS = "Progress";
    /** An identifier for the simple panel within the card layout. */
    static private String SIMPLE   = "Simple";

    /** Determines the current view that should be shown for this pane. */
    public boolean processing = false;
    /** The options pane generates all the various option sheet configuations. */
    public OptionsPane options_pane = null;
    private AppendLineOnlyFileDocument document;
    /** A card layout is used to store the separate configuration and progress panes. */
    private CardLayout card_layout = null;
    /** Stores a reference to the current collection. */
    private Collection previous_collection = null;
    /** This monitor tracks the build processes progress. */
    private GShellProgressMonitor build_monitor = null;
    /** This monitor tracks the import processes progress. */
    private GShellProgressMonitor import_monitor = null;
    /** This monitor tracks the schedule processes progress. */ 
    private GShellProgressMonitor schedule_monitor = null; 
    /** The button for begining the building processes. */
    private JButton build_button = null;
    /** The button for stopping the building processes. */
    private JButton cancel_button = null;
    /** The button for setting up scheduling */
    private JButton schedule_button = null; 
    /** The button for viewing the collection. */
    private JButton preview_button = null;
    private JButton simple_build_button;
    private JButton simple_cancel_button;
    private JButton simple_preview_button;
    /** The radio buttons for chnaging the build type (incremental/full) */
    private JRadioButton full_build_radio_button;
    private JRadioButton incremental_build_radio_button;
    private JRadioButton sfull_build_radio_button;
    private JRadioButton sincremental_build_radio_button;
    /** The label displaying the number of documents in this collection. */
    private JLabel document_count = null;
    /** The label alongside the build progress bar gets some special treatment so... */
    private JLabel progress_build_label = null;
    /** The label alongside the import progress bar gets some special treatment so... */
    private JLabel progress_import_label = null;
    private JPanel bar_area;
    /** The panel on which buttons are rendered on higher detail modes. */
    private JPanel button_pane;
    /** The pane which contains the controls for configuration. */
    private JPanel control_pane = null;
    /** The pane which has the card layout as its manager. */
    private JPanel main_pane = null;
    /** The pane which contains the progress information. */
    private JPanel progress_pane = null;
    /** The pane on the right-hand side - shows the requested options view */
    private JPanel right = null;
    /** The simple panel on which all of the available arguments are rendered. */
    private JPanel sargument_configuration_panel;
    /** The panel on which buttons are rendered on lower details modes. */
    private JPanel sbutton_panel;
    /** A simplified version for lower detail modes containing both the control and progress panes smooshed together. */
    private JSplitPane simple_panel;
    /** The lower part of the panel which is global so the log pane can be added and removed from it */
    private JPanel slower_panel;
    /** The inner panel of the simple pane which is global so that the bar_area can be added and removed from it. */
    private JPanel sinner_panel;
    /** The scrolling pane for the log. */
    private JScrollPane log_scroll;
    /** The scrolling pane the simple arguments are rendered within. */
    private JScrollPane sargument_configuration_scroll;
    /** the scrolling pane the righthand side is inside - only used for import and build options. the log and log list are not inside a scrollpane */
    private JScrollPane scroll_pane;
    /** The message log for the entire session. */
    private JTextArea log_textarea;
    /** A tree used to display the currently available option views. */
    private OptionTree tree = null;
    /** An array used to pass arguments with dictionary calls. */
    private String args[] = null;
    /** The homepage address of the current collection */
    public String homepage = null;

    /** Access to the GShell object that this CreatePane listens to events for. 
     * A handle to the GShell is needed order to interrupt any processes the GShell runs
     * when the user cancels a build operation.
    */
    private GShell shell = null;

    /** The constructor creates important helper classes, and initializes all the components.
     * @see org.greenstone.gatherer.collection.CollectionManager
     * @see org.greenstone.gatherer.gui.BuildOptions
     * @see org.greenstone.gatherer.gui.CreatePane.BuildButtonListener
     * @see org.greenstone.gatherer.gui.CreatePane.CancelButtonListener
     * @see org.greenstone.gatherer.gui.CreatePane.OptionTree
     * @see org.greenstone.gatherer.gui.OptionsPane
     * @see org.greenstone.gatherer.shell.GBuildProgressMonitor
     * @see org.greenstone.gatherer.shell.GImportProgressMonitor
     * @see org.greenstone.gatherer.shell.GShellProgressMonitor
     */
    public CreatePane() {
        this.setComponentOrientation(Dictionary.getOrientation());
	log_textarea = new JTextArea();
	log_scroll = new JScrollPane(log_textarea);
        
	// Create components
	card_layout = new CardLayout();
	// Control Pane
	control_pane = new JPanel();
        control_pane.setComponentOrientation(Dictionary.getOrientation());
	tree = new OptionTree();
        tree.setComponentOrientation(Dictionary.getOrientation());
	button_pane = new JPanel();
        button_pane.setComponentOrientation(Dictionary.getOrientation());

	// Progress Pane
	progress_pane = new JPanel(); // One owner of the bar component
        progress_pane.setComponentOrientation(Dictionary.getOrientation());
	bar_area = new JPanel(); // This component will be shared about
        bar_area.setComponentOrientation(Dictionary.getOrientation());

	progress_import_label = new JLabel(Dictionary.get("CreatePane.Import_Progress"));
	progress_import_label.setComponentOrientation(Dictionary.getOrientation());
        
	import_monitor = new GImportProgressMonitor();
	Gatherer.c_man.registerImportMonitor(import_monitor);

	progress_build_label = new JLabel(Dictionary.get("CreatePane.Build_Progress"));
	progress_build_label.setComponentOrientation(Dictionary.getOrientation());
        
	build_monitor = new GBuildProgressMonitor(import_monitor.getSharedProgress());
	Gatherer.c_man.registerBuildMonitor(build_monitor);

	//We only need a schedule monitor for text output. No use for a progress bar!
	
	//progress_schedule_label = new JLabel(Dictionary.get("CreatePane.Schedule_Progress")); 
	
	schedule_monitor = new GScheduleProgressMonitor(); 
	Gatherer.c_man.registerScheduleMonitor(schedule_monitor); 

    // And the simple panel
    slower_panel = new JPanel();
    sbutton_panel = new JPanel();
    sinner_panel = new JPanel();
    
    slower_panel.setComponentOrientation(Dictionary.getOrientation());
    sbutton_panel.setComponentOrientation(Dictionary.getOrientation());
    sinner_panel.setComponentOrientation(Dictionary.getOrientation());
    
    //Radio buttons
    incremental_build_radio_button = new JRadioButton(Dictionary.get("CreatePane.Minimal_Build"));
    incremental_build_radio_button.setComponentOrientation(Dictionary.getOrientation());
    incremental_build_radio_button.setToolTipText(Dictionary.get("CreatePane.Minimal_Build_Tooltip"));
    full_build_radio_button = new JRadioButton(Dictionary.get("CreatePane.Full_Build"));
    full_build_radio_button.setComponentOrientation(Dictionary.getOrientation());
    full_build_radio_button.setToolTipText(Dictionary.get("CreatePane.Full_Build_Tooltip"));
    sincremental_build_radio_button = new JRadioButton(Dictionary.get("CreatePane.Minimal_Build"));
    sincremental_build_radio_button.setComponentOrientation(Dictionary.getOrientation());
    sincremental_build_radio_button.setToolTipText(Dictionary.get("CreatePane.Minimal_Build_Tooltip"));
    sfull_build_radio_button = new JRadioButton(Dictionary.get("CreatePane.Full_Build"));
    sfull_build_radio_button.setComponentOrientation(Dictionary.getOrientation());
    sfull_build_radio_button.setToolTipText(Dictionary.get("CreatePane.Full_Build_Tooltip"));

    //keep them in sync
    full_build_radio_button.addActionListener(
        new ActionListener() { public void actionPerformed(ActionEvent e) {
            sfull_build_radio_button.setSelected( full_build_radio_button.isSelected() );
        }});
    sfull_build_radio_button.addActionListener(
        new ActionListener() { public void actionPerformed(ActionEvent e) {
            full_build_radio_button.setSelected( sfull_build_radio_button.isSelected() );
        }});
    incremental_build_radio_button.addActionListener(
        new ActionListener() { public void actionPerformed(ActionEvent e) {
            sincremental_build_radio_button.setSelected( incremental_build_radio_button.isSelected() );
        }});
    sincremental_build_radio_button.addActionListener(
        new ActionListener() { public void actionPerformed(ActionEvent e) {
            incremental_build_radio_button.setSelected( sincremental_build_radio_button.isSelected() );
        }});

	// Buttons
	BuildButtonListener bbl = new BuildButtonListener();
	CancelButtonListener cbl = new CancelButtonListener();
	
	build_button = new GLIButton(Dictionary.get("CreatePane.Build_Collection"), Dictionary.get("CreatePane.Build_Collection_Tooltip"));
	build_button.addActionListener(bbl);
	
	cancel_button = new GLIButton(Dictionary.get("CreatePane.Cancel_Build"), Dictionary.get("CreatePane.Cancel_Build_Tooltip"));
	cancel_button.addActionListener(cbl);
	cancel_button.setEnabled(false);
	
	preview_button = new PreviewButton(Dictionary.get("CreatePane.Preview_Collection"), Dictionary.get("CreatePane.Preview_Collection_Tooltip"));
	//preview_button.addActionListener(pbl);

	// c_man.built() is an expensive operation for isGsdlRemote case,
	// so calculate this once to set both preview and simple_preview buttons in one go
	boolean been_built = Gatherer.c_man.built();
	
	if(Gatherer.c_man != null) {
	    preview_button.setEnabled(been_built);
	}
	else {
	    preview_button.setEnabled(false);
	}
	
	BuildSimpleButtonListener bsbl = new BuildSimpleButtonListener(); 
	simple_build_button = new GLIButton(Dictionary.get("CreatePane.Build_Collection"), Dictionary.get("CreatePane.Build_Collection_Tooltip"));
	simple_build_button.addActionListener(bsbl);

	simple_cancel_button = new GLIButton(Dictionary.get("CreatePane.Cancel_Build"), Dictionary.get("CreatePane.Cancel_Build_Tooltip"));
	simple_cancel_button.addActionListener(cbl);
	simple_cancel_button.setEnabled(false);
	
	simple_preview_button = new PreviewButton(Dictionary.get("CreatePane.Preview_Collection"), Dictionary.get("CreatePane.Preview_Collection_Tooltip"));
	//simple_preview_button.addActionListener(pbl);
	if(Gatherer.c_man != null) {
	    simple_preview_button.setEnabled(been_built);
	}
	else {
	    simple_preview_button.setEnabled(false);
	}
	
	bbl = null;
	bsbl = null; 
	cbl = null;
	//pbl = null;
    }

    public void setGShell(GShell shell) {
	this.shell = shell;
    }

    public void destroy() {
	if(document != null) {
	    document.destroy();
	}
    }
    
    /** This method is called to actually layout the components.
     * @see org.greenstone.gatherer.Configuration
     * @see org.greenstone.gatherer.Gatherer
     */
    public void display() {

        int current_mode = Configuration.getMode();

        //Complete/incremental build options panel
        ButtonGroup build_type_group = new ButtonGroup();
        build_type_group.add(incremental_build_radio_button);
        build_type_group.add(full_build_radio_button);
        full_build_radio_button.setSelected(true);

        ButtonGroup sbuild_type_group = new ButtonGroup();
        sbuild_type_group.add(sincremental_build_radio_button);
        sbuild_type_group.add(sfull_build_radio_button);
        sfull_build_radio_button.setSelected(true);

        JPanel build_type_pane = new JPanel(new GridLayout(2,1));
        build_type_pane.setComponentOrientation(Dictionary.getOrientation());
        build_type_pane.add(full_build_radio_button);
        build_type_pane.add(incremental_build_radio_button);

        JPanel sbuild_type_pane = new JPanel(new GridLayout(2,1));
        sbuild_type_pane.setComponentOrientation(Dictionary.getOrientation());
        sbuild_type_pane.add(sfull_build_radio_button);
        sbuild_type_pane.add(sincremental_build_radio_button);


	// No archives_keepold/fldv file-level document-version history in remoteGS situations
	// Disable choice between incremental and full building buttons,
	// and ensure full build buttons are selected (already done above)
	if(Gatherer.isGsdlRemote) {	    
	    full_build_radio_button.setEnabled(false);
	    incremental_build_radio_button.setEnabled(false);
	    sfull_build_radio_button.setEnabled(false);
	    sincremental_build_radio_button.setEnabled(false);
	}

        // Build control_pane
        JPanel left = new JPanel();
            left.setComponentOrientation(Dictionary.getOrientation());
        left.setBorder(BorderFactory.createEmptyBorder(0,5,5,5));
        left.setLayout(new BorderLayout());
        left.add(tree, BorderLayout.CENTER);
        left.add(build_type_pane, BorderLayout.SOUTH);
        //left.add(full_build_radio_button, BorderLayout.SOUTH);

        right = new JPanel();
            right.setComponentOrientation(Dictionary.getOrientation());
        right.setBorder(BorderFactory.createEmptyBorder(0,0,5,5));
        right.setLayout(new BorderLayout());

        JPanel options_area = new JPanel();
            options_area.setComponentOrientation(Dictionary.getOrientation());
        options_area.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,5,5,5), BorderFactory.createTitledBorder(Dictionary.get("CreatePane.Options_Title"))));
        options_area.setLayout(new BorderLayout());
        options_area.add(left, BorderLayout.LINE_START);
        options_area.add(right, BorderLayout.CENTER);

        button_pane = new JPanel();
            button_pane.setComponentOrientation(Dictionary.getOrientation());
        button_pane.setBorder(BorderFactory.createEmptyBorder(5,10,10,10));
        button_pane.setLayout(new GridLayout(1,4));
        button_pane.add(build_button);
        button_pane.add(cancel_button);
        button_pane.add(preview_button);

        control_pane.setLayout(new BorderLayout());
        control_pane.add(options_area, BorderLayout.CENTER);
        control_pane.add(button_pane, BorderLayout.SOUTH);

        // Build progress_pane
        JPanel labels_pane = new JPanel();
            labels_pane.setComponentOrientation(Dictionary.getOrientation());
        labels_pane.setLayout(new GridLayout(2,1,0,5));
        labels_pane.add(progress_import_label);
        labels_pane.add(progress_build_label);

        JPanel monitors_pane = new JPanel();
            monitors_pane.setComponentOrientation(Dictionary.getOrientation());
        monitors_pane.setLayout(new GridLayout(2,1,0,5));
        monitors_pane.add(import_monitor.getProgress());
        monitors_pane.add(build_monitor.getProgress());

        bar_area.setBorder(BorderFactory.createEmptyBorder(10,10,5,10));
        bar_area.setLayout(new BorderLayout(5,5));
        bar_area.add(labels_pane, BorderLayout.LINE_START);
        bar_area.add(monitors_pane, BorderLayout.CENTER);

        progress_pane.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
        progress_pane.setLayout(new BorderLayout());
        progress_pane.add(bar_area, BorderLayout.NORTH);
        if(current_mode >= THRESHOLD) {
            progress_pane.add(log_scroll, BorderLayout.CENTER);
        }

        // Simple panel
        sbutton_panel.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
        sbutton_panel.setLayout(new GridLayout(1,3));
        sbutton_panel.add(simple_build_button);
        sbutton_panel.add(simple_cancel_button);
        sbutton_panel.add(simple_preview_button);

        JPanel simple_bar_area = new JPanel(new GridLayout(2,1));
            simple_bar_area.setComponentOrientation(Dictionary.getOrientation());
        simple_bar_area.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
        simple_bar_area.add(import_monitor.getSharedProgress());
        simple_bar_area.add(sbutton_panel);

        sinner_panel.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
        sinner_panel.setLayout(new BorderLayout());
        sinner_panel.add(sbuild_type_pane, BorderLayout.LINE_START);
        //sinner_panel.add(full_build_radio_button, BorderLayout.WEST);
        sinner_panel.add(simple_bar_area, BorderLayout.CENTER);

        if(options_pane != null) {
            sargument_configuration_panel = options_pane.buildImport(null);
            sargument_configuration_panel = options_pane.buildBuild(sargument_configuration_panel);
        }
        else {
            sargument_configuration_panel = new JPanel();
        }
            sargument_configuration_panel.setComponentOrientation(Dictionary.getOrientation());
        sargument_configuration_scroll = new JScrollPane(sargument_configuration_panel);
        sargument_configuration_scroll.setPreferredSize(ARGUMENT_SIZE);
            sargument_configuration_scroll.setComponentOrientation(Dictionary.getOrientation());
            
        slower_panel.setLayout(new BorderLayout());
        slower_panel.add(sinner_panel, BorderLayout.NORTH);
        if(current_mode < THRESHOLD) {
            slower_panel.add(log_scroll, BorderLayout.CENTER);
        }

        simple_panel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sargument_configuration_scroll, slower_panel);
            simple_panel.setComponentOrientation(Dictionary.getOrientation());
        // Main pane
        main_pane = new JPanel(card_layout);
            main_pane.setComponentOrientation(Dictionary.getOrientation());
        if(current_mode < THRESHOLD) { // Simple mode - add first
            main_pane.add(simple_panel, SIMPLE);
        }
        main_pane.add(control_pane, CONTROL);
        main_pane.add(progress_pane, PROGRESS);
        if(current_mode >= THRESHOLD) { // Expert mode - add last
            main_pane.add(simple_panel, SIMPLE);
        }

        this.setLayout(new BorderLayout());
        this.add(main_pane, BorderLayout.CENTER);
    }

    /** This method is called whenever the 'Create' tab is selected from the view bar. It allows this view to perform any preactions required prior to display. In this case this entails gathering up to date information about the status of the collection including number of documents etc.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.CollectionManager
     * @see org.greenstone.gatherer.gui.CreatePane.OptionTree
     */
    public void gainFocus() {
	if(Configuration.getMode() < THRESHOLD) {
	    card_layout.show(main_pane, SIMPLE);
	}
	else if(!processing) {
	    // Move the buttons to control
	    control_pane.add(button_pane, BorderLayout.SOUTH);
	    card_layout.show(main_pane, CONTROL);
	}
	else {
	    // Move the buttons to progress
	    progress_pane.add(button_pane, BorderLayout.SOUTH);
	    card_layout.show(main_pane, PROGRESS);
	}
	// Refresh the set of controls.
	TreePath path = tree.getSelectionPath();
	tree.clearSelection();
	tree.setSelectionPath(path);

    }

    /** We are informed when this view pane loses focus so we can update build options. */
    public void loseFocus() {
	tree.valueChanged(null);
    }

    /** All implementation of GShellListener must include this method so the listener can be informed of messages from the GShell.
     * @param event A <strong>GShellEvent</strong> that contains, among other things, the message.
     */
    public synchronized void message(GShellEvent event) {
	// Ignore the messages from RecPlug (used for progress bars)
	if (event.getMessage().startsWith("import.pl> RecPlug - ")) {
	    return;
	}
	Gatherer.invokeInEDT_replacesProceedInCurrThread("CreatePane.message()", Gatherer.SYNC, new Runnable() {
		public void run() {
		    document.appendLine(event.getMessage());
		    log_textarea.setCaretPosition(document.getLengthToNearestLine());
		}
	    });
    }

    /** Called when the detail mode has changed which in turn may cause several import/build configuration arguments to be available/hidden
     * @param mode the new mode as an int
     */
    public void modeChanged(int mode) {
	// If we are below the complexity threshold ensure the simple controls are being shown
	if(Configuration.getMode() < THRESHOLD) {
	    // Update the arguments
	    simple_panel.remove(sargument_configuration_scroll);
	    if(options_pane != null) {
		sargument_configuration_panel = options_pane.buildImport(null);
		sargument_configuration_panel = options_pane.buildBuild(sargument_configuration_panel);
	    }
	    else {
		sargument_configuration_panel = new JPanel();
	    }
	    sargument_configuration_scroll = new JScrollPane(sargument_configuration_panel);
            sargument_configuration_panel.setComponentOrientation(Dictionary.getOrientation());
            sargument_configuration_scroll.setComponentOrientation(Dictionary.getOrientation());
	    sargument_configuration_scroll.setPreferredSize(ARGUMENT_SIZE);
	    simple_panel.setTopComponent(sargument_configuration_scroll);
	    // Remove the shared components from the expert panels
	    progress_pane.remove(log_scroll);
	    // And add to simple one
	    slower_panel.add(log_scroll, BorderLayout.CENTER);
	    // And bring the card to the front
	    card_layout.show(main_pane, SIMPLE);
	}
	// And if we are above the threshold change to the complex controls
	else {
	    // Remove the shared components from the simple panel
	    slower_panel.remove(log_scroll);
	    // And add then to the expert ones
	    progress_pane.add(log_scroll, BorderLayout.CENTER);
	    // And bring the appropriate card to the front
	    if(!processing) {
		control_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, CONTROL);
	    }
	    else {
		progress_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, PROGRESS);
	    }
	}
	tree.valueChanged(null); // Ensure tree argument panels are rebuilt
    }

    /** All implementation of GShellListener must include this method so the listener can be informed when a GShell begins its task. Implementation side-effect, not actually used.
     * @param event A <strong>GShellEvent</strong> that contains details of the initial state of the <strong>GShell</strong> before task comencement.
     */
    public synchronized void processBegun(GShellEvent event) {
	// We don't care. We'll get a more acurate start from the progress monitors.
    }
    /** All implementation of GShellListener must include this method so the listener can be informed when a GShell 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 synchronized void processComplete(GShellEvent event) {
	DebugStream.println("In CreatePane::processComplete(" + event.getType() + ")...");
	
	// c_man.built() is an expensive operation in isGsdlRemote case,
	// so calculate this once now to set both preview and simple_preview buttons in one go
	boolean been_built = Gatherer.c_man.built();
	
	if(event.getStatus() == GShell.OK) {
	    if(event.getType() == GShell.SCHEDULE) {
		
		processing = false; 
		build_button.setEnabled(true);
		cancel_button.setEnabled(false);
		//preview_button.setEnabled(true);
		//only enable preview if the collection has been built. 
		preview_button.setEnabled(been_built);
		simple_build_button.setEnabled(true);
		simple_cancel_button.setEnabled(false);
		simple_preview_button.setEnabled(been_built); 
		int status = event.getStatus();
		document.setSpecialCharacter(OptionsPane.SCHEDULED); 
		options_pane.resetFileEntry();
		build_monitor.reset();
		import_monitor.reset();
		if(Configuration.getMode() >= THRESHOLD) {
		    control_pane.add(button_pane, BorderLayout.SOUTH); 
		    card_layout.show(main_pane, CONTROL); 
		}
	    }
	    else if(event.getType() == GShell.BUILD) {
		processing = false;
		build_button.setEnabled(true);
		cancel_button.setEnabled(false);
		preview_button.setEnabled(been_built);
		simple_build_button.setEnabled(true);
		simple_cancel_button.setEnabled(false);
		simple_preview_button.setEnabled(been_built);
		int status = event.getStatus();
		document.setSpecialCharacter(OptionsPane.SUCCESSFUL);
		options_pane.resetFileEntry();
		build_monitor.reset();
		import_monitor.reset();
		if(Configuration.getMode() >= THRESHOLD) {
		    control_pane.add(button_pane, BorderLayout.SOUTH);
		    card_layout.show(main_pane, CONTROL);
		}
	    }
	    // Otherwise its completed import but still got build to go
	}
	else {
	    processing = false;
	    cancel_button.setEnabled(false);
	    build_button.setEnabled(true);
	    // The build may have failed, but a previous index may still be in place
	    // In which case, previewing should still be available	    
	    preview_button.setEnabled(been_built);

	    simple_build_button.setEnabled(true);
	    simple_cancel_button.setEnabled(false);
	    simple_preview_button.setEnabled(been_built);

	    
	    // If import was cancelled OR an error occurred, move any archives_keepold to archives
	    if(event.getType() == GShell.IMPORT &&
	       (event.getStatus() == GShell.CANCELLED || event.getStatus() == GShell.ERROR)) {

		//System.err.println("processComplete(), got status: " + event.getStatus()
		//+ " - build type: " + event.getType());
		
		moveAnyKeepOldToArchives();
	    }
	    
	    if(event.getStatus() == GShell.CANCELLED) {
		document.setSpecialCharacter(OptionsPane.CANCELLED);
	    }
	    else {
		document.setSpecialCharacter(OptionsPane.UNSUCCESSFUL);
	    }

	    
	    options_pane.resetFileEntry();
	    if(Configuration.getMode() >= THRESHOLD) {
		control_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, CONTROL);
	    }
	}
    }

    /** Related to file-level document-version (fldv) history mechanism. Dr Bainbridge explained:
     * "When cancel is pressed, if there is an 'archives_keepold' then the incomplete 'archives'
     * should be removed, and 'archives_keepold' restored back to the 'archives' name."
     */
    private void moveAnyKeepOldToArchives() {

	    String archives_keepold = CollectionManager.getLoadedCollectionArchivesKeepOldDirectoryPath();
	    File archives_keepold_dir = new File(archives_keepold);
	    if(archives_keepold_dir.exists()) {
		String statusMsg = "archives_keepold folder detected. Attempting to restore it as archives...";
		log_textarea.setCaretPosition(document.getLengthToNearestLine());		
		//System.err.println(statusMsg);
		document.appendLine(statusMsg); //this.shell.fireMessage(statusMsg);
		
		String archives = CollectionManager.getLoadedCollectionArchivesDirectoryPath();
		File archives_dir = new File(archives);
		
		// no special behaviour for remote GS or fedora. The minimal (incr) rebuild
		// button is already disabled for remote GS.
		
		// First locally delete archives dir if any exists
		if(archives_dir.exists() && !Utility.delete(archives_dir)) {		    
		    statusMsg = "Failed to delete " + archives_dir
			+ "  in attempt to rename archives_keepold to archives";
		    //System.err.println(statusMsg);		    
		    document.appendLine(statusMsg); //this.shell.fireMessage(statusMsg);
		}	  

		if(!archives_dir.exists()) {
		    // Rename archives_keepold folder to archives
		    if(!archives_keepold_dir.renameTo(archives_dir)) {
			statusMsg = "Failed to rename " + archives_keepold_dir
			    + "\n  to " + archives_dir;
			//System.err.println(statusMsg);			
			document.appendLine(statusMsg); //this.shell.fireMessage(statusMsg);
		    }
		}

		// repeating what CreatePane::message() does (called by GShell::firemessage())
		log_textarea.setCaretPosition(document.getLengthToNearestLine());
	    }
    }

    /** This method is invoked at any time there has been a significant change in the collection, such as saving, loading or creating.
     * @param ready A <strong>boolean</strong> indicating if a collection is currently available.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.CollectionManager
     * @see org.greenstone.gatherer.gui.BuildOptions
     * @see org.greenstone.gatherer.gui.OptionsPane
     */
    public void refresh(int refresh_reason, boolean ready)
    {
	if (Gatherer.c_man == null || !ready) {
	    return;
	}
	Collection current_collection = Gatherer.c_man.getCollection();
	if (current_collection != previous_collection && !Gatherer.c_man.isImporting()) {
	    this.options_pane = new OptionsPane(current_collection.import_options, current_collection.build_options, current_collection.schedule_options);
	    if (previous_collection != null) {
		// clear the log
		Document log_document = log_textarea.getDocument();
		if (log_document instanceof AppendLineOnlyFileDocument) {
		    ((AppendLineOnlyFileDocument) log_document).destroy();
		}
	    }
	    previous_collection = current_collection;
	   
	} 
	// If we are in simple mode, we have to rebuild the simple arguments list
	if(Configuration.getMode() < THRESHOLD) {
	    simple_panel.remove(sargument_configuration_scroll);
	    sargument_configuration_panel = options_pane.buildImport(null);
	    sargument_configuration_panel = options_pane.buildBuild(sargument_configuration_panel);
	    //if(CollectionManager.canDoScheduling()) {
	    //sargument_configuration_panel = options_pane.buildSchedule(sargument_configuration_panel); 
	    //}
	    sargument_configuration_scroll = new JScrollPane(sargument_configuration_panel);
            sargument_configuration_scroll.setComponentOrientation(Dictionary.getOrientation());
	    sargument_configuration_scroll.setPreferredSize(ARGUMENT_SIZE);
	    simple_panel.setTopComponent(sargument_configuration_scroll);
	}
	tree.valueChanged(null);

	// Validate the preview button
	// Calling the expensive built() operation here is justified probably,
	// as long as refresh() is not called frivolously
	boolean been_built = Gatherer.c_man.built();
	if (been_built && Configuration.library_url != null) {
	    preview_button.setEnabled(true);
	    simple_preview_button.setEnabled(true);
	}
	else {
	    preview_button.setEnabled(false);
	    simple_preview_button.setEnabled(false);
	}
    }

    /** This class serves as the listener for actions on the build button. */
    private class BuildButtonListener
	implements ActionListener {
	/**
	 * This method checks to see what needs to be done for a build, and starts the process off.
	 * A complete build proccess is started by {@link CollectionManager.importCollection()}, which then imports and builds the collection.
	 * However, this doesn't always happen, as sometimes an incremental build will do :-)
	 * @param event An <strong>ActionEvent</strong> who, thanks to the power of object oriented programming, we don't give two hoots about.
	 * @see org.greenstone.gatherer.Gatherer
	 * @see org.greenstone.gatherer.collection.CollectionManager
	 * @see org.greenstone.gatherer.gui.BuildOptions
	 * @see org.greenstone.gatherer.shell.GShellProgressMonitor
	 */
    public void actionPerformed(ActionEvent event) {
        Collection collection = Gatherer.c_man.getCollection();
        String collection_name = collection.getName();

	// Take care of the case if we have a stray archives_keepold folder
	File archives_keepold_dir = new File(CollectionManager.getLoadedCollectionArchivesKeepOldDirectoryPath());
	if(archives_keepold_dir.exists()) {
	    WarnIncrBuildDialog dialog = new WarnIncrBuildDialog();
	    int result = dialog.display();
	    dialog.dispose();
	    dialog = null;
	    if(result == WarnIncrBuildDialog.CANCEL) { // user chose not to build after all
		return;
	    }
	}

        //if button is labelled for Building
        if(event.getActionCommand().equals(Dictionary.get("CreatePane.Build_Collection"))) { 

            //set a static variable marking whether this is a "Complete" or "Minimal" build
            CollectionDesignManager.setCompleteBuild( full_build_radio_button.isSelected() );

            //do the import and build, skipping import if possible
            /*if( !CollectionDesignManager.isCompleteBuild() &&
                !collection.getMetadataChanged() && !collection.getFilesChanged() && Gatherer.c_man.imported() &&
                CollectionDesignManager.getRebuildTypeRequired() <= CollectionDesignManager.BUILDCOL
                ) {
                    // Just do build (no import)
                    DebugStream.println("Just want to run buildcol.pl");
                    prepareForBuild();
                    Gatherer.c_man.buildCollection();
            }
            else {
			*/
                prepareForBuild();

                // Starting building,

                Gatherer.c_man.importCollection(); //This starts the building process.
			/*
            }
			*/

        } else { //button is labelled for setting up scheduling

            //this needs to be called to configure buttons... but no building is done
            prepareForBuild(); 
            Gatherer.c_man.scheduleBuild(); 
        }
        //Re-setting the rebuildTypeRequired is handled by CollectionManager.processComplete(GShellEvent)
    }

	    
	/**
	 * This does some stuff that is needed before a collection can be built.
	 * For example, buttons are enabled/disabled, and certain flags are set.
	 * This is called from {@link actionPerformed(ActionEvent)}
	 */
	private void prepareForBuild() {
	    // First we force the build options to be updated if we haven't already.
	    tree.valueChanged(null);
	    
	    // Remember that for lower thresholds the above doesn't work, so try this instead
	    if(Configuration.getMode() < THRESHOLD) {
		options_pane.update(sargument_configuration_panel);
	    }
	    
	    // Now go about building.
	    build_button.setEnabled(false);
	    cancel_button.setEnabled(true);
	    preview_button.setEnabled(false);
	    
	    simple_build_button.setEnabled(false);
	    simple_cancel_button.setEnabled(true);
	    simple_preview_button.setEnabled(false);
	    
	    document = options_pane.createNewLogDocument();
	    log_textarea.setDocument(document);
	    options_pane.log_textarea.setDocument(document);
	    // Change the view.
	    processing = true;
	    if(Configuration.getMode() >= THRESHOLD) {
		progress_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, PROGRESS);
	    }
	    // Reset the stop flag in all the process monitors, just incase.
	    ((GBuildProgressMonitor)build_monitor).reset();
	    import_monitor.setStop(false);
	}
    }
    
    /** I hope this works. Wendy */ 
    private class BuildSimpleButtonListener
	implements ActionListener {
	/**
	 * This method checks to see what needs to be done for a build, and starts the process off.
	 * A complete build proccess is started by {@link CollectionManager.importCollection()}, which then imports and builds the collection.
	 * However, this doesn't always happen, as sometimes an incremental build will do :-)
	 * @param event An <strong>ActionEvent</strong> who, thanks to the power of object oriented programming, we don't give two hoots about.
	 * @see org.greenstone.gatherer.Gatherer
	 * @see org.greenstone.gatherer.collection.CollectionManager
	 * @see org.greenstone.gatherer.gui.BuildOptions
	 * @see org.greenstone.gatherer.shell.GShellProgressMonitor
	 */
	public void actionPerformed(ActionEvent event) {
		Collection collection = Gatherer.c_man.getCollection();
		String collection_name = collection.getName();

		//set a static variable marking whether this is a "Complete" or "Minimal" build
		CollectionDesignManager.setCompleteBuild( full_build_radio_button.isSelected() );

		//do the import and build, skipping import if possible
		/*if( !CollectionDesignManager.isCompleteBuild() &&
			!collection.getMetadataChanged() && !collection.getFilesChanged() && Gatherer.c_man.imported() &&
			CollectionDesignManager.getRebuildTypeRequired() <= CollectionDesignManager.BUILDCOL
			) {
				// Just do build (no import)
				DebugStream.println("Just want to run buildcol.pl");
				prepareForBuild();
				Gatherer.c_man.buildCollection();
		}
		else {
		*/
			prepareForBuild();
			Gatherer.c_man.importCollection(); //This starts the building process.
		/*
		}
		*/
		
		//Re-setting the rebuildTypeRequired is handled by CollectionManager.processComplete(GShellEvent)
	}

	/**
	 * This does some stuff that is needed before a collection can be built.
	 * For example, buttons are enabled/disabled, and certain flags are set.
	 * This is called from {@link actionPerformed(ActionEvent)}
	 */
	private void prepareForBuild() {
	    // First we force the build options to be updated if we haven't already.
	    tree.valueChanged(null);
	    
	    // Remember that for lower thresholds the above doesn't work, so try this instead
	    if(Configuration.getMode() < THRESHOLD) {
		options_pane.update(sargument_configuration_panel);
	    }
	    
	    // Now go about building.
	    build_button.setEnabled(false);
	    cancel_button.setEnabled(true);
	    preview_button.setEnabled(false);
	    
	    simple_build_button.setEnabled(false);
	    simple_cancel_button.setEnabled(true);
	    simple_preview_button.setEnabled(false);
	    
	    document = options_pane.createNewLogDocument();
	    log_textarea.setDocument(document);
	    options_pane.log_textarea.setDocument(document);
	    // Change the view.
	    processing = true;
	    if(Configuration.getMode() >= THRESHOLD) {
		progress_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, PROGRESS);
	    }
	    // Reset the stop flag in all the process monitors, just incase.
	    ((GBuildProgressMonitor)build_monitor).reset();
	    import_monitor.setStop(false);
	}
    }
    

    /** This class serves as the listener for actions on the cancel button. */
    private class CancelButtonListener
	implements ActionListener {
	/** This method attempts to cancel the current GShell task. It does this by first telling CollectionManager not to carry out any further action. 
	 * Previously, this would in turn tell the GShell to break from the current job immediately, without waiting for the processEnded message, and then kills the thread in an attempt to stop the process. The results of such an action are debatable.
	 * Now, pressing cancel will send an interrupt to the GShell thread, which is the thread in which the external process is run (via the SafeProcess object). Interrupting a running SafeProcess will then interrupt any worker threads and destroy the process, with SafeProcess cleaning up after itself after its worker threads finished cleaning up after themselves. Tested on Linux.
	 * @param event An <strong>ActionEvent</strong> who, thanks to the power of object oriented programming, we don't give two hoots about.
	 * @see org.greenstone.gatherer.Gatherer
	 * @see org.greenstone.gatherer.collection.CollectionManager
	 * @see org.greenstone.gatherer.gui.BuildOptions
	 * @see org.greenstone.gatherer.shell.GShellProgressMonitor
	 */
	public void actionPerformed(ActionEvent event) {
	    build_button.setEnabled(false);
	    cancel_button.setEnabled(false);
	    preview_button.setEnabled(false);

	    simple_build_button.setEnabled(false);
	    simple_cancel_button.setEnabled(false);
	    simple_preview_button.setEnabled(false);

	    processing = false;
	    document.setSpecialCharacter(OptionsPane.CANCELLED);
	    if(Configuration.getMode() >= THRESHOLD) {
		control_pane.add(button_pane, BorderLayout.SOUTH);
		card_layout.show(main_pane, CONTROL);
	    }
	    // Set the stop flag in all the process monitor.
	    import_monitor.setStop(true);
	    import_monitor.reset();
	    build_monitor.setStop(true);
	    build_monitor.reset();
	    // Tell the GShell to cleanly stop running its external process
	    if(CreatePane.this.shell != null) {
		// We can call GShell.cancel() even if the GShell thread is blocking when running a process,
		// because this CreatePane is running in its own separate GUI thread. This is because the
		// process blocking call (process.waitFor()) and cancel() are not sequential operations.
		CreatePane.this.shell.cancel();
	    }

	    // Remove the collection lock.
	    //Gatherer.g_man.lockCollection(false, false);

	    
	}
    }

    
    /** The OptionTree is simply a tree structure that has a root node labelled "Options" and then has a child node for each available options screen. These screens are either combinations of the available import and build arguments, or a message log detailing the shell processes progress. */
    private class OptionTree
	extends JTree
	implements TreeSelectionListener {
	/** The model behind the tree. */
	private DefaultTreeModel model = null;
	/** The previous options view displayed, which is sometimes needed to refresh properly. */
	private JPanel previous_pane = null;
	/** The node corresponding to the building options view. */
	private OptionTreeNode building  = null;
	/** The node corresponding to the importing options view. */
	private OptionTreeNode importing = null;
	/** The node corresponding to the scheduling options view. */ 
	private OptionTreeNode scheduling = null; 
	/** The node corresponding to the log view. */
	private OptionTreeNode log       = null;
	/** The root node of the options tree, which has no associated options view. */
	private OptionTreeNode options   = null;
	/** Constructor.
	 * @see org.greenstone.gatherer.gui.CreatePane.OptionTreeNode
	 */
	public OptionTree() {
	    super();
            this.setComponentOrientation(Dictionary.getOrientation());
	    ToolTipManager.sharedInstance().registerComponent(this);
	    addTreeSelectionListener(this);
	    getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
	    setCellRenderer(new ToolTipTreeCellRenderer());
	    setRootVisible(false);
	    setToggleClickCount(1);

	    // Create tree.
	    building = new OptionTreeNode(Dictionary.get("CreatePane.Build"));
	    building.setToolTipText(Dictionary.get("CreatePane.Build_Tooltip"));
	    importing = new OptionTreeNode(Dictionary.get("CreatePane.Import"));
	    importing.setToolTipText(Dictionary.get("CreatePane.Import_Tooltip"));
	    if (CollectionManager.canDoScheduling()) {
	      scheduling = new OptionTreeNode(Dictionary.get("CreatePane.Schedule"));
	      scheduling.setToolTipText(Dictionary.get("CreatePane.Schedule_Tooltip")); 

	    }
	    log = new OptionTreeNode(Dictionary.get("CreatePane.Log"));
	    log.setToolTipText(Dictionary.get("CreatePane.Log_Tooltip"));
	    options = new OptionTreeNode(Dictionary.get("CreatePane.Options"));

	    model = new DefaultTreeModel(options);
	    setModel(model);
	    model.insertNodeInto(importing, options, 0);
	    model.insertNodeInto(building, options, 1);
	    if (CollectionManager.canDoScheduling()) {
	      model.insertNodeInto(scheduling, options, 2); 
	      model.insertNodeInto(log, options, 3);
	    } else {
	      model.insertNodeInto(log, options, 2);
	    }
	    // Expand the root node
	    expandPath(new TreePath(options));
	}
	/** Any implementation of TreeSelectionListener must include this method to allow this listener to know when the selection has changed. Here we swap the options view depending on the selected OptionTreeNode.
	 * @param event A <Strong>TreeSelectionEvent</strong> which contains all the information garnered when the event occured.
	 * @see org.greenstone.gatherer.gui.CreatePane.OptionTreeNode
	 */
	public void valueChanged(TreeSelectionEvent event) {
	    TreePath path = null;
	    OptionTreeNode node = null;
	    //if(event != null) {
	    //path = event.getPath();
	    path = getSelectionPath();
	    if(path != null) {
		node = (OptionTreeNode)path.getLastPathComponent();
	    }
				//}
	    if(previous_pane != null) {
		//target_pane.remove(previous_pane);
		options_pane.update(previous_pane);
		if(scroll_pane != null) {
		    right.remove(scroll_pane);
		}
		else {
		    right.remove(previous_pane);
		}
		previous_pane = null;
		scroll_pane = null;
	    }
	    if(node != null && node.equals(log)) { 
		build_button.setActionCommand(Dictionary.get("CreatePane.Build_Collection")); 
		build_button.setText(Dictionary.get("CreatePane.Build_Collection")); 	
	    }
	    if(node != null && node.equals(building)) {
	      build_button.setActionCommand(Dictionary.get("CreatePane.Build_Collection"));		
		build_button.setText(Dictionary.get("CreatePane.Build_Collection"));
 

		previous_pane = options_pane.buildBuild(null);
		scroll_pane = new JScrollPane(previous_pane);
		right.add(scroll_pane, BorderLayout.CENTER);
		//target_pane.add(previous_pane, BorderLayout.CENTER);
	    }
	    else if(node != null && node.equals(importing)) {
		build_button.setActionCommand(Dictionary.get("CreatePane.Build_Collection")); 
		build_button.setText(Dictionary.get("CreatePane.Build_Collection")); 

		previous_pane = options_pane.buildImport(null);
		scroll_pane = new JScrollPane(previous_pane);
		right.add(scroll_pane, BorderLayout.CENTER);
		//target_pane.add(previous_pane, BorderLayout.CENTER);
	    }
	    else if(node != null && CollectionManager.canDoScheduling() && node.equals(scheduling)) { 
		build_button.setActionCommand(Dictionary.get("CreatePane.Schedule_Build")); 
		build_button.setText(Dictionary.get("CreatePane.Schedule_Build"));
 
		previous_pane = options_pane.buildSchedule(null); 
		scroll_pane = new JScrollPane(previous_pane);
		right.add(scroll_pane, BorderLayout.CENTER);
	    }
	    else {
		if (options_pane != null) {
		    previous_pane = options_pane.buildLog();
		    right.add(previous_pane, BorderLayout.CENTER);
		    right.updateUI(); // we need to repaint the log pane, cos it hasn't changed since last time
		    ///ystem.err.println("I've added the log back to the right pane again.");
		    //target_pane.add(previous_pane, BorderLayout.CENTER);
		}
	    }
	    //scroll_pane.setViewportView(previous_pane);
	    //previous_pane.validate();
	    right.validate();
	    //System.err.println("Current pane size: " + previous_pane.getSize());
	    //System.err.println("While its preferred size is: " + previous_pane.getPreferredSize());
	}
    }

    /** The <strong>OptionTree</strong> is built from these nodes, each of which has several methods used in creating the option panes.
     */
    private class OptionTreeNode
	extends DefaultMutableTreeNode
	implements Comparable {
	/** The text label given to this node in the tree. */
	private String title = null;
	/** a tool tip to be used for this node in the tree */
	private String tool_tip = null;
	/** Constructor.
	 * @param title The <strong>String</strong> which serves as this nodes title.
	 */
	public OptionTreeNode(String title) {
	    super();
	    this.title = title;
	}

	/** This method compares two nodes for ordering.
	 * @param obj The <strong>Object</strong> to compare to.
	 * @return An <strong>int</strong> of one of the possible values -1, 0 or 1 indicating if this node is less than, equal to or greater than the target node respectively.
	 */
	public int compareTo(Object obj) {
	    return title.compareTo(obj.toString());
	}

	/** This method checks if two nodes are equivalent.
	 * @param obj The <strong>Object</strong> to be tested against.
	 * @return A <strong>boolean</strong> which is <i>true</i> if the objects are equal, <i>false</i> otherwise.
	 */
	public boolean equals(Object obj) {
	    if(compareTo(obj) == 0) {
		return true;
	    }
	    return false;
	}

	/** get the tool tip */
	public String getToolTipText() {
	    return tool_tip;
	}

	/** set the tool tip */
	public void setToolTipText(String tip) {
	    tool_tip = tip;
	}
	
	/** Method to translate this node into a textual representation.
	 * @return A <strong>String</strong> which in this case is the title.
	 */
	public String toString() {
	    return title;
	}
    }

    // Adds tooltips to the tree nodes
    private class ToolTipTreeCellRenderer
	extends DefaultTreeCellRenderer {

	public Component getTreeCellRendererComponent(JTree tree,
						      Object value,
						      boolean sel,
						      boolean expanded,
						      boolean leaf,
						      int row,
						      boolean hasFocus) {

	    super.getTreeCellRendererComponent(tree, value, sel,
					       expanded, leaf, row,
					       hasFocus);
	    if (value instanceof OptionTreeNode) {
		String tip = ((OptionTreeNode) value).getToolTipText();
		if (tip != null) {
		    setToolTipText(tip);
		}
	    }
	    return this;
	}
    }
}
