/*
 * File:     MultiTierControlPanel.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.viewer;

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

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

import mpi.eudico.client.annotator.gui.*;

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

import mpi.eudico.server.corpora.clomimpl.abstr.TierImpl;
import mpi.eudico.server.corpora.clomimpl.type.LinguisticType;

import mpi.util.gui.TreeFrame;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.MenuElement;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;


/**
 * JPanel that shows Tier labels for a MultiTierViewer The labels can be hidden
 * and replaced.
 */
public class MultiTierControlPanel extends JPanel implements MouseListener,
    MouseMotionListener, ActionListener, ElanLocaleListener {
    /** Holds value of property width */
    public final static int WIDTH = 120;

    /** the horizontal margins of the component */
    public final static int MARGIN = 5;

    /** Indentation for each hierarchical tier level */
    public final static int LEVEL_INDENT = 10;

    /** The size of the node expand/collapse marker */
    public static final int MARKER_SIZE = 6;
    private Dimension dimension;
    private Transcription transcription;
    private Vector tiers;
    private Vector visibleTiers;
    private JPopupMenu popup;
    private JMenu viewerMenu;
    private JMenu visTiersMenu;
    private JMenu sortTiersMenu;
    private JMenuItem hideAllMI;
    private JMenuItem showAllMI;
    private JMenuItem showHideMoreMI;
    private JMenuItem collapseAllMI;
    private JMenuItem expandAllMI;
    private JMenuItem hideMI;
    private JMenuItem changeMI;
    private JMenuItem deleteMI;
    private JMenuItem colOrExpMI;
    private JMenuItem showHierMI;
    private JRadioButtonMenuItem timelineMI;
    private JRadioButtonMenuItem interlinearMI;
    private JRadioButtonMenuItem sortByHierarchMI;
    private JRadioButtonMenuItem sortByTypeMI;
    private JRadioButtonMenuItem sortByPartMI;
    private JRadioButtonMenuItem unsortedMI;
    private MultiTierViewer viewer;
    private int[] tierPositions;
    private Hashtable tierNames;
    private BufferedImage bi;
    private FontMetrics fontMetrics;
    private Font boldFont;
    private FontMetrics boldMetrics;
    private boolean dragging;
    private int dragX;
    private int dragY;
    private int dragIndex;
    private String dragLabel;
    private Tier activeTier;
    private TierImpl rightClickTier;
    private int[] colorValues;
    private Hashtable tierRootColors;
    private int tierHeight;

    //ordering
    private DefaultMutableTreeNode sortedRootNode;

    /** Holds the current sorting mode */
    private int sortMode;

    /** Holds value of the unsorted sorting property */
    private final int UNSORTED = 0;

    /** Holds value of the sort by hierarchy sorting property */
    private final int SORT_BY_HIERARCHY = 1;

    /** Holds value of the sort by participant sorting property */
    private final int SORT_BY_PARTICIPANT = 2;

    /** Holds value of the sort by linguistic type sorting property */
    private final int SORT_BY_LINGUISTIC_TYPE = 3;

    /**
     * A flattened view on the tier tree. It contains tier nodes that are
     * visible and  that are not a child of a collapsed node.
     */
    private Vector displayableTierNodes;

    /** A constant for unspecified participant or linguistic type */
    private final String NOT_SPECIFIED = "not specified";

    /**
     * Add a marker to the action command of a tier to avoid collisions in  the
     * handling of menuitem's action events
     */
    private final String TIER_AC = "@&@";
    private Vector viewers;

    /**
     * PROBLEMS WHEN MORE THAN ONE MULTI TIER VIEWER IS VISIBLE
     *
     * @param transcription DOCUMENT ME!
     */
    public MultiTierControlPanel(Transcription transcription) {
        dimension = new Dimension();
        visibleTiers = new Vector();
        displayableTierNodes = new Vector();
        viewers = new Vector();
        setFont(Constants.DEFAULTFONT);

        sortMode = UNSORTED;

        //colorValues = new int[]{0, 128, 255};
        colorValues = new int[] { 0, 90, 160 };
        setTranscription(transcription);

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    /**
     * Overrides <code>JComponent.setFont(Font)</code> by creating a bold
     * derivative  and <code>FontMetrics</code> objects for both Font objects.
     *
     * @param f DOCUMENT ME!
     */
    public void setFont(Font f) {
        super.setFont(f);
        tierHeight = 3 * f.getSize(); // first guess
        fontMetrics = getFontMetrics(getFont());
        boldFont = getFont().deriveFont(Font.BOLD);
        boldMetrics = getFontMetrics(boldFont);
    }

    /**
     * Set the viewer that must be controlled
     *
     * @param viewer DOCUMENT ME!
     */
    public void setViewer(MultiTierViewer viewer) {
        this.viewer = viewer;
        viewer.setMultiTierControlPanel(this);

        // notify the viewer about the current visible tiers
        // this results in a callback to setTierPositions
        if (visibleTiers != null) {
            setVisibleTiers(visibleTiers);
        }

        if (activeTier != null) {
            setActiveTier(activeTier);
        }
    }

    /**
     * Add a viewer that must be controlled
     *
     * @param viewer DOCUMENT ME!
     */
    public void addViewer(MultiTierViewer viewer) {
        viewers.add(viewer);
        viewer.setMultiTierControlPanel(this);

        // notify the viewer about the current visible tiers
        // this results in a callback to setTierPositions
        if (visibleTiers != null) {
            setVisibleTiers(visibleTiers);
        }

        if (activeTier != null) {
            setActiveTier(activeTier);
        }
    }

    /**
     * Remove a viewer that must be controlled
     *
     * @param viewer DOCUMENT ME!
     */
    public void removeViewer(MultiTierViewer viewer) {
        viewer.setMultiTierControlPanel(null);
        viewers.remove(viewer);
    }

    /**
     * DOCUMENT ME!
     */
    public void updateLocale() {
        if (popup == null) {
            return;
        }

        hideAllMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.HideAllTiers"));
        showAllMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ShowAllTiers"));
        showHideMoreMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ShowHideMore"));
        viewerMenu.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.Viewer"));
        visTiersMenu.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.VisibleTiers"));
        sortTiersMenu.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.SortTiers"));
        expandAllMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ExpandAll"));
        collapseAllMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.CollapseAll"));
        unsortedMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.Unsorted"));
        sortByHierarchMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.SortByHierarchy"));
        sortByTypeMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.SortByType"));
        sortByPartMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.SortByParticipant"));
        hideMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ThisTier.Hide"));
        changeMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ThisTier.Change"));
        deleteMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ThisTier.Delete"));
        colOrExpMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ThisTier.CollapseExpand"));
        showHierMI.setText(ElanLocale.getString(
                "MultiTierControlPanel.Menu.ThisTier.ShowInHierarchy"));
    }

    private void setTranscription(Transcription transcription) {
        this.transcription = transcription;

        try {
            tiers = transcription.getTiers();
            tierNames = new Hashtable();
            tierRootColors = new Hashtable();

            popup = new JPopupMenu("");

            //			popup.setLightWeightPopupEnabled(true);
            viewerMenu = new JMenu();
            popup.add(viewerMenu);
            popup.addSeparator();

            visTiersMenu = new JMenu();
            popup.add(visTiersMenu);

            expandAllMI = new JMenuItem();
            expandAllMI.setActionCommand("expandAll");
            expandAllMI.addActionListener(this);
            visTiersMenu.add(expandAllMI);
            collapseAllMI = new JMenuItem();
            collapseAllMI.setActionCommand("collapseAll");
            collapseAllMI.addActionListener(this);
            visTiersMenu.add(collapseAllMI);
            visTiersMenu.addSeparator();

            hideAllMI = new JMenuItem("");
            hideAllMI.setActionCommand("hideAll");
            hideAllMI.addActionListener(this);
            visTiersMenu.add(hideAllMI);
            showAllMI = new JMenuItem("");
            showAllMI.setActionCommand("showAll");
            showAllMI.addActionListener(this);
            visTiersMenu.add(showAllMI);
            showHideMoreMI = new JMenuItem();
            showHideMoreMI.setActionCommand("showHideMore");
            showHideMoreMI.addActionListener(this);
            visTiersMenu.add(showHideMoreMI);
            visTiersMenu.addSeparator();

            String tierName;
            JMenuItem menuItem;

            for (int i = 0; i < tiers.size(); i++) {
                TierImpl tier = (TierImpl) tiers.elementAt(i);
                tierName = tier.getName();
                tierNames.put(tier, tierName);

                if (tier.getParentTier() == null) {
                    addToRootColors(tier);
                }

                /*
                   System.out.println("Tier: " + tier.getName());
                   System.out.println("\t\tParent: " + tier.getParent());
                   if (tier instanceof TierImpl) {
                       TierImpl tImpl = (TierImpl)tier;
                       System.out.println("\t\tParticipant: " + tImpl.getParticipant());
                       System.out.println("\t\tLinguistic Type: " + tImpl.getLinguisticType());
                       System.out.println("\t\tHas Parent: " + tImpl.hasParentTier());
                       System.out.println("\t\tParent Tier: " + tImpl.getParentTier());
                       System.out.println("\t\tRoot: " + tImpl.getRootTier());
                   }
                 */
                menuItem = new JCheckBoxMenuItem(tierName);
                menuItem.setSelected(true);

                menuItem.setActionCommand(TIER_AC + tierName);

                // use an index because maybe a tier name is not unique?
                // a tiername should be unique...
                //menuItem.setActionCommand(Integer.toString(i));
                menuItem.addActionListener(this);
                visTiersMenu.add(menuItem);

                visibleTiers.add(tier);
            }

            sortTiersMenu = new JMenu();

            ButtonGroup sortGroup = new ButtonGroup();
            unsortedMI = new JRadioButtonMenuItem();
            unsortedMI.setActionCommand("unsorted");
            unsortedMI.addActionListener(this);
            sortGroup.add(unsortedMI);
            sortTiersMenu.add(unsortedMI);
            sortByHierarchMI = new JRadioButtonMenuItem();
            sortByHierarchMI.setActionCommand("sortHier");
            sortByHierarchMI.addActionListener(this);
            sortGroup.add(sortByHierarchMI);
            sortTiersMenu.add(sortByHierarchMI);
            sortByTypeMI = new JRadioButtonMenuItem();
            sortByTypeMI.setActionCommand("sortType");
            sortByTypeMI.addActionListener(this);
            sortGroup.add(sortByTypeMI);
            sortTiersMenu.add(sortByTypeMI);
            sortByPartMI = new JRadioButtonMenuItem();
            sortByPartMI.setActionCommand("sortPart");
            sortByPartMI.addActionListener(this);
            sortGroup.add(sortByPartMI);
            sortTiersMenu.add(sortByPartMI);

            switch (sortMode) {
            case UNSORTED:
                unsortedMI.setSelected(true);

                break;

            case SORT_BY_HIERARCHY:
                sortByHierarchMI.setSelected(true);

                break;

            case SORT_BY_PARTICIPANT:
                sortByPartMI.setSelected(true);

                break;

            case SORT_BY_LINGUISTIC_TYPE:
                sortByTypeMI.setSelected(true);

                break;
            }

            popup.add(sortTiersMenu);
            popup.addSeparator();

            hideMI = new JMenuItem();
            hideMI.setActionCommand("hideThis");
            hideMI.addActionListener(this);
            hideMI.setEnabled(false);
            popup.add(hideMI);
            changeMI = new JMenuItem();
            changeMI.setActionCommand("changeThis");
            changeMI.addActionListener(this);
            changeMI.setEnabled(false);
            popup.add(changeMI);
            deleteMI = new JMenuItem();
            deleteMI.setActionCommand("deleteThis");
            deleteMI.addActionListener(this);
            deleteMI.setEnabled(false);
            popup.add(deleteMI);
            colOrExpMI = new JMenuItem();
            colOrExpMI.setActionCommand("toggleExpandThis");
            colOrExpMI.addActionListener(this);
            colOrExpMI.setEnabled(false);
            popup.add(colOrExpMI);
            showHierMI = new JMenuItem();
            showHierMI.setActionCommand("showThis");
            showHierMI.addActionListener(this);
            showHierMI.setEnabled(false);
            popup.add(showHierMI);

            setVisibleTiers(visibleTiers);

            updateLocale();

            createSortedTree();

            paintBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates a node hierarchy depending on the current sorting mode.
     */
    private void createSortedTree() {
        // create a list based on the current preferred order
        ArrayList tierList = new ArrayList(visibleTiers);

        for (int i = 0; i < tiers.size(); i++) {
            TierImpl tier = (TierImpl) tiers.elementAt(i);

            if (!tierList.contains(tier)) {
                tierList.add(tier);
            }
        }

        sortedRootNode = new DefaultMutableTreeNode("sortRoot");

        switch (sortMode) {
        case SORT_BY_HIERARCHY:

            Hashtable nodes = new Hashtable();

            for (int i = 0; i < tierList.size(); i++) {
                TierImpl tier = (TierImpl) tierList.get(i);
                TierTreeNode n = new TierTreeNode(tier);

                if ((activeTier != null) && (activeTier == tier)) {
                    n.setActive(true);
                }

                if (!visibleTiers.contains(tier)) {
                    n.setVisible(false);
                }

                nodes.put(tier, n);
            }

            for (int i = 0; i < tierList.size(); i++) {
                TierImpl tier = (TierImpl) tierList.get(i);

                if (tier.getParentTier() == null) {
                    sortedRootNode.add((TierTreeNode) nodes.get(tier));
                } else {
                    ((TierTreeNode) nodes.get(tier.getParentTier())).add((TierTreeNode) nodes.get(
                            tier));
                }
            }

            break;

        case SORT_BY_PARTICIPANT:

            Hashtable participantTable = new Hashtable();

            for (int i = 0; i < tierList.size(); i++) {
                TierImpl tier = (TierImpl) tierList.get(i);

                TierTreeNode n = new TierTreeNode(tier);

                if ((activeTier != null) && (activeTier == tier)) {
                    n.setActive(true);
                }

                if (!visibleTiers.contains(tier)) {
                    n.setVisible(false);
                }

                String part = tier.getParticipant();

                if (part.length() == 0) {
                    part = NOT_SPECIFIED;
                }

                if (participantTable.get(part) == null) {
                    ArrayList list = new ArrayList();
                    list.add(n);
                    participantTable.put(part, list);
                } else {
                    ((ArrayList) participantTable.get(part)).add(n);
                }
            }

            if (participantTable.size() > 0) {
                Set keys = participantTable.keySet();
                List names = new ArrayList(keys);
                Collections.sort(names);

                for (int j = 0; j < names.size(); j++) {
                    ArrayList pList = (ArrayList) participantTable.get(names.get(
                                j));

                    for (int k = 0; k < pList.size(); k++) {
                        sortedRootNode.add((TierTreeNode) pList.get(k));
                    }
                }
            }

            break;

        case SORT_BY_LINGUISTIC_TYPE:

            Hashtable typeTable = new Hashtable();

            for (int i = 0; i < tierList.size(); i++) {
                TierImpl tier = (TierImpl) tierList.get(i);

                TierTreeNode n = new TierTreeNode(tier);

                if ((activeTier != null) && (activeTier == tier)) {
                    n.setActive(true);
                }

                if (!visibleTiers.contains(tier)) {
                    n.setVisible(false);
                }

                LinguisticType type = tier.getLinguisticType();

                if (type == null) {
                    type = new LinguisticType(NOT_SPECIFIED);
                }

                if (typeTable.get(type) == null) {
                    ArrayList list = new ArrayList();
                    list.add(n);
                    typeTable.put(type, list);
                } else {
                    ((ArrayList) typeTable.get(type)).add(n);
                }
            }

            if (typeTable.size() > 0) {
                Enumeration e = typeTable.elements();

                while (e.hasMoreElements()) {
                    ArrayList typeList = (ArrayList) e.nextElement();

                    for (int k = 0; k < typeList.size(); k++) {
                        sortedRootNode.add((TierTreeNode) typeList.get(k));

                        //System.out.println("type sort node added: " + k + " " + ((TierTreeNode)typeList.get(k)).getTierName());
                    }
                }
            }

            break;

        case UNSORTED:
        // fallthrough default order
        default:

            for (int i = 0; i < tierList.size(); i++) {
                TierImpl tier = (TierImpl) tierList.get(i);

                TierTreeNode n = new TierTreeNode(tier);

                if ((activeTier != null) && (activeTier == tier)) {
                    n.setActive(true);
                }

                if (!visibleTiers.contains(tier)) {
                    n.setVisible(false);
                }

                sortedRootNode.add(n);
            }
        }

        // update the visibletiers and displayablenodes vector
        updateDisplayableTiers();
    }

    /**
     * Updates the visibleTiers vector and the Vector with the (flattened) tier
     * nodes.
     */
    private void updateDisplayableTiers() {
        Vector dispTiers = new Vector();
        visibleTiers.clear();
        displayableTierNodes.clear();

        if ((sortMode == SORT_BY_HIERARCHY) &&
                (sortedRootNode.getChildCount() > 0)) {
            int level = -1;
            TierTreeNode curNode = (TierTreeNode) sortedRootNode.getFirstChild();
            DefaultMutableTreeNode parent = null;
mainloop: 
            while (level != 0) {
                if (curNode.isVisible()) {
                    visibleTiers.add(curNode.getTier());
                    displayableTierNodes.add(curNode);
                    dispTiers.add(curNode.getTier());

                    if ((curNode.getChildCount() > 0) && curNode.isExpanded()) {
                        curNode = (TierTreeNode) curNode.getFirstChild();
                        level = curNode.getLevel();

                        continue mainloop;
                    } else {
                        // no children or children are not displayable because node is collapsed,
                        // traverse up
                        parent = (DefaultMutableTreeNode) curNode.getParent();
                    }
                } else {
                    // not visible; skip the children of this node, traverse up
                    parent = (DefaultMutableTreeNode) curNode.getParent();
                }

uploop: 
                while (true) {
                    if (parent.getChildAfter(curNode) != null) {
                        curNode = (TierTreeNode) parent.getChildAfter(curNode);

                        continue mainloop;
                    } else if (parent == sortedRootNode) {
                        // the rootnode does not have more children
                        break mainloop;
                    } else {
                        curNode = (TierTreeNode) parent;
                        level = curNode.getLevel();
                        parent = (DefaultMutableTreeNode) curNode.getParent();

                        continue uploop;
                    }
                }
            }
        } else {
            Enumeration nodeEnum = sortedRootNode.children();
            TierTreeNode cn = null;

            while (nodeEnum.hasMoreElements()) {
                cn = (TierTreeNode) nodeEnum.nextElement();

                if (cn.isVisible()) {
                    visibleTiers.add(cn.getTier());
                    displayableTierNodes.add(cn);
                    dispTiers.add(cn.getTier());
                }
            }
        }

        /*
           if (displayableTiers != null) {
               Enumeration e = displayableTiers.elements();
               while (e.hasMoreElements()) {
                   TierImpl t = (TierImpl)e.nextElement();

                   System.out.println("Dis. Tier: " + t.getName());

               }
           }
         */
        setVisibleTiers(dispTiers);
    }

    /**
     * Adds the MultiTier Viewer items to the popup (when neccessary) and
     * enables / disables menu items depending on the characteristics of  the
     * specified tier.
     *
     * @param tier the tier
     */
    private void updatePopup(TierImpl tier) {
        if (timelineMI == null) {
            ButtonGroup multitierViewerGroup = new ButtonGroup();
            timelineMI = new JRadioButtonMenuItem(ELANCommandFactory.getCommandAction(
                        transcription, ELANCommandFactory.SHOW_TIMELINE));
            interlinearMI = new JRadioButtonMenuItem(ELANCommandFactory.getCommandAction(
                        transcription, ELANCommandFactory.SHOW_INTERLINEAR));

            // default
            //timelineMI.setSelected(true);
            multitierViewerGroup.add(timelineMI);
            multitierViewerGroup.add(interlinearMI);
            viewerMenu.add(timelineMI);
            viewerMenu.add(interlinearMI);

            if (viewer instanceof TimeLineViewer) {
                timelineMI.setSelected(true);
            } else if (viewer instanceof InterlinearViewer) {
                interlinearMI.setSelected(true);
            }
        }

        if (tier != null) {
            String name = tier.getName();

            if ((name != null) && (name.length() > 0)) {
                hideMI.setText("<html>" +
                    ElanLocale.getString(
                        "MultiTierControlPanel.Menu.ThisTier.Hide") + " " +
                    "<i>" + name + "</i></html>");
                changeMI.setText("<html>" +
                    ElanLocale.getString(
                        "MultiTierControlPanel.Menu.ThisTier.Change") + " " +
                    "<i>" + name + "</i></html>");
                deleteMI.setText("<html>" +
                    ElanLocale.getString(
                        "MultiTierControlPanel.Menu.ThisTier.Delete") + " " +
                    "<i>" + name + "</i></html>");

                if (sortMode == SORT_BY_HIERARCHY) {
                    colOrExpMI.setText("<html>" +
                        ElanLocale.getString(
                            "MultiTierControlPanel.Menu.ThisTier.CollapseExpand") +
                        " " + "<i>" + name + "</i></html>");
                    colOrExpMI.setEnabled(true);
                } else {
                    colOrExpMI.setText(ElanLocale.getString(
                            "MultiTierControlPanel.Menu.ThisTier.CollapseExpand"));
                    colOrExpMI.setEnabled(false);
                }

                hideMI.setEnabled(true);
                changeMI.setEnabled(true);
                deleteMI.setEnabled(true);
                showHierMI.setEnabled(true);
            }
        } else {
            hideMI.setText(ElanLocale.getString(
                    "MultiTierControlPanel.Menu.ThisTier.Hide"));
            changeMI.setText(ElanLocale.getString(
                    "MultiTierControlPanel.Menu.ThisTier.Change"));
            deleteMI.setText(ElanLocale.getString(
                    "MultiTierControlPanel.Menu.ThisTier.Delete"));
            colOrExpMI.setText(ElanLocale.getString(
                    "MultiTierControlPanel.Menu.ThisTier.CollapseExpand"));
            hideMI.setEnabled(false);
            changeMI.setEnabled(false);
            deleteMI.setEnabled(false);
            colOrExpMI.setEnabled(false);
            showHierMI.setEnabled(false);
        }

        if (sortMode == SORT_BY_HIERARCHY) {
            expandAllMI.setEnabled(true);
            collapseAllMI.setEnabled(true);
        } else {
            expandAllMI.setEnabled(false);
            collapseAllMI.setEnabled(false);
        }
    }

    /**
     * Returns the y positions of the visible tiers.
     *
     * @param tierPositions the y positions of the tiers
     */
    public void setTierPositions(int[] tierPositions) {
        this.tierPositions = tierPositions;

        if (tierPositions.length > 1) {
            tierHeight = tierPositions[1] - tierPositions[0];
        }

        paintBuffer();
    }

    /**
     * DOCUMENT ME!
     *
     * @param e DOCUMENT ME!
     */
    public void mouseClicked(MouseEvent e) {
        if (SwingUtilities.isRightMouseButton(e) || e.isPopupTrigger()) {
            return;
        }

        Point p = e.getPoint();

        if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() > 1)) {
            // define the active tier
            int index = getClosestTierIndexForMouseY(p.y);
            TierTreeNode node = (TierTreeNode) displayableTierNodes.get(index);
            setActiveTier(node.getTier());
        }

        paintBuffer();
    }

    /**
     * Start dragging a tier.
     *
     * @param e the mousePressed event
     */
    public void mousePressed(MouseEvent e) {
        //Point p = e.getPoint();
        if (SwingUtilities.isRightMouseButton(e) || e.isPopupTrigger()) {
            // present a menu to set visible tiers
            // maybe the menu drops out of the screen, correct it if needed
            //SwingUtilities.convertPointToScreen(p, this);
            int y = e.getPoint().y;

            int tierIndex = getClosestTierIndexForMouseY(y);

            if (!(tierIndex >= displayableTierNodes.size()) &&
                    (displayableTierNodes.size() > 0)) {
                TierTreeNode node = (TierTreeNode) displayableTierNodes.get(tierIndex);
                TierImpl tier = null;

                if (node != null) {
                    tier = node.getTier();
                    rightClickTier = tier;
                }

                // set the menu right from the panel so it does not hide the labels
                // when this panel is created LayoutManager maybe null
                // the viewer action access the LayoutManager
                updatePopup(tier);
            } else {
                rightClickTier = null;
                updatePopup(null);
            }

            popup.show(this, WIDTH - 5, y);

            return;
        }

        dragIndex = getClosestTierIndexForMouseY(e.getPoint().y);

        if ((dragIndex == 0) && (displayableTierNodes.size() == 0)) {
            return;
        } else {
            dragLabel = (String) tierNames.get(((TierTreeNode) displayableTierNodes.elementAt(
                        dragIndex)).getTier());
            dragging = true;
        }
    }

    /**
     * Drag a tier label.
     *
     * @param e the mouseDragged event
     */
    public void mouseDragged(MouseEvent e) {
        // this part should only be done once

        /*
           if (!dragging) {
               dragging = true;

                       dragIndex = getClosestTierIndexForMouseY(e.getPoint().y);

                       if ((dragIndex == 0) && (displayableTierNodes.size() == 0)) {
                           dragLabel = "";
                       } else {
                           dragLabel = (String) tierNames.get( ((TierTreeNode)displayableTierNodes.elementAt(dragIndex)).getTier() );
                       }
                   }
         */
        if (dragging) {
            dragX = e.getPoint().x - 40;
            dragY = e.getPoint().y;
            repaint();
        }
    }

    /**
     * When this is the end of a drag operation, move a tier or a group of
     * tiers,  depending on the current sorting mode.
     *
     * @param e the mouse release event
     */
    public void mouseReleased(MouseEvent e) {
        if (dragging) {
            if (displayableTierNodes.size() <= 1) {
                dragging = false;
                dragLabel = "";
                repaint();

                return;
            }

            // re-order the visible tiers
            // insert the dragged tier at the release index
            int index = getInsertionIndexForMouseY(e.getPoint().y);

            if (index >= displayableTierNodes.size()) {
                index = displayableTierNodes.size() - 1;
            }

            if ((index != dragIndex) && ((index - 1) != dragIndex)) {
                TierTreeNode node = (TierTreeNode) displayableTierNodes.elementAt(dragIndex);
                TierTreeNode insertBeforeNode = (TierTreeNode) displayableTierNodes.get(index);
                TierTreeNode insertAfterNode = null;

                if ((index - 1) >= 0) {
                    insertAfterNode = (TierTreeNode) displayableTierNodes.get(index -
                            1);
                }

                if (index == (displayableTierNodes.size() - 1)) {
                    insertBeforeNode = null;
                    insertAfterNode = (TierTreeNode) displayableTierNodes.get(index);
                }

                /*
                   System.out.print("drag tier: " + node.getTierName());
                   if (insertAfterNode != null) System.out.print(" - insertaftertier: " + insertAfterNode.getTierName());
                       else System.out.print(" insertaftertier: null");
                   if (insertBeforeNode != null) System.out.println(" - insertbeforetier: " + insertBeforeNode.getTierName());
                       else System.out.println(" insertbeforetier: null");
                   System.out.print("drag parent: " + node.getParent());
                   if (insertAfterNode != null) System.out.print(" - parent after: " + insertAfterNode.getParent());
                   if (insertBeforeNode != null)System.out.println(" - parent before: " + insertBeforeNode.getParent());
                   System.out.println("");
                 */

                //System.out.println("Node: " + node.getTierName() + " after: " + insertAfterNode.getTierName() + " before: " + 
                //(insertBeforeNode == null ? "null" : insertBeforeNode.getTierName()));
                switch (sortMode) {
                case SORT_BY_HIERARCHY:

                    if ((insertAfterNode != null) &&
                            (node.getParent() == insertAfterNode.getParent())) {
                        node.removeFromParent();

                        int afterIndex = insertAfterNode.getParent().getIndex(insertAfterNode);
                        ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                            afterIndex + 1);
                    } else if ((insertBeforeNode != null) &&
                            (node.getParent() == insertBeforeNode.getParent())) {
                        int beforeIndex = insertBeforeNode.getParent().getIndex(insertBeforeNode);
                        ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                            beforeIndex);
                    } else if ((insertAfterNode != null) &&
                            (node.getParent() == insertAfterNode.getSharedAncestor(
                                node))) {
                        int curLevel = node.getLevel();
                        DefaultMutableTreeNode sameLevelNode = insertAfterNode;

                        while (sameLevelNode.getLevel() != curLevel) {
                            sameLevelNode = (DefaultMutableTreeNode) sameLevelNode.getParent();
                        }

                        node.removeFromParent();

                        int afterIndex = sameLevelNode.getParent().getIndex(sameLevelNode);
                        ((DefaultMutableTreeNode) sameLevelNode.getParent()).insert(node,
                            afterIndex + 1);
                    }

                    break;

                case SORT_BY_PARTICIPANT:

                    // first try within group
                    if ((insertAfterNode != null) &&
                            (insertBeforeNode != null)) {
                        if (insertAfterNode.getTier().getParticipant().equals(node.getTier()
                                                                                      .getParticipant())) {
                            node.removeFromParent();

                            int afterIndex = insertAfterNode.getParent()
                                                            .getIndex(insertAfterNode);
                            ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                                afterIndex + 1);

                            break;
                        } else if (insertBeforeNode.getTier().getParticipant()
                                                       .equals(node.getTier()
                                                                       .getParticipant())) {
                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);
                            ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                                beforeIndex);

                            break;
                        }
                    }

                    if (insertAfterNode != null) {
                        if (insertAfterNode.getTier().getParticipant().equals(node.getTier()
                                                                                      .getParticipant())) {
                            node.removeFromParent();

                            int afterIndex = insertAfterNode.getParent()
                                                            .getIndex(insertAfterNode);
                            ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                                afterIndex + 1);
                        } else {
                            // move group
                            String afterPartName = insertAfterNode.getTier()
                                                                  .getParticipant();
                            String dragPartName = node.getTier().getParticipant();
                            TierTreeNode lastNode = insertAfterNode;

                            while ((lastNode.getNextNode() != null) &&
                                    ((TierTreeNode) lastNode.getNextNode()).getTier()
                                         .getParticipant().equals(afterPartName)) {
                                lastNode = (TierTreeNode) lastNode.getNextNode();
                            }

                            ArrayList moveGroup = new ArrayList();
                            Enumeration en = sortedRootNode.preorderEnumeration();
                            en.nextElement();

                            TierTreeNode nn;

                            while (en.hasMoreElements()) {
                                nn = (TierTreeNode) en.nextElement();

                                if (nn.getTier().getParticipant().equals(dragPartName)) {
                                    moveGroup.add(nn);
                                }
                            }

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) moveGroup.get(i)).removeFromParent();
                            }

                            int afterIndex = lastNode.getParent().getIndex(lastNode);

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) lastNode.getParent()).insert((DefaultMutableTreeNode) moveGroup.get(
                                        i), afterIndex + 1);
                            }
                        }
                    } else if (insertBeforeNode != null) {
                        if (insertBeforeNode.getTier().getParticipant().equals(node.getTier()
                                                                                       .getParticipant())) {
                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);
                            ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                                beforeIndex);
                        } else {
                            // move group , before node should be the first node
                            String dragPartName = node.getTier().getParticipant();
                            ArrayList moveGroup = new ArrayList();
                            Enumeration en = sortedRootNode.preorderEnumeration();
                            en.nextElement();

                            TierTreeNode nn;

                            while (en.hasMoreElements()) {
                                nn = (TierTreeNode) en.nextElement();

                                if (nn.getTier().getParticipant().equals(dragPartName)) {
                                    moveGroup.add(nn);
                                }
                            }

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) moveGroup.get(i)).removeFromParent();
                            }

                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert((DefaultMutableTreeNode) moveGroup.get(
                                        i), beforeIndex);
                            }
                        }
                    }

                    break;

                case SORT_BY_LINGUISTIC_TYPE:

                    // first try to move within group
                    if ((insertAfterNode != null) &&
                            (insertBeforeNode != null)) {
                        if (insertAfterNode.getTier().getLinguisticType()
                                               .equals(node.getTier()
                                                               .getLinguisticType())) {
                            node.removeFromParent();

                            int afterIndex = insertAfterNode.getParent()
                                                            .getIndex(insertAfterNode);
                            ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                                afterIndex + 1);

                            break;
                        } else if (insertBeforeNode.getTier().getLinguisticType()
                                                       .equals(node.getTier()
                                                                       .getLinguisticType())) {
                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);
                            ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                                beforeIndex);

                            break;
                        }
                    }

                    if (insertAfterNode != null) {
                        if (insertAfterNode.getTier().getLinguisticType()
                                               .equals(node.getTier()
                                                               .getLinguisticType())) {
                            // move within group 
                            node.removeFromParent();

                            int afterIndex = insertAfterNode.getParent()
                                                            .getIndex(insertAfterNode);
                            ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                                afterIndex + 1);
                        } else {
                            // move group
                            LinguisticType insertType = insertAfterNode.getTier()
                                                                       .getLinguisticType();
                            LinguisticType dragType = node.getTier()
                                                          .getLinguisticType();
                            TierTreeNode lastNode = insertAfterNode;

                            while ((lastNode.getNextNode() != null) &&
                                    ((TierTreeNode) lastNode.getNextNode()).getTier()
                                         .getLinguisticType().equals(insertType)) {
                                lastNode = (TierTreeNode) lastNode.getNextNode();
                            }

                            ArrayList moveGroup = new ArrayList();
                            Enumeration en = sortedRootNode.preorderEnumeration();
                            en.nextElement();

                            TierTreeNode nn;

                            while (en.hasMoreElements()) {
                                nn = (TierTreeNode) en.nextElement();

                                if (nn.getTier().getLinguisticType().equals(dragType)) {
                                    moveGroup.add(nn);
                                }
                            }

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) moveGroup.get(i)).removeFromParent();
                            }

                            int afterIndex = lastNode.getParent().getIndex(lastNode);

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) lastNode.getParent()).insert((DefaultMutableTreeNode) moveGroup.get(
                                        i), afterIndex + 1);
                            }
                        }
                    } else if (insertBeforeNode != null) {
                        if (insertBeforeNode.getTier().getLinguisticType()
                                                .equals(node.getTier()
                                                                .getLinguisticType())) {
                            // move within group
                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);
                            ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                                beforeIndex);
                        } else {
                            // move group , before node should be the first node
                            LinguisticType dragType = node.getTier()
                                                          .getLinguisticType();
                            ArrayList moveGroup = new ArrayList();
                            Enumeration en = sortedRootNode.preorderEnumeration();
                            en.nextElement();

                            TierTreeNode nn;

                            while (en.hasMoreElements()) {
                                nn = (TierTreeNode) en.nextElement();

                                if (nn.getTier().getLinguisticType().equals(dragType)) {
                                    moveGroup.add(nn);
                                }
                            }

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) moveGroup.get(i)).removeFromParent();
                            }

                            int beforeIndex = insertBeforeNode.getParent()
                                                              .getIndex(insertBeforeNode);

                            for (int i = moveGroup.size() - 1; i >= 0; i--) {
                                ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert((DefaultMutableTreeNode) moveGroup.get(
                                        i), beforeIndex);
                            }
                        }
                    }

                    break;

                case UNSORTED:

                    if (insertAfterNode != null) {
                        node.removeFromParent();

                        int afterIndex = insertAfterNode.getParent().getIndex(insertAfterNode);
                        ((DefaultMutableTreeNode) insertAfterNode.getParent()).insert(node,
                            afterIndex + 1);
                    } else if (insertBeforeNode != null) {
                        int beforeIndex = insertBeforeNode.getParent().getIndex(insertBeforeNode);
                        ((DefaultMutableTreeNode) insertBeforeNode.getParent()).insert(node,
                            beforeIndex);
                    }

                    break;

                default:
                    return;
                }

                updateDisplayableTiers();
            }

            dragging = false;
            dragLabel = "";
            paintBuffer();
        }
    }

    private int getClosestTierIndexForMouseY(int y) {
        if ((tierPositions.length == 0) || (displayableTierNodes.size() == 0)) {
            return 0;
        }

        int index;

        for (index = 0; index < displayableTierNodes.size(); index++) {
            if (y < tierPositions[index]) {
                break;
            }
        }

        if (index > 0) {
            index--;

            if ((index == displayableTierNodes.size()) && (index != 0)) {
                index--;
            }
        }

        // determine to which label the mouse is closest
        float d1 = Math.abs(y - tierPositions[index]);
        float d2 = Float.MAX_VALUE;

        if ((index + 1) < displayableTierNodes.size()) {
            d2 = Math.abs(y - tierPositions[index + 1]);
        }

        if (d2 < d1) {
            index++;
        }

        return index;
    }

    private int getInsertionIndexForMouseY(int y) {
        int index = 0;

        if (displayableTierNodes.size() > 0) {
            for (index = 0; index < displayableTierNodes.size(); index++) {
                if (y < tierPositions[index]) {
                    break;
                }
            }
        }

        return index;
    }

    /**
     * Handle action events from menu's.
     *
     * @param e DOCUMENT ME!
     */
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();

        if (command.equals("hideAll")) {
            hideAllTiers();
        } else if (command.equals("showAll")) {
            showAllTiers();
        } else if (command.equals("showHideMore")) {
            new VisibilityDialog(transcription, visibleTiers);
        } else if (command.equals("unsorted")) {
            setSorting(UNSORTED);
        } else if (command.equals("sortHier")) {
            setSorting(SORT_BY_HIERARCHY);
        } else if (command.equals("sortPart")) {
            setSorting(SORT_BY_PARTICIPANT);
        } else if (command.equals("sortType")) {
            setSorting(SORT_BY_LINGUISTIC_TYPE);
        } else if (command.equals("expandAll")) {
            expandAllNodes();
        } else if (command.equals("collapseAll")) {
            collapseAllNodes();
        } else if (command.equals("hideThis")) {
            hideTier(rightClickTier);
        } else if (command.equals("changeThis")) {
            changeTier(rightClickTier);
        } else if (command.equals("deleteThis")) {
            deleteTier(rightClickTier);
        } else if (command.equals("toggleExpandThis")) {
            toggleExpandedState(rightClickTier);
        } else if (command.equals("showThis")) {
            showInTierDependencyFrame(rightClickTier);
        } else if (command.startsWith(TIER_AC)) {
            // a tier visibility checkbox menu item

            /*
               int index = -1;
               try {
                   index = Integer.parseInt(command);
               } catch (NumberFormatException nfe) {}
               if (index < 0 || index >= tiers.size()) {
                   return;
               }
               Tier tier = (Tier) tiers.elementAt(index);
             */
            String tierName = command.substring(TIER_AC.length());

            Tier tier = transcription.getTierWithId(tierName);

            Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

            // skip root node
            nodeEnum.nextElement();

            TierTreeNode node;

            while (nodeEnum.hasMoreElements()) {
                node = (TierTreeNode) nodeEnum.nextElement();

                if (node.getTier() == tier) {
                    node.setVisible(!node.isVisible());

                    break;
                }
            }

            updateDisplayableTiers();
        }
    }

    /**
     * Inform the viewers about the new visible tiers and determine the
     * activeTierIndex.
     *
     * @param visibleTiers the visible tiers
     */
    private void setVisibleTiers(Vector visibleTiers) {
        //this.visibleTiers = visibleTiers;
        //		viewer.setVisibleTiers(visibleTiers);
        for (int i = 0; i < viewers.size(); i++) {
            ((MultiTierViewer) viewers.elementAt(i)).setVisibleTiers(visibleTiers);
        }

        paintBuffer();
    }

    /**
     * Returns the Vector of currently visible tiers. The ordering of the Tiers
     * in the Vector  reflects the order they appear in the
     * MultiTierControlPanel and the connected Viewer.
     *
     * @return a Vector containing the visible Tiers
     */
    public Vector getVisibleTiers() {
        return visibleTiers;
    }

    /**
     * Returns a vector containing the tier names in the order they appear in
     * the panel.
     *
     * @return tier names in a vector in the order they appear on the screen
     */
    public Vector getTierOrder() {
        Vector visibleTierNames = new Vector();

        for (int i = 0; i < visibleTiers.size(); i++) {
            visibleTierNames.add(((Tier) visibleTiers.elementAt(i)).getName());
        }

        return visibleTierNames;
    }

    /**
     * Sets the tiers in a specific order.
     *
     * @param tierNames a Vector containing the ordered names of the tiers
     */
    public void setTierOrder(Vector tierNames) {
        sortMode = UNSORTED; // change the mode to unordered

        if (!unsortedMI.isSelected()) {
            unsortedMI.setSelected(true);
        }

        Vector visTiers = new Vector();
        MenuElement menu = visTiersMenu.getSubElements()[0];
        MenuElement[] items = menu.getSubElements();

        for (int i = 0; i < tierNames.size(); i++) {
            String tierName = (String) tierNames.elementAt(i);

            // add to visible tiers...
            for (int j = 0; j < tiers.size(); j++) {
                if (tierName.equals(((Tier) tiers.elementAt(j)).getName())) {
                    visTiers.add(tiers.elementAt(j));

                    break;
                }
            }
        }

        // update menu items
        for (int k = 0; k < items.length; k++) {
            if (items[k] instanceof JCheckBoxMenuItem) {
                if (tierNames.contains(((JCheckBoxMenuItem) items[k]).getText())) {
                    ((JCheckBoxMenuItem) items[k]).setSelected(true);
                } else {
                    ((JCheckBoxMenuItem) items[k]).setSelected(false);
                }
            }
        }

        visibleTiers = visTiers;
        createSortedTree();

        //setVisibleTiers(visTiers);
    }

    /**
     * Returns the name of the active tier.
     *
     * @return the name of the active tier or null
     */
    public String getActiveTierName() {
        if (activeTier == null) {
            return null;
        }

        return activeTier.getName();
    }

    /**
     * Sets the active tier.
     *
     * @param name the name of the active tier
     */
    public void setActiveTierForName(String name) {
        if (name == null) {
            return;
        }

        for (int i = 0; i < tiers.size(); i++) {
            if (name.equals(((Tier) tiers.elementAt(i)).getName())) {
                setActiveTier((Tier) tiers.elementAt(i));
            }
        }
    }

    /**
     * Sets the mode for the sorting of the tiers.
     *
     * @param mode the sorting mode
     */
    public void setSorting(int mode) {
        int oldSortMode = sortMode;

        if ((mode < UNSORTED) || (mode > SORT_BY_LINGUISTIC_TYPE)) {
            sortMode = UNSORTED;
        } else {
            sortMode = mode;
        }

        // update the menuitems, if necessary
        switch (sortMode) {
        case UNSORTED:

            if (!unsortedMI.isSelected()) {
                unsortedMI.setSelected(true);
            }

            break;

        case SORT_BY_HIERARCHY:

            if (!sortByHierarchMI.isSelected()) {
                sortByHierarchMI.setSelected(true);
            }

            break;

        case SORT_BY_LINGUISTIC_TYPE:

            if (!sortByTypeMI.isSelected()) {
                sortByTypeMI.setSelected(true);
            }

            break;

        case SORT_BY_PARTICIPANT:

            if (!sortByPartMI.isSelected()) {
                sortByPartMI.setSelected(true);
            }
        }

        /* When switching from hierarchical to any other sorting,
         * update the visible tiers checkbox menuitems:
         * the expand/collapse feature of the sort_by_hierarchy mode
         * implicitely changes the visibility of tiers.
         * Make this implicit visibility state explicit here.
         */
        if ((oldSortMode == SORT_BY_HIERARCHY) && (sortMode != oldSortMode)) {
            MenuElement menu = visTiersMenu.getSubElements()[0];
            MenuElement[] items = menu.getSubElements();
            Vector visTierNames = new Vector();

            for (int i = 0; i < tiers.size(); i++) {
                Tier t = (Tier) tiers.get(i);

                if (visibleTiers.contains(t)) {
                    visTierNames.add(t.getName());
                }
            }

            for (int i = 0; i < items.length; i++) {
                if (items[i] instanceof JCheckBoxMenuItem) {
                    if (visTierNames.contains(
                                ((JCheckBoxMenuItem) items[i]).getText())) {
                        ((JCheckBoxMenuItem) items[i]).setSelected(true);
                    } else {
                        ((JCheckBoxMenuItem) items[i]).setSelected(false);
                    }
                }
            }
        }

        createSortedTree();
    }

    /**
     * Returns the sorting mode.<br>
     * One of <code>UNSORTED</code>,  <code>SORT_BY_HIERARCHY</code>,
     * <code>SORT_BY_PARTICIPANT</code> or
     * <code>SORT_BY_LINGUISTIC_TYPE</code>.
     *
     * @return the sorting mode
     */
    public int getSorting() {
        return sortMode;
    }

    /**
     * The MultiTierControlPanel is not an ACMEditListener but gets informed of
     * relevant changes by the connected viewer.<br>
     * When a tier is added this panel should update it's list of visible
     * tiers taking into account the current ordering of the tiers and notify
     * the viewer.
     *
     * @param tier the new tier
     */
    public void tierAdded(TierImpl tier) {
        String name;
        JMenuItem menuItem;

        name = tier.getName();

        if (!tierNames.contains(tier)) {
            tierNames.put(tier, name);

            TierTreeNode node = new TierTreeNode(tier);

            if (tier.getParentTier() == null) {
                addToRootColors(tier);
            }

            menuItem = new JCheckBoxMenuItem(name);
            menuItem.setSelected(true);
            menuItem.addActionListener(this);
            menuItem.setActionCommand(TIER_AC + name);
            visTiersMenu.add(menuItem);

            switch (sortMode) {
            case UNSORTED:
                sortedRootNode.add(node);

                break;

            case SORT_BY_HIERARCHY:

                if (tier.getParentTier() == null) {
                    sortedRootNode.add(node);
                } else {
                    Enumeration en = sortedRootNode.preorderEnumeration();

                    // skip root node
                    en.nextElement();

                    TierTreeNode nextNode;

                    while (en.hasMoreElements()) {
                        nextNode = (TierTreeNode) en.nextElement();

                        if (nextNode.getTier() == tier.getParentTier()) {
                            nextNode.add(node);

                            break;
                        }
                    }
                }

                break;

            case SORT_BY_PARTICIPANT:

                String parti = tier.getParticipant();

                // append to the participants group, or to the root
                boolean groupFound = false;
                Enumeration partEnum = sortedRootNode.children();
                TierTreeNode nextNode;
                TierTreeNode lastInGroupNode = null;

                while (partEnum.hasMoreElements()) {
                    nextNode = (TierTreeNode) partEnum.nextElement();

                    if (nextNode.getTier().getParticipant().equals(parti)) {
                        groupFound = true;
                        lastInGroupNode = nextNode;
                    } else {
                        if (groupFound) {
                            ((DefaultMutableTreeNode) lastInGroupNode.getParent()).add(node);

                            break;
                        }
                    }
                }

                if (!groupFound) {
                    sortedRootNode.add(node);
                }

                break;

            case SORT_BY_LINGUISTIC_TYPE:

                LinguisticType type = tier.getLinguisticType();

                // append to the participants group, or to the root
                boolean typesFound = false;
                Enumeration typesEnum = sortedRootNode.children();
                TierTreeNode nextTypeNode;
                TierTreeNode lastInTypeGroupNode = null;

                while (typesEnum.hasMoreElements()) {
                    nextTypeNode = (TierTreeNode) typesEnum.nextElement();

                    if (((type == null) &&
                            (nextTypeNode.getTier().getLinguisticType() == null)) ||
                            type.equals(nextTypeNode.getTier()
                                                        .getLinguisticType())) {
                        typesFound = true;
                        lastInTypeGroupNode = nextTypeNode;
                    } else {
                        if (typesFound) {
                            ((DefaultMutableTreeNode) lastInTypeGroupNode.getParent()).add(node);

                            break;
                        }
                    }
                }

                if (!typesFound) {
                    sortedRootNode.add(node);
                }

                break;
            }

            //visibleTiers.add(tier);
            //setVisibleTiers(visibleTiers);
            updateDisplayableTiers();
        }
    }

    /**
     * The MultiTierControlPanel is not an ACMEditListener but gets informed of
     * relevant changes by the connected viewer.<br>
     * When a tier is removed this panel should update it's list of visible
     * tiers and notify the viewer.
     *
     * @param tier the removed tier
     */
    public void tierRemoved(TierImpl tier) {
        if (tierNames.containsKey(tier)) {
            String name = (String) tierNames.get(tier);
            MenuElement menu = visTiersMenu.getSubElements()[0];
            MenuElement[] items = menu.getSubElements();

            for (int i = 0; i < items.length; i++) {
                if (items[i] instanceof JCheckBoxMenuItem) {
                    if (((JCheckBoxMenuItem) items[i]).getText().equals(name)) {
                        visTiersMenu.remove((JCheckBoxMenuItem) items[i]);

                        break;
                    }
                }
            }

            tierNames.remove(tier);
            tierRootColors.remove(tier);

            //visibleTiers.remove(tier);
            Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

            // skip root node
            nodeEnum.nextElement();

            TierTreeNode node;

            while (nodeEnum.hasMoreElements()) {
                node = (TierTreeNode) nodeEnum.nextElement();

                if (node.getTier() == tier) {
                    ((DefaultMutableTreeNode) node.getParent()).remove(node);

                    break;
                }
            }

            //setVisibleTiers(visibleTiers);
            updateDisplayableTiers();
        }
    }

    /**
     * The MultiTierControlPanel is not an ACMEditListener but gets informed of
     * relevant changes by the connected viewer.<br>
     * When a tier is changed this panel should update it's list of visible
     * tiers and notify the viewer. The control panel seems to responsible of
     * finding out what has changed and whether the change has consequences
     * for the panel. Important are changes in: the name, the parent or the
     * participant.
     *
     * @param tier the tier that has been changed
     */
    public void tierChanged(TierImpl tier) {
        // for now only the tier name matters
        String name = tier.getName();
        String oldName = (String) tierNames.get(tier);

        if (!name.equals(oldName)) {
            //update popup
            MenuElement menu = visTiersMenu.getSubElements()[0];
            MenuElement[] items = menu.getSubElements();

            for (int i = 0; i < items.length; i++) {
                if (items[i] instanceof JCheckBoxMenuItem) {
                    if (((JCheckBoxMenuItem) items[i]).getText().equals(oldName)) {
                        ((JCheckBoxMenuItem) items[i]).setText(name);
                        ((JCheckBoxMenuItem) items[i]).setActionCommand(TIER_AC +
                            name);

                        break;
                    }
                }
            }

            Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

            // skip root node
            nodeEnum.nextElement();

            TierTreeNode node;

            while (nodeEnum.hasMoreElements()) {
                node = (TierTreeNode) nodeEnum.nextElement();

                if (node.getTier() == tier) {
                    node.setTierName(name);

                    break;
                }
            }

            // replace the name string
            tierNames.put(tier, name);
            paintBuffer();
        } else {
            // the name of the tier did not change, maybe parent or participant
            createSortedTree();
        }
    }

    /**
     * Tells the ControlPanel which is the new active tier.<br>
     * The panel updates it's own state and notifies the attached viewer.
     *
     * @param tier the new active tier
     */
    public void setActiveTier(Tier tier) {
        activeTier = tier;

        Enumeration e = sortedRootNode.preorderEnumeration();

        // skip the root node
        e.nextElement();

        while (e.hasMoreElements()) {
            TierTreeNode node = (TierTreeNode) e.nextElement();

            if (node.getTier() == activeTier) {
                node.setActive(true);
            } else {
                node.setActive(false);
            }
        }

        // notify viewers
        for (int j = 0; j < viewers.size(); j++) {
            ((MultiTierViewer) viewers.elementAt(j)).setActiveTier(tier);
        }

        paintBuffer();
    }

    /**
     * Returns the currently active tier.
     *
     * @return the active Tier, can be null
     */
    public Tier getActiveTier() {
        return activeTier;
    }

    /**
     * Sets the next or previous tier active, relative to the current active tier.
     * If there is no tier active, the first displayable tier is activated.
     *
     * @param next if true, set the tier below the current tier active, otherwise set the
     * tier above the current tier active
     */
    public void setNextActiveTier(boolean next) {
        if (displayableTierNodes.size() == 0) {
            return;
        }

        if (activeTier == null) {
            if (displayableTierNodes.size() > 0) {
                TierTreeNode node = (TierTreeNode) displayableTierNodes.get(0);
                setActiveTier(node.getTier());
            }
        } else {
            // if the current active tier is displayable, move to the next/previous
            // displayable tier
            TierTreeNode node = null;
            Tier tier = null;

            for (int i = 0; i < displayableTierNodes.size(); i++) {
                node = (TierTreeNode) displayableTierNodes.get(i);
                tier = node.getTier();

                if (tier == activeTier) {
                    if (!next) {
                        if (i > 0) {
                            node = (TierTreeNode) displayableTierNodes.get(i -
                                    1);
                            setActiveTier(node.getTier());
                        }

                        return;
                    } else {
                        if (i < (displayableTierNodes.size() - 1)) {
                            node = (TierTreeNode) displayableTierNodes.get(i +
                                    1);
                            setActiveTier(node.getTier());
                        }

                        return;
                    }
                }
            }

            // if we get here the active tier is not displayable, 
            // jump to the next displayable tier
            Enumeration e = sortedRootNode.preorderEnumeration();

            // skip the root node
            e.nextElement();

            while (e.hasMoreElements()) {
                node = (TierTreeNode) e.nextElement();

                if (node.getTier() == activeTier) {
                    if (!next) {
                        // traverse up to a displayable tier
                        while ((node.getParent() != null) &&
                                !((DefaultMutableTreeNode) node.getParent()).isRoot()) {
                            node = (TierTreeNode) node.getParent();

                            if (displayableTierNodes.contains(node)) {
                                setActiveTier(node.getTier());

                                return;
                            }
                        }
                    } else {
                        // traverse down to the next displayable tier
                        while (e.hasMoreElements()) {
                            node = (TierTreeNode) e.nextElement();

                            if (displayableTierNodes.contains(node)) {
                                setActiveTier(node.getTier());

                                return;
                            }
                        }
                    }

                    return;
                }
            }
        }
    }

    /**
     * Creates a edit tier dialog for the specified tier.
     *
     * @param tier the tier to edit
     */
    private void changeTier(TierImpl tier) {
        Command c = ELANCommandFactory.createCommand(transcription,
                ELANCommandFactory.EDIT_TIER);

        Object[] args = new Object[] { new Integer(EditTierDialog.CHANGE), tier };

        c.execute(transcription, args);
    }

    /**
     * Creates a single edit dialog to delete the specified tier.
     *
     * @param tier the tier to delete.
     */
    private void deleteTier(TierImpl tier) {
        Command c = ELANCommandFactory.createCommand(transcription,
                ELANCommandFactory.EDIT_TIER);

        Object[] args = new Object[] { new Integer(EditTierDialog.DELETE), tier };

        c.execute(transcription, args);
    }

    /**
     * Shows the tier dependency frame and tries to select the specified tier.
     *
     * @param tier the Tier to select in the dependency tree.
     */
    private void showInTierDependencyFrame(TierImpl tier) {
        CommandAction ca = ELANCommandFactory.getCommandAction(transcription,
                ELANCommandFactory.TIER_DEPENDENCIES);
        ca.actionPerformed(null);

        String name = tier.getName();

        if (ca instanceof TierDependenciesCA) {
            JFrame fr = ((TierDependenciesCA) ca).getFrame();

            if (fr instanceof TreeFrame) {
                JTree tree = ((TreeFrame) fr).getTree();

                DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel()
                                                                           .getRoot();
                Enumeration en = root.preorderEnumeration();
                DefaultMutableTreeNode target;

                while (en.hasMoreElements()) {
                    target = (DefaultMutableTreeNode) en.nextElement();

                    if ((target.getUserObject() != null) &&
                            target.getUserObject().equals(name)) {
                        TreeNode[] path = target.getPath();
                        tree.setSelectionPath(new TreePath(path));
                    }
                }
            }
        }
    }

    /**
     * Makes all tiers visible.<br> Does not change ordering.
     */
    private void showAllTiers() {
        /*
           for (int i = 0; i < tiers.size(); i++) {
               if (!visibleTiers.contains(tiers.get(i))) {
                   visibleTiers.add(tiers.get(i));
               }
           }
         */
        MenuElement menu = visTiersMenu.getSubElements()[0];
        MenuElement[] items = menu.getSubElements();

        for (int i = 0; i < items.length; i++) {
            if (items[i] instanceof JCheckBoxMenuItem) {
                ((JCheckBoxMenuItem) items[i]).setSelected(true);
            }
        }

        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();
            node.setVisible(true);
        }

        updateDisplayableTiers();

        //setVisibleTiers(visibleTiers);
    }

    /**
     * Hides all tiers.<br>
     */
    private void hideAllTiers() {
        //visibleTiers.clear();
        MenuElement menu = visTiersMenu.getSubElements()[0];
        MenuElement[] items = menu.getSubElements();

        for (int i = 0; i < items.length; i++) {
            if (items[i] instanceof JCheckBoxMenuItem) {
                ((JCheckBoxMenuItem) items[i]).setSelected(false);
            }
        }

        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();
            node.setVisible(false);
        }

        updateDisplayableTiers();
    }

    /**
     * Updates the visible tiers and tiers menu items. Called from a dialog
     * that  gives the user the possibility to change the visibility of more
     * tiers at a time.
     *
     * @param visTierNames a Vector with the names of the visible tiers
     */
    protected void updateVisibleTiers(Vector visTierNames) {
        // update the menu's
        MenuElement menu = visTiersMenu.getSubElements()[0];
        MenuElement[] items = menu.getSubElements();

        for (int i = 0; i < items.length; i++) {
            if (items[i] instanceof JCheckBoxMenuItem) {
                if (visTierNames.contains(
                            ((JCheckBoxMenuItem) items[i]).getText())) {
                    ((JCheckBoxMenuItem) items[i]).setSelected(true);
                } else {
                    ((JCheckBoxMenuItem) items[i]).setSelected(false);
                }
            }
        }

        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();

            if (visTierNames.contains(node.getTierName())) {
                node.setVisible(true);
            } else {
                node.setVisible(false);
            }
        }

        updateDisplayableTiers();
    }

    /**
     * Sets a single tier to invisible.
     *
     * @param tier the tier to set invisible
     */
    private void hideTier(TierImpl tier) {
        if (tier == null) {
            return;
        }

        // toggle the menuitem
        String name = tier.getName();

        MenuElement menu = visTiersMenu.getSubElements()[0];
        MenuElement[] items = menu.getSubElements();

        for (int i = 0; i < items.length; i++) {
            if (items[i] instanceof JCheckBoxMenuItem) {
                if (((JCheckBoxMenuItem) items[i]).getText().equals(name)) {
                    ((JCheckBoxMenuItem) items[i]).setSelected(false);

                    break;
                }
            }
        }

        // toggle the tier-node
        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();

            if (node.getTier() == tier) {
                node.setVisible(false);

                break;
            }
        }

        updateDisplayableTiers();
    }

    /**
     * Sets all tier-nodes to expanded.
     */
    private void expandAllNodes() {
        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();
            node.setExpanded(true);
        }

        updateDisplayableTiers();
    }

    /**
     * Sets all tier-nodes to collapsed.
     */
    private void collapseAllNodes() {
        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();
            node.setExpanded(false);
        }

        updateDisplayableTiers();
    }

    /**
     * Toggles the expanded state of a single tier.
     *
     * @param tier the tier-node to expand or collapse
     */
    private void toggleExpandedState(TierImpl tier) {
        if (tier == null) {
            return;
        }

        Enumeration nodeEnum = sortedRootNode.preorderEnumeration();

        // skip root node
        nodeEnum.nextElement();

        TierTreeNode node;

        while (nodeEnum.hasMoreElements()) {
            node = (TierTreeNode) nodeEnum.nextElement();

            if (node.getTier() == tier) {
                node.setExpanded(!node.isExpanded());

                break;
            }
        }

        updateDisplayableTiers();
    }

    /**
     * Adds a tier - color pair to the table of root tier colors.<br>
     * Tries to create unique colors as much as possible.
     *
     * @param t the TierImpl to add to the table
     */
    private void addToRootColors(TierImpl t) {
        int i = 0;
        Color c = getRandomColor();

        while (true) {
            if ((c.getRed() == c.getGreen()) && (c.getRed() == c.getBlue())) {
                // || (c.getRed() + c.getGreen() + c.getBlue() > 2 * colorValues[2] + colorValues[1] - 1)
                //don't permit gray colors
                c = getRandomColor();
                i++;

                continue;
            }

            if (i > 50) {
                tierRootColors.put(t, c);

                break; //prevent an endless loop
            }

            if (tierRootColors.size() >= 16) {
                tierRootColors.put(t, c);

                break;
            } else if (!tierRootColors.values().contains(c)) {
                tierRootColors.put(t, c);

                break;
            } else {
                c = getRandomColor();
                i++;
            }
        }
    }

    /**
     * Create a semi-random color based on three predefined color values.<br>
     * Based on the restrictions in this method there are 16 legal colors.
     *
     * @return a new color
     */
    private Color getRandomColor() {
        int r = colorValues[(int) (Math.random() * 3)];
        int g = colorValues[(int) (Math.random() * 3)];
        int b = colorValues[(int) (Math.random() * 3)];

        return new Color(r, g, b);
    }

    /**
     * Create a truncated String of a label to display in the panel.
     *
     * @param string the label's value
     * @param width the available width for the String
     * @param fMetrics the font metrics
     *
     * @return the truncated String
     */
    private String truncateString(String string, int width, FontMetrics fMetrics) {
        String line = string;

        if (fMetrics != null) {
            int stringWidth = fMetrics.stringWidth(line);

            if (stringWidth > (width - 4)) { // truncate

                int i = 0;
                String s = "";
                int size = line.length();

                while (i < size) {
                    if (fMetrics.stringWidth(s) > (width - 4)) {
                        break;
                    } else {
                        s = s + line.charAt(i++);
                    }
                }

                if (!s.equals("")) {
                    line = s.substring(0, s.length() - 1);
                } else {
                    line = s;
                }
            }
        }

        return line;
    }

    /*
     * Swing method, maybe our own layoutable methods will make this one obsolete
     */
    public Dimension getMinimumSize() {
        dimension.setSize(WIDTH, ((JComponent) viewer).getHeight());

        return dimension;
    }

    /*
     * Swing method, maybe our own layoutable methods will make this one obsolete
     */
    public Dimension getPreferredSize() {
        dimension.setSize(WIDTH, ((JComponent) viewer).getHeight());

        return dimension;
    }

    /**
     * Not very useful...
     *
     * @return DOCUMENT ME!
     */
    public int getWidth() {
        return WIDTH;
    }

    /**
     * Creates a BufferedImage when necessary and paints the tierlabels in this
     * buffer.
     */
    private void paintBuffer() {
        if ((getWidth() <= 0) || (getHeight() <= 0)) {
            return;
        }

        if ((displayableTierNodes == null) || (tierPositions == null) ||
                (displayableTierNodes.size() > tierPositions.length)) {
            return;
        }

        if ((bi == null) || (bi.getWidth() != getWidth()) ||
                (bi.getHeight() != getHeight())) {
            bi = new BufferedImage(getWidth(), getHeight(),
                    BufferedImage.TYPE_INT_RGB);
        }

        Graphics2D big2d = bi.createGraphics();
        big2d.setColor(getBackground());
        big2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());

        int panelWidth = getWidth();
        int availableLabelWidth = panelWidth - (2 * MARGIN);
        TierTreeNode node;

        switch (sortMode) {
        case SORT_BY_HIERARCHY:

            for (int i = 0; i < displayableTierNodes.size(); i++) {
                node = (TierTreeNode) displayableTierNodes.get(i);
                paintLabelHierarchically(big2d, node, i, availableLabelWidth);
            }

            break;

        case SORT_BY_PARTICIPANT:

            boolean startNewGroup = true;
            TierTreeNode prevNode;

            for (int i = 0; i < displayableTierNodes.size(); i++) {
                node = (TierTreeNode) displayableTierNodes.get(i);

                if (i > 0) {
                    prevNode = (TierTreeNode) displayableTierNodes.get(i - 1);

                    if (node.getTier().getParticipant().equals(prevNode.getTier()
                                                                           .getParticipant())) {
                        startNewGroup = false;
                    } else {
                        startNewGroup = true;
                    }
                }

                paintLabelInBlock(big2d, node, i, availableLabelWidth,
                    startNewGroup);
            }

            break;

        case SORT_BY_LINGUISTIC_TYPE:

            boolean startLinGroup = true;
            TierTreeNode prevLinNode;

            for (int i = 0; i < displayableTierNodes.size(); i++) {
                node = (TierTreeNode) displayableTierNodes.get(i);

                if (i > 0) {
                    prevLinNode = (TierTreeNode) displayableTierNodes.get(i -
                            1);

                    if (node.getTier().getLinguisticType().equals(prevLinNode.getTier()
                                                                                 .getLinguisticType())) {
                        startLinGroup = false;
                    } else {
                        startLinGroup = true;
                    }
                }

                paintLabelInBlock(big2d, node, i, availableLabelWidth,
                    startLinGroup);
            }

            break;

        case UNSORTED:
        // default, fall through
        default:

            for (int i = 0; i < displayableTierNodes.size(); i++) {
                node = (TierTreeNode) displayableTierNodes.get(i);

                paintLabelInBlock(big2d, node, i, availableLabelWidth, false);
            }
        }

        repaint();
    }

    /**
     * Draw the tier names right aligned and at the correct y position
     *
     * @param g DOCUMENT ME!
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        if ((bi == null) || (bi.getWidth() != getWidth()) ||
                (bi.getHeight() != getHeight())) {
            paintBuffer();

            return;
        }

        g.drawImage(bi, 0, 0, null);

        if (dragging) {
            g.setFont(boldFont);
            g.setColor(Color.blue);
            g.drawString(dragLabel, dragX, dragY);
        }
    }

    /**
     * Paints a tier label in a tree-like structure. Angled lines show the tree
     * hierarchy  and for tiers with children there is a marker to indicate
     * the expanded state  of the branche.
     *
     * @param g the Graphics object for rendering
     * @param node the tier's node to paint
     * @param positionIndex the index in the array of displayable tiers
     * @param availableLabelWidth the availbale width for tier label plus
     *        decorations
     */
    private void paintLabelHierarchically(Graphics g, TierTreeNode node,
        int positionIndex, int availableLabelWidth) {
        int level = node.getLevel();
        boolean hasChildren = (node.getChildCount() == 0) ? false : true;

        boolean atLeastOneChildVisible = false;

        if (node.getChildCount() > 0) {
            Enumeration en = node.children();

            while (en.hasMoreElements()) {
                if (((TierTreeNode) en.nextElement()).isVisible()) {
                    atLeastOneChildVisible = true;

                    break;
                }
            }
        }

        boolean[] isParentLastChild = new boolean[level];

        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();
        isParentLastChild[level - 1] = (parent.getLastChild() == node);

        DefaultMutableTreeNode upNode;

        for (int i = level - 2; i >= 0; i--) {
            upNode = parent;
            parent = (DefaultMutableTreeNode) upNode.getParent();
            isParentLastChild[i] = (parent.getLastChild() == upNode);
        }

        int widthForLabel = availableLabelWidth -
            ((level * LEVEL_INDENT) + (LEVEL_INDENT / 2));
        int actualLabelWidth = widthForLabel;
        String label = "";

        if (node.isActive()) {
            label = truncateString(node.getTierName(), widthForLabel,
                    boldMetrics);
            actualLabelWidth = SwingUtilities.computeStringWidth(boldMetrics,
                    label);
        } else {
            label = truncateString(node.getTierName(), widthForLabel,
                    fontMetrics);
            actualLabelWidth = SwingUtilities.computeStringWidth(fontMetrics,
                    label);
        }

        // start painting, vertical lines
        g.setColor(Color.DARK_GRAY);

        int y = tierPositions[positionIndex];
        int minY = y - (tierHeight / 2);
        int minX = MARGIN + (LEVEL_INDENT / 2);
        int levelCount = 1;

        for (int i = levelCount; i < level; i++) {
            if (!isParentLastChild[i - 1]) {
                g.drawLine(minX, minY, minX, minY + tierHeight);
            }

            minX += LEVEL_INDENT;
        }

        int x = (MARGIN + (level * LEVEL_INDENT)) - (LEVEL_INDENT / 2);

        if (isParentLastChild[level - 1]) {
            g.drawLine(x, minY, x, y);
        } else {
            g.drawLine(x, minY, x, minY + tierHeight);
        }

        // horizontal line
        g.drawLine(x, y, x + LEVEL_INDENT, y);

        if (hasChildren && node.isExpanded()) {
            if (atLeastOneChildVisible) {
                g.drawLine(x + LEVEL_INDENT, y, x + LEVEL_INDENT,
                    y + (tierHeight / 2));
            }

            // paint expanded marker
            g.drawRect(x - (MARKER_SIZE / 2), y - (MARKER_SIZE / 2),
                MARKER_SIZE, MARKER_SIZE);
        } else if (hasChildren && !node.isExpanded()) {
            // paint collapsed marker
            g.fillRect(x - (MARKER_SIZE / 2), y - (MARKER_SIZE / 2),
                MARKER_SIZE, MARKER_SIZE);
        }

        x += (LEVEL_INDENT + (LEVEL_INDENT / 2));
        y += (boldFont.getSize() / 2);

        if (node.isActive()) {
            g.setColor(Constants.CROSSHAIRCOLOR);
            g.setFont(boldFont);
            g.drawLine(x, y + 3, x + actualLabelWidth, y + 3);
            g.drawString(label, x, y);
        } else {
            TierImpl root = node.getTier().getRootTier();

            if (root != null) {
                g.setColor((Color) tierRootColors.get(root));
            }

            g.setFont(getFont());
            g.drawString(label, x, y);
        }
    }

    /**
     * Paints a tier label in a flattened tree; i.e. all branches are at the
     * same level,  but there are groups separated by horizontal dividing
     * lines.
     *
     * @param g the Graphics object for rendering
     * @param node the tier's node to paint
     * @param positionIndex the index in the array of displayable tiers
     * @param availableLabelWidth the availbale width for the tier label
     * @param startNewGroup true indicates that a horizontal line should be
     *        drawn above this node
     */
    private void paintLabelInBlock(Graphics g, TierTreeNode node,
        int positionIndex, int availableLabelWidth, boolean startNewGroup) {
        int y = tierPositions[positionIndex];
        int minY = y - (tierHeight / 2);

        if ((sortMode != UNSORTED) && ((positionIndex == 0) || startNewGroup)) {
            g.setColor(Color.DARK_GRAY);
            g.drawLine(MARGIN, minY, MARGIN + availableLabelWidth, minY);
        }

        if ((sortMode != UNSORTED) &&
                (positionIndex == (displayableTierNodes.size() - 1))) {
            g.setColor(Color.DARK_GRAY);
            g.drawLine(MARGIN, minY + tierHeight, MARGIN + availableLabelWidth,
                minY + tierHeight);
        }

        int actualLabelWidth = availableLabelWidth;
        String label = "";

        if (node.isActive()) {
            label = truncateString(node.getTierName(), availableLabelWidth,
                    boldMetrics);
            actualLabelWidth = SwingUtilities.computeStringWidth(boldMetrics,
                    label);

            int x = (MARGIN + availableLabelWidth) - actualLabelWidth;
            y += (getFont().getSize() / 2);
            g.setColor(Constants.CROSSHAIRCOLOR);
            g.setFont(boldFont);
            g.drawLine(x, y + 3, x + actualLabelWidth, y + 3);
            g.drawString(label, x, y);
        } else {
            label = truncateString(node.getTierName(), availableLabelWidth,
                    fontMetrics);
            actualLabelWidth = SwingUtilities.computeStringWidth(fontMetrics,
                    label);

            int x = (MARGIN + availableLabelWidth) - actualLabelWidth;
            TierImpl root = node.getTier().getRootTier();

            if (root != null) {
                g.setColor((Color) tierRootColors.get(root));
            }

            g.setFont(getFont());
            g.drawString(label, x, y + (getFont().getSize() / 2));
        }
    }

    /**
     * MouseMotionListener method. Creates a formatted tooltip that displays
     * information  on the tier at the mouse position.
     *
     * @param e the mouse event
     */
    public void mouseMoved(MouseEvent e) {
        Point p = e.getPoint();
        int tierIndex = getClosestTierIndexForMouseY(p.y);

        if (!(tierIndex < displayableTierNodes.size())) {
            return;
        }

        TierTreeNode tierNode = (TierTreeNode) displayableTierNodes.get(tierIndex);
        TierImpl tier = tierNode.getTier();

        if (tier == null) {
            return;
        }

        StringBuffer tooltip = new StringBuffer("<html><table>");

        // tier name
        tooltip.append("<tr><td><b>");
        tooltip.append(ElanLocale.getString("EditTierDialog.Label.TierName"));
        tooltip.append("</b></td><td>");
        tooltip.append(tier.getName());
        tooltip.append("</td></tr>");

        // tier parent
        tooltip.append("<tr><td><b>");
        tooltip.append(ElanLocale.getString("EditTierDialog.Label.Parent"));
        tooltip.append("</b></td><td>");

        if (tier.hasParentTier()) {
            tooltip.append(tier.getParentTier().getName());
        } else {
            tooltip.append("-");
        }

        tooltip.append("</td></tr>");

        // tier participant
        tooltip.append("<tr><td><b>");
        tooltip.append(ElanLocale.getString("EditTierDialog.Label.Participant"));
        tooltip.append("</b></td><td>");
        tooltip.append(tier.getParticipant());
        tooltip.append("</td></tr>");

        // tier lin. type
        tooltip.append("<tr><td><b>");
        tooltip.append(ElanLocale.getString(
                "EditTierDialog.Label.LinguisticType"));
        tooltip.append("</b></td><td>");

        if (tier.getLinguisticType() != null) {
            tooltip.append(tier.getLinguisticType().getLinguisticTypeName());
        }

        tooltip.append("</td></tr>");

        // tier language
        tooltip.append("<tr><td><b>");
        tooltip.append(ElanLocale.getString("EditTierDialog.Label.Language"));
        tooltip.append("</b></td><td>");

        if (tier.getDefaultLocale() != null) {
            tooltip.append(tier.getDefaultLocale().getDisplayName());
        }

        tooltip.append("</td></tr>");

        tooltip.append("</table></html>");
        setToolTipText(tooltip.toString());
    }

    // not used interface methods

    /*
     * MouseListener method, not used here
     */
    public void mouseEntered(MouseEvent e) {
    }

    /*
     * MouseListener method, not used here
     */
    public void mouseExited(MouseEvent e) {
    }

    //**********************************************************************
    //* internal classes
    //**********************************************************************

    /**
     * A class to paint the name of a tier in the <code>MultiTierControlPanel </code>.<br>
     * It has fields to hold the visibility and active state of the  Tier and
     * the expanded state of the node.
     *
     * @author Han Sloetjes
     * @version 1.0 21 Apr 2004
     */
    public class TierTreeNode extends DefaultMutableTreeNode {
        private TierImpl tier;
        private String tierName;
        private String label;
        private boolean visible = true;
        private boolean active = false;
        private boolean expanded = true;

        /**
         * Creates a new TierTreeNode instance
         *
         * @param tier DOCUMENT ME!
         */
        public TierTreeNode(TierImpl tier) {
            this.tier = tier;
            tierName = tier.getName();
            label = tierName;
        }

        /**
         * Returns the active state of this tier.
         *
         * @return true if this is the active tier, false otherwise
         */
        public boolean isActive() {
            return active;
        }

        /**
         * Returns the visibility of this tier.
         *
         * @return true if this tier is visible, false otherwise
         */
        public boolean isVisible() {
            return visible;
        }

        /**
         * Sets the active state.
         *
         * @param active the active state
         */
        public void setActive(boolean active) {
            this.active = active;
        }

        /**
         * Sets the visibility of this tier.
         *
         * @param visible the visibility of this tier
         */
        public void setVisible(boolean visible) {
            this.visible = visible;
        }

        /**
         * Returns the expanded state.
         *
         * @return true if this node is expanded, false otherwise
         */
        public boolean isExpanded() {
            return expanded;
        }

        /**
         * Sets the expanded state of this node.
         *
         * @param expanded the expanded state
         */
        public void setExpanded(boolean expanded) {
            this.expanded = expanded;
        }

        /**
         * Returns the tier that is the userobject of this node.
         *
         * @return the tier
         */
        public TierImpl getTier() {
            return tier;
        }

        /**
         * Sets the userobject of this node.
         *
         * @param tier the userobject of this node
         */
        public void setTier(TierImpl tier) {
            this.tier = tier;
        }

        /**
         * Returns the name of the tier.
         *
         * @return the name of the tier
         */
        public String getTierName() {
            return tierName;
        }

        /**
         * Sets the name of the tier.
         *
         * @param tierName the new name of the tier
         */
        public void setTierName(String tierName) {
            this.tierName = tierName;
        }

        /**
         * Returns the name of the tier.
         *
         * @return the name of the tier
         */
        public String toString() {
            return tierName;
        }
    }

    /**
     * A dialog that gives the user the possibility to change the visibility of
     * more tiers at a time.
     *
     * @author Han Sloetjes
     */
    public class VisibilityDialog extends JDialog implements ActionListener {
        private Vector allTiers;
        private Vector visibleTiers;

        // components
        private JPanel checkboxPanel;
        private JButton showAllButton;
        private JButton hideAllButton;
        private JButton applyButton;
        private JButton cancelButton;

        /**
         * Creates and shows a modal dialog, displaying checkbox items for each
         * tier in the transcription. The user can change the visibility of
         * more tiers   at a time.
         *
         * @param trans the transcription
         * @param visibleTiers the currently visible tiers
         */
        public VisibilityDialog(Transcription trans, Vector visibleTiers) {
            super(ELANCommandFactory.getRootFrame(trans),
                ElanLocale.getString("MultiTierControlPanel.Menu.VisibleTiers"),
                true);
            allTiers = trans.getTiers();

            this.visibleTiers = visibleTiers;
            initDialog();
            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            pack();
            setLocation();
            setVisible(true);
        }

        /**
         * Create components and add them to the content pane.
         */
        private void initDialog() {
            showAllButton = new JButton(ElanLocale.getString(
                        "MultiTierControlPanel.Menu.ShowAllTiers"));
            showAllButton.addActionListener(this);
            hideAllButton = new JButton(ElanLocale.getString(
                        "MultiTierControlPanel.Menu.HideAllTiers"));
            hideAllButton.addActionListener(this);
            applyButton = new JButton(ElanLocale.getString("Button.OK"));
            applyButton.addActionListener(this);
            cancelButton = new JButton(ElanLocale.getString("Button.Cancel"));
            cancelButton.addActionListener(this);

            checkboxPanel = new JPanel();

            BoxLayout box = new BoxLayout(checkboxPanel, BoxLayout.Y_AXIS);
            checkboxPanel.setLayout(box);

            JCheckBox checkbox;
            TierImpl tier;

            for (int i = 0; i < allTiers.size(); i++) {
                tier = (TierImpl) allTiers.get(i);

                checkbox = new JCheckBox(tier.getName());
                checkboxPanel.add(checkbox);

                if (visibleTiers.contains(tier)) {
                    checkbox.setSelected(true);
                } else {
                    checkbox.setSelected(false);
                }
            }

            GridLayout gl = new GridLayout(1, 2, 6, 2);
            JPanel buttonPanel1 = new JPanel(gl);
            buttonPanel1.add(showAllButton);
            buttonPanel1.add(hideAllButton);

            JPanel buttonPanel2 = new JPanel(gl);
            buttonPanel2.add(applyButton);
            buttonPanel2.add(cancelButton);

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

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.fill = GridBagConstraints.NONE;
            gbc.anchor = GridBagConstraints.NORTH;
            gbc.insets = new Insets(2, 6, 2, 6);
            getContentPane().add(buttonPanel1, gbc);
            gbc.gridy = 2;
            getContentPane().add(buttonPanel2, gbc);

            gbc.gridy = 1;
            gbc.weightx = 1.0;
            gbc.weighty = 1.0;
            gbc.fill = GridBagConstraints.BOTH;
            getContentPane().add(new JScrollPane(checkboxPanel), gbc);
            getRootPane().setDefaultButton(applyButton);
        }

        /**
         * Position the dialog relative to the multitierpanel.
         */
        private void setLocation() {
            Component parent = MultiTierControlPanel.this;
            Point p = new Point(0, 0);
            SwingUtilities.convertPointToScreen(p, parent);

            int windowHeight = SwingUtilities.getWindowAncestor(parent)
                                             .getHeight();

            if (this.getHeight() > windowHeight) {
                // don't let the dialog be higher than the window
                this.setSize(this.getWidth(), windowHeight);
            }

            p.x += parent.getWidth();

            // line at bottom
            p.y -= (this.getHeight() - parent.getHeight());
            setLocation(p);
        }

        /**
         * Checks which tier-checkboxes are selected and updates the  control
         * panel.
         */
        private void updateVisibleTiers() {
            Vector visTierNames = new Vector();
            Component[] boxes = checkboxPanel.getComponents();

            for (int i = 0; i < boxes.length; i++) {
                if (boxes[i] instanceof JCheckBox) {
                    if (((JCheckBox) boxes[i]).isSelected()) {
                        visTierNames.add(((JCheckBox) boxes[i]).getText());
                    }
                }
            }

            MultiTierControlPanel.this.updateVisibleTiers(visTierNames);
        }

        /**
         * Check or uncheck all tier checkboxes.
         *
         * @param selected if true select all checkboxes, unselect all
         *        otherwise
         */
        private void setAllSelected(boolean selected) {
            Component[] boxes = checkboxPanel.getComponents();

            for (int i = 0; i < boxes.length; i++) {
                if (boxes[i] instanceof JCheckBox) {
                    ((JCheckBox) boxes[i]).setSelected(selected);
                }
            }
        }

        /**
         * Disposes this dialog.
         */
        private void close() {
            setVisible(false);
            dispose();
        }

        /**
         * Actions following a button click.
         *
         * @param ae the Action Event
         */
        public void actionPerformed(ActionEvent ae) {
            Object source = ae.getSource();

            if (source == showAllButton) {
                setAllSelected(true);
            } else if (source == hideAllButton) {
                setAllSelected(false);
            } else if (source == applyButton) {
                updateVisibleTiers();
                close();
            } else if (source == cancelButton) {
                close();
            }
        }
    }
}
