/*
 * FigTreeFrame.java
 *
 * Copyright (C) 2006-2014 Andrew Rambaut
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package figtree.application;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.DefaultFontMapper;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import figtree.treeviewer.decorators.DiscreteColourDecorator;
import figtree.treeviewer.decorators.HSBDiscreteColourDecorator;
import figtree.treeviewer.painters.StatesPainter;
import jebl.evolution.align.Output;
import jebl.evolution.alignments.Alignment;
import jebl.evolution.alignments.BasicAlignment;
import jebl.evolution.graphs.Node;
import jebl.evolution.io.*;
import jebl.evolution.taxa.Taxon;
import jebl.evolution.trees.RootedTree;
import jebl.evolution.trees.Tree;
import jebl.util.Attributable;
import jam.controlpalettes.BasicControlPalette;
import jam.controlpalettes.ControlPalette;
import jam.framework.DocumentFrame;
import jam.panels.*;
import jam.toolbar.*;
import jam.util.IconUtils;
import figtree.application.menus.TreeMenuHandler;
import figtree.application.menus.FigTreeFileMenuHandler;
import figtree.treeviewer.*;
import figtree.treeviewer.TreeSelectionListener;
import figtree.treeviewer.annotations.*;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;

/**
 * Primary window for FigTree. The window contains a toolbar, a control panel and a panel displaying the
 * tree. It responds to menu commands and hot keys specific to the focused window.
 * @author Andrew Rambaut
 * @version $Id: FigTreeApplication.java 232 2014-03-02 15:47:10Z rambaut $
 *
 * $HeadURL: https://figtree.googlecode.com/svn/trunk/src/figtree/application/FigTreeApplication.java $
 *
 * $LastChangedBy: rambaut $
 * $LastChangedDate: 2014-03-02 15:47:10 +0000 (Sun, 02 Mar 2014) $
 * $LastChangedRevision: 232 $
 */
public class FigTreeFrame extends DocumentFrame implements FigTreeFileMenuHandler, TreeMenuHandler {

    private final static boolean FAST_MODE = false;

    private final ExtendedTreeViewer treeViewer;
    private final ControlPalette controlPalette;
    private final FigTreePanel figTreePanel;

    private StatusBar statusBar;

    private SearchPanel filterPanel;
    private JPopupMenu filterPopup;

    public FigTreeFrame(String title, boolean fastMode) {
        super();

        setTitle(title);

        setImportAction(importAction);
        //       setImportAction(importCharactersAction);
        setExportAction(exportTreesAction);

        treeViewer = new ExtendedTreeViewer(this, fastMode);
        controlPalette = new BasicControlPalette(200, BasicControlPalette.DisplayMode.ONLY_ONE_OPEN);

        figTreePanel = new FigTreePanel(this, treeViewer, controlPalette);

        this.addWindowFocusListener(new WindowAdapter() {
            public void windowGainedFocus(WindowEvent e) {
                treeViewer.requestFocusInWindow();
            }
        });
    }

    public void initializeComponents() {

        setSize(new java.awt.Dimension(1024, 768));

        Toolbar toolBar = new Toolbar();
        toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.darkGray));

        toolBar.setRollover(true);
        toolBar.setFloatable(false);

        Icon rerootToolIcon = IconUtils.getIcon(this.getClass(), "images/rerootTool.png");
        Icon rotateToolIcon = IconUtils.getIcon(this.getClass(), "images/rotateTool.png");
        Icon cartoonNodeToolIcon = IconUtils.getIcon(this.getClass(), "images/cartoonNodeTool.png");
        Icon collapseNodeToolIcon = IconUtils.getIcon(this.getClass(), "images/collapseNodeTool.png");
        Icon hilightToolIcon = IconUtils.getIcon(this.getClass(), "images/hilightTool.png");
        Icon annotationToolIcon = IconUtils.getIcon(this.getClass(), "images/annotationTool.png");
        Icon findToolIcon = IconUtils.getIcon(this.getClass(), "images/findTool.png");
        Icon infoToolIcon = IconUtils.getIcon(this.getClass(), "images/infoTool.png");
        Icon statisticsToolIcon = IconUtils.getIcon(this.getClass(), "images/statisticsTool.png");
        Icon settingsToolIcon = IconUtils.getIcon(this.getClass(), "images/projectTool.png");
        Icon colourToolIcon = IconUtils.getIcon(this.getClass(), "images/coloursTool.png");

        Icon nextIcon = IconUtils.getIcon(this.getClass(), "images/next.png");
        Icon prevIcon = IconUtils.getIcon(this.getClass(), "images/prev.png");

        final ToolbarAction cartoonToolbarAction = new ToolbarAction("Cartoon", CARTOON_NODE, cartoonNodeToolIcon) {
            public void actionPerformed(ActionEvent e){
                cartoonAction.actionPerformed(e);
            }
        };

        ToolbarButton cartoonToolButton = new ToolbarButton(cartoonToolbarAction, true);
        cartoonToolButton.setFocusable(false);
        toolBar.addComponent(cartoonToolButton);

        final ToolbarAction collapseToolbarAction = new ToolbarAction("Collapse", COLLAPSE_NODE, collapseNodeToolIcon) {
            public void actionPerformed(ActionEvent e){
                collapseAction.actionPerformed(e);
            }
        };
        ToolbarButton collapseToolButton = new ToolbarButton(collapseToolbarAction, true);
        collapseToolButton.setFocusable(false);
        toolBar.addComponent(collapseToolButton);

        final ToolbarAction rerootToolbarAction = new ToolbarAction("Reroot", ROOT_ON_BRANCH, rerootToolIcon) {
            public void actionPerformed(ActionEvent e){
                rerootAction.actionPerformed(e);
            }
        };
        ToolbarButton rerootToolButton = new ToolbarButton(rerootToolbarAction, true);
        rerootToolButton.setFocusable(false);
        toolBar.addComponent(rerootToolButton);

        final ToolbarAction rotateToolbarAction = new ToolbarAction("Rotate", ROTATE_NODE, rotateToolIcon) {
            public void actionPerformed(ActionEvent e){
                rotateAction.actionPerformed(e);
            }
        };
        ToolbarButton rotateToolButton = new ToolbarButton(rotateToolbarAction, true);
        rotateToolButton.setFocusable(false);
        toolBar.addComponent(rotateToolButton);

        final ToolbarAction annotateToolbarAction =  new ToolbarAction("Annotate", ANNOTATE, annotationToolIcon) {
            public void actionPerformed(ActionEvent e){
                annotateAction.actionPerformed(e);
            }
        };
        ToolbarButton annotationToolButton = new ToolbarButton(annotateToolbarAction, true);
        annotationToolButton.setFocusable(false);
        toolBar.addComponent(annotationToolButton);

        final ToolbarAction colourToolbarAction = new ToolbarAction("Colour", COLOUR, colourToolIcon) {
            public void actionPerformed(ActionEvent e){
                colourAction.actionPerformed(e);
            }
        };
        ToolbarButton colourToolButton = new ToolbarButton(colourToolbarAction, true);
        colourToolButton.setFocusable(false);
        toolBar.addComponent(colourToolButton);

        final ToolbarAction hilightToolbarAction = new ToolbarAction("Hilight", HILIGHT, hilightToolIcon) {
            public void actionPerformed(ActionEvent e){
                hilightAction.actionPerformed(e);
            }
        };
        ToolbarButton hilightToolButton = new ToolbarButton(hilightToolbarAction, true);
        hilightToolButton.setFocusable(false);
        toolBar.addComponent(hilightToolButton);

        toolBar.addSeparator();

        final ToolbarAction findToolbarAction = new ToolbarAction("Find", "Find Taxa...", findToolIcon) {
            public void actionPerformed(ActionEvent e){
                findAction.actionPerformed(e);
            }
        };
        JButton findToolButton = new ToolbarButton(findToolbarAction);
        findToolButton.setFocusable(false);
        toolBar.addComponent(findToolButton);
        findToolButton.setEnabled(true);

//		final ToolbarAction infoToolbarAction = new ToolbarAction("Get Info", "Get Info...", infoToolIcon) {
//			public void actionPerformed(ActionEvent e){
//				getInfoAction.actionPerformed(e);
//			}
//		};
//		JButton infoToolButton = new ToolbarButton(infoToolbarAction);
//		toolBar.addComponent(infoToolButton);

//        JButton settingsToolButton = new ToolbarButton(
//                new ToolbarAction("Statistics", "Statistics...", statisticsToolIcon) {
//                    public void actionPerformed(ActionEvent e){
//                        treeViewer.showStatistics();
//                    }
//                });
//        settingsToolButton.putClientProperty("Quaqua.Button.style", "toolBarRollover");
//        toolBar.addComponent(settingsToolButton);
//        settingsToolButton.setEnabled(false);

        toolBar.addSeparator();

        Box box1 = Box.createHorizontalBox();
        final JToggleButton toggle1 = new JToggleButton("Node");
        toggle1.setFocusable(false);
        toggle1.putClientProperty("JButton.buttonType", "segmentedTextured");
        toggle1.putClientProperty("JButton.segmentPosition", "first");
        toggle1.putClientProperty( "Quaqua.Button.style", "toggleWest");

        final JToggleButton toggle2 = new JToggleButton("Clade");
        toggle2.setFocusable(false);
        toggle2.putClientProperty("JButton.buttonType", "segmentedTextured");
        toggle2.putClientProperty("JButton.segmentPosition", "middle");
        toggle2.putClientProperty( "Quaqua.Button.style", "toggleCenter");

        final JToggleButton toggle3 = new JToggleButton("Taxa");
        toggle3.setFocusable(false);
        toggle3.putClientProperty("JButton.buttonType", "segmentedTextured");
        toggle3.putClientProperty("JButton.segmentPosition", "last");
        toggle3.putClientProperty( "Quaqua.Button.style", "toggleEast");

        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(toggle1);
        buttonGroup.add(toggle2);
        buttonGroup.add(toggle3);
        toggle1.setSelected(true);
        toggle1.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    treeViewer.setSelectionMode(TreePaneSelector.SelectionMode.NODE);
                }
            }
        });
        toggle2.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    treeViewer.setSelectionMode(TreePaneSelector.SelectionMode.CLADE);
                }
            }
        });
        toggle3.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    treeViewer.setSelectionMode(TreePaneSelector.SelectionMode.TAXA);
                }
            }
        });
        box1.add(Box.createVerticalStrut(annotationToolIcon.getIconHeight()));
        box1.add(toggle1);
        box1.add(toggle2);
        box1.add(toggle3);
        toolBar.addComponent(new GenericToolbarItem("Selection Mode", "What aspect of the tree is selected when it is clicked", box1));

        toolBar.addFlexibleSpace();

        final ToolbarAction prevTreeToolbarAction =
                new ToolbarAction(null, "Previous Tree...", prevIcon) {
                    public void actionPerformed(ActionEvent e){
                        if (treeViewer.isRootingOn() && treeViewer.getRootingType() == TreePane.RootingType.USER_ROOTING) {
                            JOptionPane.showMessageDialog(FigTreeFrame.this, "Cannot switch trees when user rooting option is on.\n" +
                                            "Turn this option off to switch trees",
                                    "Unable to switch trees",
                                    JOptionPane.ERROR_MESSAGE);

                        } else {
                            treeViewer.showPreviousTree();
                        }
                    }
                };
        JButton prevTreeToolButton = new ToolbarButton(prevTreeToolbarAction, true);
        prevTreeToolButton.setFocusable(false);
        prevTreeToolButton.putClientProperty("JButton.buttonType", "segmentedTextured");
        prevTreeToolButton.putClientProperty("JButton.segmentPosition", "first");
        prevTreeToolButton.putClientProperty( "Quaqua.Button.style", "toggleWest");

        final ToolbarAction nextTreeToolbarAction =
                new ToolbarAction(null, "Next Tree...", nextIcon) {
                    public void actionPerformed(ActionEvent e){
                        if (treeViewer.isRootingOn() && treeViewer.getRootingType() == TreePane.RootingType.USER_ROOTING) {
                            JOptionPane.showMessageDialog(FigTreeFrame.this, "Cannot switch trees when user rooting option is on.\n" +
                                            "Turn this option off to switch trees",
                                    "Unable to switch trees",
                                    JOptionPane.ERROR_MESSAGE);

                        } else {
                            treeViewer.showNextTree();
                        }
                    }
                };
        JButton nextTreeToolButton = new ToolbarButton(nextTreeToolbarAction, true);
        nextTreeToolButton.setFocusable(false);
        nextTreeToolButton.putClientProperty("JButton.buttonType", "segmentedTextured");
        nextTreeToolButton.putClientProperty("JButton.segmentPosition", "last");
        nextTreeToolButton.putClientProperty( "Quaqua.Button.style", "toggleEast");

        nextTreeToolbarAction.setEnabled(treeViewer.getCurrentTreeIndex() < treeViewer.getTreeCount() - 1);
        prevTreeToolbarAction.setEnabled(treeViewer.getCurrentTreeIndex() > 0);

        Box box2 = Box.createHorizontalBox();
        box2.add(Box.createVerticalStrut(annotationToolIcon.getIconHeight()));
        box2.add(prevTreeToolButton);
        box2.add(nextTreeToolButton);
        toolBar.addComponent(new GenericToolbarItem("Prev/Next", "Navigate through the trees", box2));

        TreeViewerListener l = new TreeViewerListener() {
            public void treeChanged() {
                boolean nextTreeEnabled = treeViewer.getCurrentTreeIndex() < treeViewer.getTreeCount() - 1;
                nextTreeAction.setEnabled(nextTreeEnabled);
                nextTreeToolbarAction.setEnabled(nextTreeEnabled);

                boolean prevTreeEnabled = treeViewer.getCurrentTreeIndex() > 0;
                previousTreeAction.setEnabled(prevTreeEnabled);
                prevTreeToolbarAction.setEnabled(prevTreeEnabled);
            }

            public void treeSettingsChanged() {
                // nothing to do
            }
        };
        treeViewer.addTreeViewerListener(l);
        l.treeChanged();

        toolBar.addFlexibleSpace();

        filterPopup = new JPopupMenu();

        final ButtonGroup bg = new ButtonGroup();
        boolean first = true;
        for (TreeViewer.TextSearchType searchType : TreeViewer.TextSearchType.values()) {
            final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(searchType.toString());
            if (first) {
                menuItem.setSelected(true);
                first = false;
            }
            filterPopup.add(menuItem);
            bg.add(menuItem);
        }
        filterPanel = new SearchPanel("Filter", filterPopup, true);
//        filterPanel.getSearchText().requestFocus();
        filterPanel.addSearchPanelListener(new SearchPanelListener() {

            /**
             * Called when the user requests a search by pressing return having
             * typed a search string into the text field. If the continuousUpdate
             * flag is true then this method is called when the user types into
             * the text field.
             *
             * @param searchString the user's search string
             */
            public void searchStarted(String searchString) {
                Enumeration e = bg.getElements();
                String value = null;
                while (e.hasMoreElements()) {
                    AbstractButton button = (AbstractButton)e.nextElement();
                    if (button.isSelected()) {
                        value = button.getText();
                    }
                }

                for (TreeViewer.TextSearchType searchType : TreeViewer.TextSearchType.values()) {
                    if (searchType.toString().equals(value)) {
                        treeViewer.selectTaxa(searchType, searchString, false);
                    }
                }
            }

            /**
             * Called when the user presses the cancel search button or presses
             * escape while the search is in focus.
             */
            public void searchStopped() {
                treeViewer.clearSelectedTaxa();
            }
        });

        JPanel panel3 = new JPanel(new FlowLayout());

        panel3.add(filterPanel);

        toolBar.addComponent(panel3);

        statusBar = new StatusBar("FigTree");
        statusBar.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createCompoundBorder(
                        BorderFactory.createMatteBorder(1, 0, 0, 0, Color.darkGray),
                        BorderFactory.createMatteBorder(1, 0, 0, 0, Color.lightGray)),
                BorderFactory.createEmptyBorder(2, 12, 2, 12)));
        statusBar.setOpaque(false);
        statusBar.setStatusProvider(treeViewer.getStatusProvider());

        JPanel topPanel = new JPanel(new BorderLayout(0,0));
        topPanel.add(toolBar, BorderLayout.NORTH);

        getContentPane().setLayout(new java.awt.BorderLayout(0, 0));
        getContentPane().add(topPanel, BorderLayout.NORTH);

        getContentPane().add(figTreePanel, BorderLayout.CENTER);

        getContentPane().add(statusBar, BorderLayout.SOUTH);

        TreeSelectionListener l2 = new TreeSelectionListener() {
            public void selectionChanged() {
                boolean hasSelection = treeViewer.hasSelection();
                cartoonToolbarAction.setEnabled(hasSelection);
                cartoonAction.setEnabled(hasSelection);
                collapseToolbarAction.setEnabled(hasSelection);
                collapseAction.setEnabled(hasSelection);
//                clearCollapsedAction.setEnabled(hasSelection);
                hilightToolbarAction.setEnabled(hasSelection);
                hilightAction.setEnabled(hasSelection);
                clearHilightingAction.setEnabled(hasSelection);
//                midpointRootAction.setEnabled(hasSelection);
                rerootToolbarAction.setEnabled(hasSelection);
                rerootAction.setEnabled(hasSelection);
                clearRootingAction.setEnabled(hasSelection);
                rotateToolbarAction.setEnabled(hasSelection);
                rotateAction.setEnabled(hasSelection);
//                increasingNodeOrderAction.setEnabled(hasSelection);
//                decreasingNodeOrderAction.setEnabled(hasSelection);
//                clearRotationsAction.setEnabled(hasSelection);
                annotateToolbarAction.setEnabled(hasSelection);
//                annotateNodesFromTipsAction.setEnabled(hasSelection);
//                annotateTipsFromNodesAction.setEnabled(hasSelection);
                annotateAction.setEnabled(hasSelection);
//                clearAnnotationsAction.setEnabled(hasSelection);
                colourToolbarAction.setEnabled(hasSelection);
                colourAction.setEnabled(hasSelection);
                clearColouringAction.setEnabled(hasSelection);
            }
        };
        treeViewer.addTreeSelectionListener(l2);
        l2.selectionChanged();

        getCutAction().setEnabled(false);
        getCopyAction().setEnabled(true);
        getDeleteAction().setEnabled(false);
        getSelectAllAction().setEnabled(true);
        getFindAction().setEnabled(true);

        getZoomWindowAction().setEnabled(false);
    }

    private void defineAnnotations() {

        Collection<AnnotationDefinition> definitions = treeViewer.getAnnotationDefinitions().values();

        if (annotationDefinitionsDialog == null) {
            annotationDefinitionsDialog = new AnnotationDefinitionsDialog(this);
        }
        annotationDefinitionsDialog.showDialog(definitions);

        treeViewer.setAnnotationDefinitions(annotationDefinitionsDialog.getAnnotations());

        treeViewer.fireAnnotationsChanged();
    }

    private void annotateNodesFromTips() {
        List<String> annotationNames = new ArrayList<String>();
        annotationNames.add("Colour");
        annotationNames.addAll(treeViewer.getAnnotationDefinitions().keySet());
        annotationNames.add("Name");

        if (selectAnnotationDialog == null) {
            selectAnnotationDialog = new SelectAnnotationDialog(this);
        }

        int result = selectAnnotationDialog.showDialog(annotationNames);
        if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
            return;
        }

        String annotationName = selectAnnotationDialog.getAnnotationName();
        if (annotationName.equals("Colour")) {
            annotationName = "!color";
        }

        treeViewer.annotateNodesFromTips(annotationName);
        setDirty();
    }

    private void annotateTipsFromNodes() {
        List<String> annotationNames = new ArrayList<String>();
        annotationNames.add("Colour");
        annotationNames.addAll(treeViewer.getAnnotationDefinitions().keySet());

        if (selectAnnotationDialog == null) {
            selectAnnotationDialog = new SelectAnnotationDialog(this);
        }

        if (selectAnnotationDialog.showDialog(annotationNames) != JOptionPane.CANCEL_OPTION) {
            String annotationName = selectAnnotationDialog.getAnnotationName();
            if (annotationName.equals("Colour")) {
                annotationName = "!color";
            }

            treeViewer.annotateTipsFromNodes(annotationName);
            setDirty();
        }
    }

    private void cartoonSelected() {
        treeViewer.cartoonSelectedNodes();
    }

    private void collapseSelected() {
        treeViewer.collapseSelectedNodes();
    }

    private void hilightSelected() {
        Color color = JColorChooser.showDialog(this, "Select Colour", lastColor);
        if (color != null) {
            treeViewer.hilightSelectedNodes(color);
            setDirty();
            lastColor = color;
        }
    }

    private void rerootTree() {
        Set<Node> nodes = treeViewer.getSelectedNodes();

        if (nodes.size() != 1 ) {
            JOptionPane.showMessageDialog(this,
                    "Exactly one branch must be selected to re-root the tree." ,
                    "Annotating Tree",
                    JOptionPane.WARNING_MESSAGE);
            return;
        }
        treeViewer.rerootOnSelectedBranch();
    }

    private void rotateTree() {
        treeViewer.rotateSelectedNode();
    }

    private void annotateSelected() {
        treeViewer.setToolMode(TreePaneSelector.ToolMode.SELECT);

        List<AnnotationDefinition> definitions = new ArrayList<AnnotationDefinition>();
        definitions.add(new AnnotationDefinition("Name", "!name", AnnotationDefinition.Type.STRING));
        definitions.addAll(treeViewer.getAnnotationDefinitions().values());

        if (annotationDialog == null) {
            annotationDialog = new AnnotationDialog(this);
        }

        Set<Node> nodes = treeViewer.getSelectedNodes();
        Set<Node> tips = treeViewer.getSelectedTips();

        Attributable item = null;
        if (nodes.size() + tips.size() == 1 ) {
            if (nodes.size() == 1) {
                item = nodes.iterator().next();
            }else if (tips.size() == 1) {
                item = tips.iterator().next();
            }
        } else {
            int result = JOptionPane.showConfirmDialog(this,
                    "More than one node selected for annotation. This operation\n" +
                            "may overwrite existing annotations. Do you wish to continue?" ,
                    "Annotating Tree",
                    JOptionPane.WARNING_MESSAGE);
            if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
                return;
            }
        }
        if (annotationDialog.showDialog(definitions, item) != JOptionPane.CANCEL_OPTION) {
            AnnotationDefinition newDefinition = annotationDialog.getDefinition();

            List<AnnotationDefinition> defs = new ArrayList<AnnotationDefinition>(treeViewer.getAnnotationDefinitions().values());
            if (!defs.contains(newDefinition)) {
                defs.add(newDefinition);
                treeViewer.setAnnotationDefinitions(defs);
            }

            String code = newDefinition.getCode();
            Object value = annotationDialog.getValue();

            treeViewer.annotateSelected(code, value);
            setDirty();
        }
    }

    private void copySelectedAnnotations() {
        // todo This function is disabled as it is not completely implemented
//        treeViewer.setToolMode(TreePaneSelector.ToolMode.SELECT);
//
//        List<AnnotationDefinition> definitions = new ArrayList<AnnotationDefinition>();
//        definitions.add(new AnnotationDefinition("Name", "!name", AnnotationDefinition.Type.STRING));
//        definitions.addAll(treeViewer.getAnnotationDefinitions().values());
//
//        if (copyAnnotationDialog == null) {
//            copyAnnotationDialog = new AnnotationDialog(this, true);
//        }
//
//        Set<Node> nodes = treeViewer.getSelectedNodes();
//        Set<Node> tips = treeViewer.getSelectedTips();
//
//        Attributable item = null;
//        if (nodes.size() + tips.size() == 1 ) {
//            if (nodes.size() == 1) {
//                item = nodes.iterator().next();
//            }else if (tips.size() == 1) {
//                item = tips.iterator().next();
//            }
//        } else {
//            int result = JOptionPane.showConfirmDialog(this,
//                    "More than one node selected for annotation. This operation\n" +
//                            "may overwrite existing annotations. Do you wish to continue?" ,
//                    "Annotating Tree",
//                    JOptionPane.WARNING_MESSAGE);
//            if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
//                return;
//            }
//        }
//        if (annotationDialog.showDialog(definitions, item) != JOptionPane.CANCEL_OPTION) {
//            String code = annotationDialog.getDefinition().getCode();
//            String code2 = annotationDialog.getDestinationDefinition().getCode();
//
//            treeViewer.copySelected(code, value);
//            setDirty();
//        }
    }

    private static Color lastColor = Color.GRAY;

    private void colourSelected() {
        treeViewer.setToolMode(TreePaneSelector.ToolMode.SELECT);

        Color color = JColorChooser.showDialog(this, "Select Colour", lastColor);
        if (color != null) {
            treeViewer.annotateSelected("!color", color);
            setDirty();
            lastColor = color;
        }
    }

    public boolean readFromFile(File file) throws IOException {
        Reader reader = null;
        try {
            reader = new FileReader(file);

            boolean isNexus = isNexus(reader);

            ProgressMonitorInputStream in = new ProgressMonitorInputStream(
                    this,
                    "Reading " + file.getName(),
                    new FileInputStream(file));
            in.getProgressMonitor().setMillisToDecideToPopup(1000);
            in.getProgressMonitor().setMillisToPopup(1000);

            reader = new InputStreamReader(in);

            boolean success = readData(reader, isNexus);

            reader.close();

            return success;

        } catch (IOException ioe) {
            if (reader != null) {
                reader.close();
            }
            throw ioe;
        }
    }

    public boolean readFromURL(URL url) throws IOException {
        InputStream in = url.openStream();
        Reader reader = new InputStreamReader(url.openStream());

        boolean isNexus = isNexus(reader);

        reader.close();

        reader = new InputStreamReader(url.openStream());

        return readData(reader, isNexus);
    }

    private boolean isNexus(Reader reader) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line = bufferedReader.readLine();
        while (line != null && line.length() == 0) {
            line = bufferedReader.readLine();
        }

        return (line != null && line.toUpperCase().contains("#NEXUS"));
    }

    public boolean readFromString(String string) throws IOException {
        boolean isNexus = string.substring(0, 80).toUpperCase().contains("#NEXUS");
        return readData(new StringReader(string), isNexus);
    }

    protected boolean readData(Reader reader, boolean isNexus) throws IOException {

        List<Tree> trees = new ArrayList<Tree>();

        boolean hasSettings = false;

        try {
            Map<String, Object> settings = new HashMap<String, Object>();
            // First of all, fully populate the settings map so that
            // all the settings have defaults
            controlPalette.getSettings(settings);

            if (isNexus) {
                FigTreeNexusImporter importer = new FigTreeNexusImporter(reader);
                while (importer.hasTree()) {
                    Tree tree = importer.importNextTree();
                    trees.add(tree);
                }
                // Try to find a figtree block and if found, parse the settings
                while (true) {
                    try {
                        importer.findNextBlock();
                        if (importer.getNextBlockName().equalsIgnoreCase("FIGTREE")) {
                            importer.parseFigTreeBlock(settings);
                            hasSettings = true;
                        }
                    } catch (EOFException ex) {
                        break;
                    }
                }
            } else {
                NewickImporter importer = new NewickImporter(reader, true);
                while (importer.hasTree()) {
                    Tree tree = importer.importNextTree();
                    trees.add(tree);
                }
            }

            if (trees.size() == 0) {
                throw new ImportException("This file contained no trees.");
            }

            checkLabelAttribute(trees);

            treeViewer.setTrees(trees);
            controlPalette.setSettings(settings);
        } catch (ImportException ie) {
            JOptionPane.showMessageDialog(this, "Error reading tree file: \n" + ie.getMessage(),
                    "Import Error",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }

        if (!hasSettings) {
            // If there weren't settings in the file then this wasn't a TreeDraw
            // created document so we don't want to be able to overwrite it without
            // explicit action of the user...
            setDirty();
            clearFile();
        }

        return true;
    }

    /**
     * This version loads the trees in a thread but this needs more thought in order
     * to tie in to the JAM framework correctly
     */
//    protected boolean readData(final Reader reader, final boolean isNexus) {
//
//        final JFrame frame = this;
//        Thread readThread = new Thread () {
//            public void run() {
//                try {
//
//                    final List<Tree> trees = new ArrayList<Tree>();
//
//                    boolean hasSettings = false;
//
//                    final Map<String, Object> settings = new HashMap<String, Object>();
//                    // First of all, fully populate the settings map so that
//                    // all the settings have defaults
//                    controlPalette.getSettings(settings);
//
//                    if (isNexus) {
//                        FigTreeNexusImporter importer = new FigTreeNexusImporter(reader);
//                        while (importer.hasTree()) {
//                            Tree tree = importer.importNextTree();
//                            trees.add(tree);
//                        }
//                        // Try to find a figtree block and if found, parse the settings
//                        while (true) {
//                            try {
//                                importer.findNextBlock();
//                                if (importer.getNextBlockName().equalsIgnoreCase("FIGTREE")) {
//                                    importer.parseFigTreeBlock(settings);
//                                    hasSettings = true;
//                                }
//                            } catch (EOFException ex) {
//                                break;
//                            }
//                        }
//                    } else {
//                        NewickImporter importer = new NewickImporter(reader, true);
//                        while (importer.hasTree()) {
//                            Tree tree = importer.importNextTree();
//                            trees.add(tree);
//                        }
//                    }
//
//                    if (trees.size() == 0) {
//                        throw new ImportException("This file contained no trees.");
//                    }
//
//                    final boolean hasSettings2 = hasSettings;
//
//                    EventQueue.invokeLater (
//                            new Runnable () {
//                                public void run () {
//                                    treeViewer.setTrees(trees);
//                                    controlPalette.setSettings(settings);
//
//                                    if (!hasSettings2) {
//                                        // If there weren't settings in the file then this wasn't a TreeDraw
//                                        // created document so we don't want to be able to overwrite it without
//                                        // explicit action of the user...
//                                        setDirty();
//                                        clearFile();
//                                    }
//                                }
//                            });
//
//                } catch (final ImportException ie) {
//                    EventQueue.invokeLater (
//                            new Runnable () {
//                                public void run () {
//                                    JOptionPane.showMessageDialog(frame, "Error reading tree file: " + ie.getMessage(),
//                                            "Import Error",
//                                            JOptionPane.ERROR_MESSAGE);
//                                }
//                            });
//                } catch (final InterruptedIOException iioex) {
//                    // The cancel dialog button was pressed - do nothing
//                } catch (final IOException ioex) {
//                    EventQueue.invokeLater (
//                            new Runnable () {
//                                public void run () {
//                                    JOptionPane.showMessageDialog(frame, "File I/O Error: " + ioex.getMessage(),
//                                            "File I/O Error",
//                                            JOptionPane.ERROR_MESSAGE);
//                                }
//                            });
//                }
//
//            }
//        };
//        readThread.start();
//
//        return true;
//    }


    private void checkLabelAttribute(List<Tree> trees) {

        boolean hasLabel = false;

        for (Tree tree : trees) {
            for (Node node : tree.getNodes()) {
                if (node.getAttribute("label") != null) {
                    hasLabel = true;
                }
            }
        }

        if (hasLabel) {
            String labelName = null;

            do {
                labelName = JOptionPane.showInputDialog(
                        "The node/branches of the tree are labelled\n" +
                                "(i.e., with bootstrap values or posterior probabilities).\n\n" +
                                "Please select a name for these values.", "label");
                if (labelName == null) {
                    labelName = "label";
                }
                labelName = labelName.trim();

                if (labelName.length() == 0) {
                    Toolkit.getDefaultToolkit().beep();
                }
            } while (labelName == null || labelName.length() == 0);

            if (!labelName.equals("label")) {
                for (Tree tree : trees) {
                    for (Node node : tree.getNodes()) {
                        Object value = node.getAttribute("label");
                        if (value != null) {
                            node.removeAttribute("label");
                            node.setAttribute(labelName, value);
                        }
                    }
                }
            }
        }
    }


    protected boolean writeToFile(File file) throws IOException {

        Map<String, Object> settings = new TreeMap<String, Object>();
        controlPalette.getSettings(settings);

        FileWriter writer = new FileWriter(file);
        FigTreeNexusExporter exporter = new FigTreeNexusExporter(writer, true);
        exporter.exportTrees(treeViewer.getTrees(), true);
        exporter.writeFigTreeBlock(settings);

        writer.close();

        return true;
    }

    public final void doImport() {

        FileDialog dialog = new FileDialog(this,
                "Import Annotations File...",
                FileDialog.LOAD);

        dialog.setVisible(true);
        if (dialog.getFile() != null) {
            File file = new File(dialog.getDirectory(), dialog.getFile());

            try {
                Map<AnnotationDefinition, Map<Taxon, Object>> annotations = importAnnotationsFromFile(file);

                treeViewer.setTaxonAnnotations(annotations);

                // Hack to show tips states...
//                String[] annotationNames = new String[annotations.keySet().size()];
//                DiscreteColourDecorator[] decorators = new DiscreteColourDecorator[annotations.keySet().size()];
//
//                int i = 0;
//                for (AnnotationDefinition definition: annotations.keySet()) {
//                    Map<Taxon, Object> annotation = annotations.get(definition);
//                    annotationNames[i] = definition.getName();
//                    decorators[i] = new HSBDiscreteColourDecorator(annotationNames[i], annotation.keySet());
//                    i++;
//                }
//                treeViewer.setTipLabelPainter(new StatesPainter(annotationNames, decorators));


            } catch (FileNotFoundException fnfe) {
                JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
                        "Unable to open file",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Unable to read file: " + ioe.getMessage(),
                        "Unable to read file",
                        JOptionPane.ERROR_MESSAGE);
            }
        }

    }

    protected Map<AnnotationDefinition, Map<Taxon, Object>> importAnnotationsFromFile(File file) throws IOException {

        BufferedReader reader = new BufferedReader(new FileReader(file));

        List<String> taxa = new ArrayList<String>();

        String line = reader.readLine();
        String[] labels = line.split("\t");
        Map<String, List<String>> columns = new HashMap<String, List<String>>();
        for (int i = 1; i < labels.length; i++) {
            columns.put(labels[i], new ArrayList<String>());
        }

        line = reader.readLine();
        while (line != null) {
            String[] values = line.split("\t");

            if (values.length > 0) {
                taxa.add(values[0]);
                for (int i = 1; i < values.length; i++) {
                    if (i < labels.length) {
                        List<String> column = columns.get(labels[i]);
                        column.add(values[i]);
                    }
                }
            }
            line = reader.readLine();
        }

        Map<AnnotationDefinition, Map<Taxon, Object>> annotations = new TreeMap<AnnotationDefinition, Map<Taxon, Object>>();

        NumberFormat nf = NumberFormat.getInstance();

        for (int i = 1; i < labels.length; i++) {
            List<String> column = columns.get(labels[i]);

            boolean isInteger = true;
            boolean isNumber = true;
            boolean isBoolean = true;

            for (String valueString : column) {
                if (!valueString.equalsIgnoreCase("TRUE") && !valueString.equalsIgnoreCase("FALSE")) {
                    isBoolean = false;
                    try {
                        double number = Double.parseDouble(valueString);
                        if (Math.round(number) != number) {
                            isInteger = false;
                        }
                    } catch (NumberFormatException pe) {
                        isInteger = false;
                        isNumber = false;
                    }
                }
            }

            Map<Taxon, Object> values = new HashMap<Taxon, Object>();
            AnnotationDefinition ad;
            int j = 0;
            for (String valueString : column) {
                Taxon taxon = Taxon.getTaxon(taxa.get(j));
                if (isBoolean) {
                    values.put(taxon, new Boolean(valueString));
                } else if (isInteger) {
                    values.put(taxon, new Integer(valueString));
                } else if (isNumber) {
                    values.put(taxon, new Double(valueString));
                } else {
                    values.put(taxon, valueString);
                }
                j++;
            }

            Set<Object> valueSet = new HashSet<Object>(values.values());

            if (isBoolean) {
                ad = new AnnotationDefinition(labels[i], AnnotationDefinition.Type.BOOLEAN );
            } else if (isInteger) {
                ad = new AnnotationDefinition(labels[i], AnnotationDefinition.Type.INTEGER );
            } else if (isNumber) {
                ad = new AnnotationDefinition(labels[i], AnnotationDefinition.Type.REAL );
            } else {
                String[] valueArray = new String[valueSet.size()];
                valueSet.toArray(valueArray);
                ad = new AnnotationDefinition(labels[i], AnnotationDefinition.Type.STRING);
//ad.setOptions(valueArray);
            }

            annotations.put(ad, values);
        }

        return annotations;
    }


    private void doImportCharacters() {

        FileDialog dialog = new FileDialog(this,
                "Import Characters File...",
                FileDialog.LOAD);

        dialog.setVisible(true);
        if (dialog.getFile() != null) {
            File file = new File(dialog.getDirectory(), dialog.getFile());

            try {
                importCharactersFromFile(file);
            } catch (FileNotFoundException fnfe) {
                JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
                        "Unable to open file",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Unable to read file: " + ioe.getMessage(),
                        "Unable to read file",
                        JOptionPane.ERROR_MESSAGE);
            }
        }

    }

    protected boolean importCharactersFromFile(File file) throws FileNotFoundException, IOException {

        final String fileName = file.getName();
        SequenceImporter importer = new NexusImporter(new FileReader(file));
        try {
            Alignment alignment = new BasicAlignment(importer.importSequences());

            treeViewer.setCharacters(alignment);
        } catch (ImportException ie) {
            JOptionPane.showMessageDialog(this, "Error reading characters file: " + ie.getMessage(),
                    "Import Error",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }

        return true;
    }

    private void doImportColourScheme() {

        FileDialog dialog = new FileDialog(this,
                "Import Colour Scheme...",
                FileDialog.LOAD);

        dialog.setVisible(true);
        if (dialog.getFile() != null) {
            File file = new File(dialog.getDirectory(), dialog.getFile());

            try {
                importColourSchemeFromFile(file);
            } catch (FileNotFoundException fnfe) {
                JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
                        "Unable to open file",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Unable to read file: " + ioe.getMessage(),
                        "Unable to read file",
                        JOptionPane.ERROR_MESSAGE);
            }
        }

    }

    protected boolean importColourSchemeFromFile(File file) throws FileNotFoundException, IOException {

        final String fileName = file.getName();
        SequenceImporter importer = new NexusImporter(new FileReader(file));
        try {
            Alignment alignment = new BasicAlignment(importer.importSequences());

            treeViewer.setCharacters(alignment);
        } catch (ImportException ie) {
            JOptionPane.showMessageDialog(this, "Error reading characters file: " + ie.getMessage(),
                    "Import Error",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }

        return true;
    }

    public final void doExport() {

        if (exportTreeDialog == null) {
            exportTreeDialog = new ExportTreeDialog(this);
        }

        if (exportTreeDialog.showDialog() == JOptionPane.OK_OPTION) {

            FileDialog dialog = new FileDialog(this,
                    "Export Tree File...",
                    FileDialog.SAVE);

            dialog.setVisible(true);
            if (dialog.getFile() != null) {
                File file = new File(dialog.getDirectory(), dialog.getFile());

                FileWriter writer = null;
                try {
                    writer = new FileWriter(file);
                    writeTreeFile(writer,
                            exportTreeDialog.getFormat(),
                            exportTreeDialog.allTrees(),
                            exportTreeDialog.asDisplayed(),
                            exportTreeDialog.includeFigTreeBlock(),
                            exportTreeDialog.includeAnnotations(),
                            false);
                    writer.close();
                } catch (IOException ioe) {
                    JOptionPane.showMessageDialog(this, "Error writing tree file: " + ioe.getMessage(),
                            "Export Error",
                            JOptionPane.ERROR_MESSAGE);
                }

            }
        }
    }

    public final void doExportGraphic(GraphicFormat format) {
        FileDialog dialog = new FileDialog(this,
                "Export " + format.getName() + " File...",
                FileDialog.SAVE);

        String name = this.getFile().getName() + "." + format.getName().toLowerCase();
        dialog.setFile(name);

        dialog.setVisible(true);
        if (dialog.getFile() != null) {
            File file = new File(dialog.getDirectory(), dialog.getFile());

            try {
                OutputStream stream = new FileOutputStream(file);

                exportGraphics(format, treeViewer.getContentPane(), stream);

                stream.flush();
                stream.close();
            } catch(DocumentException de) {
                JOptionPane.showMessageDialog(this, "Error writing PDF file: " + de,
                        "Export PDF Error",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Error writing tree file: " + ioe.getMessage(),
                        "Export Error",
                        JOptionPane.ERROR_MESSAGE);
            }

        }

    }

    public final static void exportGraphics(GraphicFormat format, JComponent comp, OutputStream stream) throws IOException, DocumentException {
        switch (format) {

            case PNG:
            case GIF:
            case BMP:
            case JPEG:
                exportGraphicsFile(format, comp, stream);
                break;
            case EPS:
                throw new UnsupportedOperationException("EPS not handled");
            case SVG:
                exportSVGFile(comp, stream);
                break;
            case PDF:
                exportPDFFile(comp, stream);
                break;
            default:
                throw new UnsupportedOperationException("Format not handled: " + format);

        }

    }
    private final static void exportGraphicsFile(GraphicFormat format, JComponent component, OutputStream stream) throws IOException {
        int imageType = BufferedImage.TYPE_INT_RGB;

        if (format == GraphicFormat.PNG) {
            // PNG allows an alpha channel
            imageType = BufferedImage.TYPE_INT_ARGB;
        }
        BufferedImage bi = new BufferedImage(component.getSize().width, component.getSize().height, imageType);
        Graphics g = bi.createGraphics();

        if (format != GraphicFormat.PNG) {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, bi.getWidth(), bi.getHeight());
        }
        component.paint(g);
        g.dispose();
        ImageIO.write(bi, format.getName(), stream);
    }

    private final static void exportSVGFile(JComponent component, OutputStream stream) throws IOException {
        // Get a DOMImplementation and create an XML document
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
        org.w3c.dom.Document document = domImpl.createDocument(null, "svg", null);

        // Create an instance of the SVG Generator
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);

        component.paint(svgGenerator);
        Element svgRoot = svgGenerator.getRoot();
        Rectangle2D bounds = component.getBounds();
        String viewBox = "0 0 " + bounds.getWidth() + " " + bounds.getHeight();
        svgRoot.setAttributeNS(null, svgGenerator.SVG_VIEW_BOX_ATTRIBUTE, viewBox);
        svgRoot.setAttributeNS(null, svgGenerator.SVG_WIDTH_ATTRIBUTE, Double.toString(bounds.getWidth()));
        svgRoot.setAttributeNS(null, svgGenerator.SVG_HEIGHT_ATTRIBUTE, Double.toString(bounds.getHeight()));

        // Write svg file
        Writer out = new OutputStreamWriter(stream, "UTF-8");
        svgGenerator.stream(svgRoot, out, true /* use css */, false /* escaped */);
    }

    public final static void exportPDFFile(JComponent component, OutputStream stream) throws DocumentException {
        Rectangle2D bounds = component.getBounds();
        Document document = new Document(new com.itextpdf
                .text.Rectangle((float)bounds.getWidth(), (float)bounds.getHeight()));
        // step 2
        PdfWriter writer;
        writer = PdfWriter.getInstance(document, stream);
        // step 3
        document.open();
        // step 4
        PdfContentByte cb = writer.getDirectContent();
        PdfTemplate tp = cb.createTemplate((float)bounds.getWidth(), (float)bounds.getHeight());
        Graphics2D g2d = tp.createGraphics((float)bounds.getWidth(), (float)bounds.getHeight(), new DefaultFontMapper());
        component.print(g2d);
        g2d.dispose();
        cb.addTemplate(tp, 0, 0);

        document.close();
    }

    public void doCopy() {
        StringWriter writer = new StringWriter();
        try {
            if (treeViewer.getSelectionMode() == TreePaneSelector.SelectionMode.TAXA) {
                writeTaxa(writer);
            } else {
                writeTreeFile(writer, ExportTreeDialog.Format.NEXUS, true, false, false, true, true);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

        StringSelection selection = new StringSelection(writer.toString());

        clipboard.setContents(selection, selection);
    }

    public void doPaste() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

        Transferable transfer = clipboard.getContents(null);
        if (transfer.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            try {
                String clip = (String)transfer.getTransferData(DataFlavor.stringFlavor);
                boolean isNexus = clip.substring(0, Math.min(80, clip.length())).toUpperCase().contains("#NEXUS");
                Reader reader = new StringReader(clip);

                List<Tree> trees = new ArrayList<Tree>();

                TreeImporter importer;
                if (isNexus) {
                    importer = new FigTreeNexusImporter(reader);
                } else {
                    importer = new NewickImporter(reader, true);
                }
                while (importer.hasTree()) {
                    Tree tree = importer.importNextTree();
                    trees.add(tree);
                }

                if (trees.size() == 0) {
                    throw new ImportException("This clipboard contained no trees.");
                }
                treeViewer.addTrees(trees);

// Show the first of the new trees
                treeViewer.showNextTree();
                setDirty();

            } catch (ImportException ie) {
                JOptionPane.showMessageDialog(this, "Error reading trees on clipboard: " + ie.getMessage(),
                        "Import Error",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Error reading trees on clipboard: " + ioe.getMessage(),
                        "Import Error",
                        JOptionPane.ERROR_MESSAGE);
            } catch (UnsupportedFlavorException e) {
                JOptionPane.showMessageDialog(this, "Clipboard data is not compatible",
                        "Import Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    public void doSelectAll() {
        treeViewer.selectAll();
    }

    protected void writeTaxa(Writer writer) throws IOException {
        PrintWriter printWriter = new PrintWriter(writer);
        for (Taxon taxon : treeViewer.getSelectedTaxa()) {
            printWriter.println(taxon.getName());
        }
        writer.close();
    }

    protected void writeTreeFile(Writer writer, ExportTreeDialog.Format format,
                                 boolean writeAllTrees,
                                 boolean writeAsDisplayed,
                                 boolean writeFigTreeBlock,
                                 boolean writeAnnotations,
                                 boolean writeSelectedSubtree) throws IOException {

        Map<String, Object> settings = null;
        if (writeFigTreeBlock) {
            settings = new TreeMap<String, Object>();
            controlPalette.getSettings(settings);
        }

        List<Tree> trees = new ArrayList<Tree>();

        if (writeSelectedSubtree) {
            RootedTree tree = treeViewer.getSelectedSubtree();
            if (tree != null) {
                trees.add(tree);
            }
        } else {
            trees.addAll(treeViewer.getTreesAsViewed());
        }

        if (trees.size() > 0) {
            switch (format) {
                case NEWICK:
                    NewickExporter newickExporter = new NewickExporter(writer);
                    newickExporter.exportTrees(trees);
                    newickExporter.close();
                    break;
                case NEXUS:
                    FigTreeNexusExporter nexusExporter = new FigTreeNexusExporter(writer, writeAnnotations);
                    nexusExporter.exportTrees(trees);
                    if (settings != null) {
                        nexusExporter.writeFigTreeBlock(settings);
                    }
                    nexusExporter.close();
                    break;
                case JSON:
                    JSONTreeExporter jsonExporter = new JSONTreeExporter(writer, writeAnnotations);
                    jsonExporter.exportTrees(trees);
                    jsonExporter.close();
                    break;
            }
        }
    }

    public final void doFind() {
        if (findPanel == null) {
            findPanel = new FindPanel(findAllAction, null);
            findPanel.setOpaque(false);

            treeViewer.addAnnotationsListener(new AnnotationsListener() {
                public void annotationsChanged() {
                    List<AnnotationDefinition> definitions = new ArrayList<AnnotationDefinition>(
                            treeViewer.getAnnotationDefinitions().values());
                    findPanel.setup(definitions);
                }
            });

            List<AnnotationDefinition> definitions = new ArrayList<AnnotationDefinition>(
                    treeViewer.getAnnotationDefinitions().values());
            findPanel.setup(definitions);
        }

        if (figTreePanel.getUtilityPanel() != findPanel) {
            figTreePanel.showUtilityPanel(findPanel);
        } else {
            figTreePanel.hideUtilityPanel();
        }
    }

    public final void doFindAll() {

        FindPanel.Target target = findPanel.getSearchTarget();
        String targetString = findPanel.getSearchTargetString();

        if (findPanel.isNumericSearchType()) {
            TreeViewer.NumberSearchType searchType = findPanel.getNumberSearchType();
            Number searchValue = findPanel.getSearchValue();
            if (target == FindPanel.Target.TAXON_LABEL) {
                throw new IllegalArgumentException("Can't do numeric search on taxon labels");
            } else if (target == FindPanel.Target.BRANCH_LENGTH) {
                treeViewer.selectNodes("!length", searchType, searchValue);
            } else if (target == FindPanel.Target.NODE_AGE) {
                treeViewer.selectNodes("!height", searchType, searchValue);
            } else if (target == FindPanel.Target.ANY_ANNOTATION) {
                throw new IllegalArgumentException("Can't do numeric search on all annotations");
            } else {
                treeViewer.selectNodes(targetString, searchType, searchValue);
            }

        } else {
            TreeViewer.TextSearchType searchType = findPanel.getTextSearchType();
            String searchText = findPanel.getSearchText();
            boolean caseSensitive = findPanel.isCaseSensitive();

            if (target == FindPanel.Target.TAXON_LABEL) {
                treeViewer.selectTaxa("!name", searchType, searchText, caseSensitive);
            } else if (target == FindPanel.Target.BRANCH_LENGTH) {
                throw new IllegalArgumentException("Can't do text search on branch lengths");
            } else if (target == FindPanel.Target.NODE_AGE) {
                throw new IllegalArgumentException("Can't do text search on node ages");
            } else if (target == FindPanel.Target.ANY_ANNOTATION) {
                treeViewer.selectNodes(null, searchType, searchText, caseSensitive);
            } else {
                treeViewer.selectNodes(targetString, searchType, searchText, caseSensitive);
            }
        }
    }

    public final void doFindNext() {

    }

    public final void doGetInfo() {
//        List<AnnotationDefinition> definitions = treeViewer.getAnnotationDefinitions();
//        JPanel panel = new FindPanel(definitions);
//        panel.setOpaque(false);
//        figTreePanel.showUtilityPanel(panel);
    }

    public JComponent getExportableComponent() {
        return treeViewer.getContentPane();
    }

    @Override
    public Action getImportColourSchemeAction() {
        return importColourSchemeAction;
    }

    @Override
    public Action getExportColourSchemeAction() {
        return exportColourSchemeAction;
    }

    public Action getExportTreesAction() {
        return exportTreesAction;
    }

//    public Action getExportGraphicAction() {
//        return exportGraphicAction;
//    }

    public Action getExportPDFAction() {
        return exportPDFAction;
    }

    public Action getExportPNGGraphicAction() {
        return exportPNGGraphicAction;
    }

    public Action getExportJPEGGraphicAction() {
        return exportJPEGGraphicAction;
    }

    public Action getExportSVGGraphicAction() {
        return exportSVGAction;
    }

    public Action getNextTreeAction() {
        return nextTreeAction;
    }

    public Action getPreviousTreeAction() {
        return previousTreeAction;
    }

    public Action getCartoonAction() {
        return cartoonAction;
    }

    public Action getCollapseAction() {
        return collapseAction;
    }

    public Action getClearCollapsedAction() {
        return clearCollapsedAction;
    }

    public Action getMidpointRootAction() {
        return midpointRootAction;
    }

    public Action getRerootAction() {
        return rerootAction;
    }

    public Action getClearRootingAction() {
        return clearRootingAction;
    }

    public Action getIncreasingNodeOrderAction() {
        return increasingNodeOrderAction;
    }

    public Action getDecreasingNodeOrderAction() {
        return decreasingNodeOrderAction;
    }

    public Action getRotateAction() {
        return rotateAction;
    }

    public Action getClearRotationsAction() {
        return clearRotationsAction;
    }

    public Action getAnnotateAction() {
        return annotateAction;
    }

    public Action getCopyAnnotationsAction() {
        return copyAnnotationsAction;
    }

    public Action getAnnotateNodesFromTipsAction() {
        return annotateNodesFromTipsAction;
    }

    public Action getAnnotateTipsFromNodesAction() {
        return annotateTipsFromNodesAction;
    }

    public AbstractAction getClearAnnotationsAction() {
        return clearAnnotationsAction;
    }

    public AbstractAction getDefineAnnotationsAction() {
        return defineAnnotationsAction;
    }

    public Action getColourAction() {
        return colourAction;
    }

    public Action getClearColouringAction() {
        return clearColouringAction;
    }

    public Action getHilightAction() {
        return hilightAction;
    }

    public Action getClearHilightingAction() {
        return clearHilightingAction;
    }

    public Action getFindAction() {
        return findAction;
    }

    private AbstractAction importAction = new AbstractAction("Import Annotations...") {
        public void actionPerformed(ActionEvent ae) {
            doImport();
        }
    };

    private AbstractAction importCharactersAction = new AbstractAction("Import Characters...") {
        public void actionPerformed(ActionEvent ae) {
            doImportCharacters();
        }
    };

    private AbstractAction importColourSchemeAction = new AbstractAction("Import Colour Scheme...") {
        public void actionPerformed(ActionEvent ae) {
            doImportColourScheme();
        }
    };

    private AbstractAction exportColourSchemeAction = new AbstractAction("Export Colour Scheme...") {
        public void actionPerformed(ActionEvent ae) {
//            doExportColourScheme();
        }
    };

    private AbstractAction exportTreesAction = new AbstractAction("Export Trees...") {
        public void actionPerformed(ActionEvent ae) {
            doExport();
        }
    };

//    private AbstractAction exportGraphicAction = new AbstractAction("Export Graphic...") {
//        public void actionPerformed(ActionEvent ae) {
//            doExportGraphic(GraphicFormat.PNG);
//        }
//    };

    private AbstractAction exportPNGGraphicAction = new AbstractAction("Export PNG...") {
        public void actionPerformed(ActionEvent ae) {
            doExportGraphic(GraphicFormat.PNG);
        }
    };

    private AbstractAction exportJPEGGraphicAction = new AbstractAction("Export JPEG...") {
        public void actionPerformed(ActionEvent ae) {
            doExportGraphic(GraphicFormat.JPEG);
        }
    };

    private AbstractAction exportSVGAction = new AbstractAction("Export SVG...") {
        public void actionPerformed(ActionEvent ae) {
            doExportGraphic(GraphicFormat.SVG);
        }
    };

    private AbstractAction exportPDFAction = new AbstractAction("Export PDF...") {
        public void actionPerformed(ActionEvent ae) {
            doExportGraphic(GraphicFormat.PDF);
        }
    };

    private AbstractAction nextTreeAction =
            new AbstractAction(NEXT_TREE) {
                public void actionPerformed(ActionEvent e){
                    treeViewer.showNextTree();
                }
            };

    private AbstractAction previousTreeAction =
            new AbstractAction(PREVIOUS_TREE) {
                public void actionPerformed(ActionEvent e){
                    treeViewer.showPreviousTree();
                }
            };

    private AbstractAction cartoonAction = new AbstractAction(CARTOON_NODE) {
        public void actionPerformed(ActionEvent e){
            cartoonSelected();
        }
    };
    private AbstractAction collapseAction = new AbstractAction(COLLAPSE_NODE) {
        public void actionPerformed(ActionEvent e){
            collapseSelected();
        }
    };
    private AbstractAction clearCollapsedAction = new AbstractAction(CLEAR_COLLAPSED) {
        public void actionPerformed(ActionEvent e){
            treeViewer.clearCollapsedNodes();
        }
    };

    private AbstractAction rerootAction = new AbstractAction(ROOT_ON_BRANCH) {
        public void actionPerformed(ActionEvent e){
            rerootTree();
        }
    };
    private AbstractAction midpointRootAction = new AbstractAction(MIDPOINT_ROOT) {
        public void actionPerformed(ActionEvent e){
            figTreePanel.toggleMidpointRoot();
        }
    };
    private AbstractAction clearRootingAction = new AbstractAction(CLEAR_ROOTING) {
        public void actionPerformed(ActionEvent e){
            treeViewer.clearRooting();
        }
    };


    private AbstractAction rotateAction = new AbstractAction(ROTATE_NODE) {
        public void actionPerformed(ActionEvent e){
            rotateTree();
        }
    };
    private AbstractAction clearRotationsAction = new AbstractAction(CLEAR_ROTATIONS) {
        public void actionPerformed(ActionEvent e){
            treeViewer.clearRotations();
        }
    };
    private AbstractAction increasingNodeOrderAction = new AbstractAction(INCREASING_NODE_ORDER) {
        public void actionPerformed(ActionEvent e){
            figTreePanel.toggleIncreasingNodeOrder();
        }
    };
    private AbstractAction decreasingNodeOrderAction = new AbstractAction(DECREASING_NODE_ORDER) {
        public void actionPerformed(ActionEvent e){
            figTreePanel.toggleDecreasingNodeOrder();
        }
    };

    private AbstractAction annotateAction = new AbstractAction(ANNOTATE) {
        public void actionPerformed(ActionEvent ae) {
            annotateSelected();
        }
    };

    private AbstractAction copyAnnotationsAction = new AbstractAction(COPY_ANNOTATION_VALUES) {
        public void actionPerformed(ActionEvent ae) {
            copySelectedAnnotations();
        }
    };

    private AbstractAction annotateNodesFromTipsAction = new AbstractAction(ANNOTATE_NODES_FROM_TIPS) {
        public void actionPerformed(ActionEvent ae) {
            annotateNodesFromTips();
        }
    };

    private AbstractAction annotateTipsFromNodesAction = new AbstractAction(ANNOTATE_TIPS_FROM_NODES) {
        public void actionPerformed(ActionEvent ae) {
            annotateTipsFromNodes();
        }
    };

    private AbstractAction clearAnnotationsAction = new AbstractAction(CLEAR_ANNOTATIONS) {
        public void actionPerformed(ActionEvent ae) {
            // treeViewer.clearAnnotation();
        }
    };

    private AbstractAction defineAnnotationsAction = new AbstractAction(DEFINE_ANNOTATIONS) {
        public void actionPerformed(ActionEvent ae) {
            defineAnnotations();
        }
    };

    private AbstractAction colourAction = new AbstractAction(COLOUR) {
        public void actionPerformed(ActionEvent ae) {
            colourSelected();
        }
    };

    private AbstractAction clearColouringAction = new AbstractAction(CLEAR_COLOURING) {
        public void actionPerformed(ActionEvent ae) {
            treeViewer.clearColouring();
        }
    };

    private AbstractAction hilightAction = new AbstractAction(HILIGHT) {
        public void actionPerformed(ActionEvent ae) {
            hilightSelected();
        }
    };

    private AbstractAction clearHilightingAction = new AbstractAction(CLEAR_HILIGHTING) {
        public void actionPerformed(ActionEvent ae) {
            treeViewer.clearHilighting();
        }
    };

    private AbstractAction findAction = new AbstractAction("Find...") {
        public void actionPerformed(ActionEvent ae) {
            doFind();
        }
    };

    private AbstractAction findAllAction = new AbstractAction("Find") {
        public void actionPerformed(ActionEvent ae) {
            doFindAll();
        }
    };

//	private AbstractAction findNextAction = new AbstractAction("Find Next") {
//		public void actionPerformed(ActionEvent ae) {
//			doFindNext();
//		}
//	};

    private AbstractAction getInfoAction = new AbstractAction("Get Info...") {
        public void actionPerformed(ActionEvent ae) {
            doGetInfo();
        }
    };

    private ExportTreeDialog exportTreeDialog = null;
    private FindPanel findPanel = null;
    private AnnotationDefinitionsDialog annotationDefinitionsDialog = null;
    private AnnotationDialog annotationDialog = null;
    private AnnotationDialog copyAnnotationDialog = null;
    private SelectAnnotationDialog selectAnnotationDialog = null;
}
