com.jtstand.swing.MainFrame.java Source code

Java tutorial

Introduction

Here is the source code for com.jtstand.swing.MainFrame.java

Source

/*
 * Copyright (c) 2009 Albert Kurucz. 
 *
 * This file, MainFrame.java is part of JTStand.
 *
 * JTStand is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JTStand is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with GTStand.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jtstand.swing;

import com.jgoodies.looks.FontPolicies;
import com.jgoodies.looks.FontPolicy;
import com.jgoodies.looks.FontSet;
import com.jgoodies.looks.FontSets;
import com.jgoodies.looks.Options;
import com.jgoodies.looks.plastic.PlasticXPLookAndFeel;
import com.jtstand.AbstractTestSequenceInstanceNamedProcessor;
import com.jtstand.AbstractTestSequenceInstanceProcessor;
import com.jtstand.Authentication;
import com.jtstand.TestSequenceInstance;
import com.jtstand.TestStation;
import com.jtstand.TestStepInstance;
import com.jtstand.TestStepScript;
import com.jtstand.query.FrameInterface;
import com.jtstand.query.ToDatabase;
import com.jtstand.swing.TestSequenceInstanceModel.SequenceColumn;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.script.ScriptException;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.event.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.tree.TreePath;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.jboss.logging.Logger;
import org.jdesktop.swingx.JXStatusBar;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.plaf.basic.BasicStatusBarUI;
import org.jdesktop.swingx.table.ColumnFactory;
import org.jdesktop.swingx.table.TableColumnExt;

/**
 *
 *
 */
public class MainFrame extends AbstractTestSequenceInstanceListTableModel implements TableModel, TableModelListener,
        TreeSelectionListener, List<TestSequenceInstance>, Set<TestSequenceInstance>, Serializable,
        ListSelectionListener, PropertyChangeListener, MouseListener, FrameInterface, TreeExpansionListener {

    private static final Logger log = Logger.getLogger(MainFrame.class.getName());
    //    public static final long serialVersionUID = 20081114L;
    public static final String STARTER_DIALOG_CLASS_NAME = "STARTER_DIALOG_CLASS_NAME";
    public static final String PROCESSOR_LIST = "PROCESSOR_LIST";
    public static final String PROCESSOR_MAP = "PROCESSOR_MAP";
    //    public static final int WIDTH0 = -1;
    private JXTable jTable = null;
    private JSplitPane jSplitPane = null;
    private JScrollPane jScrollPaneTop = null;
    private JScrollPane jScrollPaneSequence = null;
    private TestSequenceInstance selectedSequence = null;
    private TestStepInstance selectedStep = null;
    private JXTreeTable jXTreeTable = null;
    private Ticker ticker;
    private QueryDialogTestStep queryDialog = null;
    private TestStation testStation;
    private ToDatabase toDatabase;
    private Dialog starterDialog = null;
    private JXStatusBar bar;
    private JXProgressBar pbar;
    private JLabel statusLabel;
    private JLabel operatorLabel;
    private JLabel progressLabel;
    private JLabel freeMemoryLabel;
    private JLabel freeDiskLabel;
    private Fixtures fixtures;
    private JDialog login;
    //    private File saveDirectory;
    private static Runtime rt;
    private JSplitPane jSplitPaneSequenceStep;
    private RSyntaxTextArea textArea;
    private RTextScrollPane jScrollPaneStep;
    private String title;
    private String version;

    @Override
    public void tableChanged(TableModelEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                resizeSequences();
            }
        });
    }

    private void resizeSequences() {
        synchronized (lock) {
            int rc = jTable.getRowCount();
            int vrc = jTable.getVisibleRowCount();
            //            System.out.println("Sequences Model row count: " + jTable.getModel().getRowCount() + "   Table row count: " + rc + "   Visible row count: " + vrc);
            if (vrc > rc || vrc < 3 && vrc != rc) {
                Util.setVisibleRowCount(jTable, Math.min(rc, 3), jSplitPane);
                //                Util.setDividerLocation(jSplitPane, jTable);
                Util.packColumnsWidthFixedFirst(jTable, 9);
            }
            Util.packColumn(jTable, 0, 9, true);
        }
    }

    @Override
    public void treeExpanded(TreeExpansionEvent event) {
        Util.packColumnsWidthFixedFirst(jXTreeTable, 9);
    }

    @Override
    public void treeCollapsed(TreeExpansionEvent event) {
        //        Util.packColumnsWidthFixedFirst(jXTreeTable, 9);
    }

    public static enum SequencesColumn {

        ROW("", Integer.class), LOCATION("Location", String.class), OPERATOR("Operator", String.class), PN(
                "Product Type", String.class), TESTTYPE("Test Type", String.class), SN("Serial Number",
                        String.class), STARTTIME("Sequence Started", Long.class), ELAPSED("Elapsed",
                                Long.class), STATUS("State", String.class), FAILEDATSTEP("Failed at Step",
                                        String.class), ERROR("Error Code", String.class);
        public final String columnName;
        public final Class columnClass;

        SequencesColumn(String columnName, Class columnClass) {
            this.columnName = columnName;
            this.columnClass = columnClass;
        }
    }

    public static SequencesColumn[] sequencesColumns = SequencesColumn.values();
    public static final int SEQUENCES_COLUMN_COUNT = sequencesColumns.length;

    @Override
    public int getColumnCount() {
        return SEQUENCES_COLUMN_COUNT;
    }

    @Override
    public String getColumnName(int column) {
        return sequencesColumns[column].columnName;
    }

    @Override
    public Class getColumnClass(int column) {
        return sequencesColumns[column].columnClass;
    }

    @Override
    public Object getValueAt(int row, int column) {
        if (row >= size()) {
            return null;
        }
        final TestSequenceInstance seq = get(row);
        if (seq == null) {
            return null;
        }
        return getValueAt(seq, row, sequencesColumns[column]);
    }

    private Object getValueAt(TestSequenceInstance seq, int row, SequencesColumn column) {
        switch (column) {
        case ROW:
            return 1 + getJTable().convertRowIndexToView(row);
        case SN:
            return seq.getSerialNumber();
        case PN:
            return seq.getProductName();
        case TESTTYPE:
            return seq.getTestTypeName();
        case LOCATION:
            if (seq.getTestStation() == null) {
                return "";
            }
            if (seq.getTestFixture() == null) {
                return seq.getTestStation().getHostName();
            } else {
                return seq.getTestStation().getHostName() + "@" + seq.getTestFixture().getFixtureName();
            }
        case OPERATOR:
            return seq.getEmployeeNumber() == null ? "" : seq.getEmployeeNumber();
        case STARTTIME:
            return Long.valueOf(seq.getCreateTime());
        case ELAPSED:
            return seq.getElapsed();
        case STATUS:
            return seq.getStatusString();
        case FAILEDATSTEP:
            return seq.getFailureStep() == null ? null : seq.getFailureStep().getTestStepNamePath().getStepPath();
        case ERROR:
            return seq.getFailureCode();
        default:
            return null;
        }
    }

    public static long getAvailableMemory() {
        if (rt == null) {
            rt = Runtime.getRuntime();
        }
        return rt.freeMemory() + rt.maxMemory() - rt.totalMemory();
    }

    public static boolean isMemoryEnough() {
        return getAvailableMemory() > 55000000L;
    }

    //    public TestProject getTestProject() {
    //        return testStation.getTestProject();
    //    }
    @Override
    public TestStation getTestStation() {
        return testStation;
    }

    @Override
    public boolean isMemoryEnoughRetry() {
        for (int i = 0; i < 1000; i++) {
            if (isMemoryEnough()) {
                return true;
            }
            if (removeOldest() && isMemoryEnough()) {
                return true;
            }
            log.warn("Available memory is low. Sleeping before starting a new sequence...");
            //System.gc();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                return false;
            }
        }
        return false;
    }

    public void showFreeMemory(long free) {
        freeMemoryLabel.setText("M: " + Util.getBytes(free) + " ");
    }

    public void showFreeDisk(long free) {
        freeDiskLabel.setText("D: " + Util.getBytes(free) + " ");
    }

    public static final Class[] STARTER_DIALOG_CONSTRUCTOR = { Fixture.class };

    public void showStarterDialog(Fixture fixture) {
        if (starterDialog != null) {
            starterDialog.dispose();
        }
        if (fixture.getTestFixture().getTestStation().getTestProject().getAuthentication() != null && fixture
                .getTestFixture().getTestStation().getTestProject().getAuthentication().getOperator() == null) {
            login = new Login(frame, true,
                    fixture.getTestFixture().getTestStation().getTestProject().getAuthentication());
            if (fixture.getTestFixture().getTestStation().getTestProject().getAuthentication()
                    .getOperator() == null) {
                return;
            }
        }
        String starterDialogClassName = fixture.getTestFixture().getPropertyString(STARTER_DIALOG_CLASS_NAME,
                StarterCommonDialog.class.getName());
        try {
            Class<?> starterDialogClass = Class.forName(starterDialogClassName);
            Constructor<?> starterDialogContructor = starterDialogClass.getConstructor(STARTER_DIALOG_CONSTRUCTOR);
            starterDialog = (Dialog) starterDialogContructor.newInstance(fixture);
        } catch (Exception ex) {
            log.error("Exception", ex);
        }
        //        starterDialog = new StarterCommonDialog(frame, false, fixture.getTestFixture().getTestStation().getTestProject().getAuthentication() == null ? null : fixture.getTestFixture().getTestStation().getTestProject().getAuthentication().getOperator(), fixture.getTestFixture(), fixture.getTestFixture().getTestStation(), fixture.getTestFixture().getTestStation().getTestProject(), this, fixture);

        frame.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosed(java.awt.event.WindowEvent evt) {
                starterDialog = null;
            }
        });
    }

    public static void setLookAndFeel() {
        try {
            //            String crosslaf = UIManager.getCrossPlatformLookAndFeelClassName();
            //            System.out.println("Cross Platfowm Look & Feel:" + crosslaf);

            String systemlaf = UIManager.getSystemLookAndFeelClassName();
            //            System.out.println("System Look & Feel:" + systemlaf);

            String nimbus = null;
            String windows = null;
            LookAndFeelInfo[] lafis = UIManager.getInstalledLookAndFeels();
            log.info("Installed Look & Feels:");
            for (int i = 0; i < lafis.length; i++) {
                LookAndFeelInfo lafi = lafis[i];
                String name = lafi.getName();
                log.info("[" + i + "]=" + name);
                if ("Nimbus".equals(name)) {
                    nimbus = lafi.getClassName();
                } else if ("Windows".equals(name)) {
                    windows = lafi.getClassName();
                }
            }

            //            if (windows != null) {
            //                UIManager.setLookAndFeel(windows);
            //                return;
            //            }
            UIManager.put(Options.USE_SYSTEM_FONTS_APP_KEY, Boolean.TRUE);

            FontSet fontSet = FontSets.createDefaultFontSet(
                    //                    new Font("Tahoma", Font.PLAIN, 11), // control font
                    //                    new Font("Tahoma", Font.PLAIN, 11), // menu font
                    //                    new Font("Tahoma", Font.PLAIN, 11) // title font
                    //                    new Font("Sans", Font.PLAIN, 11), // control font
                    //                    new Font("Sans", Font.PLAIN, 11), // menu font
                    //                    new Font("Sans", Font.PLAIN, 11) // title font
                    new Font("Verdana", Font.PLAIN, 12), // control font
                    new Font("Verdana", Font.PLAIN, 12), // menu font
                    new Font("Verdana", Font.PLAIN, 12) // title font
            );
            FontPolicy fixedPolicy = FontPolicies.createFixedPolicy(fontSet);
            PlasticXPLookAndFeel.setFontPolicy(fixedPolicy);
            UIManager.setLookAndFeel(new PlasticXPLookAndFeel());

            //            System.out.println("getControlTextFont:" + PlasticXPLookAndFeel.getControlTextFont());
            //            System.out.println("getTitleTextFont:" + PlasticXPLookAndFeel.getTitleTextFont());
            //            System.out.println("getMenuTextFont:" + PlasticXPLookAndFeel.getMenuTextFont());
            //            System.out.println("getPlasticTheme:" + PlasticXPLookAndFeel.getPlasticTheme());

            //            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
            //UIManager.setLookAndFeel(systemlaf);

        } catch (Exception ex) {
            log.error("Exception", ex);
        }
    }

    public MainFrame(TestStation testStation, String title, String version) throws ScriptException {
        this();
        //        testStation.initializeProperties();
        this.testStation = testStation;
        this.title = title;
        this.version = version;
        setLookAndFeel();
        if (!testStation.databaseValidate()) {
            switch (getTestStation().getDriver()) {
            case h2:
            case derby:
                try {
                    getTestStation().databaseReset(null, null);
                } catch (Exception ex) {
                    log.fatal("Cannot reset the database");
                    System.exit(-1);
                }
                break;
            default:
                new ResetDatabase(null, true, getTestStation());
            }
        }
        getFrame();
        toDatabase = new ToDatabase(getTestStation(), this);
    }

    private JFrame frame;

    public String getTitle() {
        return (title == null) ? (getTestStation().getTestProject().getName() + " - revision "
                + getTestStation().getTestProject().getCreator().getRevision()
                //                + " on "
                //                + getTestStation().getHostName()
                + " - version "
                + ((version == null) ? getClass().getPackage().getImplementationVersion() : version)) : title;
    }

    public JFrame getFrame() {
        if (frame == null) {
            frame = new JFrame();
            //        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
            //        addWindowListener(new java.awt.event.WindowAdapter() {
            //
            //            @Override
            //            public void windowClosing(java.awt.event.WindowEvent evt) {
            //                formWindowClosing(evt);
            //            }
            //        });
            log.trace("Loading icon...");
            URL iconURL = getClass().getResource("/images/jtbean.png");// Thread.currentThread().getContextClassLoader().getResource("/images/jtbean.png");
            log.trace("Icon URL: " + iconURL);
            if (iconURL != null) {
                ImageIcon image = new ImageIcon(iconURL);
                frame.setIconImage(image.getImage());
            }
            frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
            frame.addWindowListener(new java.awt.event.WindowAdapter() {
                @Override
                public void windowClosing(java.awt.event.WindowEvent evt) {
                    formWindowClosing(evt);
                }
            });
            frame.setTitle(getTitle());
            if (testStation.getFixtures().size() > 0) {
                fixtures = new Fixtures(testStation, this);
                frame.getContentPane().add(fixtures, BorderLayout.NORTH);
            }
            frame.getContentPane().add(getSplitPane(), BorderLayout.CENTER);
            bar = createBar();
            frame.getContentPane().add(bar, BorderLayout.SOUTH);
            Util.maxIt(frame);
            Util.setDividerLocation(jSplitPane, jTable);
            jSplitPane.addPropertyChangeListener(this);

            fixtures.init();
        }
        frame.pack();
        frame.setVisible(true);
        //        if (SystemTray.isSupported()) {
        //            final SystemTray tray = SystemTray.getSystemTray();
        //        }
        return frame;
    }

    public static JXStatusBar.Constraint getProgressBarConstraint() {
        JXStatusBar.Constraint c = new JXStatusBar.Constraint();
        c.setFixedWidth(200);
        return c;
    }

    public static JXStatusBar.Constraint getStatusLabelConstraint() {
        return new JXStatusBar.Constraint(JXStatusBar.Constraint.ResizeBehavior.FILL);
    }

    public static JXStatusBar.Constraint getSeparatorConstraint() {
        JXStatusBar.Constraint c = new JXStatusBar.Constraint();
        c.setFixedWidth(6);
        return c;
    }

    private JXStatusBar createBar() {
        bar = new JXStatusBar();
        bar.putClientProperty(BasicStatusBarUI.AUTO_ADD_SEPARATOR, false);

        //        if (getTestProject().getAuthentication() != null) {
        //            JButton login = new JButton("Login");
        //            bar.add(login);
        //        }

        operatorLabel = new JLabel("Operator is not logged in. ");
        if (getTestStation().getTestProject().getAuthentication() != null) {
            operatorLabel.setToolTipText("Right click for log in");
            addLoginMenu(operatorLabel);
            bar.add(operatorLabel);
            bar.add(new JSeparator(JSeparator.VERTICAL), getSeparatorConstraint());
            getTestStation().getTestProject().getAuthentication().addPropertyChangeListener(this);
        }
        statusLabel = new JLabel("");
        bar.add(statusLabel, getStatusLabelConstraint()); // Fixed width of 100 with no inserts

        bar.add(new JSeparator(JSeparator.VERTICAL), getSeparatorConstraint());
        progressLabel = new JLabel("");
        progressLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        progressLabel.setVisible(false);
        bar.add(progressLabel);
        pbar = new JXProgressBar();
        //        pbar.setBorder(new EtchedBorder());
        bar.add(pbar, getProgressBarConstraint()); // Fill with no inserts - will use remaining space
        pbar.setBorder(null);
        pbar.setVisible(false);
        //        ImageIcon icon = new ImageIcon(this.getClass().getResource("resources/buton.png"));
        //        JLabel progressCancelButton = new JLabel(icon);
        //        bar.add(progressCancelButton);

        bar.add(new JSeparator(JSeparator.VERTICAL), getSeparatorConstraint());
        freeDiskLabel = new JLabel("D: x.xxx GB ");
        try {
            freeDiskLabel.setToolTipText(
                    "Available Disk Space on '" + getTestStation().getSaveDirectory().getPath() + "'");
        } catch (ScriptException ex) {
            log.error("Exception", ex);
        }
        freeDiskLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        bar.add(freeDiskLabel);
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        showFreeDisk(getTestStation().getSaveDirectory().getUsableSpace());
                    } catch (ScriptException ex) {
                        log.error("Exception", ex);
                    }
                    try {
                        Thread.sleep(2000L);
                    } catch (InterruptedException ex) {
                        log.warn("Exception", ex);
                    }
                }
            }
        });
        t.setDaemon(true);
        t.start();

        bar.add(new JSeparator(JSeparator.VERTICAL), getSeparatorConstraint());
        freeMemoryLabel = new JLabel("M: xxxx MB ");
        freeMemoryLabel.setToolTipText("Available Memory");
        freeMemoryLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        bar.add(freeMemoryLabel);
        Thread tFreeMemory = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    showFreeMemory(getAvailableMemory());
                    try {
                        Thread.sleep(2000L);
                    } catch (InterruptedException ex) {
                        log.warn("Exception", ex);
                    }
                }
            }
        });
        tFreeMemory.setDaemon(true);
        tFreeMemory.start();
        return bar;
    }

    private void formWindowClosing(java.awt.event.WindowEvent evt) {
        frame.setTitle("Closing...");
        frame.setEnabled(false);
        try {
            log.info("cancelProgress...");
            cancelProgress();
        } catch (InterruptedException ex) {
            log.warn("Exception", ex);
        }
        if (toDatabase != null) {
            try {
                log.trace("toDatabase.abort()...");
                toDatabase.abort();
            } catch (InterruptedException ex) {
                log.warn("Exception", ex);
            }
        }
    }

    public MainFrame() {
        super();
        ticker = new Ticker();
    }

    private void cancelProgress() throws InterruptedException {
        if (pbar.isVisible()) {
            pbar.cancel();
            while (pbar.isVisible()) {
                Thread.sleep(10);
            }
        }
    }

    private List<TestStepInstance> querySteps(String path, TestStepInstances.Mode mode)
            throws InterruptedException {
        TestStepInstances steps = new TestStepInstances(this, mode);
        List<String> pathList = Util.getPathList(path);
        List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
        synchronized (lock) {
            seqList.addAll(this);
        }
        if (seqList.size() == 0) {
            return steps;
        }
        cancelProgress();
        pbar.reset();
        pbar.setMaximum(seqList.size());
        pbar.setMinimum(0);
        pbar.setValue(0);
        progressLabel.setText("Chart:");
        progressLabel.setVisible(true);
        pbar.setVisible(true);
        pbar.setStringPainted(true);
        for (TestSequenceInstance seq : seqList) {
            if (seq.getFinishTime() != null) {
                if (isContained(seq)) {
                    steps.add(seq.getId(), path);
                } else {
                    TestStepInstance addstep = seq.getChild(pathList);
                    if (addstep != null) {
                        steps.add(addstep);
                    }
                }
            }
            pbar.setValue(pbar.getValue() + 1);
            if (pbar.isCancelled()) {
                pbar.setVisible(false);
                progressLabel.setVisible(false);
                return null;
            }
        }
        if (steps.size() > 0) {
            steps.getFrame();
        }
        pbar.setVisible(false);
        progressLabel.setVisible(false);
        return steps;
    }

    public boolean removeOldest() {
        synchronized (lock) {
            List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
            long oldest = 0L;
            for (TestSequenceInstance tsi : this) {
                if (seqList.size() == 0) {
                    seqList.add(tsi);
                    oldest = tsi.getCreateTime();
                } else if (tsi.getCreateTime() < oldest) {
                    seqList.set(0, tsi);
                    oldest = tsi.getCreateTime();
                }
            }
            boolean changed = super.removeAll(seqList);
            if (changed) {
                fixtures.removeAll(seqList);
                if (seqList.contains(selectedSequence)) {
                    hideSequence();
                }
                updateTableView(size());
                System.gc();
            }
            return changed;
        }
    }

    public boolean remove(long createTime, String host) {
        synchronized (lock) {
            List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
            for (TestSequenceInstance tsi : this) {
                if (tsi.getCreateTime() == createTime && tsi.getTestStation().getHostName().equals(host)) {
                    seqList.add(tsi);
                }
            }
            boolean changed = super.removeAll(seqList);
            if (changed) {
                fixtures.removeAll(seqList);
                if (seqList.contains(selectedSequence)) {
                    hideSequence();
                }
                updateTableView(size());
                System.gc();
            }
            return changed;
        }
    }

    @Override
    public TestSequenceInstance replace(long createTime, String host) {
        //        System.out.println("Replacing: " + createTime + " " + host);
        if (!isMemoryEnough()) {
            log.warn("Not enough memory to replace.");
            return null;
        }
        if (getSequence(createTime, host) == null) {
            //            System.out.println("There is no candidate to replace.");
            return null;
        }
        TestSequenceInstance seq = super.replace(createTime, host);
        fixtures.replace(seq);
        System.gc();
        return seq;
    }

    //    @Override
    //    public boolean add(
    //            final TestSequenceInstance seq) {
    //
    //
    //        return checkChangable(
    //                new Changable() {
    //
    //                    boolean isChanged() {
    //                        return MainFrame.super.add(seq);
    //                    }
    //                });
    //    }
    //
    //    @Override
    //    public boolean addAll(
    //            final Collection<? extends TestSequenceInstance> seqList) {
    //        return checkChangable(new Changable() {
    //
    //            boolean isChanged() {
    //                return MainFrame.super.addAll(seqList);
    //            }
    //        });
    //    }
    @Override
    public boolean removeAll(Collection<?> seqList) {
        synchronized (lock) {
            if (jTable != null) {
                jTable.clearSelection();
            }
            boolean changed = super.removeAll(seqList);
            if (changed) {
                fixtures.removeAll(seqList);
                if (seqList.contains(selectedSequence)) {
                    hideSequence();
                }
                updateTableView(size());
                System.gc();
            }
            return changed;
        }
    }

    public TestSequenceInstance getSequence(int rowIndex) {
        synchronized (lock) {
            if (jTable != null) {
                int i = jTable.convertRowIndexToModel(rowIndex);
                if (i >= 0) {
                    return get(i);
                }
            }
        }
        return null;
    }

    public TestSequenceInstance getSequence(long createTime, String host) {
        synchronized (lock) {
            for (TestSequenceInstance seq : this) {
                //                System.out.println("Sequence: " + seq.getCreateTime());
                if (seq.getCreateTime() == createTime && seq.getTestStation() != null
                        && seq.getTestStation().getHostName().equals(host)) {
                    return seq;
                }
            }
        }
        return null;
    }

    //    public TestSequence getTestSequence(FileRevision rev) {
    //   synchronized (lock) {
    //       for (TestSequenceInstance seq : sequences) {
    //      if (seq.getTestSequence().getId() != null && seq.getTestSequence().getCreator().equals(rev)) {
    ////                    Log.log("Sequence is found in the list!");
    //          return seq.getTestSequence();
    //      }
    //       }
    //   }
    //   return null;
    //    }
    public List<TestSequenceInstance> getSelectedSequences() {
        synchronized (lock) {
            List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
            if (jTable != null) {
                int[] rows = jTable.getSelectedRows();
                if (rows.length > 0) {
                    for (int i = 0; i < rows.length; i++) {
                        int row = rows[i];
                        int j = jTable.convertRowIndexToModel(row);
                        if (j >= 0) {
                            seqList.add(get(j));
                        }
                    }
                }
            }
            return seqList;
        }
    }

    public void scrollSelectedSequenceToVisible() {
        synchronized (lock) {
            if (jTable != null) {
                int[] rows = jTable.getSelectedRows();
                if (rows.length == 1) {
                    jTable.scrollRowToVisible(rows[0]);
                }
            }
        }
    }

    public List<TestSequenceInstance> getSelectedSequencesConditionally(int rowIndex) {
        synchronized (lock) {
            List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
            if (jTable != null) {
                int[] rows = jTable.getSelectedRows();
                if (Util.isElement(rows, rowIndex)) {
                    for (int i = 0; i < rows.length; i++) {
                        int row = rows[i];
                        int j = jTable.convertRowIndexToModel(row);
                        if (j >= 0) {
                            seqList.add(get(j));
                        }
                    }
                }
            }
            return seqList;
        }
    }

    private void updateTableView(int size) {
        if (jTable != null) {
            if (jSplitPane != null) {
                resizeSequences();
            }
            jTable.revalidate();
        }
    }

    //    public static void displayJFrame(final TestSequenceInstance tsi) {
    //        if (tsi != null) {
    //            JFrame jFrame = new JFrame();
    //            jFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    //            jFrame.setMinimumSize(new Dimension(640, 300));
    //            jFrame.setPreferredSize(Util.getMaximumWindowDimension());
    //            jFrame.setLayout(new BorderLayout());
    //            jFrame.getContentPane().add((new TestSequenceInstancesModel(tsi)).getSplitPane(), BorderLayout.CENTER);
    //            jFrame.pack();
    //            jFrame.setTitle(tsi.toString());
    //            jFrame.setVisible(true);
    //            jFrame.requestFocus();
    //        }
    //    }
    //    public static void displayJFrame(final List<TestSequenceInstance> sequences) {
    //        if (sequences != null) {
    //            JFrame jFrame = new JFrame();
    //            jFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    //            //jFrame.setMinimumSize(new Dimension(640, 300));
    //            jFrame.setPreferredSize(Util.getMaximumWindowDimension());
    //            jFrame.setLayout(new BorderLayout());
    //            jFrame.getContentPane().add((new TestSequenceInstancesModel(sequences)).getSplitPane(), BorderLayout.CENTER);
    //            jFrame.pack();
    //            jFrame.setTitle("Test Results");
    //            jFrame.setVisible(true);
    //            jFrame.requestFocus();
    //        }
    //    }
    public JXTable getJTable() {
        if (jTable == null) {
            jTable = new JXTable(this) {
                public static final long serialVersionUID = 20081114L;

                @Override
                public String getToolTipText(MouseEvent e) {
                    return MainFrame.this.getToolTipTextSequences(e);
                }
            };
            jTable.getColumnExt(jTable.convertColumnIndexToView(SequencesColumn.FAILEDATSTEP.ordinal()))
                    .setVisible(false);
            jTable.getColumnExt(jTable.convertColumnIndexToView(SequencesColumn.ERROR.ordinal())).setVisible(false);
            jTable.getTableHeader().setReorderingAllowed(false);
            //((ColumnHeaderRenderer) jTable.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
            jTable.setDefaultRenderer(Long.class, new TestSequenceInstancesRenderer());
            jTable.addHighlighter(new TestSequenceInstancesHighlighter());
            //            jTable.setShowGrid(false);
            //            jTable.setGridColor(Color.lightGray);
            jTable.setBackground(Color.white);
            //            jTable.setColumnMargin(0);
            jTable.getSelectionModel().addListSelectionListener(this);
            addMenu(jTable);
            jTable.addMouseListener(this);
            Util.packColumnsWidthFixedFirst(jTable, 9);
            jTable.setName("Sequences");
            jTable.setAutoCreateRowSorter(true);
            jTable.getRowSorter().addRowSorterListener(new RowSorterListener() {
                @Override
                public void sorterChanged(RowSorterEvent e) {
                    if (jTable.getColumn(SequencesColumn.ROW.ordinal()).equals(jTable.getSortedColumn())) {
                        jTable.resetSortOrder();
                    } else {
                        Util.scrollSelectedRowToVisible(jTable);
                    }
                }
            });
            addTableModelListener(this);
        }
        return jTable;
    }

    private TestStepInstance getSelectedStep() {
        if (jXTreeTable == null) {
            return null;
        }
        int[] rows = jXTreeTable.getSelectedRows();
        if (rows.length != 1) {
            return null;
        }
        Object myobject = jXTreeTable.getValueAt(rows[0], TestSequenceInstanceModel.SequenceColumn.NAME.ordinal());
        if (!TestStepInstance.class.isAssignableFrom(myobject.getClass())) {
            return null;
        }
        return (TestStepInstance) myobject;
    }

    @Override
    public void valueChanged(TreeSelectionEvent e) {
        if (e.getSource().equals(jXTreeTable.getTreeSelectionModel())) {
            selectedStepChanged();
        }
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            //            System.out.println("valueChanged source:" + e.getSource().getClass());

            if (e.getSource().equals(jTable.getSelectionModel())) {
                /* Sequence/Sequences selection changed on the sequences list */
                selectedSequenceChanged();
            }

            //            if (e.getSource().equals(jXTreeTable.getSelectionModel())) {
            //                selectedStepChanged();
            //            }

            if (jXTreeTable != null && e.getSource().equals(jXTreeTable.getSelectionModel())) {
                //                System.out.println("Treetable event!!!");
                TestStepInstance sStep = getSelectedStep();
                if (sStep != null) {
                    setSelectedStep(sStep);

                    //                    System.out.println("Selected step:" + selectedStep.getTestStepInstancePath());
                }
            }
        }
    }

    public void setSelectedStep(TestStepInstance step) {
        selectedStep = step;
        if (selectedStep != null && queryDialog != null) {
            queryDialog.selectStep(step);
        }
    }

    public void selectedSequenceChanged() {
        if (jTable.getRowCount() > 0) {
            int firstindex = jTable.getSelectedRow();
            if (jTable.getSelectedRowCount() != 1 || firstindex < 0 || size() < 1 || firstindex >= size()) {
                hideSequence();
                return;
            } else {
                firstindex = jTable.convertRowIndexToModel(firstindex);
                selectSequence(get(firstindex));
            }
        }
    }

    public void selectedStepChanged() {
        int[] rows = jXTreeTable.getSelectedRows();
        if (rows.length == 1) {
            if (textArea != null) {
                showStep();
            }
        } else {
            hideStep();
        }
    }

    @Override
    public void selectSequence(TestSequenceInstance select) {
        if (select == null) {
            jTable.clearSelection();
            return;
        }
        //        System.out.println("Selecting: " + select.getStartedString() + "@" + select.getHostName());
        synchronized (lock) {
            int pos = indexOf(select);
            if (pos >= 0) {
                final int row = jTable.convertRowIndexToView(pos);
                if (pos >= 0 && jTable.getSelectedRow() != row) {
                    jTable.setRowSelectionInterval(row, row);
                    jTable.scrollRowToVisible(row);
                }
                if (!select.equals(selectedSequence)) {
                    displaySequence(select);
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            jTable.scrollRowToVisible(row);
                        }
                    });

                }
            } else {
                log.info("Cannot select sequence:" + select);
            }
        }
    }

    private void hideSequence() {
        //        jScrollPaneBottom.setVisible(false);
        jSplitPane.setDividerSize(0);
        jSplitPane.setBottomComponent(null);
        if (jScrollPaneSequence != null) {
            if (jScrollPaneSequence.getComponentCount() > 0) {
                jScrollPaneSequence.removeAll();
            }
            jScrollPaneSequence = null;
        }
        jXTreeTable = null;
        //System.gc();
    }

    private void toggleStep() {
        if (textArea == null) {
            showStep();
        } else {
            hideStep();
        }
    }

    private void showStep() {
        TestStepInstance tsi = getSelectedStep();
        if (tsi != null) {
            TestStepScript s = tsi.getScript();
            if (s != null) {
                String text = s.getText();
                if (text != null && text.trim().length() > 0) {
                    textArea = new RSyntaxTextArea();
                    textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_GROOVY);
                    textArea.setText(text);
                    textArea.setEditable(false);
                    jScrollPaneStep = new RTextScrollPane(textArea);
                    jXTreeTable.setVisibleRowCount(Math.min(5, jXTreeTable.getRowCount()));
                    jSplitPaneSequenceStep.setBottomComponent(jScrollPaneStep);
                    jSplitPaneSequenceStep.setDividerSize(UIManager.getInt("SplitPane.dividerSize"));
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            int row = jXTreeTable.getSelectedRow();
                            if (row > 0) {
                                jXTreeTable.scrollRowToVisible(row);
                            }
                        }
                    });
                    return;
                }
            }
        }
        if (textArea != null) {
            textArea.setText("");
        }
    }

    private void hideStep() {
        jSplitPaneSequenceStep.setDividerSize(0);
        jSplitPaneSequenceStep.setBottomComponent(null);
        if (jScrollPaneStep != null) {
            if (jScrollPaneStep.getComponentCount() > 0) {
                jScrollPaneStep.removeAll();
            }
            jScrollPaneStep = null;
        }
        textArea = null;
    }

    public void displaySequence(TestSequenceInstance select) {
        //        System.out.println("Display: " + select.getStartedString() + "@" + select.getHostName());
        log.trace("contains:" + isContained(select));
        TestSequenceInstanceModel tsim;
        if (isContained(select)) {
            tsim = new TestSequenceInstanceModel(getTestStation().getTestSequenceInstance(select.getId()));
        } else {
            tsim = new TestSequenceInstanceModel(select);
        }
        if (jXTreeTable == null) {
            jXTreeTable = new JXTreeTable(tsim) {
                public static final long serialVersionUID = 20081114L;

                @Override
                public String getToolTipText(MouseEvent e) {
                    return MainFrame.this.getToolTipTextSequence(e);
                }
            };
            jXTreeTable.getSelectionModel().addListSelectionListener(this);
            jXTreeTable.addMouseListener(this);
            jXTreeTable.setLeafIcon(null);
            jXTreeTable.addTreeExpansionListener(this);
            jXTreeTable.setRootVisible(false);
            jXTreeTable.getTableHeader().setReorderingAllowed(false);
            //            ((org.jdesktop.swingx.table.ColumnHeaderRenderer) jXTreeTable.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
            jXTreeTable.addHighlighter(new TestSequenceInstanceHighlighter(jXTreeTable));
            jXTreeTable.getTreeSelectionModel().addTreeSelectionListener(this);

            //jXTreeTable.setDefaultRenderer(String.class, new TestSequenceInstanceRenderer());
            //            jXTreeTable.setShowGrid(true);
            //            jXTreeTable.setGridColor(Color.lightGray);
            addTreeTableMenu(jXTreeTable);
            jXTreeTable.setBackground(Color.white);
            jScrollPaneSequence = new JScrollPane();
            jScrollPaneSequence.setBorder(null);
            if (jSplitPaneSequenceStep == null) {
                jSplitPaneSequenceStep = new JSplitPane();
                //                jSplitPaneSequenceStep.setBorder(null);
                jSplitPaneSequenceStep.setOrientation(JSplitPane.VERTICAL_SPLIT);
                jSplitPaneSequenceStep.addPropertyChangeListener(this);
            }
            jSplitPaneSequenceStep.setTopComponent(jScrollPaneSequence);
            jSplitPane.setBottomComponent(jSplitPaneSequenceStep);
            jScrollPaneSequence.setViewportView(jXTreeTable);
        } else {
            jXTreeTable.setTreeTableModel(tsim);
        }
        hideStep();
        Util.packColumnsWidthFixedFirst(jXTreeTable, 9);

        //        if (selectedSequence != null) {
        //            TestStepInstance selectedStep = selectedSequence.getSelectedStep();
        //            if (selectedStep != null) {
        //                selectStep(selectedStep);
        //            }
        //        }
        int dividerSize = UIManager.getInt("SplitPane.dividerSize");
        jSplitPane.setDividerSize(dividerSize);
        jScrollPaneSequence.setVisible(true);
        selectedSequence = select;
        if (selectedStep != null) {
            selectStep(selectedStep);
        }
        //        jSplitPane.setDividerLocation(jScrollPaneSequenceList.getPreferredSize().height);
        Util.setVisibleRowCount(jTable, Math.min(jTable.getRowCount(), jTable.getVisibleRowCount()), jSplitPane);
        //        Util.setDividerLocation(jSplitPane, jTable);
        jXTreeTable.requestFocus();
    }

    public void expandFailedOrRunning() {
        if (selectedSequence == null) {
            return;
        }
        if (selectedSequence.getTestStepInstance() != null) {
            if (selectedSequence.getTestStepInstance().isFailed()) {
                canExpandFailed(selectedSequence.getTestStepInstance());
            } else if (selectedSequence.getTestStepInstance().isRunning()) {
                canExpandRunning(selectedSequence.getTestStepInstance());
            } else if (selectedSequence.getTestStepInstance().isAborted()) {
                canExpandAborted(selectedSequence.getTestStepInstance());
            }
        }
    }

    public void canExpandFailed(TestStepInstance tsi) {
        if (jXTreeTable != null) {
            for (int row = 0; row < jXTreeTable.getRowCount(); row++) {
                if (jXTreeTable.getValueAt(row,
                        TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()) instanceof TestStepInstance) {
                    if (((TestStepInstance) jXTreeTable.getValueAt(row,
                            TestSequenceInstanceModel.SequenceColumn.NAME.ordinal())).getTestStepInstancePath()
                                    .equals(tsi.getTestStepInstancePath())) {
                        jXTreeTable.expandPath(jXTreeTable.getPathForRow(row));
                        //                        System.out.println("expanded:" + tsi);
                        if (!tsi.isLeaf()) {
                            for (TestStepInstance child : tsi.getSteps()) {
                                if (child.isFailed()) {
                                    canExpandFailed(child);
                                    return;
                                }
                            }
                        }
                        jXTreeTable.setRowSelectionInterval(row, row);
                        //                    jXTreeTable.scrollRectToVisible(jXTreeTable.getCellRect(row, 0, false));
                        Util.scrollToCenter(jXTreeTable, row, 0);
                        selectedStep = tsi;
                        return;
                    }
                }
            }
        }
        log.warn("could not expand:" + tsi);
    }

    public void canExpandAborted(TestStepInstance tsi) {
        if (jXTreeTable != null) {
            for (int row = 0; row < jXTreeTable.getRowCount(); row++) {
                if (jXTreeTable.getValueAt(row,
                        TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()) instanceof TestStepInstance) {
                    if (((TestStepInstance) jXTreeTable.getValueAt(row,
                            TestSequenceInstanceModel.SequenceColumn.NAME.ordinal())).getTestStepInstancePath()
                                    .equals(tsi.getTestStepInstancePath())) {
                        jXTreeTable.expandPath(jXTreeTable.getPathForRow(row));
                        //                    System.out.println("expanded:" + tsi);
                        if (!tsi.isLeaf()) {
                            for (TestStepInstance child : tsi.getSteps()) {
                                if (child.isAborted()) {
                                    canExpandAborted(child);
                                    return;
                                }
                            }
                        }
                        jXTreeTable.setRowSelectionInterval(row, row);
                        //                    jXTreeTable.scrollRectToVisible(jXTreeTable.getCellRect(row, 0, false));
                        Util.scrollToCenter(jXTreeTable, row, 0);
                        selectedStep = tsi;
                        return;
                    }
                }
            }
        }
        log.warn("could not expand:" + tsi);
    }

    public void canExpandRunning(TestStepInstance tsi) {
        if (jXTreeTable != null) {
            for (int row = 0; row < jXTreeTable.getRowCount(); row++) {
                if (jXTreeTable.getValueAt(row,
                        TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()) instanceof TestStepInstance) {
                    if (((TestStepInstance) jXTreeTable.getValueAt(row,
                            TestSequenceInstanceModel.SequenceColumn.NAME.ordinal())).getTestStepInstancePath()
                                    .equals(tsi.getTestStepInstancePath())) {
                        jXTreeTable.expandPath(jXTreeTable.getPathForRow(row));
                        //                    System.out.println("expanded:" + tsi);
                        if (!tsi.isLeaf()) {
                            List<TestStepInstance> children = tsi.getSteps();
                            ListIterator<TestStepInstance> li = children.listIterator(children.size());
                            while (li.hasPrevious()) {
                                TestStepInstance child = li.previous();
                                if (child.isRunning()) {
                                    canExpandRunning(child);
                                    return;
                                }
                            }
                        }
                        jXTreeTable.setRowSelectionInterval(row, row);
                        //                    jXTreeTable.scrollRectToVisible(jXTreeTable.getCellRect(row, 0, false));
                        Util.scrollToCenter(jXTreeTable, row, 0);
                        selectedStep = tsi;
                        return;
                    }
                }
            }
        }
        log.warn("could not expand:" + tsi);
    }
    //    public boolean expand(TestStepInstance tsi) {
    //        List<String> pathList = tsi.getPathList();
    //        System.out.println("getting tree path for:");
    //
    //        for (String str : pathList) {
    //            System.out.println(str);
    //        }
    //
    //        TestStepInstance node = tsi.getRoot(pathList.get(0));
    //        if (node == null) {
    //            return false;
    //        }
    //        if (!canExpand(node)) {
    //            return false;
    //        }
    //        for (int i = 1; i < pathList.size(); i++) {
    //            node = node.getChild(pathList.get(i));
    //            if (node != null) {
    //                if (!canExpand(node)) {
    //                    return false;
    //                }
    //            } else {
    //                return false;
    //            }
    //        }
    //        return true;
    //    }

    private TestStepInstance getRoot(String name) {
        Object root = jXTreeTable.getTreeTableModel().getRoot();
        if (!jXTreeTable.getTreeTableModel().isLeaf(root)) {
            for (int i = 0; i < jXTreeTable.getTreeTableModel().getChildCount(root); i++) {
                Object node = jXTreeTable.getTreeTableModel().getChild(root, i);

                if (TestStepInstance.class.isAssignableFrom(node.getClass())) {
                    //                    System.out.println("checking:" + ((TestStepInstance) node).getTestStepInstancePath());
                    if (((TestStepInstance) node).getTestStepInstancePath().equals(name)) {
                        //                        System.out.println("getRoot has found the root step:" + ((TestStepInstance) node).getTestStepInstancePath());
                        return (TestStepInstance) node;
                    }
                }
            }
        }
        //System.out.println("getRoot did not found root with name: " + name);
        return null;
    }

    public boolean expand(List<String> pathList) {
        //        System.out.println("getting tree path for: ");
        //        for (String str : pathList) {
        //            System.out.println(str);
        //        }

        TestStepInstance node = getRoot(pathList.get(0));
        if (node == null) {
            return false;
        }
        if (!canExpand(node)) {
            return false;
        }
        for (int i = 1; i < pathList.size(); i++) {
            node = node.getChild(pathList.get(i));
            if (node != null) {
                if (!canExpand(node)) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    public boolean canExpand(TestStepInstance tsi) {
        for (int row = 0; row < jXTreeTable.getRowCount(); row++) {
            if (jXTreeTable.getValueAt(row,
                    TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()) instanceof TestStepInstance) {
                if (((TestStepInstance) jXTreeTable.getValueAt(row,
                        TestSequenceInstanceModel.SequenceColumn.NAME.ordinal())).getTestStepInstancePath()
                                .equals(tsi.getTestStepInstancePath())) {
                    jXTreeTable.expandPath(jXTreeTable.getPathForRow(row));
                    //                    System.out.println("expanded: " + tsi);
                    return true;
                }
            }
        }
        log.warn("could not expand: " + tsi);
        return false;
    }

    public void selectStep(TestStepInstance selectedStep) {
        expand(selectedStep.getPathList());

        for (int row = 0; row < jXTreeTable.getRowCount(); row++) {
            if (jXTreeTable.getValueAt(row,
                    TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()) instanceof TestStepInstance) {
                if (((TestStepInstance) jXTreeTable.getValueAt(row,
                        TestSequenceInstanceModel.SequenceColumn.NAME.ordinal())).getTestStepInstancePath()
                                .equals(selectedStep.getTestStepInstancePath())) {
                    //System.out.println(JXTreeTable.getValueAt(row, TestSequenceInstanceModel.SequenceColumn.NAME.ordinal()));
                    jXTreeTable.setRowSelectionInterval(row, row);
                    //                    jXTreeTable.scrollRectToVisible(jXTreeTable.getCellRect(row, TestSequenceInstanceModel.SequenceColumn.NAME.ordinal(), false));
                    Util.scrollToCenter(jXTreeTable, row, 0);
                    return;
                }
            }
        }
    }

    public JSplitPane getSplitPane() {
        if (jSplitPane == null) {
            jSplitPane = new JSplitPane();
            //jSplitPane.setBorder(null);
            jSplitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
            //            jSplitPane.setResizeWeight(0.3);
            jScrollPaneTop = new JScrollPane();
            jScrollPaneTop.setBorder(null);
            jScrollPaneTop.setViewportView(getJTable());
            //            jScrollPaneTop.revalidate();
            //            jSplitPane.setDividerLocation(jScrollPaneTop.getPreferredSize().height + jSplitPane.getInsets().top);
            jSplitPane.setTopComponent(jScrollPaneTop);
            //            jSplitPane.addPropertyChangeListener(this);
            resizeSequences();
            hideSequence();
        }
        return jSplitPane;
    }

    //    public void dividerChanged() {
    //        if (!jScrollPaneBottom.isVisible()) {
    //            return;
    //        }
    //        int current = jSplitPane.getDividerLocation();
    ////        Insets i = jSplitPane.getInsets();
    ////        Insets j = jScrollPaneTop.getInsets();
    ////        int rc = Util.getRowCount(jTable, current - i.bottom - i.top - j.bottom - j.top);
    //        int rc = Util.getRowCount(jTable, current - jScrollPaneTop.getInsets().top);
    //        if (rc == 0 && jTable.getRowCount() > 0) {
    //            rc = 1;
    //        }
    //        System.out.println("computed row count:" + rc);
    //        if (rc != jTable.getVisibleRowCount()) {
    //            Util.setVisibleRowCount(jTable, rc);
    //        }
    //        Util.setDividerLocation(jSplitPane);
    //        scrollSelectedSequenceToVisible();
    //    }
    boolean wasSequenceActive = false;

    public void tick(final boolean forceRepaint) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                if (jTable != null) {
                    boolean active = isSequenceActive();
                    if (forceRepaint || wasSequenceActive || active) {
                        jTable.repaint();
                        wasSequenceActive = active;
                    }
                }
                JXTreeTable tree = jXTreeTable;
                TestSequenceInstance seq = selectedSequence;
                if (tree != null && tree.isVisible() && seq != null) {
                    if (forceRepaint || seq.isSequenceActive()) {
                        jXTreeTable.repaint();
                    }
                }
            }
        });
    }

    public boolean isSequenceActive() {
        synchronized (lock) {
            for (TestSequenceInstance seq : this) {
                if (seq.isSequenceActive()) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        //        System.out.println("Property '" + evt.getPropertyName() + "' changed to " + evt.getNewValue());

        if (jSplitPane != null && evt.getSource().equals(jSplitPane)) {
            if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)
                    && !evt.getNewValue().equals((Integer) (-1))) {
                //                SwingUtilities.invokeLater(new Runnable() {
                //                    @Override
                //                    public void run() {
                //                        dividerChanged();
                //                        System.out.println("jSplitPane DIVIDER_LOCATION_PROPERTY");
                Util.dividerChanged(jTable, jSplitPane);
                //                    }
                //                });
            }
        } else if (jSplitPaneSequenceStep != null && evt.getSource().equals(jSplitPaneSequenceStep)) {
            if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)
                    && !evt.getNewValue().equals((Integer) (-1))) {
                //                SwingUtilities.invokeLater(new Runnable() {
                //                    @Override
                //                    public void run() {
                //                        dividerChanged();
                //                        System.out.println("jSplitPaneSequenceStep DIVIDER_LOCATION_PROPERTY");
                Util.dividerChanged(jXTreeTable, jSplitPaneSequenceStep);
                //                    }
                //                });
            }
        } else if (getTestStation().getTestProject().getAuthentication() != null
                && evt.getPropertyName().equals(Authentication.OPERATOR_PROPERTY)) {
            //            System.out.println("Property '" + evt.getPropertyName() + "' changed to " + evt.getNewValue());
            if (evt.getNewValue() == null) {
                operatorLabel.setText("Operator is logged out. ");
                operatorLabel.setToolTipText("Right click for log in");
            } else {
                operatorLabel.setText("Operator: " + evt.getNewValue() + " ");
                operatorLabel.setToolTipText("Right click for log out");
            }
        } else if (TestSequenceInstance.class.isAssignableFrom(evt.getSource().getClass())) {
            TestSequenceInstance seq = (TestSequenceInstance) evt.getSource();
            log.debug("Property change of test sequence: " + seq);
            tick(true);
            if (!seq.isSequenceActive()) {
                seq.removePropertyChangeListener(this);
            }
        } else {
            log.debug("source class: " + evt.getSource().getClass().getCanonicalName());
        }
    }

    class Ticker extends Thread {

        private boolean running = true;

        public Ticker() {
            this.setDaemon(true);
            this.start();
        }

        public void finish() {
            running = false;
        }

        @Override
        public void run() {
            try {
                while (running) {
                    tick(false);
                    sleep(1000);
                }
            } catch (InterruptedException ex) {
                //do nothing if interrupted
            }
        }
    }

    private void cancelCellEditing(JTable jTable) {
        CellEditor ce = jTable.getCellEditor();
        if (ce != null) {
            ce.cancelCellEditing();
        }
    }

    private void addLoginMenu(final JLabel jLabel) {
        jLabel.addMouseListener(new MouseAdapter() {
            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger() && jLabel.isEnabled()) {

                    Point p = new Point(e.getX(), e.getY());
                    if (jLabel.contains(p)) {
                        /* create popup menu... */
                        JPopupMenu contextMenu = createLoginContextMenu(jLabel);
                        /* ... and show it */
                        if (contextMenu != null && contextMenu.getComponentCount() > 0) {
                            contextMenu.show(jLabel, p.x, p.y);
                        }
                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }
        });
    }

    private JPopupMenu createLoginContextMenu(JLabel jLabel) {
        if (getTestStation().getTestProject().getAuthentication() == null) {
            return null;
        }
        JPopupMenu contextMenu = new JPopupMenu();

        if (getTestStation().getTestProject().getAuthentication().getOperator() == null) {
            JMenuItem loginMenu = contextMenu.add("Log in");
            loginMenu.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    login = new Login(frame, true, getTestStation().getTestProject().getAuthentication());
                }
            });
        } else {
            JMenuItem logoutMenu = contextMenu.add("Log out");
            logoutMenu.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    getTestStation().getTestProject().getAuthentication().setOperator(null);
                }
            });
        }
        return contextMenu;
    }

    private void addMenu(final JXTable jTable) {
        jTable.getTableHeader().addMouseListener(new MouseAdapter() {
            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger() && jTable.isEnabled()) {
                    Point p = new Point(e.getX(), e.getY());
                    int col = jTable.columnAtPoint(p);
                    int row = jTable.rowAtPoint(p);
                    /* Translate table index to model index */
                    int mcol = jTable.getColumn(jTable.getColumnName(col)).getModelIndex();
                    //                    if (row >= 0 && row < jTable.getRowCount()) {
                    cancelCellEditing(jTable);
                    /* create popup menu... */
                    JPopupMenu contextMenu = createContextMenuHeader(jTable, row, mcol);
                    /* ... and show it */
                    if (contextMenu != null && contextMenu.getComponentCount() > 0) {
                        contextMenu.show(jTable, p.x, p.y);
                    }
                    //                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }
        });
        jTable.addMouseListener(new MouseAdapter() {
            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger() && jTable.isEnabled()) {
                    Point p = new Point(e.getX(), e.getY());
                    int col = jTable.columnAtPoint(p);
                    int row = jTable.rowAtPoint(p);
                    /* Translate table index to model index */
                    int mcol = jTable.getColumn(jTable.getColumnName(col)).getModelIndex();
                    //                    if (row >= 0 && row < jTable.getRowCount()) {
                    cancelCellEditing(jTable);
                    /* create popup menu... */
                    JPopupMenu contextMenu = createContextMenu(jTable, row, mcol);
                    /* ... and show it */
                    if (contextMenu != null && contextMenu.getComponentCount() > 0) {
                        contextMenu.show(jTable, p.x, p.y);
                    }
                    //                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }
        });
    }

    private JPopupMenu createContextMenu(JXTable jTable, int rowIndex, int columnIndex) {
        List<TestSequenceInstance> seqList = new ArrayList<TestSequenceInstance>();
        TestSequenceInstance clickedSequence = null;
        synchronized (lock) {
            if (jTable != null) {
                int[] rows = jTable.getSelectedRows();
                if (Util.isElement(rows, rowIndex)) {

                    for (int i = 0; i < rows.length; i++) {
                        int row = rows[i];
                        int j = jTable.convertRowIndexToModel(row);
                        if (j >= 0) {
                            TestSequenceInstance seq = get(j);
                            seqList.add(seq);
                            if (row == rowIndex) {
                                clickedSequence = seq;
                            }
                        }
                    }
                }
            }
        }
        return createContextMenu(jTable, rowIndex, columnIndex, seqList, clickedSequence);
    }

    public QueryDialogTestStep getQueryDialog(QueryDialogTestStep.Mode mode) {
        if (queryDialog == null) {
            queryDialog = new QueryDialogTestStep(null, false, mode, getTestStation());
        } else {
            queryDialog.setMode(mode);
        }
        Util.centerOn(queryDialog, jSplitPane);
        return queryDialog;
    }

    //    abstract class Changable {
    //
    //        abstract boolean isChanged();
    //    }
    //
    //    private boolean checkChangable(Changable x) {
    //        synchronized (lock) {
    //            int firstIndex = -1;
    //            int lastIndex = -1;
    //            int originalSize = size();
    //            if (jTable != null) {
    //                int firstRow = Util.getFirstVisibleRowIndex(jTable);
    ////                System.out.println("first row: " + firstRow);
    //                if (firstRow >= 0 && firstRow < jTable.getRowCount()) {
    //                    firstIndex = jTable.convertRowIndexToModel(firstRow);
    //                }
    //                int lastRow = Util.getLastVisibleRowIndex(jTable);
    ////                System.out.println("last row: " + lastRow);
    //                if (lastRow > 0 && lastRow < jTable.getRowCount()) {
    //                    lastIndex = jTable.convertRowIndexToModel(lastRow);
    //                }
    //            }
    //            boolean changed = x.isChanged();
    //            if (changed) {
    //                updateTableView(size());
    //                if (firstIndex >= 0) {
    //                    int j = jTable.convertRowIndexToView(firstIndex);
    //                    if (j >= 0) {
    //                        jTable.scrollRowToVisible(j);
    //                    }
    //                }
    //                if (lastIndex >= 0) {
    //                    int j = jTable.convertRowIndexToView(lastIndex);
    //                    if (j >= 0) {
    //                        jTable.scrollRowToVisible(j);
    //                    }
    //                }
    //                if (originalSize < 12) {
    //                    SwingUtilities.invokeLater(new Runnable() {
    //
    //                        @Override
    //                        public void run() {
    //                            Util.packColumns(jTable, 9, MainFrame.WIDTH0);
    //                        }
    //                    });
    //                }
    //                tick(true);
    //
    //            }
    //            return changed;
    //        }
    //    }
    private JPopupMenu createContextMenuHeader(final JXTable jTable, final int rowIndex, final int columnIndex) {
        if (columnIndex != SequencesColumn.ROW.ordinal()) {
            return null;
        }
        JPopupMenu contextMenu = new JPopupMenu();
        addAboutMenu(contextMenu);
        return contextMenu;
    }

    private JPopupMenu createContextMenu(final JXTable jTable, final int rowIndex, final int columnIndex,
            final List<TestSequenceInstance> seqList, final TestSequenceInstance clickedSequence) {
        JPopupMenu contextMenu = new JPopupMenu();

        JMenu menu = new JMenu("Load Sequences from Database");
        JMenuItem reloadMenu = menu.add("Last 100 Sequence");
        JMenuItem qMenu = menu.add("Query Sequences");
        contextMenu.add(menu);
        //        final MainFrame list = this;
        reloadMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final String queryString = "select ts from TestSequenceInstance ts where ts.finishTime != null order by ts.createTime desc";
                addAll(queryString, 100);
                //                MainFrame.this.checkChangable(new Changable() {
                //
                //                    boolean isChanged() {
                //                        return MainFrame.super.addAll(queryString, 100);
                //                    }
                //                });
            }
        });

        qMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final QueryDialog q = getQueryDialog(QueryDialogTestStep.Mode.SEQUENCE);
                ActionListener queryAction = new ActionListener() {
                    @Override
                    public void actionPerformed(java.awt.event.ActionEvent evt) {
                        addAll(q.toString(), q.getMaxResults());
                        //                        checkChangable(new Changable() {
                        //
                        //                            boolean isChanged() {
                        //                                return addAll(q.toString(), q.getMaxResults());
                        //                            }
                        //                        });
                    }
                };
                q.setQueryAction(queryAction);
                q.setVisible(true);
            }
        });

        if (rowIndex >= 0 && rowIndex < jTable.getRowCount() && columnIndex >= 0
                && columnIndex < jTable.getColumnCount()) {
            if (seqList.size() > 0) {
                JMenu selectedMenu = new JMenu(
                        (seqList.size() > 1) ? "Selected " + Integer.toString(seqList.size()) + " sequences"
                                : "Selected sequence");

                JMenuItem copyMenu = selectedMenu.add("Copy");
                copyMenu.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        //                        System.out.println("rowIndex: " + rowIndex + " columnIndex: " + columnIndex);
                        Object value = jTable.getValueAt(rowIndex, columnIndex);
                        setClipboardContents(value == null ? "" : value.toString());
                    }
                });

                if (seqList.size() > 2) {
                    JMenuItem removeMenu = selectedMenu.add("Statistics");
                    removeMenu.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {

                            SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {

                                    Thread t = new Thread(new Runnable() {
                                        @Override
                                        public void run() {
                                            try {
                                                cancelProgress();
                                            } catch (InterruptedException ex) {
                                                return;
                                            }
                                            pbar.reset();
                                            pbar.setMaximum(seqList.size());
                                            pbar.setMinimum(0);
                                            pbar.setValue(0);
                                            progressLabel.setText("Statistics:");
                                            progressLabel.setVisible(true);
                                            pbar.setVisible(true);
                                            pbar.setStringPainted(true);
                                            TestStepStatistics tss = new TestStepStatistics(MainFrame.this,
                                                    clickedSequence);
                                            for (TestSequenceInstance tsi : seqList) {
                                                if (MainFrame.super.isContained(tsi)) {
                                                    tss.add(tsi.getId());
                                                } else {
                                                    tss.add(tsi);
                                                }
                                                pbar.setValue(pbar.getValue() + 1);
                                                if (pbar.isCancelled()) {
                                                    pbar.setVisible(false);
                                                    progressLabel.setVisible(false);
                                                    return;
                                                }
                                            }
                                            tss.getFrame();
                                            pbar.setVisible(false);
                                            progressLabel.setVisible(false);
                                        }
                                    });
                                    t.setPriority(Thread.MIN_PRIORITY);
                                    t.start();
                                }
                            });
                        }
                    });
                }

                JMenuItem removeMenu = selectedMenu.add("Remove from this list");
                removeMenu.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        removeAll(seqList);
                    }
                });

                /* add custom context menu from list */
                Object processorList = getTestStation().getPropertyObject(PROCESSOR_LIST, null);
                if (processorList != null) {
                    //List< AbstractTestSequenceInstanceNamedProcessor> list = processorList.asSubclass(List < AbstractTestSequenceInstanceNamedProcessor >);
                    if (Collection.class.isAssignableFrom(processorList.getClass())) {
                        for (final Object processor : (Collection) processorList) {
                            if (AbstractTestSequenceInstanceNamedProcessor.class
                                    .isAssignableFrom(processor.getClass())) {
                                JMenuItem customMenu = selectedMenu
                                        .add(((AbstractTestSequenceInstanceNamedProcessor) processor).getName());
                                customMenu.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        for (TestSequenceInstance seq : seqList) {
                                            ((AbstractTestSequenceInstanceNamedProcessor) processor).process(seq);
                                        }
                                    }
                                });
                            }
                        }
                    }
                }

                /* add custom context menu from map */
                Object processorMap = getTestStation().getPropertyObject(PROCESSOR_MAP, null);
                if (processorMap != null) {
                    //List< AbstractTestSequenceInstanceNamedProcessor> list = processorList.asSubclass(List < AbstractTestSequenceInstanceNamedProcessor >);
                    if (Map.class.isAssignableFrom(processorMap.getClass())) {
                        for (final Object k : ((Map) processorMap).keySet()) {
                            final Object processor = ((Map) processorMap).get(k);
                            if (AbstractTestSequenceInstanceProcessor.class
                                    .isAssignableFrom(processor.getClass())) {
                                JMenuItem customMenu = selectedMenu.add(k.toString());
                                customMenu.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        for (TestSequenceInstance seq : seqList) {
                                            ((AbstractTestSequenceInstanceProcessor) processor).process(seq);
                                        }
                                    }
                                });
                            }
                        }
                    }
                }

                //                JMenuItem removeDatabaseMenu = selectedMenu.add("Remove from database");
                //                removeDatabaseMenu.addActionListener(new ActionListener() {
                //
                //                    @Override
                //                    public void actionPerformed(ActionEvent e) {
                //                        removeAll(seqList);
                //                    }
                //                });

                contextMenu.add(selectedMenu);
            } //            if (jTable.getSortedColumn() != null) {
            //                JMenuItem resetMenu = new JMenuItem();
            //                resetMenu.setText("Reset sort order");
            //                resetMenu.addActionListener(new ActionListener() {
            //
            //                    @Override
            //                    public void actionPerformed(ActionEvent e) {
            //                        jTable.resetSortOrder();
            //                    }
            //                });
            //                contextMenu.add(resetMenu);
            //            }

            //            if (jTable.getRowCount() > 1) {
            //                int modelIndex = jTable.convertRowIndexToModel(rowIndex);
            //                List<TestSequenceInstance> seqs = ((TestSequenceInstancesModel1) jTable.getModel()).getSequences();
            //                if (modelIndex >= 0 && modelIndex < seqs.size()) {
            //                    final TestSequenceInstance baseSequence = seqs.get(modelIndex);
            //                    JMenuItem statsMenu = new JMenuItem();
            //                    statsMenu.setText("Statistics");
            //                    statsMenu.addActionListener(new ActionListener() {
            //
            //                        @Override
            //                        public void actionPerformed(ActionEvent e) {
            //                            TestStepStatistics tss = new TestStepStatistics(baseSequence);
            //                            synchronized (lock) {
            //                                for (TestSequenceInstance tsi : sequences) {
            //                                    if (getEntityManager().contains(tsi)) {
            //                                        tss.add(tsi.getId());
            //                                    } else {
            //                                        tss.add(tsi);
            //                                    }
            //                                }
            //                            }
            //                            tss.getFrame();
            //                        }
            //                    });
            //                    contextMenu.add(statsMenu);
            //                }
            //            }

        }

        addAboutMenu(contextMenu);
        return contextMenu;
    }

    private void addAboutMenu(JPopupMenu contextMenu) {
        JMenuItem aboutMenu = new JMenuItem("About JTStand");
        aboutMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new AboutDialog(frame, true);
            }
        });
        contextMenu.add(aboutMenu);
    }

    public static void setClipboardContents(String s) {
        StringSelection selection = new StringSelection(s);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
    }

    class MyColumnFactory extends ColumnFactory {

        @Override
        public TableCellRenderer getCellRenderer(JXTable table, TableColumnExt ce) {
            return null;
        }
    };

    @Override
    public void mouseClicked(MouseEvent e) {
        //        System.out.println("click count: " + e.getClickCount());
        if (e.getClickCount() == 2) {
            Point p = e.getPoint();
            Object s = e.getSource();
            if (p != null) {
                if (jTable != null && s.equals(jTable)) {
                    int tableClickedRow = jTable.rowAtPoint(p);
                    if (tableClickedRow >= 0) {
                        //                        System.out.println("tableClickedRow: " + tableClickedRow);
                        //                        selectSequence(getSequence(tableClickedRow));
                        //...
                        int tableClickedColumn = jTable.columnAtPoint(p);
                        //                        System.out.println("tableClickedColumn: " + tableClickedColumn);
                        if (SequencesColumn.STATUS.ordinal() == tableClickedColumn) {
                            expandFailedOrRunning();
                        }
                    }
                }
                if (jXTreeTable != null && s.equals(jXTreeTable)) {
                    int treeClickedRow = jXTreeTable.rowAtPoint(p);
                    if (treeClickedRow >= 0) {
                        int treeClickedColumn = jXTreeTable.columnAtPoint(p);
                        if (treeClickedColumn >= 0) {
                            if (TestSequenceInstanceModel.SequenceColumn.STEPSTATUS
                                    .ordinal() == treeClickedColumn) {
                                Object o = jXTreeTable.getModel().getValueAt(treeClickedRow,
                                        SequenceColumn.NAME.ordinal());
                                if (o != null) {
                                    log.trace("Row: " + treeClickedRow + " Column: " + treeClickedColumn
                                            + " Object:" + o);
                                    if (o instanceof TestStepInstance) {
                                        TestStepInstance clickedStep = (TestStepInstance) o;
                                        if (clickedStep.isFailed() || clickedStep.isRunning()) {
                                            expandFailedOrRunning();
                                        } else {
                                            // neither running, nor failed
                                            if (jXTreeTable.isExpanded(treeClickedRow)) {
                                                jXTreeTable.collapseRow(treeClickedRow);
                                            } else {
                                                jXTreeTable.expandRow(treeClickedRow);
                                            }
                                        }
                                    }
                                }
                            }
                            if (TestSequenceInstanceModel.SequenceColumn.NAME.ordinal() == treeClickedColumn) {
                                toggleStep();
                            }
                            if (selectedStep != null && queryDialog != null && queryDialog.isVisible()) {
                                queryDialog.requestFocus();
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    public static Color fDarkGreen = Color.green.darker();
    public static Color fDarkBrown = new Color(153, 102, 0);

    public static final Color getColor(String status) {
        if (status.equals(TestStepInstance.StepStatus.FAILED.statusString)) {
            return Color.red;
        } else if (status.equals(TestStepInstance.StepStatus.PASSED.statusString)) {
            return fDarkGreen;
        } else if (status.equals(TestStepInstance.StepStatus.NOTEST.statusString)) {
            return fDarkBrown;
        } else if (status.equals(TestStepInstance.StepStatus.RUNNING.statusString)) {
            return Color.blue;
        } else if (status.equals(TestStepInstance.StepStatus.ABORTED.statusString)) {
            return fDarkBrown;
        } else if (status.equals(TestStepInstance.StepStatus.STEPBYSTEP.statusString)) {
            return Color.blue;
        } else if (status.equals(TestStepInstance.StepStatus.STEPBYSTEP_FINISHED.statusString)) {
            return fDarkBrown;
        } else {
            return Color.black;
        }

    }

    private void cancelCellEditing() {
        CellEditor ce = jXTreeTable.getCellEditor();
        if (ce != null) {
            ce.cancelCellEditing();
        }

    }

    private void addTreeTableMenu(final JTable jTable) {
        jTable.addMouseListener(new MouseAdapter() {
            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger() && jTable.isEnabled()) {
                    Point p = new Point(e.getX(), e.getY());
                    int col = jTable.columnAtPoint(p);
                    int row = jTable.rowAtPoint(p);
                    /* Translate table index to model index */
                    int mcol = jTable.getColumn(jTable.getColumnName(col)).getModelIndex();
                    if (row >= 0 && row < jTable.getRowCount()) {
                        cancelCellEditing();
                        /* create popup menu... */
                        JPopupMenu contextMenu = createContextMenu(row, mcol);
                        /* ... and show it */
                        if (contextMenu != null && contextMenu.getComponentCount() > 0) {
                            contextMenu.show(jTable, p.x, p.y);
                        }

                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }
        });
    }

    private JPopupMenu createContextMenu(final int rowIndex, final int columnIndex) {

        JPopupMenu contextMenu = new JPopupMenu();

        JMenuItem copyMenu = new JMenuItem();
        copyMenu.setText("Copy");
        copyMenu.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //                System.out.println("rowIndex: " + rowIndex + " columnIndex: " + columnIndex);
                Object value = jXTreeTable.getValueAt(rowIndex, columnIndex);
                setClipboardContents(value == null ? "" : value.toString());
            }
        });
        contextMenu.add(copyMenu);
        log.trace("getting stepObject...");
        Object stepObject = jXTreeTable.getValueAt(rowIndex,
                TestSequenceInstanceModel.SequenceColumn.NAME.ordinal());
        if (stepObject != null && TestStepInstance.class.isAssignableFrom(stepObject.getClass())) {
            final TestStepInstance step = (TestStepInstance) stepObject;
            final String path = step.getTestStepInstancePath();
            JMenu menu = new JMenu("Chart of '" + path + "'");
            JMenuItem timeMenu = menu.add("Run Time Statistics");

            timeMenu.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            Thread t = new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        List<TestStepInstance> steps = querySteps(path,
                                                TestStepInstances.Mode.RUNTIME);
                                    } catch (InterruptedException ex) {
                                        log.warn("Exception", ex);
                                    }
                                }
                            });
                            t.setPriority(Thread.MIN_PRIORITY);
                            t.start();
                        }
                    });
                }
            });
            log.trace("Finding out if numeric...");
            if (step.isNumericKind()) {
                log.trace("Adding Parametric Statistics menu");
                JMenuItem statMenu = menu.add("Parametric Statistics");
                statMenu.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (pbar != null) {
                            pbar.cancel();
                        }
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                Thread t = new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            List<TestStepInstance> steps = querySteps(path,
                                                    TestStepInstances.Mode.PARAMETRIC);
                                        } catch (InterruptedException ex) {
                                            log.warn("Exception", ex);
                                        }
                                    }
                                });
                                t.setPriority(Thread.MIN_PRIORITY);
                                t.start();
                            }
                        });
                    }
                });
            }
            if (menu.getItemCount() > 0) {
                contextMenu.add(menu);
            }
            log.trace("Finding out if running...");
            if (step.getTestSequenceInstance().isStepByStep() && !step.isSiblingRunning()) {
                JMenuItem startMenu = new JMenuItem();
                startMenu.setText("Start " + step.getName());
                startMenu.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        step.getTestSequenceInstance().start(step);
                    }
                });
                contextMenu.add(startMenu);
            }
        }
        log.trace("Context menu created.");
        return contextMenu;
    }

    public String getToolTipTextSequence(MouseEvent e) {
        TreePath path = jXTreeTable.getPathForLocation(e.getX(), e.getY());
        if (path == null) {
            return null;
        }
        TestStepInstance tsi = (TestStepInstance) path.getLastPathComponent();

        int col = jXTreeTable.columnAtPoint(e.getPoint());
        if (col >= 0) {
            int modelCol = jXTreeTable.convertColumnIndexToModel(col);
            if (modelCol == TestSequenceInstanceModel.SequenceColumn.STEPSTATUS.ordinal() && tsi.isFailed()
                    && tsi.equals(tsi.getTestSequenceInstance().getFailureStep())) {
                return tsi.getTestSequenceInstance().getFailureCode();
            }
        }
        if (tsi != null && tsi.getTestStep() != null && tsi.getTestStep().getRemark() != null) {
            return tsi.getTestStep().getRemark();
        }
        /* This should show the source code somehow, but does not work */
        //        if (tsi.getStepClass() != null) {
        //            Object o = tsi.getTestSequenceInstance().getTestProject().getGroovyClassLoader();
        //            Method[] methods = o.getClass().getMethods();
        //            for (int i = 0; i < methods.length; i++) {
        //                Method m = methods[i];
        //                if (m.getName().equals("getSource")) {
        ////                    System.out.println("getSource found!");
        //                    Class<?>[] types = m.getParameterTypes();
        ////                    for (int j = 0; j < types.length; j++) {
        ////                        System.out.println("parameter type[" + j + "]" + types[j].getCanonicalName());
        ////                    }
        //                    if (types.length == 1 && types[0].equals(String.class)) {
        //                        m.setAccessible(true);
        //                        try {
        //                            return (String) m.invoke(o, tsi.getStepClass());
        //                        } catch (IllegalAccessException ex) {
        //                            Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
        //                        } catch (IllegalArgumentException ex) {
        //                            Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
        //                        } catch (InvocationTargetException ex) {
        //                            Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
        //                        }
        //                    }
        //                }
        //            }
        //        }
        return null;
    }

    public String getToolTipTextSequences(MouseEvent e) {
        int row = jTable.rowAtPoint(e.getPoint());
        if (row >= 0) {
            int modelRow = jTable.convertRowIndexToModel(row);
            if (modelRow >= 0 && modelRow < size()) {
                TestSequenceInstance seq = get(modelRow);
                if (seq.getFailureCode() != null) {
                    int col = jTable.columnAtPoint(e.getPoint());
                    if (col >= 0) {
                        int modelCol = jTable.convertColumnIndexToModel(col);
                        if (modelCol == SequencesColumn.STATUS.ordinal()) {
                            return seq.getFailureCode();
                        }
                    }
                }
            }
        }
        return Integer.toString(this.size()) + " sequence" + ((size() > 1) ? "s" : "");
    }
}