/*
 * File:     EditCVDialog.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package mpi.eudico.client.annotator.gui;

import mpi.eudico.client.annotator.Constants;
import mpi.eudico.client.annotator.ElanLocale;
import mpi.eudico.client.annotator.Preferences;

import mpi.eudico.client.annotator.commands.Command;
import mpi.eudico.client.annotator.commands.ELANCommandFactory;

import mpi.eudico.client.annotator.util.ElanFileFilter;

import mpi.eudico.client.im.ImUtil;

import mpi.eudico.server.corpora.clom.Transcription;

import mpi.eudico.server.corpora.clomimpl.abstr.Parser;
import mpi.eudico.server.corpora.clomimpl.abstr.ParserFactory;
import mpi.eudico.server.corpora.clomimpl.abstr.TranscriptionImpl;
import mpi.eudico.server.corpora.clomimpl.dobes.CVEntryRecord;
import mpi.eudico.server.corpora.clomimpl.dobes.EAF22Parser;
import mpi.eudico.server.corpora.clomimpl.type.CVEntry;
import mpi.eudico.server.corpora.clomimpl.type.ControlledVocabulary;

import mpi.library.util.LogUtil;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.io.File;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Vector;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;


/**
 * The Edit Controlled Vocabulary dialog is a dialog for defining and changing
 * controlled vocabularies and their entries.<br>
 *
 * @author Han Sloetjes
 * @version jun 04
 * @version Aug 2005 Identity removed
 */
public class EditCVDialog extends JDialog implements ActionListener,
    ItemListener, ListSelectionListener {
    /** a logger */
    private static final Logger LOG = Logger.getLogger(EditCVDialog.class.getName());
    private TranscriptionImpl transcription;

    /** Empty string to fill UI elements when values/description are empty. */
    private final String EMPTY = "";

    // internal caching fields
    private String oldCVName;
    private String oldCVDesc;
    private String oldEntryValue;
    private String oldEntryDesc;
    private ControlledVocabulary currentCV;

    // ui elements
    private JLabel cvNameLabel2;
    private JButton changeCVButton;
    private JLabel currentCVLabel;
    private JLabel cvDescLabel;
    private JTextArea cvDescArea;
    private JPanel cvButtonPanel;
    private JLabel cvNameLabel1;
    private JButton importButton;
    private JButton addCVButton;
    private JPanel cvEntriesPanel;
    private JPanel closeButtonPanel;
    private JButton deleteCVButton;
    private JButton closeDialogButton;
    private JButton deleteEntryButton;
    private JList entryList;
    private DefaultListModel entryListModel;
    private JButton changeEntryButton;
    private JLabel titleLabel;
    private JPanel cvPanel;
    private JButton addEntryButton;
    private JTextField entryTextField;
    private JPanel titlePanel;
    private JLabel entryValueLabel;
    private JComboBox cvComboBox;
    private JTextField cvNameTextField;
    private JLabel entriesLabel;
    private JLabel entryDescLabel;
    private JTextField entryDescField;
    private JPanel moveEntriesPanel;
    private JButton moveUpButton;
    private JButton moveToTopButton;
    private JButton moveDownButton;
    private JButton moveToBottomButton;
    private JButton undoButton;
    private JButton redoButton;
    private UndoManager undoManager;

    // language popup and Locales
    private JPopupMenu popup;
    private Locale[] availableLocales;
    private Locale currentLocale;

    /**
     * Creates a new EditCVDialog.
     *
     * @param parent the owner frame of the dialog
     * @param modal the modal state
     */
    public EditCVDialog(Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        postInit();
    }

    /**
     * Creates a new EditCVDialog.
     *
     * @param transcription the transcription containing the controlled
     *        vocabularies
     */
    public EditCVDialog(Transcription transcription) {
        super(ELANCommandFactory.getRootFrame(transcription), true);
        this.transcription = (TranscriptionImpl) transcription;
        undoManager = new UndoManager();
        initComponents();
        initCV();
        postInit();
        cvNameTextField.requestFocus();
    }

    /**
     * Extracts the CV's from the transcription and fills the cv combobox.
     */
    private void initCV() {
        // extract
        Vector v = transcription.getControlledVocabularies();

        for (int i = 0; i < v.size(); i++) {
            cvComboBox.addItem(v.get(i));
        }

        if (cvComboBox.getSelectedItem() != null) {
            updateUIForCV((ControlledVocabulary) cvComboBox.getSelectedItem());
        }

        cvComboBox.addItemListener(this);
        entryList.addListSelectionListener(this);
    }

    /**
     * Updates the CV combo box after a change in the list of CV's in the
     * Transcription.
     *
     * @param cvToSelect the CV to select in the combo box
     */
    private void reextractCVs(String cvToSelect) {
        cvComboBox.removeItemListener(this);
        entryList.removeListSelectionListener(this);
        cvComboBox.removeAllItems();

        Vector v = transcription.getControlledVocabularies();
        ControlledVocabulary conVoc = null;

        for (int i = 0; i < v.size(); i++) {
            conVoc = (ControlledVocabulary) v.get(i);
            cvComboBox.addItem(conVoc);

            if ((cvToSelect != null) && conVoc.getName().equals(cvToSelect)) {
                cvComboBox.setSelectedItem(conVoc);
            }
        }

        updateUIForCV((ControlledVocabulary) cvComboBox.getSelectedItem());

        cvComboBox.addItemListener(this);
        entryList.addListSelectionListener(this);
    }

    /**
     * Updates some UI fields after a change in the selected CV.
     *
     * @param cv the selected cv
     */
    private void updateUIForCV(ControlledVocabulary cv) {
        // reset some fields
        cvNameTextField.setText(EMPTY);
        cvDescArea.setText(EMPTY);
        entryListModel.clear();
        cvNameLabel2.setText(ElanLocale.getString("EditCVDialog.Label.Name"));
        entryTextField.setText(EMPTY);
        entryDescField.setText(EMPTY);

        if (cv != null) {
            currentCV = cv;
            cvNameTextField.setText(cv.getName());
            cvDescArea.setText(cv.getDescription());
            cvNameLabel2.setText(ElanLocale.getString("EditCVDialog.Label.Name") +
                " " + cv.getName());

            CVEntry[] entries = cv.getEntries();

            for (int i = 0; i < entries.length; i++) {
                entryListModel.addElement(entries[i]);

                if (i == 0) {
                    entryTextField.setText(entries[i].getValue());
                    entryDescField.setText(entries[i].getDescription());
                    entryList.setSelectedIndex(0);
                }
            }

            changeCVButton.setEnabled(true);
            deleteCVButton.setEnabled(true);
            addEntryButton.setEnabled(true);
        } else {
            currentCV = null;
            changeCVButton.setEnabled(false);
            deleteCVButton.setEnabled(false);
            addEntryButton.setEnabled(false);
        }

        updateEntryButtons();

        // fill internal cache
        oldCVName = cvNameTextField.getText();
        oldCVDesc = cvDescArea.getText();
        oldEntryValue = entryTextField.getText();
        oldEntryDesc = entryDescField.getText();
    }

    /**
     * Enables or disables buttons depending on the selected entries.
     */
    private void updateEntryButtons() {
        if ((entryList == null) || (entryList.getSelectedIndex() == -1)) {
            changeEntryButton.setEnabled(false);
            deleteEntryButton.setEnabled(false);
            moveToTopButton.setEnabled(false);
            moveUpButton.setEnabled(false);
            moveDownButton.setEnabled(false);
            moveToBottomButton.setEnabled(false);
        } else {
            int firstIndex = entryList.getSelectedIndices()[0];
            int numSelected = entryList.getSelectedIndices().length;
            int lastIndex = entryList.getSelectedIndices()[numSelected - 1];
            changeEntryButton.setEnabled(true);
            deleteEntryButton.setEnabled(true);

            if (firstIndex > 0) {
                moveToTopButton.setEnabled(true);
                moveUpButton.setEnabled(true);
            } else {
                moveToTopButton.setEnabled(false);
                moveUpButton.setEnabled(false);
            }

            if (lastIndex < (entryList.getModel().getSize() - 1)) {
                moveDownButton.setEnabled(true);
                moveToBottomButton.setEnabled(true);
            } else {
                moveDownButton.setEnabled(false);
                moveToBottomButton.setEnabled(false);
            }
        }
    }

    /**
     * Enables or disables the undo/redo buttons.
     */
    private void updateUndoRedoButtons() {
        if (undoManager.canUndo()) {
            undoButton.setEnabled(true);
            undoButton.setToolTipText(undoManager.getUndoPresentationName());
        } else {
            undoButton.setEnabled(false);
            undoButton.setToolTipText(null);
        }

        if (undoManager.canRedo()) {
            redoButton.setEnabled(true);
            redoButton.setToolTipText(undoManager.getRedoPresentationName());
        } else {
            redoButton.setEnabled(false);
            redoButton.setToolTipText(null);
        }
    }

    /**
     * Reextracts the entries from the current CV after an add, change or
     * delete entry operation on the CV.
     *
     * @param selEntry the value of the entry to set selected in the ui
     */
    private void reextractEntries(String selEntry) {
        if (currentCV != null) {
            entryList.removeListSelectionListener(this);
            entryListModel.clear();

            CVEntry[] entries = currentCV.getEntries();
            CVEntry curEntry = null;

            for (int i = 0; i < entries.length; i++) {
                entryListModel.addElement(entries[i]);

                if (entries[i].getValue().equals(selEntry)) {
                    entryList.setSelectedIndex(i);
                    curEntry = entries[i];
                }
            }

            if ((selEntry == null) && (entries.length > 0)) {
                entryList.setSelectedIndex(0);
                curEntry = (CVEntry) entryList.getSelectedValue();
            }

            if (curEntry != null) {
                entryTextField.setText(curEntry.getValue());
                entryDescField.setText(curEntry.getDescription());
            } else {
                entryTextField.setText(EMPTY);
                entryDescField.setText(EMPTY);
            }

            updateEntryButtons();

            oldEntryValue = entryTextField.getText();
            oldEntryDesc = entryDescField.getText();
            entryList.addListSelectionListener(this);
        }
    }

    /**
     * Reextracts the entries from the current CV after a move operation on the
     * CV.
     *
     * @param selEntries the entries to set selected in the ui
     */
    private void reextractEntries(CVEntry[] selEntries) {
        if (currentCV != null) {
            entryList.removeListSelectionListener(this);
            entryListModel.clear();

            CVEntry firstSelEntry = null;

            if ((selEntries != null) && (selEntries.length > 0)) {
                firstSelEntry = selEntries[0];
            }

            CVEntry[] entries = currentCV.getEntries();

            for (int i = 0; i < entries.length; i++) {
                entryListModel.addElement(entries[i]);

                if (selEntries != null) {
                    for (int j = 0; j < selEntries.length; j++) {
                        if (selEntries[j] == entries[i]) {
                            entryList.addSelectionInterval(i, i);

                            break;
                        }
                    }
                }
            }

            if ((firstSelEntry == null) && (entries.length > 0)) {
                entryList.setSelectedIndex(0);
                firstSelEntry = (CVEntry) entryList.getSelectedValue();
            }

            if (firstSelEntry != null) {
                entryTextField.setText(firstSelEntry.getValue());
                entryDescField.setText(firstSelEntry.getDescription());
            } else {
                entryTextField.setText(EMPTY);
                entryDescField.setText(EMPTY);
            }

            updateEntryButtons();

            oldEntryValue = entryTextField.getText();
            oldEntryDesc = entryDescField.getText();
            entryList.addListSelectionListener(this);
        }
    }

    /**
     * Pack, size and set location.
     */
    private void postInit() {
        pack();

        int w = 550;
        int h = 500;
        setSize((getSize().width < w) ? w : getSize().width,
            (getSize().height < h) ? h : getSize().height);
        setLocationRelativeTo(getParent());
    }

    /**
     * This method is called from within the constructor to initialize the
     * dialog's components.
     */
    private void initComponents() {
        GridBagConstraints gridBagConstraints;

        ImageIcon topIcon = new ImageIcon(this.getClass().getResource("/mpi/eudico/client/annotator/resources/Top16.gif"));
        ImageIcon bottomIcon = new ImageIcon(this.getClass().getResource("/mpi/eudico/client/annotator/resources/Bottom16.gif"));
        ImageIcon upIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/navigation/Up16.gif"));
        ImageIcon downIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/navigation/Down16.gif"));
        ImageIcon redoIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/general/Redo16.gif"));
        ImageIcon undoIcon = new ImageIcon(this.getClass().getResource("/toolbarButtonGraphics/general/Undo16.gif"));

        cvPanel = new JPanel();
        currentCVLabel = new JLabel();
        cvComboBox = new JComboBox();
        cvNameLabel1 = new JLabel();
        cvNameTextField = new JTextField();
        cvDescLabel = new JLabel();
        cvDescArea = new JTextArea();
        cvButtonPanel = new JPanel();
        importButton = new JButton();
        addCVButton = new JButton();
        changeCVButton = new JButton();
        changeCVButton.setEnabled(false);
        deleteCVButton = new JButton();
        deleteCVButton.setEnabled(false);
        cvEntriesPanel = new JPanel();
        cvNameLabel2 = new JLabel();
        entriesLabel = new JLabel();
        entryListModel = new DefaultListModel();
        entryList = new JList(entryListModel);
        entryValueLabel = new JLabel();
        entryTextField = new JTextField();
        addEntryButton = new JButton();
        addEntryButton.setEnabled(false);
        changeEntryButton = new JButton();
        changeEntryButton.setEnabled(false);
        deleteEntryButton = new JButton();
        deleteEntryButton.setEnabled(false);

        closeButtonPanel = new JPanel();
        closeDialogButton = new JButton();
        titlePanel = new JPanel();
        titleLabel = new JLabel();
        entryDescLabel = new JLabel();
        entryDescField = new JTextField();
        moveEntriesPanel = new JPanel();
        moveUpButton = new JButton(upIcon);
        moveUpButton.setEnabled(false);
        moveToTopButton = new JButton(topIcon);
        moveToTopButton.setEnabled(false);
        moveDownButton = new JButton(downIcon);
        moveDownButton.setEnabled(false);
        moveToBottomButton = new JButton(bottomIcon);
        moveToBottomButton.setEnabled(false);
        undoButton = new JButton(undoIcon);
        undoButton.setEnabled(false);
        redoButton = new JButton(redoIcon);
        redoButton.setEnabled(false);

        Dimension prefDim = new Dimension(240, 24);
        Dimension buttonDim = new Dimension(24, 24);

        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent evt) {
                    closeDialog();
                }
            });

        getContentPane().setLayout(new GridBagLayout());

        Insets insets = new Insets(2, 6, 2, 6);

        titleLabel.setFont(titleLabel.getFont().deriveFont((float) 16));
        titlePanel.add(titleLabel);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        getContentPane().add(titlePanel, gridBagConstraints);

        cvPanel.setLayout(new GridBagLayout());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        cvPanel.add(currentCVLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        cvPanel.add(cvComboBox, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        cvPanel.add(cvNameLabel1, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvPanel.add(cvNameTextField, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        cvPanel.add(cvDescLabel, gridBagConstraints);

        cvDescArea.setLineWrap(true);
        cvDescArea.setWrapStyleWord(true);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        cvPanel.add(new JScrollPane(cvDescArea), gridBagConstraints);

        importButton.addActionListener(this);
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        cvPanel.add(importButton, gridBagConstraints);

        cvButtonPanel.setLayout(new GridLayout(3, 1, 6, 6));

        addCVButton.addActionListener(this);
        cvButtonPanel.add(addCVButton);

        changeCVButton.addActionListener(this);
        cvButtonPanel.add(changeCVButton);

        deleteCVButton.addActionListener(this);
        cvButtonPanel.add(deleteCVButton);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.insets = insets;
        cvPanel.add(cvButtonPanel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        getContentPane().add(cvPanel, gridBagConstraints);

        cvEntriesPanel.setLayout(new GridBagLayout());

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(cvNameLabel2, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(entriesLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridheight = 8;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;

        JScrollPane entryPane = new JScrollPane(entryList);
        entryPane.setPreferredSize(prefDim);
        entryPane.setMinimumSize(prefDim);
        cvEntriesPanel.add(entryPane, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        cvEntriesPanel.add(entryValueLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        entryTextField.setPreferredSize(prefDim);
        entryTextField.setMinimumSize(prefDim);
        cvEntriesPanel.add(entryTextField, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        cvEntriesPanel.add(entryDescLabel, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        entryDescField.setPreferredSize(prefDim);
        entryDescField.setMinimumSize(prefDim);
        cvEntriesPanel.add(entryDescField, gridBagConstraints);

        addEntryButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(addEntryButton, gridBagConstraints);

        changeEntryButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(changeEntryButton, gridBagConstraints);

        deleteEntryButton.addActionListener(this);
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(deleteEntryButton, gridBagConstraints);

        // add entry sorting buttons
        moveEntriesPanel.setLayout(new GridBagLayout());

        moveToTopButton.addActionListener(this);
        moveToTopButton.setPreferredSize(buttonDim);
        moveToTopButton.setMaximumSize(buttonDim);
        moveToTopButton.setMinimumSize(buttonDim);
        moveUpButton.addActionListener(this);
        moveUpButton.setPreferredSize(buttonDim);
        moveUpButton.setMaximumSize(buttonDim);
        moveUpButton.setMinimumSize(buttonDim);
        moveDownButton.addActionListener(this);
        moveDownButton.setPreferredSize(buttonDim);
        moveDownButton.setMaximumSize(buttonDim);
        moveDownButton.setMinimumSize(buttonDim);
        moveToBottomButton.addActionListener(this);
        moveToBottomButton.setPreferredSize(buttonDim);
        moveToBottomButton.setMaximumSize(buttonDim);
        moveToBottomButton.setMinimumSize(buttonDim);
        undoButton.addActionListener(this);
        undoButton.setPreferredSize(buttonDim);
        undoButton.setMaximumSize(buttonDim);
        undoButton.setMinimumSize(buttonDim);
        redoButton.addActionListener(this);
        redoButton.setPreferredSize(buttonDim);
        redoButton.setMaximumSize(buttonDim);
        redoButton.setMinimumSize(buttonDim);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(moveToTopButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(moveUpButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(moveDownButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(moveToBottomButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(undoButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = GridBagConstraints.NONE;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        moveEntriesPanel.add(redoButton, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        cvEntriesPanel.add(moveEntriesPanel, gridBagConstraints);

        //
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = insets;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        getContentPane().add(cvEntriesPanel, gridBagConstraints);

        closeButtonPanel.setLayout(new GridLayout(1, 1, 0, 2));

        closeDialogButton.addActionListener(this);
        closeButtonPanel.add(closeDialogButton);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.insets = insets;
        getContentPane().add(closeButtonPanel, gridBagConstraints);

        updateLocale();

        InputMap iMap = ((JComponent) getContentPane()).getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        ActionMap aMap = ((JComponent) getContentPane()).getActionMap();

        if ((iMap != null) && (aMap != null)) {
            final String esc = "Esc";
            final String enter = "Enter";
            iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), esc);
            iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), enter);
            aMap.put(esc, new EscapeAction());
            aMap.put(enter, new EnterAction());
        }

        // locale support
        entryTextField.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent event) {
                    if (SwingUtilities.isRightMouseButton(event) ||
                            event.isPopupTrigger()) {
                        createPopupMenu();

                        if (popup != null) {
                            popup.show(entryTextField, event.getX(),
                                event.getY());

                            //popup.setVisible(true);
                        }
                    }
                }
            });

        entryTextField.addFocusListener(new FocusAdapter() {
                public void focusGained(FocusEvent fe) {
                    if (currentLocale != null) {
                        ImUtil.setLanguage(entryTextField, currentLocale);
                        entryTextField.setFont(Constants.DEFAULTFONT);
                    }
                }

                public void focusLost(FocusEvent fe) {
                    ImUtil.setLanguage(entryTextField, Locale.getDefault());
                    entryTextField.setFont(Constants.DEFAULTFONT);
                }
            });

        entryTextField.setFont(Constants.DEFAULTFONT);
        entryList.setFont(Constants.DEFAULTFONT);
    }

    //END:initComponents

    /**
     * Since this dialog is meant to be modal a Locale change while this dialog
     * is open  is not supposed to happen. This will set the labels etc. using
     * the current locale  strings.
     */
    private void updateLocale() {
        closeDialogButton.setText(ElanLocale.getString(
                "EditCVDialog.Button.Close"));
        moveToTopButton.setToolTipText(ElanLocale.getString(
                "EditCVDialog.Button.Top"));
        moveUpButton.setToolTipText(ElanLocale.getString(
                "EditCVDialog.Button.Up"));
        moveDownButton.setToolTipText(ElanLocale.getString(
                "EditCVDialog.Button.Down"));
        moveToBottomButton.setToolTipText(ElanLocale.getString(
                "EditCVDialog.Button.Bottom"));
        deleteEntryButton.setText(ElanLocale.getString("Button.Delete"));
        changeEntryButton.setText(ElanLocale.getString("Button.Change"));
        addEntryButton.setText(ElanLocale.getString("Button.Add"));
        entryDescLabel.setText(ElanLocale.getString(
                "EditCVDialog.Label.EntryDescription"));
        entryValueLabel.setText(ElanLocale.getString("EditCVDialog.Label.Value"));
        entriesLabel.setText(ElanLocale.getString("EditCVDialog.Label.Entries"));
        cvEntriesPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "EditCVDialog.Label.Entries")));
        cvNameLabel2.setText(ElanLocale.getString("EditCVDialog.Label.Name"));
        deleteCVButton.setText(ElanLocale.getString("Button.Delete"));
        changeCVButton.setText(ElanLocale.getString("Button.Change"));
        addCVButton.setText(ElanLocale.getString("Button.Add"));
        importButton.setText(ElanLocale.getString("Button.Import"));
        cvDescLabel.setText(ElanLocale.getString(
                "EditCVDialog.Label.CVDescription"));
        cvNameLabel1.setText(ElanLocale.getString("EditCVDialog.Label.Name"));
        cvPanel.setBorder(new TitledBorder(ElanLocale.getString(
                    "EditCVDialog.Label.CV")));
        currentCVLabel.setText(ElanLocale.getString(
                "EditCVDialog.Label.Current"));
        titleLabel.setText(ElanLocale.getString("EditCVDialog.Title"));
        setTitle(ElanLocale.getString("EditCVDialog.Title"));
    }

    /**
     * Creates a popup menu containing all Locales available in IMUtils.
     */
    private void createPopupMenu() {
        if (popup == null) {
            try {
                availableLocales = ImUtil.getLanguages();

                popup = new JPopupMenu();

                JMenuItem item;

                for (int i = 0; i < availableLocales.length; i++) {
                    item = new JMenuItem(availableLocales[i].getDisplayName());
                    item.addActionListener(this);
                    popup.add(item);
                }
            } catch (java.lang.NoSuchMethodError nsme) {
                // The SPI extensions have not been present at startup.
                //String msg = "Setup incomplete: you won't be able to set languages for editing.";
                String msg = ElanLocale.getString("InlineEditBox.Message.SPI") +
                    "\n" + ElanLocale.getString("InlineEditBox.Message.SPI2");
                JOptionPane.showMessageDialog(null, msg, null,
                    JOptionPane.ERROR_MESSAGE);
                popup = null;
            } catch (Exception exc) {
                LOG.warning(LogUtil.formatStackTrace(exc));
                popup = null;
            }
        }
    }

    /**
     * Closes the dialog
     */
    private void closeDialog() {
        setVisible(false);
        dispose();
    }

    /**
     * The button actions.
     *
     * @param actionEvent the actionEvent
     */
    public void actionPerformed(ActionEvent actionEvent) {
        Object source = actionEvent.getSource();

        // check source equality
        if (source == closeDialogButton) {
            closeDialog();
        } else if (source == importButton) {
            importCV();
        } else if (source == addCVButton) {
            addCV();
        } else if (source == changeCVButton) {
            changeCV();
        } else if (source == deleteCVButton) {
            deleteCV();
        } else if (source == addEntryButton) {
            addEntry();
        } else if (source == changeEntryButton) {
            changeEntry();
        } else if (source == deleteEntryButton) {
            deleteEntries();
        } else if (source == moveToTopButton) {
            entriesToTop();
        } else if (source == moveUpButton) {
            entriesUp();
        } else if (source == moveDownButton) {
            entriesDown();
        } else if (source == moveToBottomButton) {
            entriesToBottom();
        } else if (source == undoButton) {
            undo();
        } else if (source == redoButton) {
            redo();
        } else {
            // language menuitem
            String locale = actionEvent.getActionCommand();

            for (int i = 0; i < availableLocales.length; i++) {
                if (availableLocales[i].getDisplayName().equals(locale)) {
                    currentLocale = availableLocales[i];
                    ImUtil.setLanguage(entryTextField, currentLocale);
                    entryTextField.setFont(Constants.DEFAULTFONT);

                    return;
                }
            }
        }
    }

    /**
     * Imports a or several CV's from a template file, selected by the user.
     * When there are CV's with the same identifier as existing CV's the user
     * is prompted to either replace an existing CV or rename the imported CV
     * or just skip the CV.
     */
    private void importCV() {
        File importFile = getImportFile();

        if (importFile == null) {
            return;
        }

        Parser parser = ParserFactory.getParser(ParserFactory.EAF22);
        HashMap cvTable = ((EAF22Parser) parser).getControlledVocabularies(importFile.getAbsolutePath());

        if (cvTable.size() == 0) {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.NoCVFound"));

            return;
        }

        //create CV objects and temporarely store them
        ArrayList allCVs = new ArrayList(cvTable.size());

        ControlledVocabulary cv = null;
        Iterator cvIt = cvTable.keySet().iterator();

        while (cvIt.hasNext()) {
            String cvName = (String) cvIt.next();

            if (cvName == null) {
                continue;
            }

            cv = new ControlledVocabulary(cvName);

            // the contents vector can contain one description String
            // and many CVEntryRecords
            Vector contents = (Vector) cvTable.get(cvName);

            if (contents.size() > 0) {
                Object next;
                CVEntry entry;

                for (int i = 0; i < contents.size(); i++) {
                    next = contents.get(i);

                    if (next instanceof String) {
                        cv.setDescription((String) next);
                    } else if (next instanceof CVEntryRecord) {
                        entry = new CVEntry(((CVEntryRecord) next).getValue(),
                                ((CVEntryRecord) next).getDescription());

                        if (entry != null) {
                            cv.addEntry(entry);
                        }
                    }
                }
            }

            allCVs.add(cv);
        }

        //now add them to the transcription, ensuring that all CV-names are unique
        for (int i = 0; i < allCVs.size(); i++) {
            cv = (ControlledVocabulary) allCVs.get(i);

            if (transcription.getControlledVocabulary(cv.getName()) != null) {
                // cv with that name already exists: prompt user:
                // replace, rename or skip
                int option = showCVQuestionDialog(cv.getName());

                if (option == 1) {
                    replaceCV(cv);
                } else if (option == 2) {
                    String newName;

                    while (true) {
                        newName = showAskNameDialog(cv.getName());

                        if (transcription.getControlledVocabulary(newName) != null) {
                            showWarningDialog(ElanLocale.getString(
                                    "EditCVDialog.Message.CVExists"));

                            continue;
                        } else {
                            break;
                        }
                    }

                    if ((newName == null) || (newName.length() == 0)) {
                        continue; //means skipping
                    } else {
                        cv.setName(newName);
                        addCV(cv);
                    }
                }

                // else continue...
            } else {
                // the transcription does not contain a cv with the same name, add it
                addCV(cv);
            }
        }

        reextractCVs(null);
    }

    /**
     * Prompts the user to select an template import file.
     *
     * @return the template file, or null when no valid file was selected
     */
    private File getImportFile() {
        // setup a file chooser
        String dir = (String) Preferences.get("LastUsedEAFDir", null);

        if (dir == null) {
            dir = (new File(transcription.getName())).getParent();

            if (dir == null) {
                dir = System.getProperty("user.dir");
            }
        }

        JFileChooser chooser = new JFileChooser(dir);
        chooser.setDialogTitle(ElanLocale.getString("Button.Import"));
        chooser.setFileFilter(ElanFileFilter.createFileFilter(
                ElanFileFilter.TEMPLATE_TYPE));
        chooser.removeChoosableFileFilter(chooser.getAcceptAllFileFilter());

        JTextArea textAr = new JTextArea(ElanLocale.getString(
                    "EditCVDialog.Message.Browse"));
        textAr.setBackground(chooser.getBackground());
        textAr.setWrapStyleWord(true);
        textAr.setLineWrap(true);
        textAr.setEditable(false);
        textAr.setPreferredSize(new Dimension(160, 100));
        textAr.setMargin(new Insets(5, 5, 5, 5));

        chooser.setAccessory(textAr);

        int option = chooser.showOpenDialog(this);

        if (option == JFileChooser.CANCEL_OPTION) {
            return null;
        } else if (option == JFileChooser.APPROVE_OPTION) {
            File impFile = chooser.getSelectedFile();
            String filePath = impFile.getAbsolutePath();

            if (!impFile.exists() || impFile.isDirectory()) {
                StringBuffer strMessage = new StringBuffer(ElanLocale.getString(
                            "Menu.Dialog.Message1"));
                strMessage.append(impFile.getName());
                strMessage.append(ElanLocale.getString("Menu.Dialog.Message2"));

                String strError = ElanLocale.getString("Message.Error");
                JOptionPane.showMessageDialog(this, strMessage, strError,
                    JOptionPane.ERROR_MESSAGE);

                return null;
            }

            if (!filePath.toLowerCase().endsWith(".etf")) {
                StringBuffer strMessage = new StringBuffer(ElanLocale.getString(
                            "Menu.Dialog.Message1"));
                strMessage.append(impFile.getName());
                strMessage.append(ElanLocale.getString("Menu.Dialog.Message3"));

                String strError = ElanLocale.getString("Message.Error");
                JOptionPane.showMessageDialog(this, strMessage, strError,
                    JOptionPane.ERROR_MESSAGE);

                return null;
            }

            Preferences.set("LastUsedEAFDir", impFile.getParent(), null);

            return impFile;
        }

        return null;
    }

    /**
     * Creates a new ControlledVocabulary when there isn't already one with the
     * same name and adds it to the Transcription.
     */
    private void addCV() {
        String name = cvNameTextField.getText();

        if (name != null) {
            name = name.trim();

            if (name.length() > 0) {
                ControlledVocabulary aCV = transcription.getControlledVocabulary(name);

                if (aCV != null) {
                    // cv with that name already exists, warn
                    showWarningDialog(ElanLocale.getString(
                            "EditCVDialog.Message.CVExists"));

                    return;
                }

                //create a new CV and add it to the Transcription
                Command com = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.ADD_CV);
                Object[] args = new Object[2];
                args[0] = name;
                args[1] = cvDescArea.getText();

                com.execute(transcription, args);

                reextractCVs(name);

                undoManager.discardAllEdits();
                updateUndoRedoButtons();
            } else {
                showWarningDialog(ElanLocale.getString(
                        "EditCVDialog.Message.CVValidName"));

                return;
            }
        } else {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.CVValidName"));

            return;
        }
    }

    /**
     * Adds an existing, imported CV, probably already containing entries to
     * the  Transcription.
     *
     * @param conVoc the CV to add
     *
     * @see #importCV()
     */
    private void addCV(ControlledVocabulary conVoc) {
        if (conVoc == null) {
            return;
        }

        //create a new CV and add it to the Transcription
        Command com = ELANCommandFactory.createCommand(transcription,
                ELANCommandFactory.ADD_CV);
        Object[] args = new Object[1];
        args[0] = conVoc;

        com.execute(transcription, args);

        //reextractCVs() is done somewhere else
    }

    /**
     * Changes name and/or description of an existing CV. Checks whether the
     * specified name is unique within the Transcription.
     */
    private void changeCV() {
        String name = cvNameTextField.getText();
        String desc = cvDescArea.getText();

        if (name != null) {
            name = name.trim();

            if (name.length() < 1) {
                showWarningDialog(ElanLocale.getString(
                        "EditCVDialog.Message.CVValidName"));
                cvNameTextField.setText(oldCVName);

                return;
            }
        }

        if ((desc != null) && (desc.length() > 0)) {
            desc = desc.trim();
        }

        if ((oldCVName != null) && !oldCVName.equals(name)) {
            // check if there is already a cv with the new name
            ControlledVocabulary conVoc = transcription.getControlledVocabulary(name);

            if (conVoc != null) {
                // cv with that name already exists, warn
                showWarningDialog(ElanLocale.getString(
                        "EditCVDialog.Message.CVExists"));

                return;
            } else {
                // create a change CV command
                Command com = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.CHANGE_CV);
                Object[] args = new Object[4];
                args[0] = oldCVName;
                args[1] = oldCVDesc;
                args[2] = name;
                args[3] = desc;

                com.execute(transcription, args);
                reextractCVs(name);

                return;
            }
        } else if (((oldCVDesc == null) && (desc != null) &&
                (desc.length() > 0)) ||
                ((oldCVDesc != null) &&
                ((desc == null) || (desc.length() == 0))) ||
                (!oldCVDesc.equals(desc))) {
            // change the description
            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.CHANGE_CV);
            Object[] args = new Object[4];
            args[0] = oldCVName;
            args[1] = oldCVDesc;
            args[2] = oldCVName;
            args[3] = desc;

            com.execute(transcription, args);

            // to be sure update the dialog
            reextractCVs(name);

            return;
        }

        // could warn here that no changes were detected,
        // for now silently ignore
    }

    /**
     * Deletes the CV from the Transcription. If there are any tiers using this
     * cv  a message will ask the user for confirmation.
     */
    private void deleteCV() {
        ControlledVocabulary conVoc = (ControlledVocabulary) cvComboBox.getSelectedItem();

        if (conVoc != null) { //should always be true

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.DELETE_CV);

            // warn if there are tiers using lin. types using this cv
            if (transcription.getTiersWithCV(conVoc.getName()).size() > 0) {
                String mes = ElanLocale.getString(
                        "EditCVDialog.Message.CVInUse") + "\n" +
                    ElanLocale.getString("EditCVDialog.Message.CVConfirmDelete");

                if (!showConfirmDialog(mes)) {
                    return;
                }
            }

            com.execute(transcription, new Object[] { conVoc });

            reextractCVs(null);

            undoManager.discardAllEdits();
            updateUndoRedoButtons();
        }
    }

    /**
     * Deletes an existing CV with the name of the specified CV and adds  the
     * specified CV. The user is not promted or warned.
     *
     * @param conVoc the new ControlledVocabulary
     */
    private void replaceCV(ControlledVocabulary conVoc) {
        String name = conVoc.getName();
        ControlledVocabulary oldCv = transcription.getControlledVocabulary(name);

        if (oldCv != null) {
            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.REPLACE_CV);
            com.execute(transcription, new Object[] { oldCv, conVoc });
        }
    }

    /**
     * Adds an entry to the current CV. When checking the uniqueness of the
     * entry  value, values are compared case sensitive.
     */
    private void addEntry() {
        if (currentCV == null) {
            return;
        }

        String entry = entryTextField.getText();

        if (entry != null) {
            entry = entry.trim();

            if (entry.length() == 0) {
                showWarningDialog(ElanLocale.getString(
                        "EditCVDialog.Message.EntryValidValue"));

                return;
            }
        } else {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.EntryValidValue"));

            return;
        }

        if (currentCV.containsValue(entry)) {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.EntryExists"));
        } else {
            String desc = entryDescField.getText();

            if (desc != null) {
                desc = desc.trim();
            }

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.ADD_CV_ENTRY);

            Object[] args = new Object[2];
            args[0] = entry;
            args[1] = desc;
            com.execute(currentCV, args);

            reextractEntries(entry);
            undoManager.addEdit(new UndoableEntryEdit(currentCV, entry, desc,
                    ElanLocale.getString("CommandActions.AddCVEntry")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Changes the value and/or description of an existing entry. Checks
     * whether  the specified value is unique within the current
     * ControlledVocabulary.
     */
    private void changeEntry() {
        if (currentCV == null) {
            return;
        }

        String entry = entryTextField.getText();

        if (entry != null) {
            entry = entry.trim();

            if (entry.length() == 0) {
                showWarningDialog(ElanLocale.getString(
                        "EditCVDialog.Message.EntryValidValue"));
                entryTextField.setText(oldEntryValue);

                return;
            }
        } else {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.EntryValidValue"));
            entryTextField.setText(oldEntryValue);

            return;
        }

        String desc = entryDescField.getText();

        if (desc != null) {
            desc = desc.trim();
        }

        if (entry.equals(oldEntryValue)) {
            if ((desc != null) && !desc.equals(oldEntryDesc)) {
                Command com = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.CHANGE_CV_ENTRY);
                Object[] args = new Object[3];
                args[0] = oldEntryValue;
                args[1] = entry;
                args[2] = desc;

                com.execute(currentCV, args);

                undoManager.addEdit(new UndoableEntryEdit(currentCV,
                        oldEntryValue, entry, oldEntryDesc, desc,
                        ElanLocale.getString("CommandActions.ChangeCVEntry")));
                reextractEntries(entry);
                updateUndoRedoButtons();

                return;
            } else {
                //nothing changed
                return;
            }
        }

        // entry value has changed...
        if (currentCV.containsValue(entry)) {
            showWarningDialog(ElanLocale.getString(
                    "EditCVDialog.Message.EntryExists"));
        } else {
            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.CHANGE_CV_ENTRY);
            Object[] args = new Object[3];
            args[0] = oldEntryValue;
            args[1] = entry;
            args[2] = desc;

            com.execute(currentCV, args);

            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntryValue,
                    entry, oldEntryDesc, desc,
                    ElanLocale.getString("CommandActions.ChangeCVEntry")));
            reextractEntries(entry);
            updateUndoRedoButtons();
        }
    }

    /**
     * Deletes the selected entry/entries from the current
     * ControlledVocabulary.
     */
    private void deleteEntries() {
        Object[] selEntries = entryList.getSelectedValues();

        if (selEntries.length == 0) {
            return;
        } else {
            CVEntry[] entries = new CVEntry[selEntries.length];

            for (int i = 0; i < entries.length; i++) {
                entries[i] = (CVEntry) selEntries[i];
            }

            CVEntry[] oldEntries = currentCV.getEntries();

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.DELETE_CV_ENTRY);
            Object[] args = new Object[1];
            args[0] = entries;

            com.execute(currentCV, args);

            reextractEntries(new String());

            CVEntry[] newEntries = currentCV.getEntries();
            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntries,
                    newEntries,
                    ElanLocale.getString("CommandActions.DeleteCVEntry")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Moves the selected entries to the top of the entry list.
     */
    private void entriesToTop() {
        if (currentCV == null) {
            return;
        }

        Object[] selEntries = entryList.getSelectedValues();

        if (selEntries.length == 0) {
            return;
        } else {
            CVEntry[] entries = new CVEntry[selEntries.length];

            for (int i = 0; i < entries.length; i++) {
                entries[i] = (CVEntry) selEntries[i];
            }

            CVEntry[] oldEntries = currentCV.getEntries();

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.MOVE_CV_ENTRIES);
            Object[] args = new Object[2];
            args[0] = entries;
            args[1] = new Integer(ControlledVocabulary.MOVE_TO_TOP);

            com.execute(currentCV, args);

            reextractEntries(entries);

            CVEntry[] newEntries = currentCV.getEntries();
            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntries,
                    newEntries, ElanLocale.getString("EditCVDialog.Button.Top")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Moves the selected entries one position up in the entry list.
     */
    private void entriesUp() {
        if (currentCV == null) {
            return;
        }

        Object[] selEntries = entryList.getSelectedValues();

        if (selEntries.length == 0) {
            return;
        } else {
            CVEntry[] entries = new CVEntry[selEntries.length];

            for (int i = 0; i < entries.length; i++) {
                entries[i] = (CVEntry) selEntries[i];
            }

            CVEntry[] oldEntries = currentCV.getEntries();

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.MOVE_CV_ENTRIES);
            Object[] args = new Object[2];
            args[0] = entries;
            args[1] = new Integer(ControlledVocabulary.MOVE_UP);

            com.execute(currentCV, args);

            reextractEntries(entries);

            CVEntry[] newEntries = currentCV.getEntries();
            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntries,
                    newEntries, ElanLocale.getString("EditCVDialog.Button.Up")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Moves the selected entries one position down in the entry list.
     */
    private void entriesDown() {
        if (currentCV == null) {
            return;
        }

        Object[] selEntries = entryList.getSelectedValues();

        if (selEntries.length == 0) {
            return;
        } else {
            CVEntry[] entries = new CVEntry[selEntries.length];

            for (int i = 0; i < entries.length; i++) {
                entries[i] = (CVEntry) selEntries[i];
            }

            CVEntry[] oldEntries = currentCV.getEntries();

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.MOVE_CV_ENTRIES);
            Object[] args = new Object[2];
            args[0] = entries;
            args[1] = new Integer(ControlledVocabulary.MOVE_DOWN);

            com.execute(currentCV, args);

            reextractEntries(entries);

            CVEntry[] newEntries = currentCV.getEntries();
            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntries,
                    newEntries, ElanLocale.getString("EditCVDialog.Button.Down")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Moves the selected entries to the bottom of the entry list.
     */
    private void entriesToBottom() {
        if (currentCV == null) {
            return;
        }

        Object[] selEntries = entryList.getSelectedValues();

        if (selEntries.length == 0) {
            return;
        } else {
            CVEntry[] entries = new CVEntry[selEntries.length];

            for (int i = 0; i < entries.length; i++) {
                entries[i] = (CVEntry) selEntries[i];
            }

            CVEntry[] oldEntries = currentCV.getEntries();

            Command com = ELANCommandFactory.createCommand(transcription,
                    ELANCommandFactory.MOVE_CV_ENTRIES);
            Object[] args = new Object[2];
            args[0] = entries;
            args[1] = new Integer(ControlledVocabulary.MOVE_TO_BOTTOM);

            com.execute(currentCV, args);

            reextractEntries(entries);

            CVEntry[] newEntries = currentCV.getEntries();
            undoManager.addEdit(new UndoableEntryEdit(currentCV, oldEntries,
                    newEntries,
                    ElanLocale.getString("EditCVDialog.Button.Bottom")));
            updateUndoRedoButtons();
        }
    }

    /**
     * Invokes the undo method of the <code>UndoManager</code>.
     */
    private void undo() {
        try {
            undoManager.undo();

            reextractEntries("");
        } catch (CannotUndoException cue) {
            LOG.warning(LogUtil.formatStackTrace(cue));
        }

        updateUndoRedoButtons();
    }

    /**
     * Invokes the redo method of the <code>UndoManager</code>.
     */
    private void redo() {
        try {
            undoManager.redo();

            reextractEntries("");
        } catch (CannotRedoException cre) {
            LOG.warning(LogUtil.formatStackTrace(cre));
        }

        updateUndoRedoButtons();
    }

    /**
     * Handles a change in the cv selection.
     *
     * @param ie the item event
     */
    public void itemStateChanged(ItemEvent ie) {
        if (ie.getSource() == cvComboBox) {
            undoManager.discardAllEdits();
            updateUndoRedoButtons();
            updateUIForCV((ControlledVocabulary) cvComboBox.getSelectedItem());
        }
    }

    /**
     * Handles a change in the selection in the entry list.
     *
     * @param lse the list selection event
     */
    public void valueChanged(ListSelectionEvent lse) {
        if (lse.getSource() == entryList) {
            if (entryList.getSelectedIndex() == -1) {
                entryTextField.setText(EMPTY);
                entryDescField.setText(EMPTY);
            } else {
                int firstIndex = entryList.getSelectedIndices()[0];
                CVEntry selEntry = (CVEntry) entryListModel.get(firstIndex);
                entryTextField.setText(selEntry.getValue());
                entryDescField.setText(selEntry.getDescription());
            }

            updateEntryButtons();
        }

        oldEntryValue = entryTextField.getText();
        oldEntryDesc = entryDescField.getText();
    }

    /**
     * Shows a warning/error dialog with the specified message string.
     *
     * @param message the message to display
     */
    private void showWarningDialog(String message) {
        JOptionPane.showMessageDialog(this, message,
            ElanLocale.getString("Message.Warning"), JOptionPane.WARNING_MESSAGE);
    }

    /**
     * Shows a confirm (yes/no) dialog with the specified message string.
     *
     * @param message the messsage to display
     *
     * @return true if the user clicked OK, false otherwise
     */
    private boolean showConfirmDialog(String message) {
        int confirm = JOptionPane.showConfirmDialog(this, message,
                ElanLocale.getString("Message.Warning"),
                JOptionPane.YES_NO_OPTION);

        return confirm == JOptionPane.YES_OPTION;
    }

    /**
     * Ask the user to skip, replace existing cv or rename importing cv.
     *
     * @param cvName name of the cv
     *
     * @return 0 means skip, 1 means replace and 2 means rename
     */
    private int showCVQuestionDialog(String cvName) {
        String[] options = new String[3];
        options[0] = ElanLocale.getString("EditCVDialog.Message.Skip");
        options[1] = ElanLocale.getString("EditCVDialog.Message.Replace");
        options[2] = ElanLocale.getString("EditCVDialog.Message.Rename");

        String message = ElanLocale.getString("EditCVDialog.Message.CVExists") +
            "\n\n- " + cvName + "\n";

        JOptionPane pane = new JOptionPane(message,
                JOptionPane.QUESTION_MESSAGE, JOptionPane.DEFAULT_OPTION, null,
                options);
        pane.createDialog(this, "").show();

        Object selValue = pane.getValue();

        for (int i = 0; i < options.length; i++) {
            if (selValue == options[i]) {
                return i;
            }
        }

        return 0;
    }

    /**
     * Prompts the user to enter a new name for a CV.
     *
     * @param name the old name
     *
     * @return the new name, or null when the user has cancelled the dialog
     */
    private String showAskNameDialog(String name) {
        String message = ElanLocale.getString("EditCVDialog.Message.NewName") +
            "\n\n- " + name;
        String newName = JOptionPane.showInputDialog(this, message,
                ElanLocale.getString("EditCVDialog.Message.Rename"),
                JOptionPane.QUESTION_MESSAGE);

        return newName;
    }

    ////////////
    // action classes for handling escape and enter key.
    ////////////

    /**
     * An action to put in the dialog's action map and that is being performed
     * when the escape key has been hit.
     *
     * @author Han Sloetjes
     */
    private class EscapeAction extends AbstractAction {
        /**
         * The action that is performed when the escape key has been hit.
         *
         * @param ae the action event
         */
        public void actionPerformed(ActionEvent ae) {
            EditCVDialog.this.closeDialog();
        }
    }

    /**
     * An action to put in the dialog's action map and that is being performed
     * when the enter key has been hit.
     *
     * @author Han Sloetjes
     */
    private class EnterAction extends AbstractAction {
        /**
         * The action that is performed when the enter key has been hit.
         *
         * @param ae the action event
         */
        public void actionPerformed(ActionEvent ae) {
            Component com = EditCVDialog.this.getFocusOwner();

            if (com instanceof JButton) {
                ((JButton) com).doClick();
            }
        }
    }

    /**
     * An undoable edit class that can be used with a <code>UndoManager</code>.
     * The edit class stores the old and the new array of CVEntry objects.
     *
     * @author Han Sloetjes
     */
    private class UndoableEntryEdit extends AbstractUndoableEdit {
        private ControlledVocabulary conVoc;
        private CVEntry[] oldEntries;
        private CVEntry[] newEntries;
        private String oldValue;
        private String newValue;
        private String oldDesc;
        private String newDesc;
        private String presentationName;

        /**
         * Creates a new UndoableEntryEdit instance.
         * Used when one or more entries have been deleted or moved.
         *
         * @param conVoc the ControlledVocabulary
         * @param oldEntries the old set of CVEntries
         * @param newEntries the new set of CVEntries
         * @param presentationName presentation name of the edit
         */
        UndoableEntryEdit(ControlledVocabulary conVoc, CVEntry[] oldEntries,
            CVEntry[] newEntries, String presentationName) {
            this.conVoc = conVoc;
            this.oldEntries = oldEntries;
            this.newEntries = newEntries;
            this.presentationName = presentationName;
        }

        /**
         * Creates a new UndoableEntryEdit instance.
         * Used when an entry has been changed.
         *
         * @param conVoc the ControlledVocabulary
         * @param oldValue the old entry value
         * @param newValue the new entry value
         * @param oldDesc the old entry description
         * @param newDesc the new entry description
         * @param presentationName presentation name of the edit
         */
        UndoableEntryEdit(ControlledVocabulary conVoc, String oldValue,
            String newValue, String oldDesc, String newDesc,
            String presentationName) {
            this.conVoc = conVoc;
            this.oldValue = oldValue;
            this.newValue = newValue;
            this.oldDesc = oldDesc;
            this.newDesc = newDesc;
            this.presentationName = presentationName;
        }

        /**
         * Creates a new UndoableEntryEdit instance.
         * Used when a new CVEntry has been created.
         *
         * @param conVoc the ControlledVocabulary
         * @param newValue the new entry value
         * @param newDesc the new entry description
         * @param presentationName presentation name of the edit
         */
        UndoableEntryEdit(ControlledVocabulary conVoc, String newValue,
            String newDesc, String presentationName) {
            this.conVoc = conVoc;
            this.newValue = newValue;
            this.newDesc = newDesc;
            this.presentationName = presentationName;
        }

        /**
         * Returns the presentation name of the edit.
         *
         * @return the presentation name of the edit
         */
        public String getPresentationName() {
            return presentationName;
        }

        /**
         * Returns the redo presentation name of the edit.
         * This is something like "Redo " + the presentation name.
         *
         * @return the redo presentation name of the edit
         */
        public String getRedoPresentationName() {
            return ElanLocale.getString("Menu.Edit.Redo") + " " +
            getPresentationName();
        }

        /**
         * Returns the undo presentation name of the edit.
         * This is something like "Undo " + the presentation name.
         *
         * @return the undo presentation name of the edit
         */
        public String getUndoPresentationName() {
            return ElanLocale.getString("Menu.Edit.Undo") + " " +
            getPresentationName();
        }

        /**
         * The actual redo action.
         *
         * @throws CannotRedoException when the edit cannot be redone.
         */
        public void redo() throws CannotRedoException {
            super.redo();

            if (conVoc == null) {
                throw new CannotRedoException();
            }

            if (oldEntries != null) {
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.REPLACE_CV_ENTRIES);
                c.execute(conVoc, new Object[] { newEntries });
            } else if (oldValue != null) {
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.CHANGE_CV_ENTRY);
                c.execute(conVoc, new Object[] { oldValue, newValue, newDesc });
            } else if (newValue != null) {
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.ADD_CV_ENTRY);
                c.execute(conVoc, new Object[] { newValue, newDesc });
            }
        }

        /**
         * The actual undo action.
         *
         * @throws CannotUndoException when the edit cannot been undone.
         */
        public void undo() throws CannotUndoException {
            super.undo();

            if (conVoc == null) {
                throw new CannotUndoException();
            }

            if (oldEntries != null) {
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.REPLACE_CV_ENTRIES);
                c.execute(conVoc, new Object[] { oldEntries });
            } else if (oldValue != null) {
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.CHANGE_CV_ENTRY);
                c.execute(conVoc, new Object[] { newValue, oldValue, oldDesc });
            } else if (newValue != null) {
                CVEntry entry = conVoc.getEntryWithValue(newValue);
                Command c = ELANCommandFactory.createCommand(transcription,
                        ELANCommandFactory.DELETE_CV_ENTRY);
                c.execute(conVoc, new Object[] { new CVEntry[] { entry } });
            }
        }
    }
}
