ai.aitia.meme.viewmanager.Page_Columns.java Source code

Java tutorial

Introduction

Here is the source code for ai.aitia.meme.viewmanager.Page_Columns.java

Source

/*******************************************************************************
 * Copyright (C) 2006-2013 AITIA International, Inc.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package ai.aitia.meme.viewmanager;

import static ai.aitia.meme.gui.ExpandedEditor.makeEEButton;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;

import ai.aitia.meme.MEMEApp;
import ai.aitia.meme.UserPrefs;
import ai.aitia.meme.database.ColumnType;
import ai.aitia.meme.gui.DraggableViewport;
import ai.aitia.meme.gui.MainWindow;
import ai.aitia.meme.gui.Wizard;
import ai.aitia.meme.gui.lop.LongRunnable;
import ai.aitia.meme.pluginmanager.IVCPlugin;
import ai.aitia.meme.pluginmanager.Parameter;
import ai.aitia.meme.pluginmanager.PluginInfo;
import ai.aitia.meme.pluginmanager.PluginManager.PluginList;
import ai.aitia.meme.pluginmanager.impl.PluginContextBase;
import ai.aitia.meme.utils.FormsUtils;
import ai.aitia.meme.utils.GUIUtils;
import ai.aitia.meme.utils.Utils;
import ai.aitia.meme.utils.FormsUtils.CellInsets;
import ai.aitia.meme.utils.FormsUtils.Separator;
import ai.aitia.meme.viewmanager.JTableScrollPane.ColInfo;
import ai.aitia.meme.viewmanager.ParameterSet.Category;
import ai.aitia.meme.viewmanager.ParameterSet.Par;
import ai.aitia.meme.viewmanager.ParameterSet.ParListCellRenderer;

import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;

/** Wizard page "Computation" in the View Creation Wizard. */
public class Page_Columns extends JPanel
        implements Runnable, Wizard.IArrowsInHeader, Wizard.IWizardPage, Wizard.ICloseListener,
        JTableScrollPane.IDataTypeCalculator, Utils.NameGenerator.IFinder, java.awt.event.ActionListener,
        javax.swing.event.ListSelectionListener, javax.swing.event.ListDataListener, javax.swing.ListCellRenderer {
    private static final long serialVersionUID = 1L;
    private java.io.File helpFile = new java.io.File(MEMEApp.g_AppDir, "Documents/MEME_Beanshell.pdf");
    private static final String DEFAULT_SPLITTER_DELIMITER = "_";
    private static javax.swing.ImageIcon pluginIcon = null;
    private static final AggrComboItem VECTOR_MODE_SCRIPT = new AggrComboItem(null);
    private static final int PROBLEM_TIMEOUT = 5 * 1000;
    // warning levels
    private static final int WL_ALL = 0;
    private static final int WL_NONE = 0;
    private static final int WL_COLUMN_PROP = 0;
    private static final int WL_NO_COLUMN_PROP = 1;
    private static final int WL_TABLE = 1;
    // clearForm() constants
    private static final int CF_ADD = -1;
    private static final int CF_CANCEL = -2;

    private static final int idx_AllPars = Category.values().length;

    private static enum Mode {
        IMMEDIATE, LATER, SCHEDULE_END
    };

    private ViewCreationDialog owner = null;
    private boolean in_enableDisableButtons = false;
    private boolean clearName = false;
    private boolean updateScheduled = false;
    private boolean updateRule = true;
    private boolean editedColumn = false;
    private String generatedColName = null;
    private ArrayList<? extends Parameter> allPars = null;
    private BitSet visiblePars = null;
    private Utils.Pair<String, Integer> warning = new Utils.Pair<String, Integer>(null, WL_NONE); // text, level
    private int warningLevel = WL_ALL;
    private Utils.TimerHandle warningTimer = null;
    private int editedIdx = -1; // indicates whether a new (<0) or an existing (>=0)  
    // column's properties are being edited
    private Boolean wasSplitter = null; // az aktualisan szerkesztett oszlop eredetileg splitter volt-e
    private boolean jScriptTextAreaWasEmpty = true;
    private DefaultListCellRenderer comboRenderer = null;

    private JTextArea jFilterTextArea = new JTextArea();
    private JTextArea jScriptTextArea = new JTextArea();
    private JTextField jColNameTextField = new JTextField();
    private JTextField jSplittedDelimTextField = new JTextField();
    private JComboBox jAggregateCombo = new JComboBox();

    private JTableScrollPane jTableScrollPane = null;
    private JList[] jParList = null;
    private JTabbedPane jParListTabbedPane = null;
    private Color pltpNormalColor, pltpOtherColor; // "pltp" = "ParListTabbedPane"

    private String filterLabel = "&Filter expression:";
    private JLabel jResultTypeLabel = new JLabel("Result type:");
    private JLabel jNamingDelimiter = new JLabel("Naming delimiter:");

    private JButton jFilterEEButton = makeEEButton(jFilterTextArea, filterLabel);
    private JButton jScriptEEButton = null; // initialized later, need owner to do it
    private JButton jHelpButton = new JButton("Help");
    private JButton jMoveUpButton = new JButton("Move up");
    private JButton jMoveDownButton = new JButton("Move down");
    private JButton jAddButton = new JButton();
    private JButton jCancelButton = new JButton();
    private JButton jRemoveButton = new JButton("Remove");
    private JButton jEditButton = new JButton("Edit");
    private JToggleButton jBooleanDataTypeButton = null;
    private JToggleButton jIntDataTypeButton = null;
    private JToggleButton jRealDataTypeButton = null;
    private JToggleButton jStringDataTypeButton = null;
    private JCheckBox jSplittedCheckBox = new JCheckBox("One column per splitter value");
    private JCheckBox jHiddenCheckBox = new JCheckBox("Hidden");
    private JCheckBox jForceAggregateCheckBox = new JCheckBox(
            "Exclude from grouping / used in aggregate calculations only");
    private JCheckBox jScriptCheckBox = new JCheckBox("Custom expression or script");
    private JPopupMenu tableContextMenu = new JPopupMenu();

    private AbstractAction moveUpAction = null;
    private AbstractAction moveDownAction = null;
    private AbstractAction removeAction = null;
    private AbstractAction editAction = null;

    //=========================================================================
    //   Public methods

    //-------------------------------------------------------------------------
    public Page_Columns(ViewCreationDialog owner) {
        super();
        this.owner = owner;
        jScriptEEButton = makeEEButton(jScriptTextArea, "Custom expression or script:", true, owner.params);
        initialize();
    }

    //-------------------------------------------------------------------------
    public String getCondition() {
        return jFilterTextArea.getText();
    }

    public int getColumnCount() {
        return getData().getRowCount();
    }

    public ViewCreationRule.Column getColumn(int col) {
        return getJTableScrollPane().getViewCol(col);
    }

    //-------------------------------------------------------------------------
    /** Returns true if grouping is used: at least one column requires aggregative calculation */
    public boolean getGrouping() {
        //return jUseGroupingCheckBox.isSelected();
        for (int i = 0, n = getColumnCount(); i < n; ++i) {
            ViewCreationRule.Column c = getColumn(i);
            if (!c.isScalar())
                return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    /** 
     * @return 0: -splitter-splitted, 1: +splitter-splitted, 
     *          2: -splitter+splitted, 3: +splitter+splitted
     */
    public int isSplittingUsed() {
        boolean splitter = false, splitted = false;
        for (int i = 0, n = getColumnCount(); i < n; ++i) {
            ViewCreationRule.Column c = getColumn(i);
            splitter = splitter || c.isSplitter();
            splitted = splitted || c.isSplitted();
        }
        return (splitter ? 1 : 0) + (splitted ? 2 : 0);
    }

    public void update() {
        updateDataTypes(Mode.LATER);
    }

    //=========================================================================
    //   Interface methods

    //-------------------------------------------------------------------------
    public String getTitle() {
        return "Computation";
    }

    public Container getPanel() {
        return this;
    }

    public String getInfoText(Wizard w) {
        String s;
        if (warning.getFirst() != null && warning.getSecond() >= warningLevel)
            s = "<img src=\"gui/icons/warning.png\">&nbsp;&nbsp;" + Utils.htmlQuote(warning.getFirst());
        else
            s = "Define columns of the view table and computation rules ";
        //+ "<img src=\"gui/icons/placeholder16.png\">";

        return w.getArrowsHeader(s);
    }

    //-------------------------------------------------------------------------
    public boolean isEnabled(Wizard.Button b) {
        switch (b) {
        case FINISH:
        case NEXT: {
            int n = getColumnCount();
            boolean ok = (n > 0) && (isSplittingUsed() % 3 == 0);
            for (int i = n - 1; i >= 0 && ok; --i) {
                ok = (getColumn(i).getInitialDataType() != null);
            }
            return ok;
        }
        default:
            return true;
        }
    }

    //-------------------------------------------------------------------------
    public boolean onButtonPress(Wizard.Button b) {
        boolean ans = isEnabled(b);
        // Finish magaba foglalja Modfy-t is, ha lehet
        if (ans && b == Wizard.Button.FINISH && editedIdx >= 0 && jAddButton.isEnabled()) {
            ans = addOrModify();
        }
        if (warning(b == Wizard.Button.FINISH && ans && isAllHidden(), WL_TABLE, "There is no non-hidden column."))
            return false;
        return ans;
    }

    //-------------------------------------------------------------------------
    public void onPageChange(boolean show) {
        if (owner == null) // GUI-builder case
            return;
        // Update the data types to reveal missing columns
        updateDataTypes(Mode.LATER);

        if (show) {
            enableDisableButtons(Mode.LATER);
            setWarningLevel(WL_NO_COLUMN_PROP);
            if (owner != null) {
                // A wizard mas lapjai elallithattak a limit-et, ezert biztos ami biztos beallitjuk.
                owner.getSelectedPars().getListModel(Category.THISVIEW, editedIdx);
            }
        }
    }

    //-------------------------------------------------------------------------
    public void onClose(Wizard w) {
        if (warningTimer != null)
            warningTimer.stop();
        warningTimer = null;
    }

    //-------------------------------------------------------------------------
    /** 
     * Verifies that the source column(s) referenced by 'colInfo' is/are included
     * in the current set of input tables. Returns null if the check fails.
     */
    // [EDT]
    public ColumnType getViewColDataType(ViewCreationRule.Column col, int idx) {
        if (col == null)
            return null;
        ArrayList<Utils.Pair<SrcType, String>> sources = col.getSource();
        ArrayList<Parameter> inputPars = new ArrayList<Parameter>(sources.size());
        for (Utils.Pair<SrcType, String> src : sources) {
            Category c = srcType2Category(src.getFirst());
            Parameter p;
            if (c == null) {
                if (src.getFirst() == SrcType.GROUP_SCRIPT) // group script
                    return col.getInitialDataType();
                if (sources.size() == 1) { // scalar script
                    // the script is the only source
                    p = new Parameter(col.getName(), col.getScriptDataType());
                } else {
                    p = null;
                }
            } else {
                p = owner.getSelectedPars().findParam(src.getSecond(), c, idx);
            }
            if (p == null || p.getDatatype() == null)
                return null; // parameter not found or has unknown type
            inputPars.add(p);
        }
        // inputPars may be empty: some plugins don't need input pars 
        String fn = col.getAggrFn();
        if (fn == null || fn.length() == 0)
            return (inputPars.size() != 1) ? null : inputPars.get(0).getDatatype();
        IVCPlugin pl = getAvailablePlugins().findByName(fn);

        // 'allPars', 'visiblePars' are used in VCPluginContext
        visiblePars = null; // this ensures that 'idx' will be used
        return (pl == null) ? null : pl.getReturnType(new VCPluginContext(inputPars, idx));
    }

    //=========================================================================
    //   Controller methods

    //-------------------------------------------------------------------------
    private DefaultTableModel getData() {
        return getJTableScrollPane().getData();
    }

    private JTable getJTable() {
        return getJTableScrollPane().getJTable();
    }

    //-------------------------------------------------------------------------
    // [EDT]
    /** Enables/disables the buttons of the page. 'm' defines the executing time. */
    public void enableDisableButtons(Mode m) {
        if (in_enableDisableButtons && m != Mode.SCHEDULE_END)
            return;
        if (m == Mode.LATER) {
            in_enableDisableButtons = true;
            Utils.invokeLater(this, "enableDisableButtons", Mode.SCHEDULE_END);
            return;
        }
        boolean save = (m == Mode.SCHEDULE_END) ? false : in_enableDisableButtons;
        in_enableDisableButtons = true;
        try {
            enableDisableButtonsImpl();
        } finally {
            in_enableDisableButtons = save;
        }
    }

    //-------------------------------------------------------------------------
    /** Enables/disables the buttons of the page. */
    private void enableDisableButtonsImpl() {
        // Kenyszerito ertekek: ha van ilyen, akkor ez kell legyen az erteke, es le kell tiltani
        Boolean forceAggrChkBox = null;
        Boolean splitted = null;
        Boolean hidden = null;
        Boolean script = null;
        boolean nameEnabled = true;
        boolean ok = true;
        boolean aggrComboEnabled = true; // jUseGroupingCheckBox.isSelected();
        boolean vectorModeScript = (aggrComboEnabled && jAggregateCombo.getSelectedItem() == VECTOR_MODE_SCRIPT);

        ArrayList<Par> pars = new ArrayList<Par>(getSelectedPars());
        if (generatedColName == null)
            generatedColName = generateName(null);
        String suggestedName = generatedColName;

        // Bug fix #1446
        final Par pRun = new Par("Run ", ColumnType.INT, Category.INPUT);
        final Par pTick = new Par("Tick ", ColumnType.INT, Category.INPUT);
        aggrComboEnabled = !pars.contains(pRun) && !pars.contains(pTick);
        // End of bug fix #1446   

        switch (pars.size()) {
        case 0:
            suggestedName = generateName(null);
            ok = ok && !warning(!jScriptCheckBox.isSelected() && !vectorModeScript, WL_COLUMN_PROP,
                    "No parameter is selected");
            break;
        case 1:
            suggestedName = generateName(pars.get(0).getName());
            script = false;
            break;
        default:
            if (!aggrComboEnabled || jAggregateCombo.getSelectedItem() == null) { // Multi-column projection
                nameEnabled = false;
                forceAggrChkBox = false;
                hidden = false;
                splitted = false;
                // If the user is editing a single, existing column, multi-column projection is disabled:
                ok = ok && !warning(editedIdx >= 0, WL_COLUMN_PROP, "Too many parameters are selected");
            } else { // Aggregative operation with multiple parameters
                nameEnabled = true;
                suggestedName = generateName(null);
            }
            script = false;
            break;
        }

        if (vectorModeScript) {
            script = true;
            ok = ok && !warning(jScriptTextArea.getText().length() == 0, WL_COLUMN_PROP, "The script is empty");
        } else if (jScriptCheckBox.isSelected()) {
            pars.clear();
            pars.add(new Par(jColNameTextField.getText(), getScriptResultType(), Category.THISVIEW));
            ok = ok && !warning(jScriptTextArea.getText().length() == 0, WL_COLUMN_PROP, "The script is empty");
        }
        jAggregateCombo.setEnabled(aggrComboEnabled);
        updateAggrComboList(pars);

        jColNameTextField.setEnabled(nameEnabled);
        if (clearName) {
            jColNameTextField.setText("");
            clearName = false;
        } else {
            String s = jColNameTextField.getText();
            if (nameEnabled && (s.length() == 0 || s.equals(generatedColName))) {
                jColNameTextField.setText(suggestedName);
                generatedColName = suggestedName;
            } else {
                setWarningLevel(WL_ALL);
            }
        }

        if (aggrComboEnabled) {
            if (jAggregateCombo.getSelectedItem() != null) {
                setWarningLevel(WL_ALL);
                forceAggrChkBox = true;
            } else if (pars.size() == 1 && (pars.get(0) instanceof ParameterSet.ThisViewPar)
                    && !((ParameterSet.ThisViewPar) pars.get(0)).col.isScalar()) {
                // vektoros szamolasu oszlopnak a projekcioja is vektoros
                forceAggrChkBox = true;
            }
        }
        if (jHiddenCheckBox.isSelected()) {
            splitted = false;
        }
        switch (isSplittingUsed()) {
        case 0:
            splitted = false;
            break;
        case 1:
            warning(true, WL_TABLE, "Inconsistent specification of splitting: no splitted column");
            break;
        case 2:
            warning(true, WL_TABLE, "Inconsistent specification of splitting: no splitter column");
            break;
        default:
        case 3:
            break;
        }
        // TODO: splitted-et le kell tiltani ha erre az oszlopra hivatkoznak mashol.
        // warning-ban ki kellene irni, hogy ezert lett letiltva.

        force(jSplittedCheckBox, splitted);
        boolean b = jSplittedCheckBox.isSelected();
        jSplittedDelimTextField.setEnabled(b);
        jNamingDelimiter.setEnabled(b);

        if (jSplittedCheckBox.isSelected()) {
            hidden = false;
            forceAggrChkBox = true;
            String name = jColNameTextField.getText();
            if (name.indexOf('%') < 0) {
                jColNameTextField.setText(name + '%');
            }
        } else if (forceAggrChkBox == null && jForceAggregateCheckBox.isSelected()) {
            // Skalar szamolasu oszlop vektorossa minositese egyben segedoszloppa is minositi azt 
            hidden = true;
        }

        if (!jSplittedCheckBox.isSelected()) {
            String name = jColNameTextField.getText();
            if (name.endsWith("%"))
                jColNameTextField.setText(name.substring(0, name.length() - 1));
        }

        force(jScriptCheckBox, script);
        b = jScriptCheckBox.isSelected();
        jResultTypeLabel.setEnabled(b);
        jBooleanDataTypeButton.setEnabled(b);
        jIntDataTypeButton.setEnabled(b);
        jRealDataTypeButton.setEnabled(b);
        jStringDataTypeButton.setEnabled(b);
        jScriptTextArea.setEnabled(b);
        jScriptEEButton.setEnabled(b);
        jHelpButton.setEnabled(b);

        force(jHiddenCheckBox, hidden);
        force(jForceAggregateCheckBox, forceAggrChkBox);

        // The event is coming from the table
        ListSelectionModel lsm = getJTable().getSelectionModel();
        b = !lsm.isSelectionEmpty();
        jEditButton.setEnabled(b);
        jRemoveButton.setEnabled(b);
        editAction.setEnabled(b);
        removeAction.setEnabled(b);
        b = b && (getJTable().getRowCount() > 1);
        jMoveUpButton.setEnabled(b);
        jMoveDownButton.setEnabled(b);
        moveUpAction.setEnabled(b);
        moveDownAction.setEnabled(b);
        if (editedColumn) {
            jMoveUpButton.setEnabled(false);
            jMoveDownButton.setEnabled(false);
            jRemoveButton.setEnabled(false);
            moveUpAction.setEnabled(false);
            moveDownAction.setEnabled(false);
            removeAction.setEnabled(false);
        }
        jAddButton.setEnabled(ok);
        if (ok)
            clearProblemText(Mode.IMMEDIATE, WL_COLUMN_PROP);
    }

    //-------------------------------------------------------------------------
    public void actionPerformed(java.awt.event.ActionEvent e) {
        String ac = e.getActionCommand();
        if (e.getSource() == jAddButton || e.getSource() == jColNameTextField) {
            if (jAddButton.isEnabled())
                addOrModify();
        } else if (e.getSource() == jRemoveButton) {
            int[] selected = getJTable().getSelectedRows();
            if (selected.length == 0)
                return;
            String msg[] = new String[2];
            if (selected.length == 1)
                msg[0] = String.format("The following column will be deleted: %s",
                        getColumn(selected[0]).getName());
            else
                msg[0] = String.format("The selected columns (%d) will be deleted.", selected.length);
            msg[1] = "Are you sure?";
            boolean ok = JOptionPane.showConfirmDialog(this, msg, "Warning", JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION;
            if (ok) {
                while (selected.length != 0) {
                    getData().removeRow(selected[0]);
                    selected = getJTable().getSelectedRows();
                }
                enableDisableButtons(Mode.IMMEDIATE);
            }
        } else if (e.getSource() == jEditButton) {
            int i = getJTable().getSelectedRow();
            editedColumn = true;
            if (i >= 0)
                startEdit(i);
        } else if (e.getSource() == jCancelButton) {
            editedColumn = false;
            clearForm(CF_CANCEL);
        } else if (e.getSource() == jHelpButton) {
            MEMEApp.LONG_OPERATION.begin(String.format("Opening %s...", helpFile.toString()), new LongRunnable() {
                @Override
                public void trun() throws Exception {
                    MEMEApp.getOSUtils().openDocument(helpFile.toURI(), helpFile.getParentFile());
                }
            });
        } else if (ac != null && ac.startsWith("MOVE")) {
            // A mozgatast megengedjuk akkor is, ha elrontja a hivatkozasokat.
            // Ezert a mozgatas utan ellenorizni kell, hogy a hivatkozasok sorrendje
            // felborult-e. Ha igen, akkor azt jelezni kell a tablaban hibakeppen.
            // Errol gondoskodik 'updateDataTypes()'
            //
            int offset = Integer.parseInt(ac.substring(4));
            int[] selected = getJTable().getSelectedRows();

            List<int[]> intervals = new ArrayList<int[]>();
            int start = selected[0], end = -1, previous = selected[0] - 1;

            for (int i = 0; i < selected.length; ++i) {
                if (selected[i] == previous + 1)
                    previous = selected[i];
                else {
                    end = previous;
                    int[] intv = { start, end };
                    intervals.add(intv);
                    end = -1;
                    start = previous = selected[i];
                }
            }
            intervals.add(new int[] { start, selected[selected.length - 1] });

            getJTable().getSelectionModel().clearSelection();
            for (int[] intv : intervals) {
                int to = intv[0] + offset;
                if (0 <= intv[0] && 0 <= to && intv[1] + offset < getJTable().getRowCount()) {
                    getData().moveRow(intv[0], intv[1], to);
                    getJTable().getSelectionModel().addSelectionInterval(intv[0] + offset, intv[1] + offset);
                } else
                    getJTable().getSelectionModel().addSelectionInterval(intv[0], intv[1]);
            }
            updateDataTypes(Mode.LATER);
        } else if (e.getSource() == jAggregateCombo) {
            formIsModified();
            enableDisableButtons(Mode.IMMEDIATE);
            if (jScriptTextArea.isEnabled() && jAggregateCombo.getSelectedItem() == VECTOR_MODE_SCRIPT) {
                jScriptTextArea.requestFocus();
            }
        } else /*if (e.getSource() == jScriptCheckBox)*/ {
            formIsModified();
            enableDisableButtons(Mode.IMMEDIATE);
        }
    }

    //-------------------------------------------------------------------------
    /** Adds new column to the view or modifies the edited one. The return value is
     *  true if the operation is successful. */
    private boolean addOrModify() {
        boolean add = (editedIdx < 0);

        // Leolvassuk a GUI controlokrol az adatokat
        // Ellenorizzuk, hogy nem hibasak-e
        // es ha rendben van, akkor osszepakoljuk egy ColInfo objektumba.
        ArrayList<ColInfo> newcols = new ArrayList<ColInfo>(1);
        ArrayList<Utils.Pair<SrcType, String>> sources = new ArrayList<Utils.Pair<SrcType, String>>(1);
        Utils.Pair<SrcType, String> src = new Utils.Pair<SrcType, String>();

        String delim = null;

        Collection<Par> pars = getSelectedPars();

        if (pars.size() > 1 && jAggregateCombo.getSelectedItem() == null) {
            // Tobb oszlopot is kivalasztottak egyszeru projekciora
            sources.add(src);
            ColInfo info = new ColInfo(owner.getRule());
            for (Par p : pars) {
                info.col.setInitialDataType(p.getDatatype());
                info.col.setName(generateName(p.getName(), newcols));

                // Bug fix #1446
                if ("Run ".equals(p.getName()) || "Tick ".equals(p.getName()))
                    src.set(SrcType.SCALAR_SCRIPT, "$" + p.getName().trim() + "$");
                else
                    // End of bug fix #1446
                    src.set(category2SrcType(p.getCategory()), p.getName());
                info.col.setSource(sources);
                if (p instanceof ParameterSet.ThisViewPar) {
                    // Vektoros szamolasu oszlopnak a projekcioja is vektoros
                    info.col.setGrouping(((ParameterSet.ThisViewPar) p).col.isGrouping());
                } else {
                    info.col.setGrouping(true); // == it is scalar
                }
                newcols.add(info.clone());
            }
        } else {
            ColInfo info = new ColInfo(owner.getRule());
            newcols.add(info);
            ColumnType dt = null, st = null;
            boolean isVector = false;

            // Ellenorizzuk a nevet, hogy ne lehessen duplazodas
            String name = jColNameTextField.getText();
            if (name.length() == 0) {
                name = generateName(null);
                jColNameTextField.setText(name);
            }
            if (name.equals("#")) {
                MEMEApp.userAlert(String.format("The column name cannot be %s.", name),
                        "Please enter a different name or press Cancel!");
                jColNameTextField.requestFocus();
                return false;
            }
            int found = findColumn(name);
            if (add && found >= 0 || !add && found >= 0 && found != editedIdx) {
                MEMEApp.userAlert("A column with the same name already exists.",
                        "Please enter a different name or press Cancel!");
                jColNameTextField.requestFocus();
                return false;
            }
            if (!add && found < 0) {
                // TODO: modositottak az oszlop nevet, az uj nevet nem hasznalja mas.
                // Ilyenkor vegig kell nyalni a mar meglevo oszlopokat, es megnezni, hogy van-e olyan
                // script amiben a regi nev elofordul. Ha igen, akkor szolni kell hogy a modositasok
                // vegigvitelehez a scriptekbe is bele kellene nyulni. Harom valasz lehet: rendben;
                // modositson de scriptekbe ne nyuljon; megse modositsunk (ilyenkor meg itt kilepunk,
                // mielott torolnenk a formot).
                // Ezutan pedig MAJD vegig kell vinni a modositasokat. Ennek erre az oszlopra meg
                // nincs hatasa, mivel erre az oszlopra csak kesobbi oszlopok hivatkozhattak.
                // A dolog menete egyebkent ez: projekcioknal at kell irni a forrast, ha a regi 
                // nevre hivatkozik, fuggvenyeknel es plugineknel ujra kell szamoltatni az eredmeny 
                // tipusat, scripteknel pedig find&replace-t kell vegrehajtani.
                // Ha egy fgv/plugin eredmenyenek tipusa modosul, akkor azoknak a modosulasoknak
                // a hatasait is vegig kell kovetni (legegyszerubb ha az elso modosulastol kezdve
                // a kovetkezo oszlopokban mindenutt ujraszamoltatjuk a tipusokat). Ennek eredmenyekepp
                // lehet h. a tipus null-ra jon ki egyes oszlopoknal. Ezeknek a tablaban hibakent kell
                // megjelenniuk. -> ezt a tipus-ujraszamolast updateDataTypes() mar most is megcsinalja
            }

            info.col.setName(name);

            // Aggregate function or plugin
            Object o = jAggregateCombo.getSelectedItem();
            if (o == VECTOR_MODE_SCRIPT) {
                src.set(SrcType.GROUP_SCRIPT, jScriptTextArea.getText());
                dt = getScriptResultType();
                isVector = true;
                sources.add(src);
            } else if (o instanceof AggrComboItem) {
                AggrComboItem item = (AggrComboItem) o;
                info.col.setAggrFn(item.pinfo.getInternalName());
                dt = item.datatype;
                assert (dt != null);
                isVector = true; // bedrotoztuk h. plugint csak vektoros modban hasznalunk
            }

            // Hidden
            boolean hidden = jHiddenCheckBox.isSelected();
            info.col.setHidden(hidden);

            // Splitted
            delim = (!hidden && jSplittedCheckBox.isSelected()) ? jSplittedDelimTextField.getText() : null;
            info.col.setSplitted(delim);
            isVector = isVector || (delim != null);

            if (src.getFirst() == null) {
                if (jScriptCheckBox.isSelected()) {
                    src.set(SrcType.SCALAR_SCRIPT, jScriptTextArea.getText());
                    st = getScriptResultType();
                    if (dt == null) {
                        dt = st;
                        st = null;
                    }
                    sources.add(src);
                } else {
                    assert (!pars.isEmpty());
                    for (Par p : pars) {
                        if (dt == null) {
                            // single-column projection
                            assert (pars.size() == 1);
                            dt = p.getDatatype();
                            // Vektoros szamolasu oszlopnak a projekcioja is vektoros:
                            if (p instanceof ParameterSet.ThisViewPar)
                                isVector = !((ParameterSet.ThisViewPar) p).col.isScalar();
                        }
                        // Bug fix #1446
                        if ("Run ".equals(p.getName()) || "Tick ".equals(p.getName()))
                            src.set(SrcType.SCALAR_SCRIPT, "$" + p.getName().trim() + "$");
                        else
                            // End of bug fix #1446
                            src.set(category2SrcType(p.getCategory()), p.getName());
                        sources.add(src);
                        src = new Utils.Pair<SrcType, String>();
                    }
                }
            }
            assert (dt != null);
            info.col.setInitialDataType(dt);
            info.col.setSource(sources);
            if (st != null)
                info.col.setScriptDataType(st);

            // Grouping
            boolean grouping = !isVector && !jForceAggregateCheckBox.isSelected();
            info.col.setGrouping(grouping);
        }

        if (add) {
            for (ColInfo info : newcols)
                getJTableScrollPane().addRow(info);
        } else {
            // Ha modosult az adattipus, es ezt az oszlopot hasznaljak valahol, akkor az
            // elso hasznalattol kezdve ujra kell MAJD szamoltatni a kesobbi oszlopokban 
            // a fgv/plugin tipusokat.
            // Az alabbi clearForm() hatasara -> owner.beginUpdateParameters() -> ... 
            // -> updateDataTypes() -> getJTableScrollPane().updateDataTypes() meghivodik
            // es ez mindig ujraszamol minden ilyesmit; mas egyebet tehat itt nem kell 
            // tenni ehhez.

            assert (newcols.size() == 1); // Editalas eseten szerintem nem kene tobb sor keletkezeset megengedni...
            getData().removeRow(editedIdx);
            ColInfo info = newcols.get(0);
            // A Splitter flag-et lehet, hogy torolni kell
            if (wasSplitter != null) {
                if (warning(wasSplitter && !canBeSplitter(info.col), WL_TABLE,
                        "The column had to be removed from the splitter set!")) {
                    wasSplitter = false;
                }
                info.col.setSplitter(wasSplitter);
            }
            getJTableScrollPane().insertRow(editedIdx, info);
            getJTable().getSelectionModel().setSelectionInterval(editedIdx, editedIdx);
        }
        clearForm(CF_ADD); // this calls ParameterSet.collectParams(), too 

        // Splitter delimiter erteket, ha mas mint az alapertelmezett, kiirjuk userPrefs-be
        if (delim != null && delim.trim().length() > 0) {
            if (delim.equals(DEFAULT_SPLITTER_DELIMITER))
                MEMEApp.userPrefs.remove(UserPrefs.VWCR_DELIMITER);
            else
                MEMEApp.userPrefs.put(UserPrefs.VWCR_DELIMITER, delim);
        }
        editedColumn = false;
        return true;
    }

    //-------------------------------------------------------------------------
    // This method is called by both the parameter lists and the table.
    // [EDT]
    public void valueChanged(javax.swing.event.ListSelectionEvent e) {
        if (in_valueChanged || e.getValueIsAdjusting())
            return;
        assert (EventQueue.isDispatchThread());
        in_valueChanged = true;
        try {
            if (e.getSource() instanceof JList) {
                // The event is coming from a parameter list
                // Synchronize the selection amongst lists (Output/Thisview/Input <-> All)
                JList list = (JList) e.getSource(), otherList;
                for (int idx = Math.max(0, e.getFirstIndex()), last = Math.min(list.getModel().getSize() - 1,
                        e.getLastIndex()); idx <= last; ++idx) {
                    // The selection of one parameter was toggled
                    Par p = (Par) list.getModel().getElementAt(idx);
                    boolean isSelected = list.isSelectedIndex(idx);
                    if (list == jParList[idx_AllPars]) {
                        otherList = jParList[p.getCategory().ordinal()];
                    } else {
                        otherList = jParList[idx_AllPars];
                    }
                    if (!isSelected && otherList.isSelectionEmpty())
                        continue;
                    javax.swing.ListModel lm = otherList.getModel();
                    for (int j = lm.getSize() - 1; j >= 0; --j) {
                        Par p2 = (Par) lm.getElementAt(j);
                        if (p2.getCategory() == p.getCategory() && p2.getName().equals(p.getName())) {
                            if (isSelected)
                                otherList.getSelectionModel().addSelectionInterval(j, j);
                            else
                                otherList.getSelectionModel().removeSelectionInterval(j, j);
                            break;
                        }
                    }
                }
            }
        } finally {
            in_valueChanged = false;
        }
        enableDisableButtons(Mode.LATER); // helps to coalesce calls caused by rapid modifications 
    } // (e.g. setSelectedPars())

    private boolean in_valueChanged = false;

    //-------------------------------------------------------------------------
    /* Ezt a muveletet meg kell hivni olyankor amikor az also reszen modosult vmi */
    private void formIsModified() {
        setWarningLevel(WL_ALL);
    }

    //-------------------------------------------------------------------------
    // Azert public mert reflection-el hasznaljuk 
    public void jScriptTextAreaChanged() {
        String text = jScriptTextArea.getText().trim();
        boolean isEmpty = (text.length() == 0);
        if (jScriptTextAreaWasEmpty ^ isEmpty) {
            // ures <-> nem ures valtozas
            jScriptTextAreaWasEmpty = isEmpty;
            formIsModified();
            enableDisableButtons(Mode.LATER);
        }
    }

    //-------------------------------------------------------------------------
    /** This class doesn't allow to deletes % from a columname if the column is a splitted
     *  column.
     */
    private class JColNameTextFieldFilter extends javax.swing.text.DocumentFilter {
        //javax.swing.text.JTextComponent tc;
        <T extends javax.swing.text.JTextComponent> T install(T comp) {
            //tc = comp;
            ((javax.swing.text.AbstractDocument) comp.getDocument()).setDocumentFilter(this);
            return comp;
        }

        @Override
        public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
            if (jSplittedCheckBox.isSelected()) {
                int len = fb.getDocument().getLength();
                StringBuilder sb = new StringBuilder(fb.getDocument().getText(0, len));
                sb.delete(offset, offset + length);
                int i = sb.indexOf("%");
                if (i < 0) {
                    fb.insertString(len, "%", null);
                }
            }
            super.remove(fb, offset, length);
        }

        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
                throws BadLocationException {
            if (jSplittedCheckBox.isSelected()) {
                int len = fb.getDocument().getLength();
                StringBuilder sb = new StringBuilder(fb.getDocument().getText(0, len));
                sb.replace(offset, offset + length, text);
                int i = sb.indexOf("%");
                if (i < 0) {
                    fb.insertString(len, "%", null);
                }
            }
            super.replace(fb, offset, length, text, attrs);
        }
    }

    //-------------------------------------------------------------------------
    /** This method handles the double click events. */
    private void dblClickInList(JList list, int row) {
        boolean saved = in_valueChanged;
        in_valueChanged = true;
        try {
            setSelectedPars(java.util.Collections.<Par>emptyList());
        } finally {
            in_valueChanged = saved;
        }
        list.setSelectedIndex(row);
        addOrModify();
    }

    //-------------------------------------------------------------------------
    public void intervalRemoved(javax.swing.event.ListDataEvent e) {
        guardListWidth(e.getSource());
    }

    public void contentsChanged(javax.swing.event.ListDataEvent e) {
    }

    public void intervalAdded(javax.swing.event.ListDataEvent e) {
        guardListWidth(e.getSource());
        updateDataTypes(Mode.LATER);
    }

    //-------------------------------------------------------------------------
    /** This method should be called when the contents of the parameter lists is
     *  modified or this-view column data types need to be re-calculated.
     */
    // Some ways of calling this method:
    // a) Page_InputTables -> owner.beginUpdateParameters() -> ParameterSet.collectParams() 
    //    -> .updateCategory() -> .ParListModel.updateFrom() -> this.intervalAdded()
    // b) clearForm() -> owner.beginUpdateParameters() -> ...
    // c) clearForm() -> ParameterSet.getListModel() -> .ParListModel.updateFrom() -> ...
    // d) initialize() -> ParameterSet.getListModel() -> ...
    // e) initialize() -> ParameterSet.getListModelForAll() -> .ParListModel.updateFrom() -> ...
    // f) onPageChange()
    // g) jMove*Button -> actionPerformed()
    // h) update();
    // [EDT]
    private void updateDataTypes(Mode m) {
        if (m == Mode.LATER) {
            if (!updateScheduled) {
                updateScheduled = true;
                Utils.invokeLaterNonPublic(this, "updateDataTypes", Mode.SCHEDULE_END);
            }
            return;
        }

        // Arrange for updating 'allPars[]' and 'visiblePars[]' which   
        // cache the list of parameters for aggr.fn. result type calculation
        // (see VCPluginContext)
        allPars = null;
        visiblePars = null;

        if (updateRule) {
            // Load the rule from the owner
            getJTableScrollPane().fill(owner.getRule(), this); // triggers getViewColDataType()
            updateRule = false;
            enableDisableButtons(Mode.LATER);
            owner.beginUpdateParameters(true); // frissitse a thisview listajat
        } else {
            // Update the data types of the view table's parameters
            getJTableScrollPane().updateDataTypes(this); // triggers getViewColDataType()
        }

        // fill()/updateDataTypes() modosithatta nehany Thisview-parameter tipusat.
        // Ahhoz hogy ez a valtozas a GUI-n is megjelenjen, ujra kell rajzoltatni
        // a listat es a tablat is
        jParList[Category.THISVIEW.ordinal()].repaint();
        getJTableScrollPane().getJTable().repaint();

        owner.getWizard().enableDisableButtons();

        updateScheduled = false;
    }

    //-------------------------------------------------------------------------
    /** This method is called whenever the user want to edit an existing column. */
    private void startEdit(int idx) {
        clearForm(idx);

        ViewCreationRule.Column col = getColumn(idx);
        boolean isVector = false;
        jColNameTextField.setText(col.getName());
        jColNameTextField.selectAll();
        jColNameTextField.requestFocus();
        wasSplitter = col.isSplitter();

        ArrayList<Utils.Pair<SrcType, String>> sources = col.getSource();
        ArrayList<Par> tmp = new ArrayList<Par>();
        for (Utils.Pair<SrcType, String> src : sources) {
            switch (src.getFirst()) {
            case SCALAR_SCRIPT:
                jScriptCheckBox.setSelected(true);
                jScriptTextArea.setText(src.getSecond());
                setScriptResultType(col.getScriptDataType());
                break;

            case GROUP_SCRIPT:
                jAggregateCombo.setSelectedItem(VECTOR_MODE_SCRIPT);
                jScriptCheckBox.setSelected(true);
                jScriptTextArea.setText(src.getSecond());
                setScriptResultType(col.getInitialDataType());
                isVector = true;
                break;

            default:
                tmp.add(new Par(src.getSecond(), null, srcType2Category(src.getFirst())));
                break;
            }
        }
        setSelectedPars(tmp);
        jHiddenCheckBox.setSelected(col.isHidden());

        String delim = col.getSplitted();
        jSplittedCheckBox.setSelected(delim != null);
        jSplittedDelimTextField.setText(delim != null ? delim : DEFAULT_SPLITTER_DELIMITER);

        String aggrfn = col.getAggrFn();
        if (aggrfn != null && aggrfn.length() > 0) {
            isVector = true;
            updateAggrComboList(getSelectedPars());
            javax.swing.ComboBoxModel cm = jAggregateCombo.getModel();
            for (int i = 0, n = cm.getSize(); i < n; ++i) {
                Object o = cm.getElementAt(i);
                if (o == null || o == VECTOR_MODE_SCRIPT)
                    continue;
                if (aggrfn.equals(((AggrComboItem) o).pinfo.getInternalName())) {
                    cm.setSelectedItem(o);
                    break;
                }
            }
        }

        jForceAggregateCheckBox.setSelected(isVector || !col.isGrouping());
        enableDisableButtons(Mode.LATER);
    }

    //-------------------------------------------------------------------------
    /** Clears the column editor area. */
    private void clearForm(int editedIdx) {
        this.editedIdx = editedIdx;
        if (editedIdx >= 0) {
            jAddButton.setText("Modify");
            jCancelButton.setText("Cancel");
        } else {
            jAddButton.setText("Add");
            jCancelButton.setText("Reset form");
        }

        generatedColName = null;
        clearName = (editedIdx < 0); // after Cancel & Add
        wasSplitter = null;
        setSelectedPars(java.util.Collections.<Par>emptyList()); // triggers enableDisableButtons() (later)
        jSplittedCheckBox.setSelected(false);
        jHiddenCheckBox.setSelected(false);
        jForceAggregateCheckBox.setSelected(false);
        jScriptCheckBox.setSelected(false);
        jScriptTextArea.setText("");
        jScriptTextAreaWasEmpty = true;
        jColNameTextField.setText("");
        jAggregateCombo.setSelectedItem(null); // this may trigger enableDisableButtons() (immediate)

        setWarningLevel((editedIdx >= 0) ? WL_COLUMN_PROP : WL_NO_COLUMN_PROP);
        if (warning.getSecond() < warningLevel && warning.getFirst() != null)
            clearProblemText(Mode.LATER, warning.getSecond());

        if (owner != null) {
            if (editedIdx == CF_ADD) {
                owner.beginUpdateParameters(true); // triggers wizard.enableDisableButtons()
            } else {
                owner.getWizard().enableDisableButtons();
            }
            // Atallitja limit-et es ennek megfeleloen frissiti a Thisview es 
            // az All parameterlistakat: 
            owner.getSelectedPars().getListModel(Category.THISVIEW, editedIdx);
        }
    }

    //-------------------------------------------------------------------------
    private static void force(javax.swing.AbstractButton button, Boolean forcedValue) {
        if (forcedValue != null) {
            button.setSelected(forcedValue);
            button.setEnabled(false);
        } else {
            button.setEnabled(true);
        }
    }

    //-------------------------------------------------------------------------
    private void updateAggrComboList(Collection<? extends Parameter> selectedPars) {
        Object curr = jAggregateCombo.getSelectedItem();
        VCPluginContext ctx = null;

        // Filter out those plugins that do not accept the selected parameters
        //
        // Itt meg nem getReturnType()-ot hivunk, mert a pontos tipus kiszamitasa
        // esetleg sokkal koltsegesebb lehet mint egy igen/nem valasze, es itt
        // egyelore eleg ennyi is.
        ArrayList<AggrComboItem> tmp = new ArrayList<AggrComboItem>();
        tmp.add(null);
        if (jAggregateCombo.isEnabled()) {
            for (PluginInfo<IVCPlugin> pinfo : getAvailablePlugins()) {
                if (ctx == null)
                    ctx = new VCPluginContext(selectedPars, editedIdx);
                if (pinfo.getInstance().isSelectionOK(ctx))
                    tmp.add(new AggrComboItem(pinfo));
            }
            if (selectedPars.isEmpty())
                tmp.add(VECTOR_MODE_SCRIPT);
        }

        javax.swing.ComboBoxModel cm = new javax.swing.DefaultComboBoxModel(tmp.toArray());
        int i = tmp.indexOf(curr);
        if (i >= 0) {
            curr = tmp.get(i);
            cm.setSelectedItem(curr);
            if (curr instanceof AggrComboItem) {
                AggrComboItem a = (AggrComboItem) curr;
                if (a.pinfo != null) {
                    // Az eredmeny pontos tipusat csak azzal az egy pluginnal
                    // szamoltatjuk ki, amit a user kivalasztott. 
                    //
                    if (ctx == null)
                        ctx = new VCPluginContext(selectedPars, editedIdx);
                    a.datatype = a.pinfo.getInstance().getReturnType(ctx);
                }
            }
        }
        jAggregateCombo.setModel(cm);
    }

    //-------------------------------------------------------------------------
    /** This triggers {@link #findName()} */
    private String generateName(String tryThis, ArrayList<ColInfo> more) {
        return new Utils.NameGenerator().defName("Column").finder(this).generate(tryThis, more);
    }

    /** This triggers {@link #findName()} */
    private String generateName(String tryThis) {
        return generateName(tryThis, null);
    }

    //-------------------------------------------------------------------------
    @SuppressWarnings("unchecked")
    public boolean findName(String name, Object userData) {
        if (findColumn(name) >= 0)
            return true;
        if (userData != null) {
            for (ColInfo info : (ArrayList<ColInfo>) userData)
                if (name.equals(info.col.getName())) {
                    return true;
                }
        }
        return false;
    }

    //-------------------------------------------------------------------------
    /** Returns the index of the column named 'name' or -1. */
    private int findColumn(String name) {
        if (name != null) {
            for (int i = 0, n = getColumnCount(); i < n; ++i)
                if (name.equals(getColumn(i).getName()))
                    return i;
        }
        return -1;
    }

    //-------------------------------------------------------------------------
    private Collection<Par> getSelectedPars() {
        java.util.TreeMap<Par, Par> ans = new java.util.TreeMap<Par, Par>();
        for (int i = 0; i < jParList.length; ++i) {
            boolean isEmpty = true;
            for (Object o : jParList[i].getSelectedValues()) {
                isEmpty = false;
                Par p = (Par) o, key = new Par(p.getName(), null, p.getCategory());
                if (!ans.containsKey(key)) {
                    ans.put(key, p);
                }
            }
            jParListTabbedPane.setForegroundAt(i, isEmpty ? pltpNormalColor : pltpOtherColor);
        }
        return ans.values();
    }

    //-------------------------------------------------------------------------
    /** Side effect: schedules delayed call of enableDisableButtons() */
    private void setSelectedPars(java.util.List<Par> pars) {
        // Clear the selection
        boolean save = in_valueChanged;
        in_valueChanged = true; // make implied valueChanged() calls faster
        try {
            for (int i = 0; i < jParList.length; ++i)
                jParList[i].clearSelection();
        } finally {
            in_valueChanged = save;
        }
        // Select specified parameters
        for (Par p : pars) {
            JList list = jParList[p.getCategory().ordinal()];
            javax.swing.ListModel lm = list.getModel();
            for (int i = 0, n = lm.getSize(); i < n; ++i) {
                if (lm.getElementAt(i).equals(p.getName())) {
                    list.addSelectionInterval(i, i);
                    break;
                }
            }
        }
        enableDisableButtons(Mode.LATER); // just to be sure
    }

    //-------------------------------------------------------------------------
    /** Returns the result type of a the current script. */
    private ColumnType getScriptResultType() {
        if (jBooleanDataTypeButton.isSelected())
            return ColumnType.BOOLEAN;
        if (jIntDataTypeButton.isSelected())
            return ColumnType.INT;
        if (jRealDataTypeButton.isSelected())
            return ColumnType.DOUBLE;
        return ColumnType.STRING;
    }

    //-------------------------------------------------------------------------
    private void setScriptResultType(ColumnType dt) {
        if (dt.equals(ColumnType.BOOLEAN))
            jBooleanDataTypeButton.setSelected(true);
        else if (dt.equals(ColumnType.DOUBLE))
            jRealDataTypeButton.setSelected(true);
        else if (dt.equals(ColumnType.INT) || dt.equals(ColumnType.LONG))
            jIntDataTypeButton.setSelected(true);
        else
            jStringDataTypeButton.setSelected(true);
    }

    //-------------------------------------------------------------------------
    /** Converts a category to a source type. */
    private static SrcType category2SrcType(Category c) {
        switch (c) {
        case INPUT:
            return SrcType.PROJECTION_INPUT;
        case OUTPUT:
            return SrcType.PROJECTION_OUTPUT;
        case THISVIEW:
            return SrcType.PROJECTION_VIEW;
        }
        throw new IllegalArgumentException(c.toString());
    }

    //-------------------------------------------------------------------------
    /** Converts a source type to a category. */
    private static Category srcType2Category(SrcType t) {
        switch (t) {
        case PROJECTION_INPUT:
            return Category.INPUT;
        case PROJECTION_OUTPUT:
            return Category.OUTPUT;
        case PROJECTION_VIEW:
            return Category.THISVIEW;
        }
        return null;
    }

    //-------------------------------------------------------------------------
    // This method is used via reflection
    void setWarningLevel(int level) {
        warningLevel = level;
    }

    //-------------------------------------------------------------------------
    /** Displays the message if 'condition' is true. Returns 'condition' */
    private boolean warning(boolean condition, int level, String message) {
        if (level >= warningLevel) {
            String before = warning.getFirst();
            warning.set(condition ? message : null, level);
            if (!Utils.equals(warning.getFirst(), before) && owner != null) {
                owner.getWizard().updateInfo();
                if (warningTimer != null)
                    warningTimer.stop();
                if (warning.getFirst() != null)
                    warningTimer = Utils.invokeAfter(PROBLEM_TIMEOUT, this);
            }
        }
        return condition;
    }

    //-------------------------------------------------------------------------
    /** used by {@link #warningTimer} */
    public void run() {
        clearProblemText(Mode.IMMEDIATE, warning.getSecond());
    }

    //-------------------------------------------------------------------------
    /** Clears the warning message from the information panel. */
    private void clearProblemText(Mode m, int level) {
        if (m == Mode.IMMEDIATE) {
            String before = warning.getFirst();
            if (warning.getSecond() <= level) {
                warning.set(null, WL_NONE);
                if (warningTimer != null) {
                    warningTimer.stop();
                    warningTimer = null;
                }
            }
            if (!Utils.equals(warning.getFirst(), before) && owner != null)
                owner.getWizard().updateInfo();
        } else {
            javax.swing.SwingUtilities.invokeLater(this);
        }
    }

    //-------------------------------------------------------------------------
    static PluginList<IVCPlugin> getAvailablePlugins() {
        return MEMEApp.getPluginManager().getVCPluginInfos();
    }

    //-------------------------------------------------------------------------
    /** Record class to encapsulate a view creation plugin and a columntype. */
    static class AggrComboItem {
        PluginInfo<IVCPlugin> pinfo;
        ColumnType datatype;

        AggrComboItem(PluginInfo<IVCPlugin> p) {
            pinfo = p;
        }

        @Override
        public String toString() {
            return pinfo == null ? null : pinfo.getInstance().getLocalizedName();
        }

        // Used in updateAggrComboList() via 'tmp.indexOf(...)'
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof AggrComboItem)
                return ((AggrComboItem) obj).pinfo == pinfo;
            return super.equals(obj);
        }
    }

    //-------------------------------------------------------------------------
    /** 
     * Type for arguments of IVCPlugin methods.
     */
    @SuppressWarnings("serial")
    class VCPluginContext extends PluginContextBase implements IVCPlugin.IContext {
        java.util.List<? extends Parameter> selectedPars;
        Integer limit;

        //-------------------------------------------------------------------------
        VCPluginContext(Collection<? extends Parameter> pars, Integer limit) {
            setSelectedPars(pars);
            this.limit = limit;
        }

        //-------------------------------------------------------------------------
        private void setSelectedPars(Collection<? extends Parameter> pars) {
            if (pars == null)
                selectedPars = null;
            else if (pars instanceof java.util.List)
                selectedPars = (java.util.List<? extends Parameter>) pars;
            else
                selectedPars = new ArrayList<Parameter>(pars);
        }

        //-------------------------------------------------------------------------
        //   Interface methods

        //-------------------------------------------------------------------------
        public List<? extends Parameter> getAllPars() {
            if (allPars == null) {
                allPars = owner.getSelectedPars().getAllPars();
            }
            return java.util.Collections.unmodifiableList(allPars);
        }

        //-------------------------------------------------------------------------
        public List<Object[]> getAllValues() {
            return new NCopiesModifiable<Object[]>(getAllPars().size(), null);
        }

        //-------------------------------------------------------------------------
        public BitSet getVisibleParIndices() {
            if (visiblePars == null) {
                visiblePars = new BitSet();
                for (Integer i : owner.getSelectedPars().getVisibleParIndices(getAllPars(), limit))
                    visiblePars.set(i);
            }
            return visiblePars;
        }

        //-------------------------------------------------------------------------
        public int indexOf(String parName) {
            List<? extends Parameter> all = getAllPars();
            BitSet visible = getVisibleParIndices();
            for (int i = visible.nextSetBit(0); i >= 0; i = visible.nextSetBit(i + 1)) {
                if (all.get(i).equals(parName))
                    return i;
            }
            return -1;
        }

        //-------------------------------------------------------------------------
        public List<? extends Parameter> getArgumentPars() {
            if (selectedPars == null)
                setSelectedPars(getSelectedPars());
            return selectedPars;
        }

        //-------------------------------------------------------------------------
        public List<Object[]> getArguments() {
            return new NCopiesModifiable<Object[]>(getArgumentPars().size(), null);
        }

        //-------------------------------------------------------------------------
        public boolean isScalarMode() {
            return false; // ezzel tk. bedrotoztuk h. plugint csak vektoros modban hasznalunk
        }

    }

    //-------------------------------------------------------------------------
    /** This class represents a list which contains only one value but it contains that n times. */
    public static class NCopiesModifiable<T> extends java.util.AbstractList<T> implements java.util.RandomAccess {
        int size;
        T value;

        public NCopiesModifiable(int n, T val) {
            size = n;
            value = val;
        }

        @Override
        public T get(int index) {
            return value;
        }

        @Override
        public int size() {
            return size;
        }

        @Override
        public T set(int index, T element) {
            return value;
        } // do nothing
    }

    //=========================================================================
    //   GUI (View) methods

    //-------------------------------------------------------------------------
    public static void test(String[] args) {
        new Wizard(new Page_Columns(null)).showInDialog(null).setVisible(true);
        System.exit(0);
    }

    /**
    * This method initializes this
    * 
    * @return void
    */
    private void initialize() {
        jBooleanDataTypeButton = new JToggleButton(JTableScrollPane.getColumnTypeIcon(ColumnType.BOOLEAN));
        jIntDataTypeButton = new JToggleButton(JTableScrollPane.getColumnTypeIcon(ColumnType.INT));
        jRealDataTypeButton = new JToggleButton(JTableScrollPane.getColumnTypeIcon(ColumnType.DOUBLE));
        jStringDataTypeButton = new JToggleButton(JTableScrollPane.getColumnTypeIcon(ColumnType.STRING));

        //tooltips
        jBooleanDataTypeButton.setToolTipText("Logical");
        jIntDataTypeButton.setToolTipText("Integer");
        jRealDataTypeButton.setToolTipText("Real number");
        jStringDataTypeButton.setToolTipText("String");

        GUIUtils.createButtonGroup(jBooleanDataTypeButton, jIntDataTypeButton, jRealDataTypeButton,
                jStringDataTypeButton);
        jRealDataTypeButton.setSelected(true);

        initializeContextMenu();

        jMoveUpButton.setActionCommand("MOVE-1");
        jMoveDownButton.setActionCommand("MOVE1");

        GUIUtils.addActionListener(this, jColNameTextField, jFilterEEButton, jScriptEEButton, jHelpButton,
                jMoveUpButton, jMoveDownButton, jRemoveButton, jEditButton, jAddButton, jCancelButton,
                jAggregateCombo, jSplittedCheckBox, jHiddenCheckBox, jForceAggregateCheckBox, jScriptCheckBox,
                jBooleanDataTypeButton, jIntDataTypeButton, jRealDataTypeButton, jStringDataTypeButton);

        GUIUtils.bind(this, JComponent.WHEN_IN_FOCUSED_WINDOW, "DELETE", jRemoveButton);

        new JColNameTextFieldFilter().install(jColNameTextField);

        jScriptTextArea.getDocument().addDocumentListener(
                java.beans.EventHandler.create(DocumentListener.class, this, "jScriptTextAreaChanged"));
        jScriptTextArea.addFocusListener(java.beans.EventHandler.create(FocusListener.class, this,
                "jScriptTextAreaChanged", null, "focusLost"));
        jScriptTextArea.setColumns(30);
        jScriptTextArea.setRows(3);

        jAggregateCombo.setRenderer(this);

        getJTable().getSelectionModel().addListSelectionListener(this);
        getJTable().addMouseListener(new java.awt.event.MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                // Double-click in the table should trigger "Edit"
                if (!javax.swing.SwingUtilities.isLeftMouseButton(e) || e.getClickCount() != 2
                        || getJTable().rowAtPoint(e.getPoint()) < 0)
                    return;
                jEditButton.doClick();
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                // Context menu
                if (!javax.swing.SwingUtilities.isRightMouseButton(e))
                    return;
                if (e.getComponent().isEnabled()) {
                    int idx = getJTable().rowAtPoint(e.getPoint());
                    if (idx == -1)
                        idx = getJTable().getRowCount() - 1;

                    if (!ai.aitia.chart.util.Utilities.contains(getJTable().getSelectedRows(), idx))
                        getJTable().getSelectionModel().setSelectionInterval(idx, idx);

                    tableContextMenu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

        // Plug parameter list data models to JList components
        javax.swing.ListCellRenderer r = new ParListCellRenderer();
        GUIUtils.ToggleClickMultiSelection s = new GUIUtils.ToggleClickMultiSelection() {
            @Override
            protected void doubleClick(java.awt.event.MouseEvent e, int rowIdx) {
                dblClickInList((JList) e.getSource(), rowIdx);
            }
        };

        jParListTabbedPane = new JTabbedPane();
        jParList = new JList[Category.values().length + 1];
        for (int i = 0; i < jParList.length; ++i) {
            jParList[i] = new JList();
            jParList[i].setCellRenderer(r);
            jParList[i].addListSelectionListener(this);
            jParList[i].setToolTipText("Double-click to add single item");
            s.installOn(jParList[i]);
            if (owner != null) {
                javax.swing.ListModel lm = (i == idx_AllPars) ? owner.getSelectedPars().getListModelForALL()
                        : owner.getSelectedPars().getListModel(Category.values()[i], -1);
                lm.addListDataListener(this); // this will trigger update()
                jParList[i].setModel(lm);
                guardListWidth(jParList[i]);
            }
            JScrollPane sp = new JScrollPane(jParList[i]);
            sp.setBorder(null);
            jParListTabbedPane.add(i == idx_AllPars ? "All" : Category.values()[i].tabTitle, sp);
        }
        pltpNormalColor = jParListTabbedPane.getForeground();
        pltpOtherColor = getOtherColor(pltpNormalColor, jParListTabbedPane.getBackground());

        if (owner != null) {
            ViewCreationRule rule = owner.getRule();
            String where = rule.getCondition();
            if (where != null)
                jFilterTextArea.setText(where);
            //jUseGroupingCheckBox.setSelected(rule.getGrouping());
        }

        jSplittedDelimTextField
                .setText(MEMEApp.userPrefs.get(UserPrefs.VWCR_DELIMITER, DEFAULT_SPLITTER_DELIMITER));
        if (jSplittedDelimTextField.getText().trim().length() == 0)
            jSplittedDelimTextField.setText(DEFAULT_SPLITTER_DELIMITER);

        clearForm(CF_CANCEL); // sets the text on some buttons
        // also triggers enableDisableButtons() (later)
        layoutGUI();
    }

    //-------------------------------------------------------------------------
    @SuppressWarnings("serial")
    private void initializeContextMenu() {
        moveUpAction = new AbstractAction() {
            {
                putValue(NAME, "Move up");
            }

            public void actionPerformed(ActionEvent e) {
                jMoveUpButton.doClick();
            }
        };
        moveDownAction = new AbstractAction() {
            {
                putValue(NAME, "Move down");
            }

            public void actionPerformed(ActionEvent e) {
                jMoveDownButton.doClick();
            }
        };
        removeAction = new AbstractAction() {
            {
                putValue(NAME, "Remove");
            }

            public void actionPerformed(ActionEvent e) {
                jRemoveButton.doClick();
            }
        };
        editAction = new AbstractAction() {
            {
                putValue(NAME, "Edit");
            }

            public void actionPerformed(ActionEvent e) {
                jEditButton.doClick();
            }
        };
        tableContextMenu.add(moveUpAction);
        tableContextMenu.add(moveDownAction);
        tableContextMenu.add(removeAction);
        tableContextMenu.add(editAction);
    }

    //-------------------------------------------------------------------------
    /** Returns the 'avarage' color of 'fg' and 'bg'. */
    public static Color getOtherColor(Color fg, Color bg) {
        int f[] = { fg.getRed(), fg.getGreen(), fg.getBlue() };
        int b[] = { bg.getRed(), bg.getGreen(), bg.getBlue() };
        int rgb[] = { (f[0] + b[0]) / 2, (f[1] + b[1]) / 2, (f[2] + b[2]) / 2 };
        rgb[0] = f[0];
        return new Color(rgb[0], rgb[1], rgb[2], fg.getAlpha());
    }

    //-------------------------------------------------------------------------
    // A JList-ek preferredWidth szelessege alapertelmezesben nagyon ugrandozik
    // ures <-> nem ures allapot kozott, es ez nagyon zavaro. Ez a muvelet ezt
    // az ugrandozast probalja kikuszobolni azaltal, hogy ures allapotban kicsire
    // veszi a lista szelesseget, nem-ures allapotban pedig az elemek szelessegetol
    // fuggove teszi.
    // Az 'o' parameter JList is lehet es ListModel is. Utobbi esetben megkeresi 
    // a hozza tartozo JList-et. 
    private void guardListWidth(Object o) {
        JList list = (o instanceof JList) ? (JList) o : null;
        if (o instanceof javax.swing.ListModel) {
            javax.swing.ListModel lm = (javax.swing.ListModel) o;
            for (int i = jParList.length - 1; i >= 0 && list == null; --i)
                if (jParList[i].getModel() == lm)
                    list = jParList[i];
        }
        if (list != null) {
            if (list.getModel().getSize() == 0)
                list.setFixedCellWidth(GUIUtils.dluX(40, list));
            else
                list.setFixedCellWidth(-1);
        }
    }

    //-------------------------------------------------------------------------
    // TextArea wrapper
    @SuppressWarnings("serial")
    static class TAWrapper extends JViewport {
        public TAWrapper(javax.swing.text.JTextComponent tc) {
            this.setView(tc);
            this.setMinimumSize(tc.getMinimumSize());
        }
    }

    //-------------------------------------------------------------------------
    private void layoutGUI() {
        final JPanel panel = new JPanel(new BorderLayout());

        this.setLayout(new BorderLayout());
        this.add(new DraggableViewport(panel));

        panel.setBorder(Borders.DIALOG_BORDER);
        panel.add(FormsUtils.build("pref - min:grow - 30px", "012 min|~|3|=", filterLabel,
                new TAWrapper(jFilterTextArea), jFilterEEButton, // 0-2
                new Separator("Columns of the view table") // 3
        ).getPanel(), BorderLayout.NORTH);

        Box box = new Box(BoxLayout.Y_AXIS);
        box.add(FormsUtils.build("f:80dlu:g - pref", "01 f:min:g|-", getJTableScrollPane(), FormsUtils
                .buttonStack(jMoveUpButton, jMoveDownButton, FormsUtils.BS_UNRELATED, jRemoveButton, jEditButton)
                .getPanel()).getPanel());

        // Use different preferred size for docked and floating conditions 
        JScrollPane jScriptScrollPane = new JScrollPane(jScriptTextArea) /*{
                                                                         private static final long serialVersionUID = 1L;
                                                                         public Dimension getPreferredSize() {
                                                                         if (SwingUtilities.getRoot(this) == SwingUtilities.getRoot(panel)) {
                                                                         return new Dimension(100, 24);                           // docked
                                                                         }
                                                                         return new Dimension(100, Sizes.dialogUnitYAsPixel(120, this));   // floating
                                                                         }
                                                                         }*/;
        MEMEApp.getLF().makeTransparent(jScriptScrollPane);

        // Ezt nem szabad bekapcsolni mert akkor sajnos rosszul szamolja a meretet  
        // es a JScrollPane-ben a fugg.scrollbar letakarhatja a listaelemek veget.
        // Ezen esetleg a LookAndFeel.makeTransparent() tud segiteni. 
        //tp.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);

        java.awt.Component columnProps = FormsUtils.titledPanel(" Column properties ",
                FormsUtils.build("- d:g ~~ f:max(120dlu;pref)", "01|%|02|~|03 f:m:g",
                        // Left side  #0
                        FormsUtils.build("pref ' 25dlu:g ' 30px",
                                "011 p|~|" + "233||" + "444||" + "555||" + "666||" + "777||" + "889 f:d:g",
                                "&Column name:", jColNameTextField, // 0-1
                                "Aggregative operation:", jAggregateCombo, // 2-3  
                                FormsUtils.build("l:d:g'r:m'20dlu", "012", jSplittedCheckBox, jNamingDelimiter,
                                        jSplittedDelimTextField).getPanel(), // 4
                                jHiddenCheckBox, CellConstraints.LEFT, // 5
                                jForceAggregateCheckBox, // 6
                                FormsUtils.build("l:m:g = r:m'22px'22px'22px'22px'p", "0123456 p", jScriptCheckBox,
                                        jResultTypeLabel, jBooleanDataTypeButton, jIntDataTypeButton,
                                        jRealDataTypeButton, jStringDataTypeButton, jHelpButton).getPanel(), // 7
                                jScriptScrollPane, new CellInsets(0, 20, 0, 0), // 8
                                jScriptEEButton, CellConstraints.TOP // 9
                        ).getPanel(),
                        // Right side  #1,2,3
                        FormsUtils.build("0:g, fill:max(40dlu;pref) % fill:max(40dlu;pref), 0:g",
                                "[ColGroups=24]" + "_01_ p", jAddButton, jCancelButton).getPanel(),
                        "Available parameters:", jParListTabbedPane))
                .getPanel();

        /*boolean useToolbar = false;
        if (useToolbar) {
           JToolBar tb = new JToolBar("Column properties", JToolBar.HORIZONTAL);
           tb.add(columnProps);
           columnProps = tb;
        }*/
        box.add(columnProps);

        panel.add(box, BorderLayout.CENTER);
    }

    //-------------------------------------------------------------------------
    @SuppressWarnings("serial")
    private JTableScrollPane getJTableScrollPane() {
        if (jTableScrollPane == null) {
            jTableScrollPane = new JTableScrollPane() {
                @Override
                protected Mode getMode() {
                    return Mode.PAGE_COLUMNS;
                }

                @Override
                protected boolean isCellEditable(int row, int column) {
                    if (Mode.PAGE_COLUMNS.isSplitter(column) && canBeSplitter(getColumn(row)))
                        return true;
                    if (Mode.PAGE_COLUMNS.isHidden(column) && canBeHidden(getColumn(row)))
                        return true;
                    return false;
                }

                @Override
                protected void splitterChange(int row, boolean value) {
                    getColumn(row).setSplitter(value);
                    if (value && !getColumn(row).isHidden()) {
                        // a splitter bekapcsolja hidden-t is, amit aztan kikapcsolhat a user
                        getColumn(row).setHidden(value);
                        updateRow(row);
                    }
                    enableDisableButtons(Page_Columns.Mode.IMMEDIATE);
                    owner.getWizard().enableDisableButtons();
                }

                @Override
                protected void hiddenChange(int row, boolean value) {
                    getColumn(row).setHidden(value);
                    enableDisableButtons(Page_Columns.Mode.IMMEDIATE);
                }
            };
            jTableScrollPane.getJTable().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
            jTableScrollPane.getJTable().getSelectionModel().addListSelectionListener(this);
        }
        return jTableScrollPane;
    }

    //------------------------------------------------------------------------
    /** Tests if 'col' can be a splitter column. */
    private static boolean canBeSplitter(ViewCreationRule.Column col) {
        return col.isScalar() && !col.isSplitted();
    }

    //------------------------------------------------------------------------
    /** Tests if 'col' can be a hidden column. */
    private static boolean canBeHidden(ViewCreationRule.Column col) {
        return !col.isSplitted();
    }

    //------------------------------------------------------------------------
    public java.awt.Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        if (comboRenderer == null) {
            comboRenderer = new DefaultListCellRenderer();
            comboRenderer.setFont(jAggregateCombo.getFont());
        }
        if (value == null) {
            comboRenderer.setText("<none>");
            comboRenderer.setIcon(null);
        } else if (value == VECTOR_MODE_SCRIPT) {
            comboRenderer.setText("<Aggregative script>");
            comboRenderer.setIcon(null);
        } else {
            AggrComboItem item = (AggrComboItem) value;
            if (item.pinfo.getInstance() instanceof IVCPlugin.IBuiltinAggregateFn) {
                comboRenderer.setIcon(null);
            } else {
                if (pluginIcon == null)
                    pluginIcon = MainWindow.getIcon("type_javaplugin.png");
                comboRenderer.setIcon(pluginIcon);
            }
            comboRenderer.setText(item.toString());
        }
        return comboRenderer;
    }

    //-------------------------------------------------------------------------
    private boolean isAllHidden() {
        for (int i = 0; i < getData().getRowCount(); ++i) {
            if (!(Boolean) getData().getValueAt(i, 3))
                return false;
        }
        return true;
    }
}