/*
 * $Id$
 *
 * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.javatest.exec;

import com.sun.javatest.InterviewParameters;
import com.sun.javatest.Status;
import com.sun.javatest.TestFilter;
import com.sun.javatest.report.CustomReport;
import com.sun.javatest.report.CustomReport.ReportConfigPanel;
import com.sun.javatest.report.Report;
import com.sun.javatest.report.Report.StartGenListener;
import com.sun.javatest.report.ReportDirChooser;
import com.sun.javatest.report.ReportSettings;
import com.sun.javatest.tool.ToolDialog;
import com.sun.javatest.tool.UIFactory;
import com.sun.javatest.util.DynamicArray;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputAdapter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

class NewReportDialog extends ToolDialog {

    //----------------------------------------------------------------------------

    // keys for option values used for save & restore
    static final String REPORT_DIR = "reportDir";
    private static final String ACTION_MAP_KEY = "listMnemonics";
    private static final String FILTER = "filter";
    private static final int WAIT_DIALOG_DELAY = 3000;      // 3 second delay

    // ---------------------------------------------------------------------------
    private Observer[] obs = new Observer[0];
    private ExecModel model;
    private ReportDirChooser reportDirChooser;
    private DefaultListModel<JCheckBox> listModel;
    private JList<? extends JComponent> list;
    private JTextArea infoArea;
    private CustomReport[] customReports;
    private HashMap<JCheckBox, CustomReport> customBoxes;
    private JComponent[] panes;

    // ---------------------------------------------------------------------------
    private JCheckBox cbHtml;
    private JCheckBox cbPlain;
    private JCheckBox cbXml;
    private JCheckBox cbCof;
    private JCheckBox cbCofTestCases;
    private JCheckBox cbConfig;
    private JCheckBox cbQl;
    private JCheckBox cbEnv;
    private JCheckBox cbStd;
    private JCheckBox cbKfl;
    private JCheckBox cbResults;
    private JCheckBox cbKws;
    private JCheckBox cbPass;
    private JCheckBox cbFail;
    private JCheckBox cbErr;
    private JCheckBox cbNr;
    private JCheckBox cbHtmlRpt;
    private JCheckBox cbHtmlInd;
    private JCheckBox cbKflTc;
    private JCheckBox cbKflF2f;


    //------------------------------end of listener--------------------------
    private JCheckBox cbKflF2e;
    private JCheckBox cbKflMissing;
    private JCheckBox cbBak;
    private JTextField numBak;
    private List<JCheckBox> htmlGroup = new ArrayList<>();
    private List<JCheckBox> configGroup = new ArrayList<>();
    private ActionListener cbHtmlListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            Object src = e.getSource();

            if (src == cbHtml || src == cbConfig) {
                updateHtmlCheckboxStates();
            } else if (src == cbBak) {
                numBak.setEnabled(cbBak.isSelected());
            } else if (src == cbCof) {
                updateCofCheckboxStates();
            }
        }
    };
    private List<JCheckBox> kflGroup = new ArrayList<>();
    private File reportDir;
    private JButton browseBtn;
    private JButton okBtn;
    private JButton cancelBtn;
    private JTextField dirField;
    private JTabbedPane tabs;
    private ReportSettings lastSettings;
    private ReportBrowser reportBrowser;
    private InterviewParameters interviewParams;
    private FilterSelectionHandler filterHandler;
    private JDialog waitDialog;
    private ActionListener listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            Object src = e.getSource();

            if (src == browseBtn) {
                showReportChooserDialog();
            } else if (src == cancelBtn) {
                // revert settings
                // hide dialog
                setVisible(false);

                setState(lastSettings);
                if (okBtn != null) {
                    okBtn.setEnabled(hasSelectedCheckBox());
                }
            } else if (src == okBtn) {
                String s = dirField.getText();

                // check null input
                if (s == null || s.isEmpty()) {
                    uif.showError("nrd.emptyInput");
                    return;
                }

                // check for empty input
                if (cbBak.isSelected() && numBak.getText().isEmpty()) {
                    uif.showError("nrd.emptyBak");
                    // select the last tab (assumes this has the backup settings)
                    tabs.setSelectedIndex(tabs.getTabCount() - 1);
                    return;
                }

                // validate custom reports
                for (CustomReport cr : getActiveCustomReports()) {
                    String error = cr.validateOptions();
                    if (error != null) {
                        for (int i = 0; i < listModel.getSize(); i++) {
                            JCheckBox cb = listModel.get(i);
                            if (cb.getName().equals(cr.getReportId())) {
                                list.setSelectedIndex(i);
                            }
                        }
                        uif.showError("nrd.optionsErr", error);
                        return;
                    }
                }

                reportDir = new File(s);

                try {
                    // check if dir needs to be created.
                    if (!reportDir.isDirectory()) {
                        reportDir.mkdirs();
                    } else {
                        // check if dir is a report dir
                        if (!Report.isReportDirectory(reportDir) &&
                                !isEmptyDirectory(reportDir)) {
                            uif.showError("nrd.cantUse", reportDir);
                            return;
                        }
                    }

                    // XXX save settings in prefs

                    setVisible(false);
                    lastSettings = captureState();
                    notifyStarting();
                    doBgReport(lastSettings);
                } catch (SecurityException se) {
                    uif.showError("nrd.cantCreate", se.getMessage());
                }
            }
        }

        // does work on background thread
        private void doBgReport(final ReportSettings snap) {
            /*final JDialog*/
            waitDialog = uif.createWaitDialog("nrd.wait", parent);

            final Stopper stopper = new Stopper();
            final Thread worker = new Thread() {
                @Override
                public void run() {
                    try {
                        runReport(snap);
                        // go away to switch back to GUI thread
                        finishReport(waitDialog, snap, stopper);
                    } catch (CustomReport.ReportException ie) {
                        showError("nrd.custom", ie.getMessage(), waitDialog);
                        notifyError(ie.getMessage());
                    } catch (IOException ie) {
                        showError("nrd.cantWrite", ie.getMessage(), waitDialog);
                        notifyError(ie.getMessage());
                        // should reshow dialog
                    } catch (SecurityException se) {
                        showError("nrd.cantCreate", se.getMessage(), waitDialog);
                        notifyError(se.getMessage());
                        // should reshow dialog
                    } catch (RuntimeException re) {
                        showError("nrd.errorWriting", re.getMessage(), waitDialog);
                        notifyErrorWriting(re.getMessage());
                    }
                }   // run()
            };  // thread

            // this code is copied from ReportTool....
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.fill = GridBagConstraints.NONE;
            gbc.anchor = GridBagConstraints.CENTER;
            gbc.insets.bottom = 10;
            gbc.insets.top = 10;
            gbc.gridy = 2;
            gbc.gridx = 0;
            JButton cancelBtn = uif.createButton("nrd.cancel");
            waitDialog.getContentPane().add(cancelBtn, gbc);
            waitDialog.pack();
            final String cancelling = uif.getI18NString("nrd.cancelling");
            cancelBtn.addActionListener(e -> {
                JButton butt = (JButton) e.getSource();
                butt.setEnabled(false);
                Component[] cmp = waitDialog.getContentPane().getComponents();
                if (worker != null && worker.isAlive()) {
                    stopper.waitWasHidden = true;
                    worker.interrupt();
                }
                for (Component aCmp : cmp) {
                    if ("nrd.wait".equals(aCmp.getName())) {
                        if (aCmp instanceof JTextComponent) {
                            ((JTextComponent) aCmp).setText(cancelling);
                        }
                        break;
                    }
                }

            });


            ActionListener al = evt -> {
                // show dialog if still processing
                if (worker.isAlive()) {
                    waitDialog.show();
                }
            };

            // show wait dialog if operation is still running after
            // WAIT_DIALOG_DELAY
            Timer timer = new Timer(WAIT_DIALOG_DELAY, al);
            timer.setRepeats(false);
            timer.start();

            // do it!
            worker.start();
        }

        private void finishReport(final JDialog waitDialog,
                                  final ReportSettings snap, final Stopper stopper) {
            // done generating report, switch back to GUI thread
            EventQueue.invokeLater(() -> {
                waitDialog.hide();
                if (!stopper.waitWasHidden) {
                    int r = uif.showYesNoDialog("nrd.showReport");

                    switch (r) {
                        case JOptionPane.YES_OPTION:
                            File index = new File(reportDir, Report.INDEX_FILE_NAME);
                            if (index.exists() && index.canRead()) {
                                showReportBrowser(index);
                            } else {
                                showReportBrowser(selectFileToShow(snap));
                            }
                            break;
                        case JOptionPane.NO_OPTION:
                            break;
                        default:
                            break;
                    }
                }

                notifyDone();
                notifyUpdate(getLastState());
            }
            );
        }


        /**
         * @param uiKey Key to use to call UIFactory.showError()
         * @param msg The localized error message to show.
         */
        private void showError(final String uiKey, final String msg,
                               final JDialog waitDialog) {
            // switch back to GUI thread
            EventQueue.invokeLater(() -> {
                waitDialog.hide();
                uif.showError(uiKey, msg);
            }
            );
        }

        /**
         * Decide which file in the report dir to show to the user when
         * they ask to view a generated 'report'.
         */
        private File selectFileToShow(ReportSettings snap) {
            File result = new File(reportDir, Report.INDEX_FILE_NAME);

            if (!result.exists()) {
                if (snap.isHtmlEnabled()) {
                    // show main report file or file listing
                    result = searchForFile(reportDir, Report.getHtmlReportFilenames());
                    if (result == null) {
                        result = reportDir;
                    }
                }
                /* not useful right now to see raw xml, could do XSLT later
                else if (snap.isPlainEnabled()) {
                    // show that file
                    result = searchForFile(reportDir, Report.getPlainReportFilenames());
                    if (result == null)
                        result = reportDir;
                }
                else if (snap.isXmlEnabled()) {
                    result = new File(reportDir, "xml");
                }
                */
                else {
                    result = reportDir;
                }
            }

            return result;
        }

        private File searchForFile(File dir, String... names) {
            for (String name : names) {
                File f = new File(reportDir, name);
                if (f.exists()) {
                    return f;
                }
            }   // for

            return null;
        }
    };
    NewReportDialog(Component parent, UIFactory uif, FilterConfig f,
                    ReportBrowser reportBrowser, ExecModel model) {
        super(parent, uif, "nrd");

        this.model = model;
        filterHandler = f.createFilterSelectionHandler();
        this.reportBrowser = reportBrowser;
    }

    public void setInterviewParameters(InterviewParameters p) {
        interviewParams = p;
    }

    Map<String, String> getLastState() {
        String rd = dirField.getText();
        String filter = filterHandler.getActiveFilter().getName();
        Map<String, String> lastState = new HashMap<>();

        if (rd != null && !rd.isEmpty()) {
            lastState.put(REPORT_DIR, rd);
        }

        if (filter != null && !filter.isEmpty()) {
            lastState.put(FILTER, filter);
        }

        return lastState;
    }

    void setLastState(Map<String, String> h) {
        String rd = h.get(REPORT_DIR);
        String filter = h.get(FILTER);

        if (dirField == null) {
            initGUI();
        }

        dirField.setText(rd);

        if (filter != null && !filter.isEmpty()) {
            filterHandler.setFilter(filter);
        }

        updateHtmlCheckboxStates();
        updateCofCheckboxStates();
    }

    void addObserver(Observer o) {
        obs = DynamicArray.append(obs, o);
    }

    void removeObserver(Observer o) {
        obs = DynamicArray.remove(obs, o);
    }

    // Notify the observers that a change has been made.
    private void notifyUpdate(Map<String, String> s) {
        for (Observer o : obs) {
            o.update(s);
        }
    }

    private void notifyStarting() {
        for (Observer o : obs) {
            o.writingReport();
        }
    }

    private void notifyDone() {
        for (Observer o : obs) {
            o.wroteReport();
        }
    }

    private void notifyError(String problem) {
        for (Observer o : obs) {
            o.wroteReport();
        }
    }

    private void notifyErrorWriting(String problem) {
        for (Observer o : obs) {
            o.errorWriting(problem);
        }
    }

    @Override
    protected void initGUI() {
        setHelp("report.newReport.csh");
        setI18NTitle("nrd.title");

        JPanel body = new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                Dimension d = super.getPreferredSize();
                int dpi = uif.getDotsPerInch();
                return new Dimension(Math.max(d.width, 5 * dpi), d.height);
            }
        };
        body.setName("nrd.body");
        body.setFocusable(false);
        body.setLayout(new GridBagLayout());
        body.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        int dpi = uif.getDotsPerInch();

        GridBagConstraints lc = new GridBagConstraints();
        lc.anchor = GridBagConstraints.EAST;
        lc.insets.right = 10;
        lc.insets.bottom = 11;
        lc.weighty = 0.0;
        lc.weightx = 0.0;
        lc.fill = GridBagConstraints.HORIZONTAL;
        lc.gridwidth = 1;

        // first row, directory name
        JLabel dirLabel = uif.createLabel("nrd.dir", true);
        body.add(dirLabel, lc);

        dirField = uif.createInputField("nrd.dir.choice", dirLabel);
        lc.weightx = 3.0;
        body.add(dirField, lc);

        browseBtn = uif.createButton("nrd.browse", listener);
        browseBtn.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createEtchedBorder(),
                BorderFactory.createEmptyBorder(0, 3, 0, 3)));
        lc.gridwidth = GridBagConstraints.REMAINDER;
        lc.insets.left = 11;
        lc.weightx = 0.0;
        body.add(browseBtn, lc);

        // second row, filter
        lc.insets.left = 0;
        lc.gridwidth = 1;

        // add filter selector
        if (filterHandler != null) {
            JLabel filterLabel = uif.createLabel("nrd.filter", true);
            body.add(filterLabel, lc);

            JComponent selector = filterHandler.getFilterSelector();
            filterLabel.setLabelFor(selector);
            lc.gridwidth = GridBagConstraints.REMAINDER;
            lc.weightx = 4.0;
            lc.anchor = GridBagConstraints.WEST;
            lc.fill = GridBagConstraints.NONE;
            body.add(selector, lc);
        }

        // third row, the tabs
        lc.anchor = GridBagConstraints.NORTHWEST;
        lc.gridwidth = GridBagConstraints.REMAINDER;
        lc.fill = GridBagConstraints.BOTH;
        lc.insets.bottom = 12;
        lc.weighty = 1.0;

//------------------------------------------------------------------------

        JPanel middle = uif.createPanel("nrd.middle", false);
        middle.setLayout(new BorderLayout());
        middle.setBorder(BorderFactory.createCompoundBorder(
                uif.createTitledBorder("nrd.middle"),
                BorderFactory.createEmptyBorder(12, 12, 12, 12)));

        panes = new JComponent[]{
                createHtmlBlock(),
                createFilesBlock(),
                createKflBlock()
        };

        tabs = uif.createTabbedPane("nrd.tabs", panes);
        tabs.setTabPlacement(SwingConstants.TOP);
        tabs.setBorder(BorderFactory.createEmptyBorder());

        listModel = new DefaultListModel<>();

        // populate list and card panel
        CardLayout cards = new CardLayout();
        JPanel p = uif.createPanel("nrd.typecards", cards, false);

        cbHtml = uif.createCheckBox("nrd.type.html", true);
        //cbHtml.addActionListener(cbListener);
        listModel.addElement(cbHtml);
        p.add("nrd.type.html", tabs);

        cbPlain = uif.createCheckBox("nrd.type.pt", true);
        listModel.addElement(cbPlain);
        p.add("nrd.type.pt", uif.createPanel("nrd.blank", false));

        cbXml = uif.createCheckBox("nrd.type.xml", false);
        listModel.addElement(cbXml);
        p.add("nrd.type.xml", uif.createPanel("nrd.blank", false));

        cbCof = uif.createCheckBox("nrd.type.cof", false);
        listModel.addElement(cbCof);

        JPanel cofpanel = createCofPane();
        p.add("nrd.type.cof", cofpanel);

        getCustomReports(p);

        list = uif.createList("nrd.typel", listModel);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        SelectListener sl = new SelectListener(list, p, cards);
        list.addMouseListener(sl);
        list.addKeyListener(sl);
        list.addListSelectionListener(sl);
        list.setCellRenderer(new CheckBoxListCellRenderer());
        list.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createEtchedBorder(),
                BorderFactory.createEmptyBorder(0, 3, 0, 3)));

        infoArea = uif.createMessageArea("nrd.info");
        infoArea.setRows(3);
        Font f = infoArea.getFont();
        f = f.deriveFont(Font.PLAIN, f.getSize2D() - 1);
        infoArea.setFont(f);

        // create container for right side (next to list)
        JPanel right = uif.createPanel("nrd.rptright", false);
        right.setLayout(new BorderLayout());
        right.add(infoArea, BorderLayout.PAGE_START);
        right.add(p, BorderLayout.CENTER);
        middle.add(right, BorderLayout.CENTER);

        JScrollPane listPane = uif.createScrollPane(list);

        middle.add(listPane, BorderLayout.WEST);
        body.add(middle, lc);
//-----------------------------------------------------------------

        Action listMnemonics = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                int ord;
                try {
                    ord = Integer.parseInt(e.getActionCommand()) - 1;
                } catch (Exception ex) {
                    // ignore
                    return;
                }
                if (ord == -1) {
                    ord = 9;
                }
                if (ord <= list.getModel().getSize()) {
                    list.requestFocusInWindow();
                    list.setSelectedIndex(ord);
                }
            }

        };

        list.getActionMap().put(ACTION_MAP_KEY, listMnemonics);

        int itemCount = listModel.size();
        if (itemCount > 9) {
            itemCount = 9;
        }
        for (int i = 1; i <= itemCount; ++i) {
            list.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(i + '0', InputEvent.ALT_DOWN_MASK), ACTION_MAP_KEY);
            JCheckBox box = listModel.getElementAt(i - 1);
            box.setMnemonic(i + '0');
            box.setText(Character.toString((char) ('0' + i)) + " " + box.getText());
        }

        if (listModel.size() == 10) {
            list.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke('0', InputEvent.ALT_DOWN_MASK), ACTION_MAP_KEY);
            JCheckBox box = listModel.getElementAt(9);
            box.setMnemonic('0');
            box.setText("0 " + box.getText());
        }

//-----------------------------------------------------------------
        JPanel bottom = createBackupOpsPane();
        bottom.setBorder(BorderFactory.createCompoundBorder(
                uif.createTitledBorder("nrd.backup"),
                BorderFactory.createEmptyBorder(12, 12, 12, 12)));

        body.add(bottom, lc);
        setBody(body);
//--------------------------------------------------------------------------------------

        okBtn = uif.createButton("nrd.ok", listener);
        sl.setOkBtn(okBtn);
        cancelBtn = uif.createCancelButton("nrd.cancel", listener);
        JButton helpBtn = uif.createHelpButton("nrd.help", "report.newReport.csh");
        setButtons(new JButton[]{okBtn, cancelBtn, helpBtn}, cancelBtn);

        list.setSelectedIndex(0);
        setState(Report.getSettingsPrefs());
    }

    /**
     * Options for the HTML report.
     */
    private JComponent createHtmlBlock() {
        JPanel p = uif.createPanel("nrd.htmlops", new GridBagLayout(),
                false);
        p.setName("htmlops");
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.NONE;
        gbc.anchor = GridBagConstraints.WEST;
        gbc.gridwidth = 2;
        gbc.gridy = 0;

        // info about this block of buttons
        JTextArea info = uif.createMessageArea("nrd.htmlops");
        //info.setBorder(BorderFactory.createLineBorder(Color.black));
        p.add(info, gbc);

        // checkboxes
        // these are given settings here, but setting will be reloaded
        // elsewhere before the user sees them
        JCheckBox cb = uif.createCheckBox("nrd.htmlops.config", true);
        cbConfig = cb;
        cbConfig.addActionListener(cbHtmlListener);
        htmlGroup.add(cb);
        gbc.gridy = 1;
        p.add(cb, gbc);

        // question log
        gbc.gridwidth = 1;
        gbc.gridy = 2;
        cb = cbQl = uif.createCheckBox("nrd.htmlops.ql", true);
        configGroup.add(cb);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        p.add(cb, gbc);

        // environment
        gbc.gridy = 3;
        cb = cbEnv = uif.createCheckBox("nrd.htmlops.env", false);
        configGroup.add(cb);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        p.add(cb, gbc);

        // standard values
        gbc.gridy = 4;
        cb = cbStd = uif.createCheckBox("nrd.htmlops.std", false);
        configGroup.add(cb);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        p.add(cb, gbc);

        // result summary
        gbc.gridy = 5;
        gbc.gridwidth = 2;
        cb = cbResults = uif.createCheckBox("nrd.htmlops.res", true);
        htmlGroup.add(cb);
        p.add(cb, gbc);

        // KFL summary
        gbc.gridy = 6;
        gbc.gridwidth = 2;
        cb = cbKfl = uif.createCheckBox("nrd.htmlops.kfl", true);
        htmlGroup.add(cb);
        p.add(cb, gbc);

        // keyword summary
        cb = cbKws = uif.createCheckBox("nrd.htmlops.kw", true);
        htmlGroup.add(cb);
        gbc.gridy = 7;
        p.add(cb, gbc);

        JScrollPane sp = uif.createScrollPane(p,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        sp.setViewportBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createEmptyBorder(10, 5, 5, 5),
                uif.createTitledBorder("nrd.htmlops")
        ));
        sp.addPropertyChangeListener("enabled", new PanelEnableListener(sp));
        return sp;
    }

    /**
     * Options for the HTML report files.
     */
    private JComponent createFilesBlock() {
        JPanel p = uif.createPanel("nrd.htmlf", new GridBagLayout(),
                false);
        p.setName("htmlf");
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.WEST;
        //gbc.gridx = 0;
        gbc.gridwidth = 3;
        gbc.gridy = 0;

        // info about this block of buttons
        JTextArea info = uif.createMessageArea("nrd.htmlf");
        //info.setBorder(BorderFactory.createLineBorder(Color.black));
        p.add(info, gbc);

        // settings are given here, but settings will be reloaded
        // elsewhere before the user sees them

        // index.html and/or report.html
        JLabel lab = uif.createLabel("nrd.htmlf.main", false);
        gbc.gridy = 1;
        p.add(lab, gbc);

        gbc.gridwidth = 1;
        gbc.gridy = 2;
        JCheckBox cb = cbHtmlRpt = uif.createCheckBox("nrd.htmlf.rpt", true);
        cbHtmlRpt.addActionListener(cbHtmlListener);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        gbc.gridy = 3;
        gbc.gridwidth = 1;
        cb = cbHtmlInd = uif.createCheckBox("nrd.htmlf.idx", false);
        cbHtmlInd.addActionListener(cbHtmlListener);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        // pass, fail, error, notrun files
        gbc.gridy = 4;
        gbc.gridwidth = 3;
        lab = uif.createLabel("nrd.htmlf.xtra", false);
        p.add(lab, gbc);

        gbc.gridy = 5;
        cb = uif.createCheckBox("nrd.htmlf.pass", true);
        htmlGroup.add(cb);
        cbPass = cb;
        gbc.gridwidth = 1;
        p.add(uif.createHorizontalStrut(25), gbc);
        p.add(cb, gbc);

        cb = cbErr = uif.createCheckBox("nrd.htmlf.err", true);
        htmlGroup.add(cb);
        p.add(cb, gbc);

        gbc.gridy = 6;
        cb = cbFail = uif.createCheckBox("nrd.htmlf.fail", true);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        p.add(cb, gbc);

        cb = cbNr = uif.createCheckBox("nrd.htmlf.nr", true);
        htmlGroup.add(cb);
        p.add(cb, gbc);

        JScrollPane sp = uif.createScrollPane(p,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        sp.setViewportBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createEmptyBorder(10, 5, 5, 5),
                uif.createTitledBorder("nrd.htmlf")
        ));

        sp.addPropertyChangeListener("enabled", new PanelEnableListener(sp));
        return sp;
    }

    /**
     * Options for COF report
     */
    private JPanel createCofPane() {
        JPanel cofpanel = uif.createPanel("nrd.cofops");
        cofpanel.setLayout(new GridBagLayout());
        cbCofTestCases = uif.createCheckBox("nrd.cofops.allowtestcases", true);
        GridBagConstraints cb = new GridBagConstraints();
        cb.anchor = GridBagConstraints.NORTHWEST;
        cb.weightx = 1.;
        cb.weighty = 1.;
        cb.ipadx = 8;
        cb.ipady = 8;
        cofpanel.add(cbCofTestCases, cb);
        cofpanel.addPropertyChangeListener("enabled", new PanelEnableListener(cofpanel));
        return cofpanel;
    }

    /**
     * Options for the KFL report files.
     */
    private JComponent createKflBlock() {
        JPanel p = uif.createPanel("nrd.htmlkfl", new GridBagLayout(),
                false);
        p.setName("htmlkfl");
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        //gbc.gridx = 0;
        gbc.gridwidth = 3;
        gbc.gridy = 0;

        // info about this block of buttons
        JTextArea info = uif.createMessageArea("nrd.htmlkfl");
        //info.setBorder(BorderFactory.createLineBorder(Color.black));
        p.add(info, gbc);

        // settings are given here, but settings will be reloaded
        // elsewhere before the user sees them

        // general KFL options
        JLabel lab = uif.createLabel("nrd.htmlkfl.ops", false);
        gbc.gridy = 1;
        p.add(lab, gbc);

        gbc.gridwidth = 1;
        gbc.gridy = 2;

        JCheckBox cb = cbKflTc = uif.createCheckBox("nrd.htmlkfl.checktc", true);
        cbKflTc.addActionListener(cbHtmlListener);
        cbKflTc.setSelected(true);
        kflGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        // kfl file options
        gbc.gridy = 3;
        gbc.gridwidth = 3;
        lab = uif.createLabel("nrd.htmlkfl.xtra", false);
        p.add(lab, gbc);

        gbc.gridy = 4;
        gbc.gridwidth = 1;

        cb = cbKflF2f = uif.createCheckBox("nrd.htmlkfl.f2f", true);
        cbKflF2f.addActionListener(cbHtmlListener);
        cbKflF2f.setSelected(true);
        kflGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        gbc.gridy = 5;
        gbc.gridwidth = 1;
        cb = cbKflF2e = uif.createCheckBox("nrd.htmlkfl.f2e", false);
        cbKflF2e.addActionListener(cbHtmlListener);
        cbKflF2e.setSelected(true);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        gbc.gridy = 6;
        gbc.gridwidth = 1;
        cb = cbKflMissing = uif.createCheckBox("nrd.htmlkfl.missing", false);
        cbKflMissing.addActionListener(cbHtmlListener);
        cbKflMissing.setSelected(true);
        htmlGroup.add(cb);
        p.add(uif.createHorizontalStrut(25), gbc);
        gbc.gridwidth = 2;
        p.add(cb, gbc);

        JScrollPane sp = uif.createScrollPane(p,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        sp.setViewportBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createEmptyBorder(10, 5, 5, 5),
                uif.createTitledBorder("nrd.htmlkfl")
        ));

        sp.addPropertyChangeListener("enabled", new PanelEnableListener(sp));
        return sp;
    }

    private JPanel createBackupOpsPane() {
        JPanel p = uif.createPanel("nrd.backup", new GridBagLayout(),
                false);
        p.setName("backup");
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.WEST;
        gbc.gridx = 0;
        gbc.gridwidth = 3;

        // info about this block of buttons
        JTextArea info = uif.createMessageArea("nrd.backup");
        //info.setBorder(BorderFactory.createLineBorder(Color.black));
        p.add(info, gbc);

        // checkboxes
        // these are given settings here, but setting will be reloaded
        // elsewhere before the user sees them
        JCheckBox cb = cbBak = uif.createCheckBox("nrd.backup.bak", true);
        cbBak.addChangeListener(e -> {
            if (numBak != null) {
                numBak.setEnabled(cbBak.isSelected());
            }
        });
        p.add(cb, gbc);

        gbc.gridwidth = 1;
        gbc.gridy = 2;
        gbc.gridx = GridBagConstraints.RELATIVE;
        p.add(uif.createHorizontalStrut(25), gbc);

        JLabel lab = uif.createLabel("nrd.backup.bak.level", true);
        p.add(lab, gbc);

        // backup levels
        numBak = uif.createInputField("nrd.backup.bak.level", 2, lab);
        gbc.insets.left = 12;
        numBak.setText("1");

        Action action = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JTextField src = (JTextField) e.getSource();
                String cmd = e.getActionCommand();
                char ch = cmd.charAt(0);
                int savedDot = src.getCaret().getDot();
                int savedMark = src.getCaret().getMark();
                String savedText = src.getText();

                // reject all non-numeric chars
                if (Character.isDigit(ch) && ch != '0') {
                    Action delegate = new javax.swing.text.DefaultEditorKit.InsertContentAction();
                    delegate.actionPerformed(e);

                    String newText = src.getText();
                    // reject new input if it leaves an invalid value,
                    // i.e. more than one digit (>9)
                    if (newText.length() > 1) {
                        src.setText(savedText);
                    }
                }
            }
        };
        Keymap keymap = JTextComponent.addKeymap("intField", numBak.getKeymap());
        keymap.setDefaultAction(action);
        numBak.setKeymap(keymap);

        p.add(numBak, gbc);

        p.setMaximumSize(p.getPreferredSize());
        return p;
    }

    /**
     * @return number of custom reports added
     */
    private int getCustomReports(JPanel p) {
        int result = 0;

        ContextManager cm = model.getContextManager();
        if (cm == null) {
            return 0;
        }

        customReports = cm.getCustomReports();
        if (customReports == null || customReports.length == 0) {
            customReports = null;
            return 0;
        }

        customBoxes = new HashMap<>();

        for (CustomReport customReport : customReports) {
            JCheckBox cb = new JCheckBox(customReport.getName());
            cb.setName(customReport.getReportId());
            listModel.addElement(cb);
            customBoxes.put(cb, customReport);

            ReportConfigPanel[] ops = customReport.getOptionPanes();
            if (ops == null || ops.length == 0) {
                // no config panels, use blank
                p.add(customReport.getReportId(),
                        uif.createPanel("nrd.blank", false));
            } else {
                // tabbed pane for all supplied panels
                JTabbedPane tp = uif.createTabbedPane("nrd.custom.tabs");
                for (ReportConfigPanel op : ops) {
                    tp.addTab(op.getPanelName(), op);
                }

                p.add(customReport.getReportId(), tp);
            }
            result++;
        }   // for

        return result;
    }

    /**
     * Walks through the list of check boxes.
     *
     * @return false iff none of check boxes is selected, true otherwise
     */
    private boolean hasSelectedCheckBox() {
        for (int i = 0; i < listModel.getSize(); i++) {
            if (listModel.getElementAt(i).isSelected()) {
                return true;
            }
        }
        return false;
    }

    private void showReportChooserDialog() {
        ReportDirChooser rdc = getReportDirChooser();
        rdc.setMode(ReportDirChooser.NEW);
        int option = rdc.showDialog(dirField);
        if (option != JFileChooser.APPROVE_OPTION) {
            return;
        }

        dirField.setText(rdc.getSelectedFile().getAbsolutePath());
    }

    private ReportDirChooser getReportDirChooser() {
        if (reportDirChooser == null) {
            reportDirChooser = new ReportDirChooser();
        }
        return reportDirChooser;
    }

    void showReportBrowser(File f) {
        if (f.equals(new File(reportDir, Report.INDEX_FILE_NAME))) {
            reportBrowser.show(f);
        } else {
            String[] names = Report.getHtmlReportFilenames();
            List<File> possible = new ArrayList<>();

            for (String name : names) {
                File rpt = new File(reportDir, name);
                if (rpt.exists() && rpt.canRead()) {
                    possible.add(rpt);
                } else {
                    rpt = null;
                }
            }

            if (!possible.isEmpty()) {
                // show the most recently dated file
                File rpt = null;
                File newestF = null;
                long newestTime = 0;

                for (File file : possible) {
                    if (file.lastModified() > newestTime) {
                        newestF = file;
                        newestTime = file.lastModified();
                    }
                }       // for
                reportBrowser.show(newestF);
            } else {
                reportBrowser.show(reportDir);
            }
        }
    }

    /**
     * Normally called by the background thread.
     */
    private void runReport(ReportSettings settings) throws IOException {
        if (interviewParams != null) {
            Report r = new Report();

            // if no work directory selected, throw exception.
            if (interviewParams.getWorkDirectory() == null) {
                throw new IOException(uif.getI18NString("nrd.noWorkDir.err"));
            }
            r.addStartGenListener(new ReportGenListener());
            settings.setCustomReports(getActiveCustomReports());
            r.writeReports(settings, reportDir);

        } else {
            throw new IOException(uif.getI18NString("nrd.nullParams.err"));
        }
    }

    private ArrayList<CustomReport> getActiveCustomReports() {

        ArrayList<CustomReport> customReps = new ArrayList<>();
        if (customBoxes != null && !customBoxes.isEmpty()) {
            for (JCheckBox box : customBoxes.keySet()) {
                if (box.isSelected()) {
                    customReps.add(customBoxes.get(box));
                }
            }
        }
        return customReps;
    }

    private boolean isEmptyDirectory(File f) {
        return f.isDirectory() && f.list().length == 0;
    }

    private void updateHtmlCheckboxStates() {
        // config buttons done before html buttons to ensure that
        // config gets disabled if HTML reporting is off

        if (!cbHtml.isSelected()) {
            for (JCheckBox aHtmlGroup : htmlGroup) {
                aHtmlGroup.setEnabled(false);
            }
        } else {
            for (JCheckBox aHtmlGroup : htmlGroup) {
                aHtmlGroup.setEnabled(true);
            }
            for (JCheckBox aConfigGroup : configGroup) {
                aConfigGroup.setEnabled(cbConfig.isSelected());
            }
        }
    }

    private void updateCofCheckboxStates() {
        if (!cbCof.isSelected()) {
            cbCofTestCases.setEnabled(false);
        } else {
            cbCofTestCases.setEnabled(true);
        }
    }

    private ReportSettings captureState() {
        ReportSettings snap = new ReportSettings(interviewParams);

        snap.setEnableHtmlReport(cbHtml.isSelected());
        snap.setEnableXmlReport(cbXml.isSelected());
        snap.setEnablePlainReport(cbPlain.isSelected());
        snap.setEnableCOFReport(cbCof.isSelected());

        snap.setUseTestCases(cbCofTestCases.isSelected());

        snap.setShowConfigSection(cbConfig.isSelected());
        snap.setShowEnvLog(cbEnv.isSelected());
        snap.setShowQuestionLog(cbQl.isSelected());
        snap.setShowStdValues(cbStd.isSelected());
        snap.setShowResults(cbResults.isSelected());
        snap.setShowKflReport(cbKfl.isSelected());
        snap.setShowKeywordSummary(cbKws.isSelected());

        snap.setHtmlMainReport(cbHtmlRpt.isSelected(), cbHtmlInd.isSelected());
        snap.setEnableHtmlStateFile(Status.PASSED, cbPass.isSelected());
        snap.setEnableHtmlStateFile(Status.FAILED, cbFail.isSelected());
        snap.setEnableHtmlStateFile(Status.ERROR, cbErr.isSelected());
        snap.setEnableHtmlStateFile(Status.NOT_RUN, cbNr.isSelected());

        snap.setEnableKflF2e(cbKflF2e.isSelected());
        snap.setEnableKflF2f(cbKflF2f.isSelected());
        snap.setEnableKflMissing(cbKflMissing.isSelected());
        snap.setEnableKflTestCases(cbKflTc.isSelected());

        snap.setEnableBackups(cbBak.isSelected());

        try {
            int i = Integer.parseInt(numBak.getText());
            snap.setBackupLevels(i);
        } catch (NumberFormatException e) {
        }

        TestFilter filter = filterHandler.getActiveFilter();

        // see Report.Settings for info on what this is:
        /*
          // commented out because Optimization will be enforced
          // when filter is ParameterFilter
        if (filter instanceof ParameterFilter)
            snap.setAllowInitFilesOptimize(true);
        else
            snap.setAllowInitFilesOptimize(false);
         */

        snap.setFilter(filter);

        return snap;
    }

    private void setState(ReportSettings snap) {
        if (snap == null) {
            return;
        }

        lastSettings = snap;


        cbHtml.setSelected(snap.isHtmlEnabled());
        cbXml.setSelected(snap.isXmlEnabled());
        cbPlain.setSelected(snap.isPlainEnabled());
        boolean isCofEnabled = snap.isCOFEnabled();
        cbCof.setSelected(isCofEnabled);

        cbCofTestCases.setSelected(snap.isCOFTestCasesEnabled());
        cbCofTestCases.setEnabled(isCofEnabled);

        // ---
        boolean section = snap.isConfigSectionEnabled();
        cbConfig.setSelected(section);

        cbEnv.setSelected(snap.isEnvEnabled());
        cbQl.setSelected(snap.isQuestionLogEnabled());
        cbStd.setSelected(snap.isStdEnabled());

        cbEnv.setEnabled(section);
        cbQl.setEnabled(section);
        cbStd.setEnabled(section);

        // ---
        cbResults.setSelected(snap.isResultsEnabled());
        cbKfl.setSelected(snap.isKflEnabled());
        cbKws.setSelected(snap.isKeywordSummaryEnabled());

        cbHtmlRpt.setSelected(snap.isReportHtmlEnabled());
        cbHtmlInd.setSelected(snap.isIndexHtmlEnabled());
        cbPass.setSelected(snap.isStateFileEnabled(Status.PASSED));
        cbFail.setSelected(snap.isStateFileEnabled(Status.FAILED));
        cbErr.setSelected(snap.isStateFileEnabled(Status.ERROR));
        cbNr.setSelected(snap.isStateFileEnabled(Status.NOT_RUN));

        cbKflF2e.setSelected(snap.isKflF2eEnabled());
        cbKflF2f.setSelected(snap.isKflF2fEnabled());
        cbKflMissing.setSelected(snap.isKflMissingEnabled());
        cbKflTc.setSelected(snap.isKflTestCasesEnabled());

        cbBak.setSelected(snap.isBackupsEnabled());
        numBak.setText(Integer.toString(snap.getBackupLevel()));

        // a null value will be ignored
        filterHandler.setFilter(snap.getTestFilter());
    }
    interface Observer {
        // if ever made public, should rethink method names and if any helpful
        // parameters are passed

        /**
         * The dialog is done. Notify the observer to keep a snapshot of
         * current state.
         */
        void update(Map<String, String> l);

        // starting
        void writingReport();

        // done
        void wroteReport();

        // error creating report
        // upgrade to use resource bundle+key as message
        // the given error occurred, and the user was informed
        void errorWriting(String problem);
    }

    private static class Stopper {
        boolean waitWasHidden = false;
    }

    /*
     * PropertyChangeListener for enabling/disabling container's content
     */
    private static class PanelEnableListener implements PropertyChangeListener {
        private Container theContainer;
        private HashSet<Component> enabledComp;

        /**
         * @param container Container for controlling
         */
        PanelEnableListener(Container container) {
            theContainer = container;
        }

        /**
         * Catches changes of "enabled" property
         * and changes enabled status for all child components
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("enabled".equals(evt.getPropertyName())) {
                boolean oldV = (Boolean) evt.getOldValue();
                boolean newV = (Boolean) evt.getNewValue();
                if (oldV && !newV) {
                    // disable
                    enabledComp = new HashSet<>();
                    for (Component c : collectChildren(theContainer, new ArrayList<>())) {
                        if (c.isEnabled()) {
                            enabledComp.add(c);
                            c.setEnabled(false);
                        }
                    }
                } else if (!oldV && newV && enabledComp != null) {
                    // enable
                    for (Component c : collectChildren(theContainer, new ArrayList<>())) {
                        if (enabledComp.contains(c)) {
                            c.setEnabled(true);
                        }
                    }
                }
            }
        }

        /**
         * Recursively gathers all children components
         */
        private Collection<Component> collectChildren(Container comp, Collection<Component> c) {
            Component[] ch = comp.getComponents();
            for (Component aCh : ch) {
                c.add(aCh);
                if (aCh instanceof Container) {
                    collectChildren((Container) aCh, c);
                }
            }
            return c;
        }
    }

    private static class CheckBoxListCellRenderer implements ListCellRenderer<JComponent> {
        @Override
        public Component getListCellRendererComponent(
                JList<? extends JComponent> list,
                JComponent component,
                int index,
                boolean isSelected,
                boolean cellHasFocus) {
            // assert: component is a JCheckBox
            if (isSelected) {
                component.setOpaque(true);
                component.setBackground(list.getSelectionBackground());
                component.setForeground(list.getSelectionForeground());
            } else {
                component.setOpaque(false);
                component.setForeground(list.getForeground());
            }

            return component;
        }
    }

    private class ReportGenListener implements StartGenListener {
        @Override
        public void startReportGeneration(ReportSettings s, String reportID) {
            String reportName;
            if (reportID.equals("xml") || reportID.equals("pt") ||
                    reportID.equals("html")) {
                reportName = uif.getI18NString("nrd.type." + reportID + ".ckb");
            } else {
                reportName = reportID;
            }

            String status = uif.getI18NString("nrd.wait.report_gen",
                    reportName);

            Component[] cmp = waitDialog.getContentPane().getComponents();
            for (Component aCmp : cmp) {
                if ("nrd.wait".equals(aCmp.getName())) {
                    if (aCmp instanceof JTextComponent) {
                        ((JTextComponent) aCmp).setText(status);
                    }
                    break;
                }
            }
        }
    }

    /**
     * This listener changes options state against checkboxes
     */
    private class SelectListener extends MouseInputAdapter implements KeyListener, ListSelectionListener {

        Object lastSelected;
        JList<?> list;
        ListModel<?> listModel;
        JPanel panel;
        CardLayout cards;
        JButton okBtn = null; // should be disable iff all check boxes are off
        double emptyCBW = new JCheckBox("").getPreferredSize().getWidth() + 2;

        /**
         * @param lst        JList of checkboxes
         * @param p          parent Panel
         * @param cardLayout The CardLayout for options
         */
        SelectListener(JList<?> lst, JPanel p, CardLayout cardLayout) {
            list = lst;
            listModel = list.getModel();
            lastSelected = listModel.getElementAt(0);
            panel = p;
            cards = cardLayout;
        }

        @Override
        public void keyTyped(KeyEvent e) {
            if (e.getKeyChar() == ' ') {
                process(list.getSelectedIndex());
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (e.getPoint().getX() <= emptyCBW) {
                process(list.locationToIndex(e.getPoint()));
            }
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            int index = list.getSelectedIndex();
            if (index < 0) {
                // no element selected in the list
                return;
            }
            JCheckBox box = (JCheckBox) listModel.getElementAt(index);

            if (lastSelected != box) {
                cards.show(panel, box.getName());
                lastSelected = box;
            }
            enablePanel(box);
        }

        private void enablePanel(JCheckBox box) {
            for (int i = 0; i < panel.getComponentCount(); i++) {
                JComponent tab = (JComponent) panel.getComponent(i);
                tab.setEnabled(box.isSelected());
            }
            if (customBoxes != null) {
                CustomReport rep = customBoxes.get(box);
                if (rep != null && rep.getOptionPanes() != null) {
                    for (int i = 0; i < rep.getOptionPanes().length; i++) {
                        rep.getOptionPanes()[i].setEnabled(box.isSelected());
                    }
                } else if (box == cbHtml) {
                    for (JComponent pane : panes) {
                        pane.setEnabled(box.isSelected());
                    }
                }
            }

            if (box == cbHtml) {
                infoArea.setText(uif.getI18NString("nrd.info.html.txt"));
            } else if (box == cbPlain) {
                infoArea.setText(uif.getI18NString("nrd.info.plain.txt"));
            } else if (box == cbXml) {
                infoArea.setText(uif.getI18NString("nrd.info.xml.txt"));
            } else if (box == cbCof) {
                infoArea.setText(uif.getI18NString("nrd.info.cof.txt"));
            } else if (customBoxes != null) {
                CustomReport rep = customBoxes.get(box);
                if (rep != null) {
                    infoArea.setText(rep.getDescription());
                }
            }

        }

        private void process(int index) {
            if (index < 0) {
                return;
            }
            JCheckBox box = (JCheckBox) listModel.getElementAt(index);

            if (lastSelected == box) {
                box.doClick();
                if (okBtn != null) {
                    okBtn.setEnabled(hasSelectedCheckBox());
                }
                if (box == cbHtml) {
                    updateHtmlCheckboxStates();
                }
                if (box == cbCof) {
                    updateCofCheckboxStates();
                }
                list.repaint();     // important!
                enablePanel(box);
            }
            lastSelected = box;
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
        }

        /**
         * Sets the button to change enable/disable state. If none of
         * check boxes is selected, the button will be disabled until a check
         * box is selected.
         *
         * @param okBtn - button
         */
        private void setOkBtn(JButton okBtn) {
            this.okBtn = okBtn;
        }
    }

}
