be.ibridge.kettle.chef.Chef.java Source code

Java tutorial

Introduction

Here is the source code for be.ibridge.kettle.chef.Chef.java

Source

/**********************************************************************
**                                                                   **
**               This code belongs to the KETTLE project.            **
**                                                                   **
** Kettle, from version 2.2 on, is released into the public domain   **
** under the Lesser GNU Public License (LGPL).                       **
**                                                                   **
** For more details, please read the document LICENSE.txt, included  **
** in this project                                                   **
**                                                                   **
** http://www.kettle.be                                              **
** info@kettle.be                                                    **
**                                                                   **
**********************************************************************/

package be.ibridge.kettle.chef;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import be.ibridge.kettle.core.AddUndoPositionInterface;
import be.ibridge.kettle.core.Const;
import be.ibridge.kettle.core.DragAndDropContainer;
import be.ibridge.kettle.core.GUIResource;
import be.ibridge.kettle.core.KettleVariables;
import be.ibridge.kettle.core.LastUsedFile;
import be.ibridge.kettle.core.LogWriter;
import be.ibridge.kettle.core.NotePadMeta;
import be.ibridge.kettle.core.Point;
import be.ibridge.kettle.core.PrintSpool;
import be.ibridge.kettle.core.Props;
import be.ibridge.kettle.core.Row;
import be.ibridge.kettle.core.TransAction;
import be.ibridge.kettle.core.WindowProperty;
import be.ibridge.kettle.core.XMLHandler;
import be.ibridge.kettle.core.XMLTransfer;
import be.ibridge.kettle.core.database.DatabaseMeta;
import be.ibridge.kettle.core.dialog.DatabaseDialog;
import be.ibridge.kettle.core.dialog.DatabaseExplorerDialog;
import be.ibridge.kettle.core.dialog.EnterOptionsDialog;
import be.ibridge.kettle.core.dialog.EnterSearchDialog;
import be.ibridge.kettle.core.dialog.EnterStringsDialog;
import be.ibridge.kettle.core.dialog.ErrorDialog;
import be.ibridge.kettle.core.dialog.PreviewRowsDialog;
import be.ibridge.kettle.core.dialog.SQLEditor;
import be.ibridge.kettle.core.dialog.SQLStatementsDialog;
import be.ibridge.kettle.core.exception.KettleDatabaseException;
import be.ibridge.kettle.core.exception.KettleException;
import be.ibridge.kettle.core.exception.KettleStepException;
import be.ibridge.kettle.core.exception.KettleXMLException;
import be.ibridge.kettle.core.reflection.StringSearchResult;
import be.ibridge.kettle.core.value.Value;
import be.ibridge.kettle.core.widget.TreeMemory;
import be.ibridge.kettle.core.wizards.createdatabase.CreateDatabaseWizard;
import be.ibridge.kettle.job.JobEntryLoader;
import be.ibridge.kettle.job.JobHopMeta;
import be.ibridge.kettle.job.JobMeta;
import be.ibridge.kettle.job.JobPlugin;
import be.ibridge.kettle.job.dialog.JobDialog;
import be.ibridge.kettle.job.dialog.JobLoadProgressDialog;
import be.ibridge.kettle.job.dialog.JobSaveProgressDialog;
import be.ibridge.kettle.job.entry.JobEntryCopy;
import be.ibridge.kettle.job.entry.JobEntryDialogInterface;
import be.ibridge.kettle.job.entry.JobEntryInterface;
import be.ibridge.kettle.job.entry.sql.JobEntrySQL;
import be.ibridge.kettle.job.entry.trans.JobEntryTrans;
import be.ibridge.kettle.repository.PermissionMeta;
import be.ibridge.kettle.repository.Repository;
import be.ibridge.kettle.repository.RepositoryDirectory;
import be.ibridge.kettle.repository.UserInfo;
import be.ibridge.kettle.repository.dialog.RepositoriesDialog;
import be.ibridge.kettle.repository.dialog.RepositoryExplorerDialog;
import be.ibridge.kettle.repository.dialog.SelectObjectDialog;
import be.ibridge.kettle.repository.dialog.UserDialog;
import be.ibridge.kettle.spoon.Spoon;
import be.ibridge.kettle.spoon.UndoInterface;
import be.ibridge.kettle.spoon.dialog.GetJobSQLProgressDialog;
import be.ibridge.kettle.spoon.wizards.RipDatabaseWizardPage1;
import be.ibridge.kettle.spoon.wizards.RipDatabaseWizardPage2;
import be.ibridge.kettle.spoon.wizards.RipDatabaseWizardPage3;
import be.ibridge.kettle.trans.StepLoader;
import be.ibridge.kettle.trans.TransHopMeta;
import be.ibridge.kettle.trans.TransMeta;
import be.ibridge.kettle.trans.step.StepMeta;
import be.ibridge.kettle.trans.step.tableinput.TableInputMeta;
import be.ibridge.kettle.trans.step.tableoutput.TableOutputMeta;
import be.ibridge.kettle.version.BuildVersion;

/**
 * Chef is an editor for Kettle jobs.
 * 
 * @author Matt
 * @since 16-05-2003
 *
 */
public class Chef implements AddUndoPositionInterface {
    public static final String APP_NAME = Messages.getString("Chef.Application.Name"); //$NON-NLS-1$

    private LogWriter log;
    public Display disp;
    private Shell shell;
    private boolean destroy;
    public Props props;

    public Repository rep;

    public JobMeta jobMeta;

    private SashForm sashform;
    public CTabFolder tabfolder;
    private CTabItem tiTabsGraph;
    private CTabItem tiTabsLog;
    private CTabItem tiTabsHist;

    private ChefGraph chefgraph;
    private ChefHistory chefhist;
    private ChefLog cheflog;

    // public  Font variable_font, fixed_font, graph_font, grid_font;

    private ToolBar tBar;

    private Menu msFile;
    private MenuItem miFileSep3;
    private MenuItem miEditUndo, miEditRedo;

    // public  WidgetContainer widgets;
    private Listener lsNew, lsEdit, lsDupe, lsDel, lsSQL, lsCache, lsExpl;
    private SelectionAdapter lsEditDef, lsNewDef, lsEditSel;

    public static final String STRING_CONNECTIONS = Messages.getString("Chef.Tree.Connections"); //$NON-NLS-1$
    public static final String STRING_JOBENTRIES = Messages.getString("Chef.Tree.JobEntries"); //$NON-NLS-1$
    public static final String STRING_BASE_JOBENTRIES = Messages.getString("Chef.Tree.BaseJobEntryTypes"); //$NON-NLS-1$
    public static final String STRING_PLUGIN_JOBENTRIES = Messages.getString("Chef.Tree.PluginJobEntries"); //$NON-NLS-1$

    public static final String STRING_SPECIAL = JobEntryInterface.typeDesc[JobEntryInterface.TYPE_JOBENTRY_SPECIAL];

    private static final String APPL_TITLE = Messages.getString("Chef.Application.Title"); //$NON-NLS-1$

    private static final String STRING_DEFAULT_EXT = ".kjb"; //$NON-NLS-1$
    private static final String STRING_FILTER_EXT[] = { "*.kjb;*.xml", "*.xml", "*.*" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    private static final String STRING_FILTER_NAMES[] = { Messages.getString("Chef.FileType.KettleJobs"), //$NON-NLS-1$
            Messages.getString("Chef.FileType.XMLJobs"), Messages.getString("Chef.FileType.AllJobs") }; //$NON-NLS-1$ //$NON-NLS-2$

    private Tree tMain;
    private TreeItem tiSection[];

    public KeyAdapter defKeys;

    public ToolItem tiFileRun;

    public Row variables;

    public Chef(LogWriter log, Display d, Repository rep) {
        this.log = log;
        this.rep = rep;

        if (d != null) {
            disp = d;
            destroy = false;
        } else {
            disp = new Display();
            destroy = true;
        }
        shell = new Shell(disp);
        shell.setText(APPL_TITLE);
        FormLayout layout = new FormLayout();
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        shell.setLayout(layout);

        // Clean out every time we start, auto-loading etc, is not a good idea
        // If they are needed that often, set them in the kettle.properties file
        //
        variables = new Row();

        // INIT Data structure
        jobMeta = new JobMeta(log);

        if (!Props.isInitialized()) {
            Props.init(disp, Props.TYPE_PROPERTIES_CHEF);
        }
        props = Props.getInstance();

        // Load settings in the props
        loadSettings();

        defKeys = new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.F5) {
                    refreshGraph();
                    refreshTree();
                }

                // ESC --> Unselect All steps
                if (e.keyCode == SWT.ESC) {
                    jobMeta.unselectAll();
                    refreshGraph();
                }
                ;

                // F3 --> create database connection wizard
                if (e.keyCode == SWT.F3) {
                    createDatabaseWizard();
                }

                // F5 --> refresh
                if (e.keyCode == SWT.F5) {
                    refreshGraph();
                    refreshTree(true);
                }

                // F9 --> run
                if (e.keyCode == SWT.F9) {
                    tabfolder.setSelection(1);
                    cheflog.startJob();
                }

                // F10 --> ripDB wizard
                if (e.keyCode == SWT.F10) {
                    ripDBWizard();
                }

                // CTRL-A --> Select All steps
                if (e.character == 1) {
                    jobMeta.selectAll();
                    refreshGraph();
                }
                ;

                // CTRL-D --> Disconnect from repository
                if (e.character == 4) {
                    closeRepository();
                }
                ;

                // CTRL-E --> Explore the repository
                if (e.character == 5) {
                    exploreRepository();
                }
                ;

                // CTRL-F --> Java examination
                if (e.character == 6 && ((e.stateMask & SWT.CONTROL) != 0) && ((e.stateMask & SWT.ALT) == 0)) {
                    searchMetaData();
                    chefgraph.clearSettings();
                }
                ;

                // CTRL-I --> Import file from XML
                if (e.character == 9) {
                    openFile(true);
                }
                ;

                // CTRL-J --> Job Dialog : edit job settings
                if (e.character == 10) {
                    setJob();
                }
                ;

                // CTRL-K --> Get variables
                if (e.character == 11 && ((e.stateMask & SWT.CONTROL) != 0) && ((e.stateMask & SWT.ALT) == 0)) {
                    getVariables();
                    chefgraph.clearSettings();
                }
                ;

                // CTRL-L --> Show variables
                if (e.character == 12 && ((e.stateMask & SWT.CONTROL) != 0) && ((e.stateMask & SWT.ALT) == 0)) {
                    showVariables();
                    chefgraph.clearSettings();
                }
                ;

                // CTRL-N --> new
                if (e.character == 14) {
                    newFile();
                }

                // CTRL-O --> open
                if (e.character == 15) {
                    openFile(false);
                }

                // CTRL-P --> print
                if (e.character == 16) {
                    printFile();
                }

                // CTRL-R --> Connect to repository
                if (e.character == 18) {
                    openRepository();
                }
                ;

                // CTRL-S --> save
                if (e.character == 19) {
                    saveFile();
                }

                // CTRL-Y --> save
                if (e.character == 25) {
                    redoAction();
                }

                // CTRL-Z --> save
                if (e.character == 26) {
                    undoAction();
                }
            }
        };

        addBar();

        FormData fdBar = new FormData();
        fdBar.left = new FormAttachment(0, 0);
        fdBar.top = new FormAttachment(0, 0);
        tBar.setLayoutData(fdBar);

        sashform = new SashForm(shell, SWT.HORIZONTAL);

        FormData fdSash = new FormData();
        fdSash.left = new FormAttachment(0, 0);
        fdSash.top = new FormAttachment(tBar, 0);
        fdSash.bottom = new FormAttachment(100, 0);
        fdSash.right = new FormAttachment(100, 0);
        sashform.setLayoutData(fdSash);

        addMenu();
        addTree();
        addTabs();

        refreshTree();

        shell.setImage(GUIResource.getInstance().getImageChef());

        // In case someone dares to press the [X] in the corner ;-)
        shell.addShellListener(new ShellAdapter() {
            public void shellClosed(ShellEvent e) {
                e.doit = quitFile();
            }
        });
        sashform.setWeights(new int[] { 25, 75 });
        sashform.setVisible(true);

        shell.layout();

        // Set the shell size, based upon previous time...
        WindowProperty winprop = props.getScreen(APPL_TITLE);
        if (winprop != null)
            winprop.setShell(shell);
        else
            shell.pack();
    }

    /**
     * @param destroy The destroy to set.
     */
    public void setDestroy(boolean destroy) {
        this.destroy = destroy;
    }

    /**
     * @return Returns the destroy.
     */
    public boolean isDestroy() {
        return destroy;
    }

    public void open() {
        shell.open();

        // Shared database entries to load from repository?
        loadRepositoryObjects();

        // Perhaps the job contains elements at startup?
        if (jobMeta.nrJobEntries() > 0 || jobMeta.nrDatabases() > 0 || jobMeta.nrJobHops() > 0) {
            refreshTree(true); // Do a complete refresh then...
        }

        /* WANTED: Build tips for Chef too...
         * 
        if (props.showTips()) 
        {
           TipsDialog tip = new TipsDialog(shell, job.props);
           tip.open();
        }
        */
    }

    public boolean readAndDispatch() {
        return disp.readAndDispatch();
    }

    public void dispose() {
        if (destroy)
            disp.dispose();
    }

    public boolean isDisposed() {
        return disp.isDisposed();
    }

    public void sleep() {
        disp.sleep();
    }

    public void addMenu() {
        Menu mBar = new Menu(shell, SWT.BAR);
        shell.setMenuBar(mBar);

        /*
         * File menu
         * 
         */
        MenuItem mFile = new MenuItem(mBar, SWT.CASCADE);
        mFile.setText(Messages.getString("Chef.Menu.File")); //$NON-NLS-1$
        msFile = new Menu(shell, SWT.DROP_DOWN);
        mFile.setMenu(msFile);
        MenuItem miFileNew = new MenuItem(msFile, SWT.CASCADE);
        miFileNew.setText(Messages.getString("Chef.Menu.File.New")); //$NON-NLS-1$
        MenuItem miFileOpen = new MenuItem(msFile, SWT.CASCADE);
        miFileOpen.setText(Messages.getString("Chef.Menu.File.Open")); //$NON-NLS-1$
        MenuItem miFileImport = new MenuItem(msFile, SWT.CASCADE);
        miFileImport.setText(Messages.getString("Chef.Menu.File.Import")); //"&Import from an XML file\tCTRL-I"
        MenuItem miFileExport = new MenuItem(msFile, SWT.CASCADE);
        miFileExport.setText(Messages.getString("Chef.Menu.File.Export")); //&Export to an XML file
        MenuItem miFileSave = new MenuItem(msFile, SWT.CASCADE);
        miFileSave.setText(Messages.getString("Chef.Menu.File.Save")); //$NON-NLS-1$
        MenuItem miFileSaveAs = new MenuItem(msFile, SWT.CASCADE);
        miFileSaveAs.setText(Messages.getString("Chef.Menu.File.SaveAs")); //$NON-NLS-1$
        new MenuItem(msFile, SWT.SEPARATOR);
        MenuItem miFilePrint = new MenuItem(msFile, SWT.CASCADE);
        miFilePrint.setText(Messages.getString("Chef.Menu.File.Print")); //$NON-NLS-1$
        new MenuItem(msFile, SWT.SEPARATOR);
        MenuItem miFileQuit = new MenuItem(msFile, SWT.CASCADE);
        miFileQuit.setText(Messages.getString("Chef.Menu.File.Quit")); //$NON-NLS-1$
        miFileSep3 = new MenuItem(msFile, SWT.SEPARATOR);
        addMenuLast();

        Listener lsFileNew = new Listener() {
            public void handleEvent(Event e) {
                newFile();
            }
        };
        Listener lsFileOpen = new Listener() {
            public void handleEvent(Event e) {
                openFile(false);
            }
        };
        Listener lsFileImport = new Listener() {
            public void handleEvent(Event e) {
                openFile(true);
            }
        };
        Listener lsFileExport = new Listener() {
            public void handleEvent(Event e) {
                saveXMLFile();
            }
        };
        Listener lsFileSave = new Listener() {
            public void handleEvent(Event e) {
                saveFile();
            }
        };
        Listener lsFileSaveAs = new Listener() {
            public void handleEvent(Event e) {
                saveFileAs();
            }
        };
        Listener lsFilePrint = new Listener() {
            public void handleEvent(Event e) {
                printFile();
            }
        };
        Listener lsFileQuit = new Listener() {
            public void handleEvent(Event e) {
                quitFile();
            }
        };

        miFileNew.addListener(SWT.Selection, lsFileNew);
        miFileOpen.addListener(SWT.Selection, lsFileOpen);
        miFileImport.addListener(SWT.Selection, lsFileImport);
        miFileExport.addListener(SWT.Selection, lsFileExport);
        miFileSave.addListener(SWT.Selection, lsFileSave);
        miFileSaveAs.addListener(SWT.Selection, lsFileSaveAs);
        miFilePrint.addListener(SWT.Selection, lsFilePrint);
        miFileQuit.addListener(SWT.Selection, lsFileQuit);

        /*
         * Edit menu
         * 
         */

        MenuItem mEdit = new MenuItem(mBar, SWT.CASCADE);
        mEdit.setText(Messages.getString("Chef.Menu.Edit")); //$NON-NLS-1$
        Menu msEdit = new Menu(shell, SWT.DROP_DOWN);
        mEdit.setMenu(msEdit);
        miEditUndo = new MenuItem(msEdit, SWT.CASCADE);
        miEditRedo = new MenuItem(msEdit, SWT.CASCADE);
        setUndoMenu();
        new MenuItem(msEdit, SWT.SEPARATOR);
        MenuItem miEditSearch = new MenuItem(msEdit, SWT.CASCADE);
        miEditSearch.setText(Messages.getString("Chef.Menu.Edit.Search")); //Search Metadata \tCTRL-F
        MenuItem miEditVars = new MenuItem(msEdit, SWT.CASCADE);
        miEditVars.setText(Messages.getString("Chef.Menu.Edit.Variables")); //Edit/Enter variables \tCTRL-K
        MenuItem miEditSVars = new MenuItem(msEdit, SWT.CASCADE);
        miEditSVars.setText(Messages.getString("Chef.Menu.Edit.ShowVariables")); //Show variables \tCTRL-L
        new MenuItem(msEdit, SWT.SEPARATOR);
        MenuItem miEditUnselectAll = new MenuItem(msEdit, SWT.CASCADE);
        miEditUnselectAll.setText(Messages.getString("Chef.Menu.Edit.ClearSelection")); //$NON-NLS-1$
        MenuItem miEditSelectAll = new MenuItem(msEdit, SWT.CASCADE);
        miEditSelectAll.setText(Messages.getString("Chef.Menu.Edit.SelectAllSteps")); //$NON-NLS-1$
        new MenuItem(msEdit, SWT.SEPARATOR);
        MenuItem miEditOptions = new MenuItem(msEdit, SWT.CASCADE);
        miEditOptions.setText(Messages.getString("Chef.Menu.Edit.Options")); //$NON-NLS-1$

        Listener lsEditUndo = new Listener() {
            public void handleEvent(Event e) {
                undoAction();
            }
        };
        Listener lsEditRedo = new Listener() {
            public void handleEvent(Event e) {
                redoAction();
            }
        };
        Listener lsEditSearch = new Listener() {
            public void handleEvent(Event e) {
                searchMetaData();
            }
        };
        Listener lsEditVars = new Listener() {
            public void handleEvent(Event e) {
                getVariables();
            }
        };
        Listener lsEditSVars = new Listener() {
            public void handleEvent(Event e) {
                showVariables();
            }
        };
        Listener lsEditUnselectAll = new Listener() {
            public void handleEvent(Event e) {
                editUnselectAll();
            }
        };
        Listener lsEditSelectAll = new Listener() {
            public void handleEvent(Event e) {
                editSelectAll();
            }
        };
        Listener lsEditOptions = new Listener() {
            public void handleEvent(Event e) {
                editOptions();
            }
        };

        miEditUndo.addListener(SWT.Selection, lsEditUndo);
        miEditRedo.addListener(SWT.Selection, lsEditRedo);
        miEditSearch.addListener(SWT.Selection, lsEditSearch);
        miEditVars.addListener(SWT.Selection, lsEditVars);
        miEditSVars.addListener(SWT.Selection, lsEditSVars);
        miEditUnselectAll.addListener(SWT.Selection, lsEditUnselectAll);
        miEditSelectAll.addListener(SWT.Selection, lsEditSelectAll);
        miEditOptions.addListener(SWT.Selection, lsEditOptions);

        // main Repository menu...
        MenuItem mRep = new MenuItem(mBar, SWT.CASCADE);
        mRep.setText(Messages.getString("Chef.Menu.Repository")); //$NON-NLS-1$
        Menu msRep = new Menu(shell, SWT.DROP_DOWN);
        mRep.setMenu(msRep);
        MenuItem miRepConnect = new MenuItem(msRep, SWT.CASCADE);
        miRepConnect.setText(Messages.getString("Chef.Menu.Repository.Connect")); //$NON-NLS-1$
        MenuItem miRepDisconnect = new MenuItem(msRep, SWT.CASCADE);
        miRepDisconnect.setText(Messages.getString("Chef.Menu.Repository.Disconnect")); //$NON-NLS-1$
        MenuItem miRepExplore = new MenuItem(msRep, SWT.CASCADE);
        miRepExplore.setText(Messages.getString("Chef.Menu.Repository.Explore")); //$NON-NLS-1$
        new MenuItem(msRep, SWT.SEPARATOR);
        MenuItem miRepUser = new MenuItem(msRep, SWT.CASCADE);
        miRepUser.setText(Messages.getString("Chef.Menu.Repository.EditUser")); //$NON-NLS-1$

        Listener lsRepConnect = new Listener() {
            public void handleEvent(Event e) {
                openRepository();
            }
        };
        Listener lsRepDisconnect = new Listener() {
            public void handleEvent(Event e) {
                closeRepository();
            }
        };
        Listener lsRepExplore = new Listener() {
            public void handleEvent(Event e) {
                exploreRepository();
            }
        };
        Listener lsRepUser = new Listener() {
            public void handleEvent(Event e) {
                editRepositoryUser();
            }
        };

        miRepConnect.addListener(SWT.Selection, lsRepConnect);
        miRepDisconnect.addListener(SWT.Selection, lsRepDisconnect);
        miRepExplore.addListener(SWT.Selection, lsRepExplore);
        miRepUser.addListener(SWT.Selection, lsRepUser);

        /*
         * Job menu
         * 
         */

        MenuItem mJob = new MenuItem(mBar, SWT.CASCADE);
        mJob.setText(Messages.getString("Chef.Menu.Job")); //$NON-NLS-1$
        Menu msJob = new Menu(shell, SWT.DROP_DOWN);
        mJob.setMenu(msJob);
        MenuItem miJobRun = new MenuItem(msJob, SWT.CASCADE);
        miJobRun.setText(Messages.getString("Chef.Menu.Job.Run")); //$NON-NLS-1$
        new MenuItem(msJob, SWT.SEPARATOR);
        MenuItem miJobCopy = new MenuItem(msJob, SWT.CASCADE);
        miJobCopy.setText(Messages.getString("Chef.Menu.Job.CopyToClipboard")); //$NON-NLS-1$
        new MenuItem(msJob, SWT.SEPARATOR);
        MenuItem miJobInfo = new MenuItem(msJob, SWT.CASCADE);
        miJobInfo.setText(Messages.getString("Chef.Menu.Job.Settings")); //$NON-NLS-1$

        Listener lsJobRun = new Listener() {
            public void handleEvent(Event e) {
                tabfolder.setSelection(1);
                cheflog.startJob();
            }
        };
        miJobRun.addListener(SWT.Selection, lsJobRun);
        Listener lsJobInfo = new Listener() {
            public void handleEvent(Event e) {
                setJob();
            }
        };
        miJobInfo.addListener(SWT.Selection, lsJobInfo);
        Listener lsJobCopy = new Listener() {
            public void handleEvent(Event e) {
                toClipboard(XMLHandler.getXMLHeader() + jobMeta.getXML());
            }
        };
        miJobCopy.addListener(SWT.Selection, lsJobCopy);

        // Wizard menu
        MenuItem mWizard = new MenuItem(mBar, SWT.CASCADE);
        mWizard.setText(Messages.getString("Chef.Menu.Wizard")); //$NON-NLS-1$
        Menu msWizard = new Menu(shell, SWT.DROP_DOWN);
        mWizard.setMenu(msWizard);

        MenuItem miWizardNewConnection = new MenuItem(msWizard, SWT.CASCADE);
        miWizardNewConnection.setText(Messages.getString("Chef.Menu.Wizard.CreateDatabaseConnection")); //$NON-NLS-1$
        Listener lsWizardNewConnection = new Listener() {
            public void handleEvent(Event e) {
                createDatabaseWizard();
            }
        };
        miWizardNewConnection.addListener(SWT.Selection, lsWizardNewConnection);

        MenuItem miWizardRipDatabase = new MenuItem(msWizard, SWT.CASCADE);
        miWizardRipDatabase.setText(Messages.getString("Chef.Menu.Wizard.CopyTables")); //$NON-NLS-1$
        Listener lsWizardRipDatabase = new Listener() {
            public void handleEvent(Event e) {
                ripDBWizard();
            }
        };
        miWizardRipDatabase.addListener(SWT.Selection, lsWizardRipDatabase);

        /*
         * Help menu
         * 
         */

        MenuItem mHelp = new MenuItem(mBar, SWT.CASCADE);
        mHelp.setText(Messages.getString("Chef.Menu.Help")); //$NON-NLS-1$
        Menu msHelp = new Menu(shell, SWT.DROP_DOWN);
        mHelp.setMenu(msHelp);
        MenuItem miHelpAbout = new MenuItem(msHelp, SWT.CASCADE);
        miHelpAbout.setText(Messages.getString("Chef.Menu.Help.About")); //$NON-NLS-1$

        Listener lsHelpAbout = new Listener() {
            public void handleEvent(Event e) {
                helpAbout();
            }
        };
        miHelpAbout.addListener(SWT.Selection, lsHelpAbout);
    }

    private void addMenuLast() {
        int idx = msFile.indexOf(miFileSep3);
        int max = msFile.getItemCount();

        // Remove everything until end... 
        for (int i = max - 1; i > idx; i--) {
            MenuItem mi = msFile.getItem(i);
            mi.dispose();
        }

        // Previously loaded files...
        // Previously loaded files...
        List lastUsedFiles = props.getLastUsedFiles();
        for (int i = 0; i < lastUsedFiles.size(); i++) {
            final LastUsedFile lastUsedFile = (LastUsedFile) lastUsedFiles.get(i);
            MenuItem miFileLast = new MenuItem(msFile, SWT.CASCADE);

            char chr = (char) ('1' + i);
            int accel = SWT.CTRL | chr;

            if (i < 9) {
                miFileLast.setAccelerator(accel);
                miFileLast.setText("&" + chr + "  " + lastUsedFile + "\tCTRL-" + chr);
            } else {
                miFileLast.setText("   " + lastUsedFile);
            }

            Listener lsFileLast = new Listener() {
                public void handleEvent(Event e) {
                    if (showChangedWarning()) {
                        // If the file comes from a repository and it's not the same as 
                        // the one we're connected to, ask for a username/password!
                        // 
                        boolean noRepository = false;
                        if (lastUsedFile.isSourceRepository() && (rep == null || !rep.getRepositoryInfo().getName()
                                .equalsIgnoreCase(lastUsedFile.getRepositoryName()))) {
                            int perms[] = new int[] { PermissionMeta.TYPE_PERMISSION_JOB };
                            RepositoriesDialog rd = new RepositoriesDialog(disp, perms, APP_NAME);
                            rd.setRepositoryName(lastUsedFile.getRepositoryName());
                            if (rd.open()) {
                                //   Close the previous connection...
                                if (rep != null)
                                    rep.disconnect();
                                rep = new Repository(log, rd.getRepository(), rd.getUser());
                                try {
                                    rep.connect(APP_NAME);
                                } catch (KettleException ke) {
                                    rep = null;
                                    new ErrorDialog(shell,
                                            Messages.getString("Chef.Dialog.UnableToConnectToRepository.Title"),
                                            Messages.getString("Chef.Dialog.UnableToConnectToRepository.Message"),
                                            ke);
                                }
                            } else {
                                noRepository = true;
                            }
                        }

                        if (lastUsedFile.isSourceRepository()) {
                            if (!noRepository && rep != null && rep.getRepositoryInfo().getName()
                                    .equalsIgnoreCase(lastUsedFile.getRepositoryName())) {
                                // OK, we're connected to the new repository...
                                // Load the job...
                                RepositoryDirectory fdRepdir = rep.getDirectoryTree()
                                        .findDirectory(lastUsedFile.getDirectory());
                                try {
                                    if (fdRepdir != null) {
                                        jobMeta = new JobMeta(log, rep, lastUsedFile.getFilename(), fdRepdir);
                                        props.addLastFile(LastUsedFile.FILE_TYPE_JOB, lastUsedFile.getFilename(),
                                                fdRepdir.getPath(), true, rep.getName());
                                    } else {
                                        throw new KettleException(
                                                Messages.getString("Chef.Exception.RepositoryDirectoryDoesNotExist") //$NON-NLS-1$
                                                        + lastUsedFile.getDirectory());
                                    }
                                } catch (KettleException ke) {
                                    jobMeta.clear();
                                    new ErrorDialog(shell,
                                            Messages.getString("Chef.ErrorDialog.ErrorLoadingJob.Title"), //$NON-NLS-1$
                                            Messages.getString("Chef.ErrorDialog.ErrorLoadingJob.Message"), ke); //$NON-NLS-1$
                                }
                            } else {
                                jobMeta.clear();
                                MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
                                mb.setMessage(Messages
                                        .getString("Chef.Dialog.CanNotLoadJobConnectToRepositoryFirst.Message")); //$NON-NLS-1$
                                mb.setText(Messages
                                        .getString("Chef.Dialog.CanNotLoadJobConnectToRepositoryFirst.Title")); //$NON-NLS-1$
                                mb.open();
                            }
                        } else
                        // Load from XML!
                        {
                            try {
                                jobMeta = new JobMeta(log, lastUsedFile.getFilename(), rep);
                                props.addLastFile(LastUsedFile.FILE_TYPE_JOB, lastUsedFile.getFilename(), null,
                                        false, null);
                            } catch (KettleException ke) {
                                jobMeta.clear();
                                new ErrorDialog(shell,
                                        Messages.getString("Chef.ErrorDialog.UnableToLoadJobFromXML.Title"), //$NON-NLS-1$
                                        Messages.getString("Chef.ErrorDialog.UnableToLoadJobFromXML.Message"), ke); //$NON-NLS-1$
                            }
                        }

                        saveSettings();
                        addMenuLast();

                        refreshTree(true);
                        refreshGraph();

                    }
                }
            };

            miFileLast.addListener(SWT.Selection, lsFileLast);
        }
    }

    private void addBar() {
        tBar = new ToolBar(shell, SWT.HORIZONTAL | SWT.FLAT);
        //tBar.setSize(200, 20);
        final ToolItem tiFileNew = new ToolItem(tBar, SWT.PUSH);
        final Image imFileNew = new Image(disp, getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "new.png")); //$NON-NLS-1$
        tiFileNew.setImage(imFileNew);
        tiFileNew.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                newFile();
            }
        });
        tiFileNew.setToolTipText(Messages.getString("Chef.ToolBarButton.NewFile.ToolTip")); //$NON-NLS-1$

        final ToolItem tiFileOpen = new ToolItem(tBar, SWT.PUSH);
        final Image imFileOpen = new Image(disp,
                getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "open.png")); //$NON-NLS-1$
        tiFileOpen.setImage(imFileOpen);
        tiFileOpen.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                openFile(false);
            }
        });
        tiFileOpen.setToolTipText(Messages.getString("Chef.ToolBarButton.OpenFile.ToolTip")); //$NON-NLS-1$

        final ToolItem tiFileSave = new ToolItem(tBar, SWT.PUSH);
        final Image imFileSave = new Image(disp,
                getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "save.png")); //$NON-NLS-1$
        tiFileSave.setImage(imFileSave);
        tiFileSave.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                saveFile();
            }
        });
        tiFileSave.setToolTipText(Messages.getString("Chef.ToolBarButton.SaveCurrentFile.ToolTip")); //$NON-NLS-1$

        final ToolItem tiFileSaveAs = new ToolItem(tBar, SWT.PUSH);
        final Image imFileSaveAs = new Image(disp,
                getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "saveas.png")); //$NON-NLS-1$
        tiFileSaveAs.setImage(imFileSaveAs);
        tiFileSaveAs.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                saveFileAs();
            }
        });
        tiFileSaveAs.setToolTipText(Messages.getString("Chef.ToolBarButton.SaveFileAs.ToolTip")); //$NON-NLS-1$

        new ToolItem(tBar, SWT.SEPARATOR);
        final ToolItem tiFilePrint = new ToolItem(tBar, SWT.PUSH);
        final Image imFilePrint = new Image(disp,
                getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "print.png")); //$NON-NLS-1$
        tiFilePrint.setImage(imFilePrint);
        tiFilePrint.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                printFile();
            }
        });
        tiFilePrint.setToolTipText(Messages.getString("Chef.ToolBarButton.Print.ToolTip")); //$NON-NLS-1$

        new ToolItem(tBar, SWT.SEPARATOR);
        tiFileRun = new ToolItem(tBar, SWT.PUSH);
        final Image imFileRun = new Image(disp, getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "run.png")); //$NON-NLS-1$
        tiFileRun.setImage(imFileRun);
        tiFileRun.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                tabfolder.setSelection(1);
                cheflog.startJob();
            }
        });
        tiFileRun.setToolTipText(Messages.getString("Chef.ToolBarButton.RunThisJob.ToolTip")); //$NON-NLS-1$

        new ToolItem(tBar, SWT.SEPARATOR);
        final ToolItem tiSQL = new ToolItem(tBar, SWT.PUSH);
        final Image imSQL = new Image(disp,
                getClass().getResourceAsStream(Const.IMAGE_DIRECTORY + "SQLbutton.png")); //$NON-NLS-1$
        tiSQL.setImage(imSQL);
        tiSQL.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                getSQL();
            }
        });
        tiSQL.setToolTipText(Messages.getString("Chef.ToolBarButton.GenerateSQL.ToolTip")); //$NON-NLS-1$

        tBar.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                imFileNew.dispose();
                imFileOpen.dispose();
                imFileSave.dispose();
                imFileSaveAs.dispose();
            }
        });
        tBar.addKeyListener(defKeys);
        tBar.pack();
    }

    /**
     * Get & show the SQL required to run the loaded job entry...
     *
     */
    public void getSQL() {
        GetJobSQLProgressDialog pspd = new GetJobSQLProgressDialog(shell, jobMeta, rep);
        ArrayList stats = pspd.open();
        if (stats != null) // null means error, but we already displayed the error
        {
            if (stats.size() > 0) {
                SQLStatementsDialog ssd = new SQLStatementsDialog(shell, SWT.NONE, stats);
                ssd.open();
            } else {
                MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION);
                mb.setMessage(Messages.getString("Chef.Dialog.NoSQLNeeded.Message")); //$NON-NLS-1$
                mb.setText(Messages.getString("Chef.Dialog.NoSQLNeeded.Title")); //$NON-NLS-1$
                mb.open();
            }
        }
    }

    private static final String STRING_CHEF_MAIN_TREE_NAME = "Chef Main tree";

    private void addTree() {
        SashForm leftsplit = new SashForm(sashform, SWT.VERTICAL);
        leftsplit.setLayout(new FillLayout());

        // CSH: Connections, Steps and Transformations
        Composite cCSH = new Composite(leftsplit, SWT.NONE);
        cCSH.setLayout(new FillLayout());

        leftsplit.setWeights(new int[] { 100 });

        // Now set up the main CSH tree
        tMain = new Tree(cCSH, SWT.SINGLE | SWT.BORDER);
        TreeMemory.addTreeListener(tMain, STRING_CHEF_MAIN_TREE_NAME);

        // Add the connections subtree
        //
        TreeItem tiConnections = new TreeItem(tMain, SWT.NONE);
        tiConnections.setText(STRING_CONNECTIONS);

        // Job-entries
        // 
        TreeItem tiEntries = new TreeItem(tMain, SWT.NONE);
        tiEntries.setText(STRING_JOBENTRIES);

        // Job entry base type
        // 
        TreeItem tiBaseEntries = new TreeItem(tMain, SWT.NONE);
        tiBaseEntries.setText(STRING_BASE_JOBENTRIES);

        // Set the entry types on it using the JobEntryLoader
        JobEntryLoader jobEntryLoader = JobEntryLoader.getInstance();
        JobPlugin baseJobEntries[] = jobEntryLoader.getJobEntriesWithType(JobPlugin.TYPE_NATIVE);
        for (int i = 0; i < baseJobEntries.length; i++) {
            JobPlugin plugin = baseJobEntries[i];
            if (!plugin.getID().equals("SPECIAL")) //$NON-NLS-1$
            {
                TreeItem tiBase = new TreeItem(tiBaseEntries, SWT.NONE);
                tiBase.setText(baseJobEntries[i].getDescription());
                Image image = (Image) GUIResource.getInstance().getImagesJobentriesSmall().get(plugin.getID());
                tiBase.setImage(image);
            }
        }

        // Job entry base type
        // 
        TreeItem tiPluginEntries = new TreeItem(tMain, SWT.NONE);
        tiPluginEntries.setText(STRING_PLUGIN_JOBENTRIES);

        // Set the entry types on it using the JobEntryLoader
        JobPlugin pluginJobEntries[] = jobEntryLoader.getJobEntriesWithType(JobPlugin.TYPE_PLUGIN);
        for (int i = 0; i < pluginJobEntries.length; i++) {
            TreeItem tiPlugin = new TreeItem(tiPluginEntries, SWT.NONE);
            tiPlugin.setText(pluginJobEntries[i].getDescription());
            Image image = (Image) GUIResource.getInstance().getImagesJobentriesSmall()
                    .get(pluginJobEntries[i].getID());
            tiPlugin.setImage(image);
        }

        props.setLook(tMain);

        // Popup-menu selection
        lsNew = new Listener() {
            public void handleEvent(Event e) {
                newSelected();
            }
        };
        lsEdit = new Listener() {
            public void handleEvent(Event e) {
                editSelected();
            }
        };
        lsDupe = new Listener() {
            public void handleEvent(Event e) {
                dupeSelected();
            }
        };
        lsDel = new Listener() {
            public void handleEvent(Event e) {
                delSelected();
            }
        };
        lsSQL = new Listener() {
            public void handleEvent(Event e) {
                sqlSelected();
            }
        };
        lsCache = new Listener() {
            public void handleEvent(Event e) {
                clearDBCache();
            }
        };
        lsExpl = new Listener() {
            public void handleEvent(Event e) {
                exploreDB();
            }
        };

        // Default selection (double-click, enter)
        lsEditDef = new SelectionAdapter() {
            public void widgetDefaultSelected(SelectionEvent e) {
                editSelected();
            }
        };
        lsNewDef = new SelectionAdapter() {
            public void widgetDefaultSelected(SelectionEvent e) {
                newSelected();
            }
        };
        lsEditSel = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                setMenu(e);
            }
        };

        // Add all the listeners... 
        tMain.addSelectionListener(lsEditDef); // double click somewhere in the tree...
        tMain.addSelectionListener(lsNewDef); // double click somewhere in the tree...
        tMain.addSelectionListener(lsEditSel);

        addDragSourceToTree(tMain);

        // Keyboard shortcuts!
        tMain.addKeyListener(defKeys);

        // Set some images on the tree
        //
        tiConnections.setImage(GUIResource.getInstance().getImageConnection());
        tiEntries.setImage(GUIResource.getInstance().getImageBol());
        tiBaseEntries.setImage(GUIResource.getInstance().getImageBol());
        tiPluginEntries.setImage(GUIResource.getInstance().getImageBol());
    }

    private void addDragSourceToTree(Tree tree) {
        final Tree fTree = tree;

        // Drag & Drop for steps

        Transfer[] ttypes = new Transfer[] { XMLTransfer.getInstance() };

        DragSource ddSource = new DragSource(fTree, DND.DROP_MOVE);
        ddSource.setTransfer(ttypes);
        ddSource.addDragListener(new DragSourceListener() {
            public void dragStart(DragSourceEvent event) {
            }

            public void dragSetData(DragSourceEvent event) {
                TreeItem ti[] = fTree.getSelection();

                if (ti.length > 0) {
                    String data = null;
                    int type = 0;

                    String ts[] = Const.getTreeStrings(ti[0]);

                    if (ts != null && ts.length > 0) {
                        // Drop of existing hidden step onto canvas?
                        if (ts[0].equalsIgnoreCase(STRING_JOBENTRIES)) {
                            type = DragAndDropContainer.TYPE_JOB_ENTRY;
                            data = ti[0].getText(); // name of the step.
                        } else if (ts[0].equalsIgnoreCase(STRING_BASE_JOBENTRIES)
                                || ts[0].equalsIgnoreCase(STRING_PLUGIN_JOBENTRIES)) {
                            type = DragAndDropContainer.TYPE_BASE_JOB_ENTRY;
                            data = ti[0].getText(); // Step type
                        } else if (ts[0].equalsIgnoreCase(STRING_CONNECTIONS)) {
                            type = DragAndDropContainer.TYPE_DATABASE_CONNECTION;
                            data = ti[0].getText(); // Database connection name to use
                        } else {
                            event.doit = false;
                            return; // ignore anything else you drag.
                        }

                        event.data = new DragAndDropContainer(type, data);
                        // System.out.println("data = "+event.data.getClass());
                    }
                } else // Nothing got dragged, only can happen on OSX :-)
                {
                    event.doit = false;
                    // System.out.println("NOTHING DRAGGED! WTF!");
                }
            }

            public void dragFinished(DragSourceEvent event) {
            }
        });

    }

    private void setMenu(SelectionEvent e) {
        TreeItem ti = (TreeItem) e.item;
        log.logDebug(toString(), Messages.getString("Chef.Log.ClickedOn") + ti.getText()); //$NON-NLS-1$

        Menu mCSH = new Menu(shell, SWT.POP_UP);

        // Find the level we clicked on: Top level (only NEW in the menu) or below (edit, insert, ...)
        TreeItem parent = ti.getParentItem();
        if (parent == null) // Top level
        {
            MenuItem miNew = new MenuItem(mCSH, SWT.PUSH);
            miNew.setText(Messages.getString("Chef.TreeMenu.New")); //$NON-NLS-1$
            miNew.addListener(SWT.Selection, lsNew);
        } else {
            String section = parent.getText();
            if (section.equalsIgnoreCase(STRING_CONNECTIONS)) {
                MenuItem miNew = new MenuItem(mCSH, SWT.PUSH);
                miNew.setText(Messages.getString("Chef.TreeMenu.Connection.New")); //$NON-NLS-1$
                MenuItem miEdit = new MenuItem(mCSH, SWT.PUSH);
                miEdit.setText(Messages.getString("Chef.TreeMenu.Connection.Edit")); //$NON-NLS-1$
                MenuItem miDupe = new MenuItem(mCSH, SWT.PUSH);
                miDupe.setText(Messages.getString("Chef.TreeMenu.Connection.Duplicate")); //$NON-NLS-1$
                MenuItem miDel = new MenuItem(mCSH, SWT.PUSH);
                miDel.setText(Messages.getString("Chef.TreeMenu.Connection.Delete")); //$NON-NLS-1$
                new MenuItem(mCSH, SWT.SEPARATOR);
                MenuItem miSQL = new MenuItem(mCSH, SWT.PUSH);
                miSQL.setText(Messages.getString("Chef.TreeMenu.Connection.SQLEditor")); //$NON-NLS-1$
                MenuItem miCache = new MenuItem(mCSH, SWT.PUSH);
                miCache.setText(Messages.getString("Chef.TreeMenu.Connection.ClearDBCache") + ti.getText()); //$NON-NLS-1$
                new MenuItem(mCSH, SWT.SEPARATOR);
                MenuItem miExpl = new MenuItem(mCSH, SWT.PUSH);
                miExpl.setText(Messages.getString("Chef.TreeMenu.Connection.Explore")); //$NON-NLS-1$
                miNew.addListener(SWT.Selection, lsNew);
                miEdit.addListener(SWT.Selection, lsEdit);
                miDupe.addListener(SWT.Selection, lsDupe);
                miDel.addListener(SWT.Selection, lsDel);
                miSQL.addListener(SWT.Selection, lsSQL);
                miCache.addListener(SWT.Selection, lsCache);
                miExpl.addListener(SWT.Selection, lsExpl);
            } else if (!ti.getText().equalsIgnoreCase(STRING_SPECIAL)
                    && !section.equalsIgnoreCase(STRING_SPECIAL)) {
                MenuItem miNew = new MenuItem(mCSH, SWT.PUSH);
                miNew.setText(Messages.getString("Chef.TreeMenu.JobEntry.New")); //$NON-NLS-1$
                MenuItem miEdit = new MenuItem(mCSH, SWT.PUSH);
                miEdit.setText(Messages.getString("Chef.TreeMenu.JobEntry.Edit")); //$NON-NLS-1$
                MenuItem miDupe = new MenuItem(mCSH, SWT.PUSH);
                miDupe.setText(Messages.getString("Chef.TreeMenu.JobEntry.Duplicate")); //$NON-NLS-1$
                MenuItem miDel = new MenuItem(mCSH, SWT.PUSH);
                miDel.setText(Messages.getString("Chef.TreeMenu.JobEntry.Delete")); //$NON-NLS-1$
                miNew.addListener(SWT.Selection, lsNew);
                miEdit.addListener(SWT.Selection, lsEdit);
                miDupe.addListener(SWT.Selection, lsDupe);
                miDel.addListener(SWT.Selection, lsDel);
            }
        }
        tMain.setMenu(mCSH);
    }

    private void addTabs() {
        Composite child = new Composite(sashform, SWT.BORDER);
        child.setLayout(new FillLayout());

        tabfolder = new CTabFolder(child, SWT.BORDER);
        props.setLook(tabfolder, Props.WIDGET_STYLE_TAB);

        tiTabsGraph = new CTabItem(tabfolder, SWT.NONE);
        tiTabsGraph.setText(Messages.getString("Chef.Tab.GraphicalView.Text")); //$NON-NLS-1$
        tiTabsGraph.setToolTipText(Messages.getString("Chef.Tab.GraphicalView.ToolTip")); //$NON-NLS-1$
        // chefgraph = new ChefGraph(tabfolder, this);

        tiTabsLog = new CTabItem(tabfolder, SWT.NULL);
        tiTabsLog.setText(Messages.getString("Chef.Tab.LogView.Text")); //$NON-NLS-1$
        tiTabsLog.setToolTipText(Messages.getString("Chef.Tab.LogView.ToolTip")); //$NON-NLS-1$
        // cheflog = new ChefLog(tabfolder, this);

        tiTabsHist = new CTabItem(tabfolder, SWT.NULL);
        tiTabsHist.setText(Messages.getString("Chef.Tab.HistoryView.Text")); //$NON-NLS-1$
        tiTabsHist.setToolTipText(Messages.getString("Chef.Tab.HistoryView.ToolTip")); //$NON-NLS-1$
        // chefhist = new ChefHistory(tabfolder, this, log, null, cheflog, shell);

        tiTabsGraph.setControl(chefgraph);
        tiTabsLog.setControl(cheflog);
        tiTabsHist.setControl(chefhist);

        tabfolder.setSelection(0);

        sashform.addKeyListener(defKeys);

        ChefHistoryRefresher chefHistoryRefresher = new ChefHistoryRefresher(tiTabsHist, chefhist);
        tabfolder.addSelectionListener(chefHistoryRefresher);
        cheflog.setChefHistoryRefresher(chefHistoryRefresher);
    }

    public String getRepositoryName() {
        return rep == null ? "" : rep.getName(); //$NON-NLS-1$
    }

    public void newSelected() // Double click in tree
    {
        // Determine what menu we selected from...
        TreeItem ti[] = tMain.getSelection();

        if (ti.length >= 1) {
            String name = ti[0].getText();
            TreeItem parent = ti[0].getParentItem();
            if (parent == null) // Double click on parent: new entry!
            {
                if (name.equalsIgnoreCase(STRING_CONNECTIONS)) {
                    newConnection();
                } else {
                    newChefGraphEntry(name, true);
                }
            } else // Double-click on entry: edit it!
            {
                // This is handled separately in editSelected through listener lsDef.
            }
        }
    }

    public void editSelected() {
        // Determine what menu we selected from...

        TreeItem ti[] = tMain.getSelection();
        if (ti.length == 1) {
            String name = ti[0].getText();
            TreeItem parent = ti[0].getParentItem();
            if (parent != null) {
                if (parent.getText().equalsIgnoreCase(STRING_CONNECTIONS)) {
                    editConnection(name);
                } else {
                    JobEntryCopy getjge = jobMeta.findJobEntry(name, 0, true);
                    if (getjge != null) {
                        editChefGraphEntry(getjge);
                    }
                }
            }
        }
    }

    public void dupeSelected() {
        // Determine what menu we selected from...

        TreeItem ti[] = tMain.getSelection();

        // Then call editConnection or editStep or editTrans
        if (ti.length == 1) {
            String name = ti[0].getText();
            TreeItem parent = ti[0].getParentItem();
            if (parent != null) {
                dupeChefGraphEntry(name);
            }
        }
    }

    public void delSelected() {
        // Determine what menu we selected from...
        int i;

        TreeItem ti[] = tMain.getSelection();
        String name[] = new String[ti.length];
        TreeItem parent[] = new TreeItem[ti.length];

        for (i = 0; i < ti.length; i++) {
            name[i] = ti[i].getText();
            parent[i] = ti[i].getParentItem();
        }

        // Then call editConnection or editStep or editTrans
        for (i = name.length - 1; i >= 0; i--) {
            log.logDebug(toString(),
                    Messages.getString("Chef.Log.TryingToDelete") + i + "/" + (ti.length - 1) + " : " + name[i]); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            if (parent[i] != null) {
                String type = parent[i].getText();
                if (type.equalsIgnoreCase(STRING_CONNECTIONS)) {
                    delConnection(name[i]);
                }
                if (type.equalsIgnoreCase(STRING_JOBENTRIES)) {
                    deleteChefGraphEntry(name[i]);
                }
            }
        }
    }

    public void sqlSelected() {
        // Determine what menu we selected from...
        int i;

        TreeItem ti[] = tMain.getSelection();
        for (i = 0; i < ti.length; i++) {
            String name = ti[i].getText();
            TreeItem parent = ti[i].getParentItem();
            String type = parent.getText();
            if (type.equalsIgnoreCase(STRING_CONNECTIONS)) {
                DatabaseMeta ci = jobMeta.findDatabase(name);
                SQLEditor sql = new SQLEditor(shell, SWT.NONE, ci, jobMeta.dbcache, ""); //$NON-NLS-1$
                sql.open();
            }

        }
    }

    public void editConnection(String name) {
        DatabaseMeta db = jobMeta.findDatabase(name);
        if (db != null) {
            DatabaseMeta before = (DatabaseMeta) db.clone();

            DatabaseDialog con = new DatabaseDialog(shell, db);
            con.setDatabases(jobMeta.getDatabases());
            String newname = con.open();
            if (newname != null) // null: CANCEL
            {
                // Store undo/redo information
                DatabaseMeta after = (DatabaseMeta) db.clone();
                addUndoChange(new DatabaseMeta[] { before }, new DatabaseMeta[] { after },
                        new int[] { jobMeta.indexOfDatabase(db) });

                saveConnection(db);

                // It's saved, remove the changed flag
                db.setChanged(false);

                if (!name.equalsIgnoreCase(newname))
                    refreshTree();
            }
        }
        setShellText();
    }

    public void dupeConnection(String name) {
        int i, pos = 0;
        DatabaseMeta db = null, look = null;

        for (i = 0; i < jobMeta.nrDatabases() && db == null; i++) {
            look = jobMeta.getDatabase(i);
            if (look.getName().equalsIgnoreCase(name)) {
                db = look;
                pos = i;
            }
        }
        if (db != null) {
            DatabaseMeta newdb = (DatabaseMeta) db.clone();
            String dupename = Messages.getString("Chef.JobEntryName.Duplicate.Prefix") + name; //$NON-NLS-1$
            newdb.setName(dupename);
            jobMeta.addDatabase(pos + 1, newdb);
            refreshTree();

            DatabaseDialog con = new DatabaseDialog(shell, newdb);
            String newname = con.open();
            if (newname != null) // null: CANCEL
            {
                jobMeta.removeDatabase(pos + 1);
                jobMeta.addDatabase(pos + 1, newdb);

                if (!newname.equalsIgnoreCase(dupename))
                    refreshTree();
            } else {
                addUndoNew(new DatabaseMeta[] { (DatabaseMeta) db.clone() },
                        new int[] { jobMeta.indexOfDatabase(db) });

                saveConnection(db);
            }
        }
    }

    /**
    * Delete a database connection
    * @param name The name of the database connection.
    */
    public void delConnection(String name) {
        DatabaseMeta db = jobMeta.findDatabase(name);
        int pos = jobMeta.indexOfDatabase(db);
        if (db != null) {
            boolean worked = false;

            // delete from repository?
            if (rep != null) {
                if (!rep.getUserInfo().isReadonly()) {
                    try {
                        long id_database = rep.getDatabaseID(db.getName());
                        rep.delDatabase(id_database);

                        worked = true;
                    } catch (KettleDatabaseException dbe) {

                        new ErrorDialog(shell, Messages.getString("Chef.ErrorDialog.ErrorDeletingConnection.Title"), //$NON-NLS-1$
                                Messages.getString("Chef.ErrorDialog.ErrorDeletingConnection.Message") + db //$NON-NLS-1$
                                        + Messages.getString("Chef.ErrorDialog.ErrorDeletingConnection.Message2"), //$NON-NLS-1$
                                dbe);
                    }
                } else {
                    new ErrorDialog(shell,
                            Messages.getString("Chef.ErrorDialog.DeleteConnectionUserIsReadOnly.Title"), //$NON-NLS-1$
                            Messages.getString("Chef.ErrorDialog.DeleteConnectionUserIsReadOnly.Message") + db //$NON-NLS-1$
                                    + Messages
                                            .getString("Chef.ErrorDialog.DeleteConnectionUserIsReadOnly.Message2"), //$NON-NLS-1$
                            null);
                }
            }

            if (rep == null || worked) {
                addUndoDelete(new DatabaseMeta[] { (DatabaseMeta) db.clone() }, new int[] { pos });
                jobMeta.removeDatabase(pos);
            }

            refreshTree();
        }
        setShellText();
    }

    public void newJobHop(JobEntryCopy fr, JobEntryCopy to) {
        log.logBasic(toString(),
                Messages.getString("Chef.Log.NewJobHop") + fr.getName() + ", " + to.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        JobHopMeta hi = new JobHopMeta(fr, to);
        jobMeta.addJobHop(hi);
        addUndoNew(new JobHopMeta[] { hi }, new int[] { jobMeta.indexOfJobHop(hi) });
        refreshGraph();
        refreshTree();
    }

    public boolean showChangedWarning() {
        boolean answer = true;
        if (jobMeta.hasChanged()) {
            MessageBox mb = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_WARNING);
            mb.setMessage(Messages.getString("Chef.Dialog.OpenNewFileHasChanged.Message")); //$NON-NLS-1$
            mb.setText(Messages.getString("Chef.Dialog.OpenNewFileHasChanged.Title")); //$NON-NLS-1$
            answer = mb.open() == SWT.YES;
        }
        return answer;
    }

    public void openRepository() {
        int perms[] = new int[] { PermissionMeta.TYPE_PERMISSION_TRANSFORMATION };
        RepositoriesDialog rd = new RepositoriesDialog(disp, perms, APP_NAME);
        rd.getShell().setImage(GUIResource.getInstance().getImageSpoon());
        if (rd.open()) {
            // Close previous repository...

            if (rep != null)
                rep.disconnect();

            rep = new Repository(log, rd.getRepository(), rd.getUser());
            try {
                rep.connect(Messages.getString("Chef.AppName.RepositoryConnect")); //$NON-NLS-1$
            } catch (KettleException ke) {
                rep = null;
                new ErrorDialog(shell, Messages.getString("Chef.Dialog.ErrorConnectingToTheRepository.Title"), //$NON-NLS-1$
                        Messages.getString("Chef.Dialog.ErrorConnectingToTheRepository.Message1") + Const.CR //$NON-NLS-1$
                                + Messages.getString("Chef.Dialog.ErrorConnectingToTheRepository.Message2"), //$NON-NLS-1$
                        ke);
            }

            // Set for the existing databases, the ID's at -1!
            for (int i = 0; i < jobMeta.nrDatabases(); i++) {
                jobMeta.getDatabase(i).setID(-1L);
            }
            // Set for the existing job the ID at -1!
            jobMeta.setID(-1L);

            ArrayList oldDatabases = jobMeta.getDatabases(); // Save the list.

            // In order to re-match the databases on name (not content), we need to load the databases from the new repository.
            // NOTE: for purposes such as DEVELOP - TEST - PRODUCTION sycles.
            loadRepositoryObjects();

            // Then we need to re-match the databases at save time...
            for (int i = 0; i < oldDatabases.size(); i++) {
                DatabaseMeta oldDatabase = (DatabaseMeta) oldDatabases.get(i);
                DatabaseMeta newDatabase = DatabaseMeta.findDatabase(jobMeta.getDatabases(), oldDatabase.getName());

                // If it exists, change the settings...
                if (newDatabase != null) {
                    // A database connection with the same name exists in the new repository.
                    // Change the old connections to reflect the settings in the new repository 
                    oldDatabase.setDatabaseInterface(newDatabase.getDatabaseInterface());
                } else {
                    // The old database is not present in the new repository: simply add it to the list.
                    // When the transformation gets saved, it will be added to the repository.
                    jobMeta.addDatabase(oldDatabase);
                }
            }

            // For the existing transformation, change the directory too:
            // Try to find the same directory in the new repository...
            RepositoryDirectory redi = rep.getDirectoryTree().findDirectory(jobMeta.getDirectory().getPath());
            if (redi != null) {
                jobMeta.setDirectory(redi);
            } else {
                jobMeta.setDirectory(rep.getDirectoryTree()); // the root is the default!
            }

            refreshTree();

            setShellText();
        } else {
            // Not cancelled? --> Clear repository...
            if (!rd.isCancelled()) {
                closeRepository();
            }
        }
    }

    public void readDatabases() throws KettleException {
        jobMeta.readDatabases(rep);
    }

    public void exploreRepository() {
        RepositoryExplorerDialog erd = new RepositoryExplorerDialog(shell, SWT.NONE, rep, rep.getUserInfo());
        erd.open();
    }

    public void closeRepository() {
        rep.disconnect();
        rep = null;
        setShellText();
    }

    public void editRepositoryUser() {
        if (rep != null) {
            UserInfo userinfo = rep.getUserInfo();
            UserDialog ud = new UserDialog(shell, SWT.NONE, rep, userinfo);
            UserInfo ui = ud.open();
            if (!userinfo.isReadonly()) {
                if (ui != null) {
                    try {
                        ui.saveRep(rep);
                    } catch (KettleException e) {
                        MessageBox mb = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
                        mb.setMessage(Messages.getString("Chef.Dialog.UnableToChangeUser.Message") + Const.CR //$NON-NLS-1$
                                + e.getMessage());
                        mb.setText(Messages.getString("Chef.Dialog.UnableToChangeUser.Title")); //$NON-NLS-1$
                        mb.open();
                    }
                }
            } else {
                MessageBox mb = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
                mb.setMessage(Messages.getString("Chef.Dialog.NotAllowedToChangeUser.Message")); //$NON-NLS-1$
                mb.setText(Messages.getString("Chef.Dialog.NotAllowedToChangeUser.Title")); //$NON-NLS-1$
                mb.open();
            }
        }
    }

    public void clearDBCache() {
        // Determine what menu we selected from...

        TreeItem ti[] = tMain.getSelection();
        // Then call editConnection or editStep or editTrans
        if (ti.length == 1) {
            String name = ti[0].getText();
            TreeItem parent = ti[0].getParentItem();
            if (parent != null) {
                String type = parent.getText();
                if (type.equalsIgnoreCase(STRING_CONNECTIONS)) {
                    jobMeta.dbcache.clear(name);
                }
            } else {
                if (name.equalsIgnoreCase(STRING_CONNECTIONS))
                    jobMeta.dbcache.clear(null);
            }
        }
    }

    public void exploreDB() {
        // Determine what menu we selected from...
        TreeItem ti[] = tMain.getSelection();

        // Then call editConnection or editStep or editTrans
        if (ti.length == 1) {
            String name = ti[0].getText();
            TreeItem parent = ti[0].getParentItem();
            if (parent != null) {
                String type = parent.getText();
                if (type.equalsIgnoreCase(STRING_CONNECTIONS)) {
                    DatabaseMeta dbinfo = jobMeta.findDatabase(name);
                    if (dbinfo != null) {
                        DatabaseExplorerDialog std = new DatabaseExplorerDialog(shell, SWT.NONE, dbinfo,
                                jobMeta.databases, true);
                        std.open();
                    } else {
                        MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
                        mb.setMessage(Messages.getString("Chef.Dialog.UnableToFindConnection.Message")); //$NON-NLS-1$
                        mb.setText(Messages.getString("Chef.Dialog.UnableToFindConnection.Title")); //$NON-NLS-1$
                        mb.open();
                    }
                }
            } else {
                if (name.equalsIgnoreCase(STRING_CONNECTIONS))
                    jobMeta.dbcache.clear(null);
            }
        }
    }

    public void openFile(boolean importfile) {
        if (showChangedWarning()) {
            if (rep == null || importfile) // Load from XML
            {
                FileDialog dialog = new FileDialog(shell, SWT.OPEN);
                // dialog.setFilterPath("C:\\Projects\\kettle\\source\\");
                dialog.setFilterExtensions(Const.STRING_JOB_FILTER_EXT);
                dialog.setFilterNames(Const.getJobFilterNames());
                String fname = dialog.open();
                if (fname != null) {
                    try {
                        jobMeta = new JobMeta(log, fname, rep);
                        props.addLastFile(LastUsedFile.FILE_TYPE_JOB, fname, null, false, null);
                    } catch (KettleXMLException xe) {
                        jobMeta.clear();
                        MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
                        mb.setMessage(Messages.getString("Chef.Dialog.ErrorOpeningJob.Message") + fname + Const.CR //$NON-NLS-1$
                                + xe.getMessage());
                        mb.setText(Messages.getString("Chef.Dialog.ErrorOpeningJob.Title")); //$NON-NLS-1$
                        mb.open();
                    }

                    saveSettings();
                    addMenuLast();

                    refreshGraph();
                    refreshTree(true);
                    setUndoMenu();
                }
            } else // Read a job from the repository!
            {
                // Refresh the directory tree for now...
                SelectObjectDialog sod = new SelectObjectDialog(shell, rep);
                String fname = sod.open();
                RepositoryDirectory repdir = sod.getDirectory();
                if (fname != null && repdir != null) {
                    JobLoadProgressDialog jlpd = new JobLoadProgressDialog(shell, rep, fname, repdir);
                    JobMeta jobInfo = jlpd.open();
                    if (jobInfo != null) {
                        jobMeta = jobInfo;
                        props.addLastFile(LastUsedFile.FILE_TYPE_JOB, fname, repdir.getPath(), true, rep.getName());
                        saveSettings();
                        addMenuLast();
                    }
                    refreshGraph();
                    refreshTree(true);
                    setUndoMenu();
                }
            }
        }
    }

    public void newFile() {
        // AYS: Y/N??
        MessageBox mb = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_WARNING);
        mb.setMessage(Messages.getString("Chef.Dialog.ConfirmNewFile.Message")); //$NON-NLS-1$
        mb.setText(Messages.getString("Chef.Dialog.ConfirmNewFile.Title")); //$NON-NLS-1$
        int answer = mb.open();

        if (answer == SWT.YES) {
            jobMeta.clear();
            loadRepositoryObjects(); // Add databases if connected to repository
            setFilename(null);
            refreshTree();
            refreshGraph();
            setUndoMenu();
        }
    }

    public void loadDatabases() {
        // Load common database info from active repository...
        if (rep != null) {
            try {
                jobMeta.readDatabases(rep);
            } catch (KettleException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public boolean quitFile() {
        boolean exit = true;
        boolean showWarning = true;

        log.logDetailed(toString(), "Quit application."); //$NON-NLS-1$
        saveSettings();
        if (jobMeta.hasChanged()) {
            MessageBox mb = new MessageBox(shell, SWT.YES | SWT.NO | SWT.CANCEL | SWT.ICON_WARNING);
            mb.setMessage(Messages.getString("Chef.Dialog.FileChangedSaveFirst.Message")); //$NON-NLS-1$
            mb.setText(Messages.getString("Chef.Dialog.FileChangedSaveFirst.Title")); //$NON-NLS-1$
            int answer = mb.open();

            switch (answer) {
            case SWT.YES:
                saveFile();
                exit = true;
                showWarning = false;
                break;
            case SWT.NO:
                exit = true;
                showWarning = false;
                break;
            case SWT.CANCEL:
                exit = false;
                showWarning = false;
                break;
            }
        }

        // System.out.println("exit="+exit+", showWarning="+showWarning+", running="+spoonlog.isRunning()+", showExitWarning="+props.showExitWarning());

        // Show warning on exit when spoon is still running
        // Show warning on exit when a warning needs to be displayed, but only if we didn't ask to save before. (could have pressed cancel then!)
        // 
        if ((exit && cheflog.isRunning()) || (exit && showWarning && props.showExitWarning())) {
            String message = Messages.getString("Chef.Dialog.ExitApplicationAreYouSure.Message"); //$NON-NLS-1$
            if (cheflog.isRunning())
                message = Messages.getString("Chef.Dialog.ExitApplicationAreYouSure.MessageRunning"); //$NON-NLS-1$

            MessageDialogWithToggle md = new MessageDialogWithToggle(shell,
                    Messages.getString("Chef.Dialog.ExitApplicationAreYouSure.Title"), //$NON-NLS-1$
                    null, message, MessageDialog.WARNING,
                    new String[] { Messages.getString("System.Button.Yes"), //$NON-NLS-1$
                            Messages.getString("System.Button.No") }, //$NON-NLS-1$
                    1, Messages.getString("Chef.Dialog.ExitApplicationAreYouSure.Toggle"), //$NON-NLS-1$
                    !props.showExitWarning());
            int idx = md.open();
            props.setExitWarningShown(!md.getToggleState());
            props.saveProps();

            if ((idx & 0xFF) == 1)
                exit = false; // No selected: don't exit!
            else
                exit = true;
        }

        if (exit)
            dispose();

        return exit;
    }

    public void loadRepositoryObjects() {
        // Load common database info from active repository...
        if (rep != null) {
            try {
                jobMeta.readDatabases(rep);
            } catch (KettleException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public void saveFile() {
        log.logDetailed(toString(), "Save file..."); //$NON-NLS-1$
        if (rep != null) {
            saveRepository();
        } else {
            if (jobMeta.getFilename() != null) {
                save(jobMeta.getFilename());
            } else {
                saveFileAs();
            }
        }
    }

    private boolean saveXMLFile() {
        boolean saved = false;

        FileDialog dialog = new FileDialog(shell, SWT.SAVE);
        dialog.setFilterExtensions(Const.STRING_JOB_FILTER_EXT);
        dialog.setFilterNames(Const.getJobFilterNames());
        String fname = dialog.open();
        if (fname != null) {
            // Is the filename ending on .ktr, .xml?
            boolean ending = false;
            for (int i = 0; i < Const.STRING_JOB_FILTER_EXT.length - 1; i++) {
                if (fname.endsWith(Const.STRING_JOB_FILTER_EXT[i].substring(1))) {
                    ending = true;
                }
            }
            if (fname.endsWith(Const.STRING_JOB_DEFAULT_EXT))
                ending = true;
            if (!ending) {
                fname += Const.STRING_JOB_DEFAULT_EXT;
            }
            // See if the file already exists...
            File f = new File(fname);
            int id = SWT.YES;
            if (f.exists()) {
                MessageBox mb = new MessageBox(shell, SWT.NO | SWT.YES | SWT.ICON_WARNING);
                mb.setMessage(Messages.getString("Chef.Dialog.PromptOverwriteFile.Message"));//"This file already exists.  Do you want to overwrite it?"
                mb.setText(Messages.getString("Chef.Dialog.PromptOverwriteFile.Title"));//"This file already exists!"
                id = mb.open();
            }
            if (id == SWT.YES) {
                saved = save(fname);
                setFilename(fname);
            }
        }

        return saved;
    }

    public void saveRepository() {
        saveRepository(false);
    }

    public boolean saveRepository(boolean ask_name) {
        log.logDetailed(toString(), "Save to repository..."); //$NON-NLS-1$
        if (rep != null) {
            boolean answer = true;
            boolean ask = ask_name;
            while (answer && (ask || jobMeta.getName() == null || jobMeta.getName().length() == 0)) {
                if (!ask) {
                    MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_WARNING);
                    mb.setMessage(Messages.getString("Chef.Dialog.GiveJobANameBeforeSaving.Message")); //$NON-NLS-1$
                    mb.setText(Messages.getString("Chef.Dialog.GiveJobANameBeforeSaving.Title")); //$NON-NLS-1$
                    mb.open();
                }
                ask = false;
                answer = setJob();
            }

            if (answer && jobMeta.getName() != null && jobMeta.getName().length() > 0) {
                if (!rep.getUserInfo().isReadonly()) {
                    boolean saved = false;
                    int response = SWT.YES;
                    if (jobMeta.showReplaceWarning(rep)) {
                        MessageBox mb = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_QUESTION);
                        mb.setMessage(Messages.getString("Chef.Dialog.JobExistsOverwrite.Message1") //$NON-NLS-1$
                                + jobMeta.getName() + Messages.getString("Chef.Dialog.JobExistsOverwrite.Message2") //$NON-NLS-1$
                                + Const.CR + Messages.getString("Chef.Dialog.JobExistsOverwrite.Message3")); //$NON-NLS-1$
                        mb.setText(Messages.getString("Chef.Dialog.JobExistsOverwrite.Title")); //$NON-NLS-1$
                        response = mb.open();
                    }

                    if (response == SWT.YES) {
                        // Keep info on who & when this transformation was changed...
                        jobMeta.modifiedDate = new Value("MODIFIED_DATE", Value.VALUE_TYPE_DATE); //$NON-NLS-1$
                        jobMeta.modifiedDate.sysdate();
                        jobMeta.modifiedUser = rep.getUserInfo().getLogin();

                        JobSaveProgressDialog jspd = new JobSaveProgressDialog(shell, rep, jobMeta);
                        if (jspd.open()) {
                            if (!props.getSaveConfirmation()) {
                                MessageDialogWithToggle md = new MessageDialogWithToggle(shell,
                                        Messages.getString("Chef.Dialog.JobWasStoredInTheRepository.Title"), //$NON-NLS-1$
                                        null, Messages.getString("Chef.Dialog.JobWasStoredInTheRepository.Message"), //$NON-NLS-1$
                                        MessageDialog.QUESTION,
                                        new String[] { Messages.getString("System.Button.OK") }, //$NON-NLS-1$
                                        0, Messages.getString("Chef.Dialog.JobWasStoredInTheRepository.Toggle"), //$NON-NLS-1$
                                        props.getSaveConfirmation());
                                md.open();
                                props.setSaveConfirmation(md.getToggleState());
                            }

                            // Handle last opened files...
                            props.addLastFile(LastUsedFile.FILE_TYPE_JOB, jobMeta.getName(),
                                    jobMeta.getDirectory().getPath(), true, rep.getName());
                            saveSettings();
                            addMenuLast();

                            setShellText();
                        }
                    }
                    return saved;
                } else {
                    MessageBox mb = new MessageBox(shell, SWT.CLOSE | SWT.ICON_ERROR);
                    mb.setMessage(
                            Messages.getString("Chef.Dialog.UserCanOnlyReadFromTheRepositoryJobNotSaved.Message")); //$NON-NLS-1$
                    mb.setText(Messages.getString("Chef.Dialog.UserCanOnlyReadFromTheRepositoryJobNotSaved.Title")); //$NON-NLS-1$
                    mb.open();
                }
            }
        } else {
            MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
            mb.setMessage(Messages.getString("Chef.Dialog.NoRepositoryConnectionAvailable.Message")); //$NON-NLS-1$
            mb.setText(Messages.getString("Chef.Dialog.NoRepositoryConnectionAvailable.Title")); //$NON-NLS-1$
            mb.open();
        }
        return false;
    }

    public boolean saveFileAs() {
        boolean saved = false;

        if (rep != null) {
            jobMeta.setID(-1L);
            saved = saveRepository(true);
        } else {
            saved = saveXMLFile();
        }

        return saved;
    }

    public void saveFileAsXML() {
        log.logBasic(toString(), "Save file as..."); //$NON-NLS-1$

        FileDialog dialog = new FileDialog(shell, SWT.SAVE);
        //dialog.setFilterPath("C:\\Projects\\kettle\\source\\");
        dialog.setFilterExtensions(STRING_FILTER_EXT);
        dialog.setFilterNames(STRING_FILTER_NAMES);
        String fname = dialog.open();
        if (fname != null) {
            // Is the filename ending on .ktr, .xml?
            boolean ending = false;
            for (int i = 0; i < STRING_FILTER_EXT.length - 1; i++) {
                if (fname.endsWith(STRING_FILTER_EXT[i].substring(1)))
                    ending = true;
            }
            if (fname.endsWith(STRING_DEFAULT_EXT))
                ending = true;
            if (!ending) {
                fname += STRING_DEFAULT_EXT;
            }
            // See if the file already exists...
            File f = new File(fname);
            int id = SWT.YES;
            if (f.exists()) {
                MessageBox mb = new MessageBox(shell, SWT.NO | SWT.YES | SWT.ICON_WARNING);
                mb.setMessage(Messages.getString("Chef.Dialog.FileExistsOverWrite.Message")); //$NON-NLS-1$
                mb.setText(Messages.getString("Chef.Dialog.FileExistsOverWrite.Title")); //$NON-NLS-1$
                id = mb.open();
            }
            if (id == SWT.YES) {
                save(fname);
            }
        }
    }

    private boolean save(String fname) {
        boolean saved = false;
        String xml = XMLHandler.getXMLHeader() + jobMeta.getXML();
        try {
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(fname)));
            dos.write(xml.getBytes(Const.XML_ENCODING));
            dos.close();

            saved = true;

            // Handle last opened files...
            props.addLastFile(LastUsedFile.FILE_TYPE_JOB, fname, RepositoryDirectory.DIRECTORY_SEPARATOR, false,
                    ""); //$NON-NLS-1$
            saveSettings();
            addMenuLast();

            log.logDebug(toString(), "File written to [" + fname + "]"); //$NON-NLS-1$ //$NON-NLS-2$
            jobMeta.setFilename(fname);
            jobMeta.clearChanged();
            setShellText();
        } catch (Exception e) {
            log.logDebug(toString(), "Error opening file for writing! --> " + e.toString()); //$NON-NLS-1$
            MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_ERROR);
            mb.setMessage(Messages.getString("Chef.Dialog.ErrorSavingFile.Message") + Const.CR + e.toString()); //$NON-NLS-1$
            mb.setText(Messages.getString("Chef.Dialog.ErrorSavingFile.Title")); //$NON-NLS-1$
            mb.open();
        }
        return saved;
    }

    public void helpAbout() {
        MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION | SWT.CENTER);
        String mess = Messages.getString("Chef.Dialog.About.KettleChefVersion") + Const.VERSION + Const.CR //$NON-NLS-1$
                + Const.CR + Const.CR;
        mess += Messages.getString("Chef.Dialog.About.Company") + Const.CR //$NON-NLS-1$
                + Messages.getString("Chef.Dialog.About.WebSite") + Const.CR; //$NON-NLS-1$
        mess += Const.CR;
        mess += Const.CR;
        mess += Const.CR;
        mess += "         Build version : " + BuildVersion.getInstance().getVersion() + Const.CR;
        mess += "         Build date    : " + BuildVersion.getInstance().getBuildDate() + Const.CR;

        mb.setMessage(mess);
        mb.setText(Messages.getString("Chef.Application.Name")); //$NON-NLS-1$
        mb.open();
    }

    public void editUnselectAll() {
        jobMeta.unselectAll();
        chefgraph.redraw();
    }

    public void editSelectAll() {
        jobMeta.selectAll();
        chefgraph.redraw();
    }

    public void editOptions() {
        EnterOptionsDialog eod = new EnterOptionsDialog(shell);
        if (eod.open() != null) {
            props.saveProps();
            loadSettings();
            changeLooks();

            MessageBox mb = new MessageBox(shell, SWT.ICON_INFORMATION);
            mb.setMessage(Messages.getString("Chef.Dialog.PleaseRestartApplication.Message"));
            mb.setText(Messages.getString("Chef.Dialog.PleaseRestartApplication.Title"));
            mb.open();

        }
    }

    /**
     * @deprecated
     * @param section
     * @param item
     */
    public void addToTree(String section, String item) {
        addToTree(section, item, 0);
    }

    /**
     * @deprecated
     * @param section
     * @param item
     * @param steptype
     */
    public void addToTree(String section, String item, int steptype) {

        TreeItem ti = null;
        for (int i = 1; i < tiSection.length; i++) {
            if (tiSection[i].getText().equalsIgnoreCase(section))
                ti = tiSection[i];
        }

        if (ti != null && item != null) {
            TreeItem ni = new TreeItem(ti, SWT.NONE);
            ni.setText(item);
            setTreeImage(ni, section, steptype);
        }
        setShellText();
    }

    public void setTreeImage(TreeItem ni, String section) {
        setTreeImage(ni, section, 0);
    }

    public void setTreeImage(TreeItem ni, String section, int steptype) {
    }

    public void refreshTree() {
        refreshTree(false);
    }

    public void refreshTree(boolean complete) {
        log.logDetailed(toString(), "refreshTree() called"); //$NON-NLS-1$

        // Get some TreeItems...
        //
        TreeItem tiJobEntries = null;
        TreeItem tiConnections = null;

        TreeItem ti[] = tMain.getItems();
        for (int i = 0; i < ti.length; i++) {
            if (ti[i].getText().equalsIgnoreCase(STRING_JOBENTRIES))
                tiJobEntries = ti[i];
            if (ti[i].getText().equalsIgnoreCase(STRING_CONNECTIONS))
                tiConnections = ti[i];
        }

        if (tiConnections != null) {
            TreeItem entries[] = tiConnections.getItems();
            for (int i = 0; i < entries.length; i++)
                entries[i].dispose();
        }

        if (tiJobEntries != null) {
            TreeItem entries[] = tiJobEntries.getItems();
            for (int i = 0; i < entries.length; i++)
                entries[i].dispose();
        }

        for (int i = 0; i < jobMeta.nrDatabases(); i++) {
            DatabaseMeta dbinfo = jobMeta.getDatabase(i);
            TreeItem item = new TreeItem(tiConnections, SWT.NONE);
            item.setText(dbinfo.getName());
            item.setImage(GUIResource.getInstance().getImageConnection());
        }

        for (int i = 0; i < jobMeta.nrJobEntries(); i++) {
            JobEntryCopy je = jobMeta.getJobEntry(i);
            TreeItem item = new TreeItem(tiJobEntries, SWT.NONE);
            if (je.isStart()) {
                item.setImage(GUIResource.getInstance().getImageStart());
            } else if (je.isDummy()) {
                item.setImage(GUIResource.getInstance().getImageDummy());
            } else {
                Image image = (Image) GUIResource.getInstance().getImagesJobentriesSmall().get(je.getTypeDesc());
                item.setImage(image);
            }
            if (je.getName() != null)
                item.setText(je.getName());
        }

        TreeMemory.setExpandedFromMemory(tMain, STRING_CHEF_MAIN_TREE_NAME);

        tMain.setFocus();
        setShellText();
    }

    public void refreshGraph() {
        chefgraph.redraw();
        setShellText();
    }

    public void newConnection() {
        DatabaseMeta db = new DatabaseMeta();
        DatabaseDialog con = new DatabaseDialog(shell, db);
        String con_name = con.open();
        if (con_name != null) {
            jobMeta.addDatabase(db);
            addUndoNew(new DatabaseMeta[] { (DatabaseMeta) db.clone() }, new int[] { jobMeta.indexOfDatabase(db) });
            saveConnection(db);
            refreshTree();
        }
    }

    public void saveConnection(DatabaseMeta db) {
        // Also add to repository?
        if (rep != null) {
            if (!rep.getUserInfo().isReadonly()) {
                try {
                    rep.lockRepository();
                    rep.insertLogEntry("saving database connection '" + db.getName() + "'");
                    db.saveRep(rep);
                    log.logDetailed(toString(), "Saved database connection [" + db + "] to the repository."); //$NON-NLS-1$ //$NON-NLS-2$

                    // Put a commit behind it!
                    rep.commit();
                } catch (KettleException ke) {
                    rep.rollback();
                    new ErrorDialog(shell, Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Title"), //$NON-NLS-1$
                            Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Message1") + db //$NON-NLS-1$
                                    + Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Message2"), //$NON-NLS-1$
                            ke);
                } finally {
                    try {
                        rep.unlockRepository();
                    } catch (KettleException e) {
                        new ErrorDialog(shell, Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Title"), //$NON-NLS-1$
                                Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Message1") + db //$NON-NLS-1$
                                        + Messages.getString("Chef.ErrorDialog.ErrorSavingConnection.Message2"), //$NON-NLS-1$
                                e);
                    }
                }
            } else {
                new ErrorDialog(shell,
                        Messages.getString("Chef.ErrorDialog.ReadOnlyUserErrorSavingConnection.Title"), //$NON-NLS-1$
                        Messages.getString("Chef.ErrorDialog.ReadOnlyUserErrorSavingConnection.Message"), //$NON-NLS-1$
                        new KettleException(Messages
                                .getString("Chef.ErrorDialog.ReadOnlyUserErrorSavingConnection.Exception"))); //$NON-NLS-1$
            }
        }
    }

    public JobEntryCopy newChefGraphEntry(String type_desc, boolean openit) {
        JobEntryLoader jobLoader = JobEntryLoader.getInstance();
        JobPlugin jobPlugin = null;

        try {
            jobPlugin = jobLoader.findJobEntriesWithDescription(type_desc);

            if (jobPlugin != null) {
                // System.out.println("new job entry of type: "+type+" ["+type_desc+"]");

                // Determine name & number for this entry.
                String basename = type_desc;
                int nr = jobMeta.generateJobEntryNameNr(basename);
                String entry_name = basename + " " + nr; //$NON-NLS-1$

                // Generate the appropriate class...
                JobEntryInterface jei = jobLoader.getJobEntryClass(jobPlugin);
                jei.setName(entry_name);

                if (openit) {
                    JobEntryDialogInterface d = jei.getDialog(shell, jei, jobMeta, entry_name, rep);
                    if (d.open() != null) {
                        JobEntryCopy jge = new JobEntryCopy();
                        jge.setEntry(jei);
                        jge.setLocation(50, 50);
                        jge.setNr(0);
                        jobMeta.addJobEntry(jge);
                        addUndoNew(new JobEntryCopy[] { jge }, new int[] { jobMeta.indexOfJobEntry(jge) });
                        refreshGraph();
                        refreshTree();
                        return jge;
                    } else {
                        return null;
                    }
                } else {
                    JobEntryCopy jge = new JobEntryCopy();
                    jge.setEntry(jei);
                    jge.setLocation(50, 50);
                    jge.setNr(0);
                    jobMeta.addJobEntry(jge);
                    addUndoNew(new JobEntryCopy[] { jge }, new int[] { jobMeta.indexOfJobEntry(jge) });
                    refreshGraph();
                    refreshTree();
                    return jge;
                }
            } else {
                return null;
            }
        } catch (Throwable e) {
            new ErrorDialog(shell,
                    Messages.getString("Chef.ErrorDialog.UnexpectedErrorCreatingNewChefGraphEntry.Title"), //$NON-NLS-1$
                    Messages.getString("Chef.ErrorDialog.UnexpectedErrorCreatingNewChefGraphEntry.Message"), //$NON-NLS-1$
                    new Exception(e));
            return null;
        }
    }

    public void editChefGraphEntry(JobEntryCopy je) {
        try {
            log.logBasic(toString(), "edit job graph entry: " + je.getName()); //$NON-NLS-1$

            JobEntryCopy before = (JobEntryCopy) je.clone_deep();
            boolean entry_changed = false;

            JobEntryInterface jei = je.getEntry();

            JobEntryDialogInterface d = jei.getDialog(shell, jei, jobMeta, je.getName(), rep);
            if (d != null) {
                if (d.open() != null) {
                    entry_changed = true;
                }

                if (entry_changed) {
                    addUndoChange(new JobEntryCopy[] { before }, new JobEntryCopy[] { je },
                            new int[] { jobMeta.indexOfJobEntry(je) });
                    refreshGraph();
                    refreshTree();
                }
            } else {
                MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION);
                mb.setMessage(Messages.getString("Chef.Dialog.JobEntryCanNotBeChanged.Message")); //$NON-NLS-1$
                mb.setText(Messages.getString("Chef.Dialog.JobEntryCanNotBeChanged.Title")); //$NON-NLS-1$
                mb.open();
            }

        } catch (Exception e) {
            if (!shell.isDisposed())
                new ErrorDialog(shell, Messages.getString("Chef.ErrorDialog.ErrorEditingJobEntry.Title"), //$NON-NLS-1$
                        Messages.getString("Chef.ErrorDialog.ErrorEditingJobEntry.Message"), e); //$NON-NLS-1$
        }
    }

    public JobEntryTrans newJobEntry(int type) {
        JobEntryTrans je = new JobEntryTrans();
        je.setType(type);
        String basename = JobEntryInterface.typeDesc[type];
        int nr = jobMeta.generateJobEntryNameNr(basename);
        je.setName(basename + " " + nr); //$NON-NLS-1$

        setShellText();

        return je;
    }

    public void deleteChefGraphEntry(String name) {
        // First delete all the hops using entry with name:
        JobHopMeta hi[] = jobMeta.getAllJobHopsUsing(name);
        if (hi.length > 0) {
            int hix[] = new int[hi.length];
            for (int i = 0; i < hi.length; i++)
                hix[i] = jobMeta.indexOfJobHop(hi[i]);

            addUndoDelete(hi, hix);
            for (int i = hix.length - 1; i >= 0; i--)
                jobMeta.removeJobHop(hix[i]);
        }

        // Then delete all the entries with name:
        JobEntryCopy je[] = jobMeta.getAllChefGraphEntries(name);
        int jex[] = new int[je.length];
        for (int i = 0; i < je.length; i++)
            jex[i] = jobMeta.indexOfJobEntry(je[i]);

        addUndoDelete(je, jex);
        for (int i = jex.length - 1; i >= 0; i--)
            jobMeta.removeJobEntry(jex[i]);

        refreshGraph();
        refreshTree();
    }

    public void dupeChefGraphEntry(String name) {
        JobEntryCopy jge = jobMeta.findJobEntry(name, 0, true);
        if (jge != null) {
            JobEntryCopy dupejge = (JobEntryCopy) jge.clone();
            dupejge.setNr(jobMeta.findUnusedNr(dupejge.getName()));
            if (dupejge.isDrawn()) {
                Point p = jge.getLocation();
                dupejge.setLocation(p.x + 10, p.y + 10);
            }
            jobMeta.addJobEntry(dupejge);
            refreshGraph();
            refreshTree();
        }
        setShellText();
    }

    public void copyJobEntries(JobEntryCopy jec[]) {
        if (jec == null || jec.length == 0)
            return;

        String xml = XMLHandler.getXMLHeader();
        xml += "<jobentries>" + Const.CR; //$NON-NLS-1$

        for (int i = 0; i < jec.length; i++) {
            xml += jec[i].getXML();
        }

        xml += "    </jobentries>" + Const.CR; //$NON-NLS-1$

        toClipboard(xml);
    }

    public void pasteXML(String clipcontent, Point loc) {
        try {
            Document doc = XMLHandler.loadXMLString(clipcontent);

            // De-select all, re-select pasted steps...
            jobMeta.unselectAll();

            Node entriesnode = XMLHandler.getSubNode(doc, "jobentries"); //$NON-NLS-1$
            int nr = XMLHandler.countNodes(entriesnode, "entry"); //$NON-NLS-1$
            log.logDebug(toString(), "I found " + nr + " job entries to paste on location: " + loc); //$NON-NLS-1$ //$NON-NLS-2$
            JobEntryCopy entries[] = new JobEntryCopy[nr];

            //Point min = new Point(loc.x, loc.y);
            Point min = new Point(99999999, 99999999);

            for (int i = 0; i < nr; i++) {
                Node entrynode = XMLHandler.getSubNodeByNr(entriesnode, "entry", i); //$NON-NLS-1$
                entries[i] = new JobEntryCopy(entrynode, jobMeta.getDatabases(), rep);

                String name = jobMeta.getAlternativeJobentryName(entries[i].getName());
                entries[i].setName(name);

                if (loc != null) {
                    Point p = entries[i].getLocation();

                    if (min.x > p.x)
                        min.x = p.x;
                    if (min.y > p.y)
                        min.y = p.y;
                }
            }

            // What's the difference between loc and min?
            // This is the offset:
            Point offset = new Point(loc.x - min.x, loc.y - min.y);

            // Undo/redo object positions...
            int position[] = new int[entries.length];

            for (int i = 0; i < entries.length; i++) {
                Point p = entries[i].getLocation();
                String name = entries[i].getName();

                entries[i].setLocation(p.x + offset.x, p.y + offset.y);

                // Check the name, find alternative...
                entries[i].setName(jobMeta.getAlternativeJobentryName(name));
                jobMeta.addJobEntry(entries[i]);
                position[i] = jobMeta.indexOfJobEntry(entries[i]);
            }

            // Save undo information too...
            addUndoNew(entries, position);

            if (jobMeta.hasChanged()) {
                refreshTree();
                refreshGraph();
            }
        } catch (KettleException e) {
            new ErrorDialog(shell, Messages.getString("Chef.ErrorDialog.ErrorPasingJobEntries.Title"), //$NON-NLS-1$
                    Messages.getString("Chef.ErrorDialog.ErrorPasingJobEntries.Message"), e); //$NON-NLS-1$
        }
    }

    public void toClipboard(String cliptext) {
        GUIResource.getInstance().toClipboard(cliptext);
    }

    public String fromClipboard() {
        return GUIResource.getInstance().fromClipboard();
    }

    public void setShellText() {
        String fname = jobMeta.getFilename();
        if (shell.isDisposed())
            return;

        String text = "";

        if (rep != null) {
            text += APPL_TITLE + " - [" + getRepositoryName() + "] ";
        } else {
            text += APPL_TITLE + " - ";
        }

        if (rep != null && jobMeta.getID() > 0) {
            String jobname = jobMeta.getName();
            if (jobname == null) {
                jobname = Messages.getString("Chef.ShellText.NoJobName");
            } else {
                text += "  " + jobname;
            }
        } else {
            if (fname != null) {
                text += fname;
            }
        }

        if (jobMeta.hasChanged()) {
            text += " " + Messages.getString("Chef.ShellText.Changed");
        }

        shell.setText(text);
    }

    private void setFilename(String fname) {
        jobMeta.setFilename(fname);
        setShellText();
    }

    private void printFile() {
        PrintSpool ps = new PrintSpool();
        Printer printer = ps.getPrinter(shell);

        // Create an image of the screen
        Point max = jobMeta.getMaximum();

        //Image img_screen = new Image(trans, max.x, max.y);
        //img_screen.dispose();

        PaletteData pal = ps.getPaletteData();

        ImageData imd = new ImageData(max.x, max.y, printer.getDepth(), pal);
        Image img = new Image(printer, imd);

        GC img_gc = new GC(img);

        // Clear the background first, fill with background color...
        img_gc.setForeground(GUIResource.getInstance().getColorBackground());
        img_gc.fillRectangle(0, 0, max.x, max.y);

        // Draw the transformation...
        chefgraph.drawJob(img_gc);

        //ShowImageDialog sid = new ShowImageDialog(shell, jobMeta.props, img);
        //sid.open();

        ps.printImage(shell, img);

        img_gc.dispose();
        img.dispose();
        ps.dispose();
    }

    private boolean setJob() {
        JobDialog jd = new JobDialog(shell, SWT.NONE, jobMeta, rep);
        JobMeta ji = jd.open();
        setShellText();
        return ji != null;
    }

    public boolean ripDB(String jobName, RepositoryDirectory repositoryDirectory, final DatabaseMeta srcDbInfo,
            final DatabaseMeta tgtDbInfo, String[] tablesToRip) {
        final String[] tables = tablesToRip;
        final DatabaseMeta sourceDbInfo = srcDbInfo;
        final DatabaseMeta targetDbInfo = tgtDbInfo;
        final RepositoryDirectory repdir = repositoryDirectory;
        final String jobname = jobName;
        //
        // Create a new job...
        //
        jobMeta = new JobMeta(log);
        try {
            jobMeta.readDatabases(rep);
        } catch (KettleException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        setFilename(null);
        jobMeta.setName(jobname);
        jobMeta.setDirectory(repdir);
        refreshTree();
        refreshGraph();

        final Point location = new Point(50, 50);

        // The start entry...
        final JobEntryCopy start = jobMeta.findStart();
        start.setLocation(new Point(location.x, location.y));
        start.setDrawn();

        // final Thread parentThread = Thread.currentThread();

        // Create a dialog with a progress indicator!
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                // This is running in a new process: copy some KettleVariables info
                // LocalVariables.getInstance().createKettleVariables(Thread.currentThread().getName(), parentThread.getName(), true);

                monitor.beginTask(Messages.getString("Spoon.RipDB.Monitor.BuildingNewJob"), tables.length); //$NON-NLS-1$
                monitor.worked(0);
                JobEntryCopy previous = start;

                // Loop over the table-names...
                for (int i = 0; i < tables.length && !monitor.isCanceled(); i++) {
                    monitor.setTaskName(
                            Messages.getString("Spoon.RipDB.Monitor.ProcessingTable") + tables[i] + "]..."); //$NON-NLS-1$ //$NON-NLS-2$
                    //
                    // Create the new transformation...
                    //
                    String transname = Messages.getString("Spoon.RipDB.Monitor.Transname1") + sourceDbInfo + "].[" //$NON-NLS-1$//$NON-NLS-2$
                            + tables[i] + Messages.getString("Spoon.RipDB.Monitor.Transname2") + targetDbInfo + "]"; //$NON-NLS-1$ //$NON-NLS-2$

                    TransMeta ti = new TransMeta((String) null, transname, null);

                    ti.setDirectory(repdir);

                    //
                    // Add a note
                    //
                    String note = Messages.getString("Spoon.RipDB.Monitor.Note1") + tables[i] //$NON-NLS-1$
                            + Messages.getString("Spoon.RipDB.Monitor.Note2") + sourceDbInfo + "]" + Const.CR; //$NON-NLS-1$ //$NON-NLS-2$
                    note += Messages.getString("Spoon.RipDB.Monitor.Note3") + tables[i] //$NON-NLS-1$
                            + Messages.getString("Spoon.RipDB.Monitor.Note4") + targetDbInfo + "]"; //$NON-NLS-1$ //$NON-NLS-2$
                    NotePadMeta ni = new NotePadMeta(note, 150, 10, -1, -1);
                    ti.addNote(ni);

                    //
                    // Add the TableInputMeta step...
                    // 
                    String fromstepname = Messages.getString("Spoon.RipDB.Monitor.FromStep.Name") + tables[i] + "]"; //$NON-NLS-1$ //$NON-NLS-2$
                    TableInputMeta tii = new TableInputMeta();
                    tii.setDatabaseMeta(sourceDbInfo);
                    tii.setSQL("SELECT * FROM " + srcDbInfo.quoteField(tables[i])); //$NON-NLS-1$

                    String fromstepid = StepLoader.getInstance().getStepPluginID(tii);
                    StepMeta fromstep = new StepMeta(fromstepid, fromstepname, tii);
                    fromstep.setLocation(150, 100);
                    fromstep.setDraw(true);
                    fromstep.setDescription(Messages.getString("Spoon.RipDB.Monitor.FromStep.Description") //$NON-NLS-1$
                            + tables[i] + Messages.getString("Spoon.RipDB.Monitor.FromStep.Description2") //$NON-NLS-1$
                            + sourceDbInfo + "]"); //$NON-NLS-1$
                    ti.addStep(fromstep);

                    //
                    // Add the TableOutputMeta step...
                    //
                    String tostepname = Messages.getString("Spoon.RipDB.Monitor.ToStep.Name") + tables[i] + "]"; //$NON-NLS-1$ //$NON-NLS-2$
                    TableOutputMeta toi = new TableOutputMeta();
                    toi.setDatabaseMeta(targetDbInfo);
                    toi.setTablename(tables[i]);
                    toi.setCommitSize(100);
                    toi.setTruncateTable(true);

                    String tostepid = StepLoader.getInstance().getStepPluginID(toi);
                    StepMeta tostep = new StepMeta(tostepid, tostepname, toi);
                    tostep.setLocation(500, 100);
                    tostep.setDraw(true);
                    tostep.setDescription(Messages.getString("Spoon.RipDB.Monitor.ToStep.Description1") + tables[i] //$NON-NLS-1$
                            + Messages.getString("Spoon.RipDB.Monitor.ToStep.Description2") + targetDbInfo + "]"); //$NON-NLS-1$ //$NON-NLS-2$
                    ti.addStep(tostep);

                    //
                    // Add a hop between the two steps...
                    //
                    TransHopMeta hi = new TransHopMeta(fromstep, tostep);
                    ti.addTransHop(hi);

                    //
                    // Now we generate the SQL needed to run for this transformation.
                    //
                    // First set the limit to 1 to speed things up!
                    String tmpSql = tii.getSQL();
                    tii.setSQL(tii.getSQL() + sourceDbInfo.getLimitClause(1));
                    String sql = ""; //$NON-NLS-1$
                    try {
                        sql = ti.getSQLStatementsString();
                    } catch (KettleStepException kse) {
                        throw new InvocationTargetException(kse,
                                Messages.getString("Spoon.RipDB.Exception.ErrorGettingSQLFromTransformation") + ti //$NON-NLS-1$
                                        + "] : " + kse.getMessage()); //$NON-NLS-1$
                    }
                    // remove the limit
                    tii.setSQL(tmpSql);

                    //
                    // Now, save the transformation...
                    //
                    try {
                        ti.saveRep(rep);
                    } catch (KettleException dbe) {
                        throw new InvocationTargetException(dbe,
                                Messages.getString("Spoon.RipDB.Exception.UnableToSaveTransformationToRepository")); //$NON-NLS-1$
                    }

                    // We can now continue with the population of the job...
                    ////////////////////////////////////////////////////////////////////////

                    location.x = 250;
                    if (i > 0)
                        location.y += 100;

                    //
                    // We can continue defining the job.
                    //
                    // First the SQL, but only if needed!
                    // If the table exists & has the correct format, nothing is done 
                    //
                    if (sql != null && sql.length() > 0) {
                        String jesqlname = Messages.getString("Spoon.RipDB.JobEntrySQL.Name") + tables[i] + "]"; //$NON-NLS-1$ //$NON-NLS-2$
                        JobEntrySQL jesql = new JobEntrySQL(jesqlname);
                        jesql.setDatabase(targetDbInfo);
                        jesql.setSQL(sql);
                        jesql.setDescription(Messages.getString("Spoon.RipDB.JobEntrySQL.Description") //$NON-NLS-1$
                                + targetDbInfo + "].[" + tables[i] + "]"); //$NON-NLS-1$ //$NON-NLS-2$

                        JobEntryCopy jecsql = new JobEntryCopy();
                        jecsql.setEntry(jesql);
                        jecsql.setLocation(new Point(location.x, location.y));
                        jecsql.setDrawn();
                        jobMeta.addJobEntry(jecsql);

                        // Add the hop too...
                        JobHopMeta jhi = new JobHopMeta(previous, jecsql);
                        jobMeta.addJobHop(jhi);
                        previous = jecsql;
                    }

                    //
                    // Add the jobentry for the transformation too...
                    //
                    String jetransname = Messages.getString("Spoon.RipDB.JobEntryTrans.Name") + tables[i] + "]"; //$NON-NLS-1$ //$NON-NLS-2$
                    JobEntryTrans jetrans = new JobEntryTrans(jetransname);
                    jetrans.setTransname(ti.getName());
                    jetrans.setDirectory(ti.getDirectory());

                    JobEntryCopy jectrans = new JobEntryCopy(log, jetrans);
                    jectrans.setDescription(Messages.getString("Spoon.RipDB.JobEntryTrans.Description1") + Const.CR //$NON-NLS-1$
                            + Messages.getString("Spoon.RipDB.JobEntryTrans.Description2") + sourceDbInfo + "].[" //$NON-NLS-1$//$NON-NLS-2$
                            + tables[i] + "]" + Const.CR //$NON-NLS-1$
                            + Messages.getString("Spoon.RipDB.JobEntryTrans.Description3") + targetDbInfo + "].[" //$NON-NLS-1$//$NON-NLS-2$
                            + tables[i] + "]"); //$NON-NLS-1$
                    jectrans.setDrawn();
                    location.x += 400;
                    jectrans.setLocation(new Point(location.x, location.y));
                    jobMeta.addJobEntry(jectrans);

                    // Add a hop between the last 2 job entries.
                    JobHopMeta jhi2 = new JobHopMeta(previous, jectrans);
                    jobMeta.addJobHop(jhi2);
                    previous = jectrans;

                    monitor.worked(1);
                }

                monitor.worked(100);
                monitor.done();
            }
        };

        try {
            ProgressMonitorDialog pmd = new ProgressMonitorDialog(shell);
            pmd.run(false, true, op);
        } catch (InvocationTargetException e) {
            new ErrorDialog(shell, Messages.getString("Spoon.ErrorDialog.RipDB.ErrorRippingTheDatabase.Title"), //$NON-NLS-1$
                    Messages.getString("Chef.ErrorDialog.RipDB.ErrorRippingTheDatabase.Message"), e); //$NON-NLS-1$
            return false;
        } catch (InterruptedException e) {
            new ErrorDialog(shell, Messages.getString("Spoon.ErrorDialog.RipDB.ErrorRippingTheDatabase.Title"), //$NON-NLS-1$
                    Messages.getString("Chef.ErrorDialog.RipDB.ErrorRippingTheDatabase.Message"), e); //$NON-NLS-1$
            return false;
        } finally {
            refreshGraph();
            refreshTree();
        }
        return true;
    }

    /**
     * Create a job that extracts tables & data from a database.<p><p>
     * 
     * 0) Select the database to rip<p>
     * 1) Select the tables in the database to rip<p>
     * 2) Select the database to dump to<p>
     * 3) Select the repository directory in which it will end up<p>
     * 4) Select a name for the new job<p>
     * 5) Create an empty job with the selected name.<p>
     * 6) Create 1 transformation for every selected table<p>
     * 7) add every created transformation to the job & evaluate<p>
     * 
     */
    private void ripDBWizard() {
        final RipDatabaseWizardPage1 page1 = new RipDatabaseWizardPage1("1", jobMeta.databases); //$NON-NLS-1$
        page1.createControl(shell);
        final RipDatabaseWizardPage2 page2 = new RipDatabaseWizardPage2("2"); //$NON-NLS-1$
        page2.createControl(shell);
        final RipDatabaseWizardPage3 page3 = new RipDatabaseWizardPage3("3", rep); //$NON-NLS-1$
        page3.createControl(shell);

        Wizard wizard = new Wizard() {
            public boolean performFinish() {
                return ripDB(page3.getJobname(), page3.getRepositoryDirectory(), page1.getSourceDatabase(),
                        page1.getTargetDatabase(), page2.getSelection());
            }

            /**
             * @see org.eclipse.jface.wizard.Wizard#canFinish()
             */
            public boolean canFinish() {
                return page3.canFinish();
            }
        };

        wizard.addPage(page1);
        wizard.addPage(page2);
        wizard.addPage(page3);

        WizardDialog wd = new WizardDialog(shell, wizard);
        wd.setMinimumPageSize(700, 400);
        wd.open();
    }

    /**
     * Shows a wizard that creates a new database connection...
     *
     */
    private void createDatabaseWizard() {
        CreateDatabaseWizard cdw = new CreateDatabaseWizard();
        DatabaseMeta newDBInfo = cdw.createAndRunDatabaseWizard(shell, props, jobMeta.getDatabases());
        if (newDBInfo != null) { //finished
            jobMeta.addDatabase(newDBInfo);
            refreshTree(true);
            refreshGraph();
        }
    }

    public void saveSettings() {
        WindowProperty winprop = new WindowProperty(shell);
        winprop.setName(APPL_TITLE);

        props.setScreen(winprop);
        props.setScreen(new WindowProperty(shell));
        props.setLogLevel(log.getLogLevelDesc());
        props.setSashWeights(sashform.getWeights());
        props.saveProps();
    }

    public void loadSettings() {
        log.setLogLevel(props.getLogLevel());
        log.setFilter(props.getLogFilter());

        jobMeta.setMaxUndo(props.getMaxUndo());
    }

    public void changeLooks() {
        props.setLook(tMain);

        chefgraph.newProps();

        refreshTree();
        refreshGraph();
    }

    /**
     * Search the transformation meta-data.
     *
     */
    public void searchMetaData() {
        EnterSearchDialog esd = new EnterSearchDialog(shell);
        if (esd.open()) {
            String filterString = esd.getFilterString();
            String filter = filterString;
            if (filter != null)
                filter = filter.toUpperCase();

            List stringList = jobMeta.getStringList(esd.isSearchingSteps(), esd.isSearchingDatabases(),
                    esd.isSearchingNotes());
            ArrayList rows = new ArrayList();
            for (int i = 0; i < stringList.size(); i++) {
                StringSearchResult result = (StringSearchResult) stringList.get(i);

                boolean add = Const.isEmpty(filter);
                if (filter != null && result.getString().toUpperCase().indexOf(filter) >= 0)
                    add = true;
                if (filter != null && result.getFieldName().toUpperCase().indexOf(filter) >= 0)
                    add = true;
                if (filter != null && result.getParentObject().toString().toUpperCase().indexOf(filter) >= 0)
                    add = true;

                if (add)
                    rows.add(result.toRow());
            }

            if (rows.size() != 0) {
                PreviewRowsDialog prd = new PreviewRowsDialog(shell, SWT.NONE, "String searcher", rows);
                prd.open();
            } else {
                MessageBox mb = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION);
                mb.setMessage(Messages.getString("Spoon.Dialog.NothingFound.Message")); // Nothing found that matches your criteria
                mb.setText(Messages.getString("Spoon.Dialog.NothingFound.Title")); // Sorry!
                mb.open();
            }
        }
    }

    public void getVariables() {
        Properties sp = new Properties();
        KettleVariables kettleVariables = KettleVariables.getInstance();
        sp.putAll(kettleVariables.getProperties());
        sp.putAll(System.getProperties());

        List list = jobMeta.getUsedVariables();
        for (int i = 0; i < list.size(); i++) {
            String varName = (String) list.get(i);
            String varValue = sp.getProperty(varName, "");
            System.out.println("variable [" + varName + "] is defined as : " + varValue);
            if (variables.searchValueIndex(varName) < 0 && !varName.startsWith(Const.INTERNAL_VARIABLE_PREFIX)) {
                variables.addValue(new Value(varName, varValue));
            }
        }

        // Now ask the use for more info on these!
        EnterStringsDialog esd = new EnterStringsDialog(shell, SWT.NONE, variables);
        esd.setTitle(Messages.getString("Chef.Dialog.SetVariables.Title"));
        esd.setMessage(Messages.getString("Chef.Dialog.SetVariables.Message"));
        esd.setReadOnly(false);
        if (esd.open() != null) {
            for (int i = 0; i < variables.size(); i++) {
                Value varval = variables.getValue(i);
                if (!Const.isEmpty(varval.getString())) {
                    kettleVariables.setVariable(varval.getName(), varval.getString());
                    System.out.println("Variable ${" + varval.getName() + "} set to [" + varval.getString()
                            + "] for thread [" + Thread.currentThread() + "]");
                }
            }
        }
    }

    public void showVariables() {
        Properties sp = new Properties();
        KettleVariables kettleVariables = KettleVariables.getInstance();
        sp.putAll(kettleVariables.getProperties());
        sp.putAll(System.getProperties());

        Row allVars = new Row();

        Enumeration keys = kettleVariables.getProperties().keys();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            String value = kettleVariables.getVariable(key);

            allVars.addValue(new Value(key, value));
        }

        // Now ask the use for more info on these!
        EnterStringsDialog esd = new EnterStringsDialog(shell, SWT.NONE, allVars);
        esd.setTitle(Messages.getString("Chef.Dialog.ShowVariables.Title"));
        esd.setMessage(Messages.getString("Chef.Dialog.ShowVariables.Message"));
        esd.setReadOnly(true);
        esd.open();
    }

    public void undoAction() {
        chefgraph.forceFocus();

        TransAction ta = jobMeta.previousUndo();
        if (ta == null)
            return;
        setUndoMenu(); // something changed: change the menu
        switch (ta.getType()) {
        //
        // NEW
        //

        // We created a new entry : undo this...
        case TransAction.TYPE_ACTION_NEW_JOB_ENTRY:
        // Delete the entry at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeJobEntry(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        // We created a new note : undo this...
        case TransAction.TYPE_ACTION_NEW_NOTE:
        // Delete the note at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeNote(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        // We created a new hop : undo this...
        case TransAction.TYPE_ACTION_NEW_JOB_HOP:
        // Delete the hop at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeJobHop(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        //
        // DELETE
        //

        // We delete an entry : undo this...
        case TransAction.TYPE_ACTION_DELETE_STEP:
        // un-Delete the entry at correct location: re-insert
        {
            JobEntryCopy ce[] = (JobEntryCopy[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < ce.length; i++)
                jobMeta.addJobEntry(idx[i], ce[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        // We delete new note : undo this...
        case TransAction.TYPE_ACTION_DELETE_NOTE:
        // re-insert the note at correct location:
        {
            NotePadMeta ni[] = (NotePadMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++)
                jobMeta.addNote(idx[i], ni[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        // We deleted a new hop : undo this...
        case TransAction.TYPE_ACTION_DELETE_JOB_HOP:
        // re-insert the hop at correct location:
        {
            JobHopMeta hi[] = (JobHopMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < hi.length; i++) {
                jobMeta.addJobHop(idx[i], hi[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        //
        // CHANGE
        //

        // We changed a job entry: undo this...
        case TransAction.TYPE_ACTION_CHANGE_JOB_ENTRY:
        // Delete the current job entry, insert previous version.
        {
            JobEntryCopy prev[] = (JobEntryCopy[]) ta.getPrevious();
            int idx[] = ta.getCurrentIndex();

            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeJobEntry(idx[i]);
                jobMeta.addJobEntry(idx[i], prev[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        // We changed a note : undo this...
        case TransAction.TYPE_ACTION_CHANGE_NOTE:
        // Delete & re-insert
        {
            NotePadMeta prev[] = (NotePadMeta[]) ta.getPrevious();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeNote(idx[i]);
                jobMeta.addNote(idx[i], prev[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        // We changed a hop : undo this...
        case TransAction.TYPE_ACTION_CHANGE_JOB_HOP:
        // Delete & re-insert
        {
            JobHopMeta prev[] = (JobHopMeta[]) ta.getPrevious();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeJobHop(idx[i]);
                jobMeta.addJobHop(idx[i], prev[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        //
        // POSITION
        //

        // The position of a step has changed: undo this...
        case TransAction.TYPE_ACTION_POSITION_JOB_ENTRY:
        // Find the location of the step:
        {
            int idx[] = ta.getCurrentIndex();
            Point p[] = ta.getPreviousLocation();
            for (int i = 0; i < p.length; i++) {
                JobEntryCopy entry = jobMeta.getJobEntry(idx[i]);
                entry.setLocation(p[i]);
            }
            refreshGraph();
        }
            break;

        // The position of a note has changed: undo this...
        case TransAction.TYPE_ACTION_POSITION_NOTE:
            int idx[] = ta.getCurrentIndex();
            Point prev[] = ta.getPreviousLocation();
            for (int i = 0; i < idx.length; i++) {
                NotePadMeta npi = jobMeta.getNote(idx[i]);
                npi.setLocation(prev[i]);
            }
            refreshGraph();
            break;
        default:
            break;
        }
    }

    public void redoAction() {
        chefgraph.forceFocus();

        TransAction ta = jobMeta.nextUndo();
        if (ta == null)
            return;
        setUndoMenu(); // something changed: change the menu
        switch (ta.getType()) {
        //
        // NEW
        //
        case TransAction.TYPE_ACTION_NEW_JOB_ENTRY:
        // re-delete the entry at correct location:
        {
            JobEntryCopy si[] = (JobEntryCopy[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++)
                jobMeta.addJobEntry(idx[i], si[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        case TransAction.TYPE_ACTION_NEW_NOTE:
        // re-insert the note at correct location:
        {
            NotePadMeta ni[] = (NotePadMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++)
                jobMeta.addNote(idx[i], ni[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        case TransAction.TYPE_ACTION_NEW_JOB_HOP:
        // re-insert the hop at correct location:
        {
            JobHopMeta hi[] = (JobHopMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();
            for (int i = 0; i < idx.length; i++)
                jobMeta.addJobHop(idx[i], hi[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        //   
        // DELETE
        //
        case TransAction.TYPE_ACTION_DELETE_JOB_ENTRY:
        // re-remove the entry at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeJobEntry(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        case TransAction.TYPE_ACTION_DELETE_NOTE:
        // re-remove the note at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeNote(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        case TransAction.TYPE_ACTION_DELETE_JOB_HOP:
        // re-remove the hop at correct location:
        {
            int idx[] = ta.getCurrentIndex();
            for (int i = idx.length - 1; i >= 0; i--)
                jobMeta.removeJobHop(idx[i]);
            refreshTree();
            refreshGraph();
        }
            break;

        //
        // CHANGE
        //

        // We changed a step : undo this...
        case TransAction.TYPE_ACTION_CHANGE_JOB_ENTRY:
        // Delete the current step, insert previous version.
        {
            JobEntryCopy ce[] = (JobEntryCopy[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();

            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeJobEntry(idx[i]);
                jobMeta.addJobEntry(idx[i], ce[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        // We changed a note : undo this...
        case TransAction.TYPE_ACTION_CHANGE_NOTE:
        // Delete & re-insert
        {
            NotePadMeta ni[] = (NotePadMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();

            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeNote(idx[i]);
                jobMeta.addNote(idx[i], ni[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        // We changed a hop : undo this...
        case TransAction.TYPE_ACTION_CHANGE_JOB_HOP:
        // Delete & re-insert
        {
            JobHopMeta hi[] = (JobHopMeta[]) ta.getCurrent();
            int idx[] = ta.getCurrentIndex();

            for (int i = 0; i < idx.length; i++) {
                jobMeta.removeJobHop(idx[i]);
                jobMeta.addJobHop(idx[i], hi[i]);
            }
            refreshTree();
            refreshGraph();
        }
            break;

        //
        // CHANGE POSITION
        //
        case TransAction.TYPE_ACTION_POSITION_JOB_ENTRY: {
            // Find the location of the step:
            int idx[] = ta.getCurrentIndex();
            Point p[] = ta.getCurrentLocation();
            for (int i = 0; i < p.length; i++) {
                JobEntryCopy entry = jobMeta.getJobEntry(idx[i]);
                entry.setLocation(p[i]);
            }
            refreshGraph();
        }
            break;
        case TransAction.TYPE_ACTION_POSITION_NOTE: {
            int idx[] = ta.getCurrentIndex();
            Point curr[] = ta.getCurrentLocation();
            for (int i = 0; i < idx.length; i++) {
                NotePadMeta npi = jobMeta.getNote(idx[i]);
                npi.setLocation(curr[i]);
            }
            refreshGraph();
        }
            break;
        default:
            break;
        }
    }

    public void setUndoMenu() {
        TransAction prev = jobMeta.viewThisUndo();
        TransAction next = jobMeta.viewNextUndo();

        if (prev != null) {
            miEditUndo.setEnabled(true);
            miEditUndo.setText(Messages.getString("Chef.Menu.Edit.Undo") + prev.toString() + " \tCTRL-Z"); //$NON-NLS-1$ //$NON-NLS-2$
        } else {
            miEditUndo.setEnabled(false);
            miEditUndo.setText(Messages.getString("Chef.Menu.Edit.UndoNotAvailable")); //$NON-NLS-1$
        }

        if (next != null) {
            miEditRedo.setEnabled(true);
            miEditRedo.setText(Messages.getString("Chef.Menu.Edit.Redo") + next.toString() + " \tCTRL-Y"); //$NON-NLS-1$ //$NON-NLS-2$
        } else {
            miEditRedo.setEnabled(false);
            miEditRedo.setText(Messages.getString("Chef.Menu.Edit.RedoNotAvailable")); //$NON-NLS-1$
        }

    }

    public void addUndoNew(Object obj[], int position[]) {
        // New step?
        jobMeta.addUndo(obj, null, position, null, null, JobMeta.TYPE_UNDO_NEW, false);
        setUndoMenu();
    }

    // Undo delete object
    public void addUndoDelete(Object obj[], int position[]) {
        jobMeta.addUndo(obj, null, position, null, null, JobMeta.TYPE_UNDO_DELETE, false);
        setUndoMenu();
    }

    // Change of step, connection, hop or note...
    public void addUndoPosition(Object obj[], int pos[], Point prev[], Point curr[]) {
        addUndoPosition(jobMeta, obj, pos, prev, curr);
    }

    // Change of step, connection, hop or note...
    public void addUndoPosition(UndoInterface undoInterface, Object obj[], int pos[], Point prev[], Point curr[]) {
        // It's better to store the indexes of the objects, not the objects itself!
        jobMeta.addUndo(obj, null, pos, prev, curr, JobMeta.TYPE_UNDO_POSITION, false);
        setUndoMenu();
    }

    // Change of step, connection, hop or note...
    public void addUndoChange(Object from[], Object to[], int[] pos) {
        jobMeta.addUndo(from, to, pos, null, null, JobMeta.TYPE_UNDO_CHANGE, false);
        setUndoMenu();
    }

    public ChefGraph getChefGraph() {
        return chefgraph;
    }

    public String toString() {
        return this.getClass().getName();
    }

    public static void main(String[] a) throws KettleException {
        Spoon.main(a);

        /*
        EnvUtil.environmentInit();
        ArrayList args = new ArrayList();
        for (int i=0;i<a.length;i++) args.add(a[i]);
            
        Display display = new Display();
        Splash splash = new Splash(display);
            
        // System.out.println("Welcome to Chef!");
        StringBuffer optionRepname, optionUsername, optionPassword, optionJobname, optionFilename, optionDirname, optionLogfile;
            
        CommandLineOption options[] = new CommandLineOption[] 
        {
         new CommandLineOption("rep", Messages.getString("Chef.CommandLine.RepositoryName.Description"), optionRepname=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("user", Messages.getString("Chef.CommandLine.Username.Description"), optionUsername=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("pass", Messages.getString("Chef.CommandLine.Password.Description"), optionPassword=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("job", Messages.getString("Chef.CommandLine.JobName.Description"), optionJobname=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("dir", Messages.getString("Chef.CommandLine.RepositoryDirectory.Description"), optionDirname=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("file", Messages.getString("Chef.CommandLine.Filename.Description"), optionFilename=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("logfile", Messages.getString("Chef.CommandLine.LogFile.Description"), optionLogfile=new StringBuffer()), //$NON-NLS-1$ //$NON-NLS-2$
         new CommandLineOption("log", Messages.getString("Chef.CommandLine.LogFileDeprecated.Description"), optionLogfile=new StringBuffer(), false, true), //$NON-NLS-1$ //$NON-NLS-2$
        };
            
        // Parse the options...
        CommandLineOption.parseArguments(args, options);
            
        String kettleRepname  = Const.getEnvironmentVariable("KETTLE_REPOSITORY", null); //$NON-NLS-1$
        String kettleUsername = Const.getEnvironmentVariable("KETTLE_USER", null); //$NON-NLS-1$
        String kettlePassword = Const.getEnvironmentVariable("KETTLE_PASSWORD", null); //$NON-NLS-1$
            
        if (kettleRepname !=null && kettleRepname .length()>0) optionRepname  = new StringBuffer(kettleRepname);
        if (kettleUsername!=null && kettleUsername.length()>0) optionUsername = new StringBuffer(kettleUsername);
        if (kettlePassword!=null && kettlePassword.length()>0) optionPassword = new StringBuffer(kettlePassword);
            
        Locale.setDefault(Const.DEFAULT_LOCALE);
            
        LogWriter log;
        LogWriter.setConsoleAppenderDebug();
        if (Const.isEmpty(optionLogfile))
        {
        log=LogWriter.getInstance(Const.SPOON_LOG_FILE, false, LogWriter.LOG_LEVEL_BASIC);
        }
        else
        {
        log=LogWriter.getInstance( optionLogfile.toString(), true, LogWriter.LOG_LEVEL_BASIC );
        }
            
        if (log.getRealFilename()!=null) log.logBasic(APP_NAME, Messages.getString("Chef.Log.LoggingGoesTo")+log.getRealFilename()); //$NON-NLS-1$
              
        // Load the plugins etc.
        StepLoader stloader = StepLoader.getInstance();
        if (!stloader.read())
        {
         log.logError(APP_NAME, Messages.getString("Chef.Log.Error.LoadingSteps")); //$NON-NLS-1$
         return;
        }
            
        // Load the plugins etc.
        JobEntryLoader jeloader = JobEntryLoader.getInstance();
        if (!jeloader.read())
        {
        log.logError(APP_NAME, Messages.getString("Chef.Log.Error.LoadingJobEntries")); //$NON-NLS-1$
        return;
        }
            
            
        final Chef win = new Chef(log, display,  null);
        win.setDestroy(true);
            
        log.logDetailed(APP_NAME, "Main window is created."); //$NON-NLS-1$
            
        RepositoryMeta repinfo = null;
        UserInfo userinfo = null;
            
        if (Const.isEmpty(optionRepname) && Const.isEmpty(optionFilename) && win.props.showRepositoriesDialogAtStartup())
        {      
        log.logDetailed(APP_NAME, "Asking for repository"); //$NON-NLS-1$
            
         int perms[] = new int[] { PermissionMeta.TYPE_PERMISSION_JOB };
         splash.hide();
         RepositoriesDialog rd = new RepositoriesDialog(win.disp, SWT.NONE, perms, Messages.getString("Chef.Application.Name")); //$NON-NLS-1$
         if (rd.open())
         {
        repinfo = rd.getRepository();
        userinfo = rd.getUser();
        if (!userinfo.useJobs())
        {
           MessageBox mb = new MessageBox(win.shell, SWT.OK | SWT.ICON_ERROR );
           mb.setMessage(Messages.getString("Chef.Message.UserHasNoRightsToWorkWithJobs.Message")); //$NON-NLS-1$
           mb.setText(Messages.getString("Chef.Message.UserHasNoRightsToWorkWithJobs.Title")); //$NON-NLS-1$
           mb.open();
               
           userinfo = null;
           repinfo  = null;
        }
         }
         else
         {
        // Exit point: user pressed CANCEL!
        if (rd.isCancelled()) 
        {
           splash.dispose();
           win.quitFile();
           return;
        }
         }
        }
            
            
        try
        {
         // Read kettle job specified on command-line?
         if (!Const.isEmpty(optionRepname) || !Const.isEmpty(optionFilename))
         {
        if (!Const.isEmpty(optionRepname))
        {
           RepositoriesMeta repsinfo = new RepositoriesMeta(log);
           if (repsinfo.readData())
           {
              repinfo = repsinfo.findRepository(optionRepname.toString());
              if (repinfo!=null)
              {
                 // Define and connect to the repository...
                 win.rep = new Repository(log, repinfo, userinfo);
                 if (win.rep.connect(Messages.getString("Chef.Application.Name"))) //$NON-NLS-1$
                 {
                    if (Const.isEmpty(optionDirname)) optionDirname=new StringBuffer(RepositoryDirectory.DIRECTORY_SEPARATOR);
            
                    // Check username, password
                    win.rep.userinfo = new UserInfo(win.rep, optionUsername.toString(), optionPassword.toString());
                        
                    if (win.rep.getUserInfo().getID()>0)
                    {
                       RepositoryDirectory repdir = win.rep.getDirectoryTree().findDirectory(optionDirname.toString());
                           
                       win.jobMeta = new JobMeta(log, win.rep, optionJobname.toString(), repdir);
                       win.setFilename(optionFilename.toString());
                       win.jobMeta.clearChanged();
                    }
                    else
                    {
                                log.logError(APP_NAME, Messages.getString("Chef.Log.Error.VerifyingUsernamePassword")); //$NON-NLS-1$
                       win.rep=null;
                    }
                 }
                 else
                 {
                            log.logError(APP_NAME, Messages.getString("Chef.Log.Error.ConnectingToRepository")); //$NON-NLS-1$
                 }
              }
              else
              {
                        log.logError(APP_NAME, Messages.getString("Chef.Log.Error.NoRepositoryProvidedCanNotLoadJob")); //$NON-NLS-1$
              }
           }
           else
           {
                    log.logError(APP_NAME, Messages.getString("Chef.Log.Error.NoRepositoriesOnThisSystem")); //$NON-NLS-1$
           }
        }
        else
        if (!Const.isEmpty(optionFilename))
        {
           win.jobMeta = new JobMeta(log, optionFilename.toString(), win.rep);
           win.jobMeta.clearChanged();
        }
         } // Nothing on commandline...
         else
         {
        // Can we connect to the repository?
        if (repinfo!=null && userinfo!=null)
        {
           win.rep = new Repository(log, repinfo, userinfo);
           if (!win.rep.connect(Messages.getString("Chef.Application.Name"))) //$NON-NLS-1$
           {
              win.rep = null;
           }
        }
            
        if (win.props.openLastFile())
        {
           String lastfiles[]  = win.props.getLastFiles();
           String lastdirs[]   = win.props.getLastDirs();
           boolean lasttypes[] = win.props.getLastTypes();
           String lastrepos[]  = win.props.getLastRepositories();
             
           if (lastfiles.length>0)
           {
              boolean use_repository = win.rep!=null;
            
              if (use_repository || !lasttypes[0])
              {
                 if (win.rep!=null) // load from repository...
                 {
                    if (win.rep.getName().equalsIgnoreCase(lastrepos[0]))
                    {
                       RepositoryDirectory repdir = win.rep.getDirectoryTree().findDirectory(lastdirs[0]);
                       if (repdir!=null)
                       {
                                    log.logDetailed(APP_NAME, Messages.getString("Chef.Log.AutoLoading")+lastfiles[0]+Messages.getString("Chef.Log.AutoLoading2")+repdir.getPath()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                          // win.jobinfo = new JobInfo(log, win.rep, lastfiles[0], repdir);
                          JobLoadProgressDialog jlpd = new JobLoadProgressDialog(win.shell, win.rep, lastfiles[0], repdir);
                          JobMeta jobInfo = jlpd.open();
                          if (jobInfo!=null)
                          {
                             win.jobMeta = jobInfo;
                             win.setFilename(lastfiles[0]);
                          }
                       }
                    }
                 }
                 else // Load from XML?
                 {
                    win.jobMeta = new JobMeta(log, lastfiles[0], win.rep);
                 }
                 win.setFilename(lastfiles[0]);
              }
              win.jobMeta.clearChanged();
           }
        }
         }
            
        }
        catch(KettleException ke)
        {
        log.logError(APP_NAME, Messages.getString("Chef.Log.Error.ErrorLoadingJob")+ke.getMessage()); //$NON-NLS-1$
        }
            
        // Set the arguments on the job metadata as well...
        if (win.jobMeta!=null)
        {
        win.jobMeta.setArguments((String[]) args.toArray(new String[args.size()]));
        }
            
        win.open ();
        splash.dispose();
            
        while (!win.isDisposed ()) 
        {
         if (!win.readAndDispatch ()) win.sleep ();
        }
        win.dispose();
            
        log.logBasic(APP_NAME, APP_NAME+Messages.getString("Chef.Log.ApplicationHasEnded")); //$NON-NLS-1$
            
        // Close the logfile...
        log.close();
            
        // Kill all remaining things in this VM!
        System.exit(0);
        */
    }

    /**
     * @return Returns the jobMeta.
     */
    public JobMeta getJobMeta() {
        return jobMeta;
    }

    /**
     * @param jobMeta The jobMeta to set.
     */
    public void setJobMeta(JobMeta jobMeta) {
        this.jobMeta = jobMeta;
    }
}