/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.panel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.util.HashMap;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import net.sf.jaxodraw.graph.JaxoGraph;
import net.sf.jaxodraw.gui.JaxoCanvas;
import net.sf.jaxodraw.gui.JaxoDialogs;
import net.sf.jaxodraw.gui.JaxoDrawingArea;
import net.sf.jaxodraw.gui.JaxoTab;
import net.sf.jaxodraw.gui.menu.popup.JaxoCanvasPopupMenu;
import net.sf.jaxodraw.gui.panel.button.JaxoButtonPanel;
import net.sf.jaxodraw.gui.swing.JaxoClosableTabbedPane;
import net.sf.jaxodraw.gui.swing.TabClosingEvent;
import net.sf.jaxodraw.gui.swing.TabClosingListener;
import net.sf.jaxodraw.io.JaxoIO;
import net.sf.jaxodraw.io.JaxoPreview;
import net.sf.jaxodraw.io.JaxoPrint;
import net.sf.jaxodraw.io.exports.JaxoExportLatexPS;
import net.sf.jaxodraw.plugin.JaxoImportPlugin;
import net.sf.jaxodraw.plugin.JaxoPluginInfo;
import net.sf.jaxodraw.util.JaxoColor;
import net.sf.jaxodraw.util.JaxoConstants;
import net.sf.jaxodraw.util.JaxoInfo;
import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.JaxoLocalized;
import net.sf.jaxodraw.util.JaxoPrefs;
import net.sf.jaxodraw.util.JaxoSoundsHandler;
import net.sf.jaxodraw.util.JaxoUtils;


/**
 * A tabbed panel. This holds properties that are common for the whole application,
 * eg the collection of tabs, the canvas (which is unique for all tabs), etc.
 *
 * @since 2.0
 */
public class JaxoTabbedCanvasPanel extends MouseAdapter
    implements ActionListener, PropertyChangeListener, JaxoLocalized {
    private static final JaxoDictionary LANGUAGE
        = new JaxoDictionary(JaxoTabbedCanvasPanel.class);
    private static final String SNAP = "Jaxo.snap";
    private final JaxoCommunicator thePanel;
    private final Component parentComponent;
    private final JaxoCanvasPopupMenu canvasPopup;
    private final JPanel root;
    private final JaxoIO io;
    private final JaxoClosableTabbedPane tabbedPane;
    private JaxoTab selectedTab;
    private final Icon modifiedIcon = JaxoUtils.newImageIcon("modified.png");
    private final Icon unmodifiedIcon = JaxoUtils.newImageIcon("unmodified.png");
    private final ChangeListener tabListener;
    private JaxoExportPanel exportPanel;
    private JaxoImportPanel importPanel;
    private final Map<String, Integer> nameIndex = new HashMap<String, Integer>(16);
    private final Map<JaxoTab, TabInfo> tabInfos = new HashMap<JaxoTab, TabInfo>(16);

    private final JaxoPreview preview;

    /** The Canvas where all the objects will be drawn */
    private final JaxoDrawingArea theCanvas;

    private final JaxoLatexTextToolTip latexTextToolTip = new JaxoLatexTextToolTip();

    private boolean watchMode;
    private boolean antialiasEnabled;
    private boolean canUndo;
    private boolean canRedo;

    private int currentMode = JaxoConstants.STANDBY; // -> tab ?
    // TODO: remove! currentMode should be enough
    private int vertexType = JaxoConstants.VERTEXT1;


    /** Constructor: adds MouseListener and ChangeListener to the tabbedPane.
     * @param panel Instance of the main panel
     * @param parent the parent component of this panel.
     */
    public JaxoTabbedCanvasPanel(final JaxoCommunicator panel, final Component parent) {
        super();
        this.thePanel = panel;
        this.parentComponent = parent;

        this.io = new JaxoIO(parentComponent);
        this.preview = new JaxoPreview(parentComponent);

        this.root = new JPanel(new BorderLayout(), false);
        root.setBorder(JaxoButtonPanel.RAISED_ETCHED_BORDER);

        this.tabbedPane = new JaxoClosableTabbedPane();
        root.add(tabbedPane);

        this.theCanvas = new JaxoCanvas(this);
        // need to set these early for size calculations
        theCanvas.setMinimumCanvasSize(new Dimension(JaxoPrefs.getIntPref(
                    JaxoPrefs.PREF_SCREENSIZEX),
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_SCREENSIZEY)));
        theCanvas.setMaximumCanvasSize(JaxoInfo.SCREEN_SIZE);
        theCanvas.setCanvasBackground(JaxoColor.getColor(JaxoPrefs.getStringPref(
            JaxoPrefs.PREF_CANVASBACKGROUND), JaxoColor.ALL_COLORS_MODE));


        this.canvasPopup = new JaxoCanvasPopupMenu(thePanel);

        tabbedPane.addClosingListener(new TabClosingListener() {
            public void closing(final TabClosingEvent e) {
                setSelectedTabIndex(e.getIndex());
                JaxoTabbedCanvasPanel.this.closing();
            }
        });

        tabbedPane.addMouseListener(this);

        tabbedPane.addChangeListener(new ChangeListener() {
            public void stateChanged(final ChangeEvent e) {
                updateSelectedTab();
            }
        });

        tabListener = new ChangeListener() {
            public void stateChanged(final ChangeEvent e) {
                final JaxoTab t = (JaxoTab) e.getSource();
                updateSaveState(t);
                tabChanged(t);
            }
        };

        aNewT();

        setupLatexTextToolTip();
        resetMode();
    }

    private void setupLatexTextToolTip() {
        latexTextToolTip.setGraph(getSelectedTab().getTabGraph());
        theCanvas.addMouseListener(latexTextToolTip);
        theCanvas.addMouseMotionListener(latexTextToolTip);
    }

    /** Returns an instance of the canvas.
     * @return The canvas.
     */
    // only used in MainPanel, should not be necessary
    public JaxoDrawingArea getCanvas() {
        return theCanvas;
    }

    // instantiate lazily to improve start-up time
    private void initExportPanel() {
        this.exportPanel =
            new JaxoExportPanel(parentComponent,
                JaxoPluginInfo.getPluginInfo().getExportFormats(), preview);
    }

    private void initImportPanel() {
        this.importPanel = new JaxoImportPanel(parentComponent,
                JaxoPluginInfo.getPluginInfo().getImportFormats());
    }

    /**
     * Root component displaying current tabs and a way to
     * switch between them.
     * @return The root component.
     */
    public final JComponent getRoot() {
        return root;
    }

    //////////////////////////////////////////////////////////////////////
    // List-like interface for tabs (ad hoc)

    /**
     * Gets the current number of tabs.
     * @return The current number of tabs.
     */
    public final int getTabCount() {
        return tabbedPane.getTabCount();
    }

    /**
     * Returns the tab at given index.
     * @param index The index of tab to get.
     * @return The tab
     */
    public final JaxoTab getTabAt(final int index) {
        return JaxoTab.asJaxoTab(tabbedPane.getComponentAt(index));
    }

    /**
     * Get the index of the given tab.
     * @param t The tab.
     * @return The index of the tab.
     */
    public int getTabIndex(final JaxoTab t) {
        return tabbedPane.indexOfComponent(t.getRoot());
    }

    /**
     * Return the currently active tab.
     * @return The active tab.
     */
    public final JaxoTab getSelectedTab() {
        return JaxoTab.asJaxoTab(tabbedPane.getSelectedComponent());
    }

    /**
     * Sets the given tab as currently selected.
     * @param value True for selected.
     */
    public void setSelectedTab(final JaxoTab value) {
        tabbedPane.setSelectedComponent((value == null) ? null : value.getRoot());
    }

    /**
     * Sets the tab at given index as currently active.
     * @param value The index.
     */
    private void setSelectedTabIndex(final int value) {
        tabbedPane.setSelectedIndex(value);
    }

    /**
     * (An arbitrary) Tab with the given saveFileName, or 'null' if
     * none exists.
     * @param name The save file name of the tab.
     * @return The tab with given name, or 'null' if if name does not exist.
     */
    public final JaxoTab getTabWithSaveFileName(final String name) {
        for (int i = getTabCount() - 1; i >= 0; --i) {
            if (getTabAt(i).getTabGraph().getSaveFileName().equals(name)) {
                return getTabAt(i);
            }
        }

        return null;
    }

    private void updateSaveState(final JaxoTab t) {
        final int index = getTabIndex(t);

        tabbedPane.setClosableIconAt(index,
            t.isSaved() ? unmodifiedIcon : modifiedIcon);
        tabbedPane.setFontStyleAt(index,
            (t.getSaveFileName().length() > 0) ? Font.BOLD : Font.PLAIN);
    }

    private void updateSelectedTab() {
        final JaxoTab t = getSelectedTab();

        if (t != selectedTab) {
            final JaxoTab old = selectedTab;
            selectedTab = t;

            if (selectedTab != null) {
                selectedTab.revalidate();
            }

            if (old != null) { // old is only null during construction
                updateSelectedTab(old, t);
            }

            latexTextToolTip.setGraph(t.getTabGraph());
        }
    }

    private TabInfo info(final JaxoTab t) {
        return tabInfos.get(t);
    }

    private String getName(final JaxoTab t) {
        final TabInfo n = info(t);

        return (n == null) ? null : n.getName();
    }

    private boolean isUntitled(final JaxoTab t) {
        return info(t).isUntitled();
    }

    private void setUntitled(final JaxoTab t, final boolean value) {
        info(t).setUntitled(value);
    }

    private void setNameAndIndex(final JaxoTab t, final String name) {
        final String old = getName(t);

        if ((old == null) ? (name == null) : old.equals(name)) {
            return;
        }

        final TabInfo n = info(t);

        if ((n != null) && (n.getName() != null)) {
            int count = 0;
            int max = -1;

            for (int i = getTabCount() - 1; i >= 0; --i) {
                final TabInfo n2 = info(getTabAt(i));

                if (n.getName().equals(n2.getName())) {
                    if (n2 != n) {
                        max = Math.max(max, n2.getIndex());
                    }

                    count++;
                }
            }

            if (count < 2) {
                nameIndex.remove(n.getName());
            } else {
                nameIndex.put(n.getName(), Integer.valueOf(max));
            }
        }

        Integer count;

        if (name == null) {
            count = Integer.valueOf(0);
        } else {
            count = nameIndex.get(name);

            if (count == null) {
                count = Integer.valueOf(0);
            } else {
                count = Integer.valueOf(count.intValue() + 1);
            }

            nameIndex.put(name, count);
        }

        TabInfo nn = info(t);

        if (nn == null) {
            nn = new TabInfo();
            tabInfos.put(t, nn);
        }

        nn.setName(name);
        nn.setIndex(count.intValue());

        updateTabTitle(t);
    }

    // Update tab titled from name and index
    private void updateTabTitle(final JaxoTab t) {
        final TabInfo n = info(t);

        if (n.getIndex() > 0) {
            t.setTabTitle(LANGUAGE.message(
                    "tabTitle%0No%1", n.getName(),
                    Integer.valueOf(n.getIndex())));
        } else {
            t.setTabTitle(LANGUAGE.message(
                    "tabTitle%0", n.getName()));
        }
    }

    // Update
    private void pushTabTitle(final JaxoTab t) {
        final int index = getTabIndex(t);

        tabbedPane.setClosableTitleAt(index, t.getTabTitle());
    }

    /** {@inheritDoc} */
    public final void updateLanguage() {
        for (int i = getTabCount() - 1; i >= 0; --i) {
            final JaxoTab t = getTabAt(i);

            final TabInfo n = info(t);

            if (n.isUntitled()) {
                setNameAndIndex(t,
                    LANGUAGE.value("untitled"));
            } else {
                updateTabTitle(t);
            }

            tabbedPane.setClosableTitleAt(i, t.getTabTitle());
        }

        canvasPopup.updateLanguage();

        theCanvas.updateLanguage();

        if (exportPanel != null) {
            exportPanel.updateLanguage();
        }

        if (importPanel != null) {
            importPanel.updateLanguage();
        }
    }

    /**
     * Called when an action item has been chosen from a sub-panel or via key shortcuts.
     * An action item calls for immediate action, without further input.
     *
     * @param i Integer specifying the action mode.
     */
    private void actionEvent(final int i) {

        switch (i) {
            case JaxoConstants.UNDO:
                getSelectedTab().undoMove();
                break;
            case JaxoConstants.REDO:
                getSelectedTab().redoMove();
                break;
            case JaxoConstants.CLEAR:
                theCanvas.clear();
                break;
            case JaxoConstants.CUT:
                theCanvas.cutMarkedObjects();
                break;
            case JaxoConstants.SCOPY:
                theCanvas.copyMarkedObjects();
                break;
            case JaxoConstants.SGROUP:
                theCanvas.groupMarkedObjects();
                break;
            case JaxoConstants.SUNGROUP:
                theCanvas.ungroupMarkedObjects();
                break;
            case JaxoConstants.SFORE:
                theCanvas.moveSelection(false);
                break;
            case JaxoConstants.SBACK:
                theCanvas.moveSelection(true);
                break;
            case JaxoConstants.PASTE:
                theCanvas.pasteFromClipboard();
                break;
            case JaxoConstants.REFRESH:
                theCanvas.refresh();
                break;
            case JaxoConstants.PREVIEW:
                final JaxoExportLatexPS l = new JaxoExportLatexPS();
                l.setParentComponent(root);
                l.setGraph(getSelectedTab().getTabGraph());
                l.preview(preview.copy(), isWatchMode());
                break;
            case JaxoConstants.WATCHFILE:
                setWatchMode(!isWatchMode());
                break;
            case JaxoConstants.RENAME_TAB:
                renameTab();
                break;
            case JaxoConstants.DESCRIBE:
                final String s =
                    JaxoDialogs.getDescription(root, getSelectedTab().getTabGraph().getDescription());
                if (s != null) {
                    getSelectedTab().getTabGraph().setDescription(s);
                    commitGraphChanges();
                }
                break;
            case JaxoConstants.MOVE_GRAPH:
                final JaxoMoveGraph g = new JaxoMoveGraph(root, theCanvas);
                g.show();
                if (g.hasChanged()) {
                    commitGraphChanges();
                }
                break;
            case JaxoConstants.PACKAGE:
                final JaxoLatexPackage latexPackage =
                    new JaxoLatexPackage(root, getSelectedTab().getTabGraph().getPackageList());
                if (!latexPackage.wasCancelled()) {
                    commitGraphChanges();
                }
                break;
            case JaxoConstants.LATEX_PREVIEW_SELECTION:
                final JaxoExportLatexPS lps = new JaxoExportLatexPS();
                lps.setParentComponent(root);
                lps.setGraph(theCanvas.getClipboard());
                lps.preview(preview.copy(), isWatchMode());
                break;
            default:
                break;
        }
    }

    /** forward to selected JaxoTab. */
    private void commitGraphChanges() {
        getSelectedTab().commitGraphChanges();
    }

    /** forward to selected JaxoTab. */
    private void commitRepeatableGraphChanges() {
        getSelectedTab().commitRepeatableGraphChanges();
    }

    /**
     * forward to selected JaxoTab.
     * @param count The count of changes.
     */
    private void commitRepeatableGraphChanges(final int count) {
        getSelectedTab().commitRepeatableGraphChanges(count);
    }

    /** Executes the methods defined as file events in {@link JaxoConstants}.
     * @param i Integer specifying the file event.
     */
    private void fileEvent(final int i) {
        switch (i) {
            case JaxoConstants.NEWG:
                aNewG();
                break;
            case JaxoConstants.NEWT:
                aNewT();
                break;
            case JaxoConstants.OPEN:
                open();
                break;
            case JaxoConstants.IMPORT:
                importGraph();
                break;
            case JaxoConstants.CLOSE:
                closing();
                break;
            case JaxoConstants.SAVE:
                save();
                break;
            case JaxoConstants.SAVE_AS:
                saveAs();
                break;
            case JaxoConstants.SAVE_SELECTION_AS:
                save(true, theCanvas.getClipboard(),
                    LANGUAGE.value("selection"));
                break;
            case JaxoConstants.EXPORT:
                export();
                break;
            case JaxoConstants.EXPORT_SELECTION:
                export(theCanvas.getClipboard(),
                    LANGUAGE.value("selection"));
                break;
            case JaxoConstants.PRINT:
                print();
                break;
            case JaxoConstants.QUIT:
                quit();
                break;
            default:
                break;
        }
    }

    /**
     * Called when a button in the grid panel has been pressed.
     *
     * @param i Integer specifying the grid event.
     */
    private void gridEvent(final int i) {
        switch (i) {
            case JaxoConstants.QUIT:
                quit();
                break;
            case JaxoConstants.ZOOM:
                setMode(i);
                break;
            case JaxoConstants.GRID:
                final boolean snap = !getSelectedTab().isSnappingToGrid();
                thePanel.distributePropertyChange(SNAP, !snap, snap);
                break;
            case JaxoConstants.RECTANGULAR_GRID:
                getSelectedTab().setGridType(i);
                break;
            case JaxoConstants.HEXAGONAL_GRID:
                getSelectedTab().setGridType(i);
                break;
            case JaxoConstants.GRID_STYLE_DOT:
                getSelectedTab().setGridStyle(i);
                break;
            case JaxoConstants.GRID_STYLE_CROSS:
                getSelectedTab().setGridStyle(i);
                break;
            case JaxoConstants.GRID_STYLE_LINE:
                getSelectedTab().setGridStyle(i);
                break;
            case JaxoConstants.GRID_STYLE_LINE_HONEYCOMB:
                getSelectedTab().setGridStyle(i);
                break;
            default:
                break;
        }
    }

    /**
     * Called when a help item has been chosen from the menu , the tool bar or
     * via key shortcuts. Calls the corresponding methods in the JaxoHelpMenu.
     *
     * @param mode The mode specifying the help event.
     */
    private void helpEvent(final int mode) {
        switch (mode) {
            case JaxoConstants.ABOUT:
                about();
                break;
            case JaxoConstants.USR_GUIDE:
                usrGuide();
                break;
            case JaxoConstants.SYS_INFO:
                sysInfo();
                break;
            case JaxoConstants.MAC_README:
                macReadme();
                break;
            default:
                break;
        }
    }

    /**
     * Performs the action corresponding to the given mode.
     *
     * @param mode A mode as defined in {@link JaxoConstants}.
     */
    public void performAction(final int mode) {
        if (JaxoConstants.isFileMode(mode)) {
            fileEvent(mode);
        } else if (JaxoConstants.isActionMode(mode)) {
            actionEvent(mode);
        } else if (JaxoConstants.isHelpMode(mode)) {
            helpEvent(mode);
        } else if (JaxoConstants.isGridMode(mode)) {
            gridEvent(mode);
        } else if (JaxoConstants.isEditMode(mode)) {
            setMode(mode);
        } else if (JaxoConstants.isParticleMode(mode)) {
            setMode(mode);
        } else if (JaxoConstants.isMiscMode(mode)) {
            setMode(mode);
        } else if (JaxoConstants.isVertexMode(mode)) {
            setVertexType(mode);
        }
    }

    /** Brings up a dialog to rename the current tab. */
    private void renameTab() {
        final JaxoTab t = getSelectedTab();

        final String oldName = isUntitled(t) ? "" : getName(t);

        final String newName = JaxoDialogs.getNewTabName(tabbedPane, oldName);

        if ((newName.length() != 0) && !(newName.equals(oldName))) {
            setUntitled(t, false);
            renameTab(t, newName);
        }
    }

    // Add tab 't' with desired 'name' (which will be indexed).
    // If reuse, close the current tab if unused (effectively reusing it)
    private void addTab(final JaxoTab t, final String name, final boolean untitled, final boolean use) {
        t.setTabMode(JaxoConstants.defaultActionToMode(
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_DEFAULTACTION)));

        boolean reuse = use;

        if (reuse) {
            reuse =
                (selectedTab != null) && !selectedTab.hasBeenUsed()
                && (
                    JaxoIO.shortName(selectedTab.getTabGraph().getSaveFileName())
                          .length() == 0
                );
        }

        final JaxoTab oldSelectedTab = selectedTab;

        setNameAndIndex(t, name);
        setUntitled(t, untitled);

        t.addChangeListener(tabListener);

        tabbedPane.addClosableTab(t.getTabTitle(), t.getRoot());

        updateSaveState(t);

        setSelectedTab(t);

        if (reuse) {
            removeTab(oldSelectedTab);
        }
    }

    /** Rename the name of 't' to 'newName' (indexed). */
    private void renameTab(final JaxoTab t, final String newName) {
        setNameAndIndex(t, newName);

        pushTabTitle(t);
    }

    /** Starts a new graph.
     * If the current graph is not saved, a warning message is displayed.
     */
    private void aNewG() {
        final JaxoTab t = getSelectedTab();

        if (!t.isSaved()) {
            final Object[] options =
            {
                LANGUAGE.value(
                    "newGraph.continue"),
                LANGUAGE.value("newGraph.cancel")
            };

            final int choice =
                showTabQuestionDialog("newGraph.title",
                    "newGraph.message%0", t, options);

            if (choice == 0) {
                t.newGraph();
            }
        }
    }

    /** Create a new canvas tab. */
    private void aNewT() {
        addTab(new JaxoTab(theCanvas), LANGUAGE.value("untitled"), true, false);
    }

    /** Brings up a file chooser dialog to open a file saved in a previous session. */
    private void open() {
        final JaxoGraph newGraph = io.open();
        open(newGraph);
    }

    /**
     * Open given file (JaxoDraw or plugin based on extension) in a new tab.
     *
     * @param fileName The file to open or import.
     */
    public void openOrImport(final String fileName) {
        if (JaxoIO.extension(fileName).equals(JaxoInfo.EXTENSION)) {
            open(io.open(fileName));
        } else {
            final JaxoImportPlugin plugin =
                    JaxoPluginInfo.getPluginInfo().getImportForExtension(
                    JaxoIO.extension(fileName));
            if (plugin != null) {
                open(plugin.importGraph(fileName));
            }
        }
    }

    private void importGraph() {
        if (importPanel == null) {
            initImportPanel();
        } else {
            importPanel.setImports(JaxoPluginInfo.getPluginInfo().getImportFormats());
        }

        importPanel.show();

        open(importPanel.getImportedGraph());
    }

    /** Closes the current tab (removing it from the tab panel)
     * If the tab has been modified, ask the user whether to save
     * first or to cancel.
     */
    private void closing() {
        final JaxoTab t = getSelectedTab();

        if (!t.isSaved()) {
            final Object[] options =
            {
                LANGUAGE.value("close.save"),
                LANGUAGE.value("close.dontSave"),
                LANGUAGE.value("close.cancel")
            };

            final int choice =
                showTabQuestionDialog("close.title",
                    "close.message%0", t, options);

            if (choice == 2) {
                return;
            }

            if (choice == 0) {
                save();

                if (!t.isSaved()) {
                    return;
                }
            }
        }

        close();
    }

    private void close() {
        final JaxoTab t = getSelectedTab();

        if (getTabCount() == 1) {
            setNameAndIndex(t, null);
            aNewT();
        }

        removeTab(t);
    }

    private int showTabQuestionDialog(final String titleKey, final String messageKey,
        final JaxoTab t, final Object[] options) {
        return JOptionPane.showOptionDialog(parentComponent,
            LANGUAGE.message(messageKey, t.getTabTitle()),
            JaxoDialogs.windowTitle(LANGUAGE, titleKey), JOptionPane.DEFAULT_OPTION,
            JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
    }

    /** Removes the given tab and refreshes the new one. */
    private void removeTab(final JaxoTab t) {
        setNameAndIndex(t, null);
        tabInfos.remove(t);
        tabbedPane.remove(t.getRoot());

        t.removeChangeListener(tabListener);

        updateSelectedTab();
    }

    /** If the current JaxoGraph has no save file name associated to it, brings up a file
     * chooser dialog to save the current JaxoGraph to a file. Otherwise saves it to
     * the default file.
     */
    private void save() {
        save(false);
    }

    /** Brings up a file chooser dialog to save the current JaxoGraph to a file. */
    private void saveAs() {
        save(true);
    }

    private void save(final boolean saveas) {
        boolean as = saveas;
        final boolean suggestUntitled = true; // Suggest "Untitled" as file name?

        final JaxoTab t = getSelectedTab();
        String saveFileName = t.getTabGraph().getSaveFileName();
        String shortName = JaxoIO.shortName(saveFileName);

        // If the tab name has been modified, always show save-as
        // for clarity
        if (isUntitled(t) || !JaxoIO.baseName(shortName).equals(getName(t))) {
            as = true;
        }

        if (as) {
            io.saveAs(t.getTabGraph(), t.getTabTitle(),
                (isUntitled(t) && !suggestUntitled) ? "" : getName(t));
        } else {
            io.save(t.getTabGraph(), t.getTabTitle(), shortName);
        }

        // re-set as it may have been modified by save-as
        saveFileName = t.getTabGraph().getSaveFileName();

        shortName = JaxoIO.shortName(saveFileName);

        t.setSaveFileName(saveFileName);

        if (saveFileName.length() != 0) {
            thePanel.distributePropertyChange("Jaxo.recentFile", null, saveFileName);

            setUntitled(t, false);

            renameTab(t, JaxoIO.baseName(shortName));

            t.setSaved(t.getTabGraph().isSaved());
        }
    }

    // Overlaoad the previous method with the graph that needs to be saved and title
    // (a selection of the current graph made by using the faint box)
    private void save(final boolean as, final JaxoGraph g, final String title) {
        if (as) {
            io.saveAs(g, title, "");
        } else {
            io.save(g, title, "");
        }
    }

    /** Brings up a dialog that allows to export the current JaxoGraph to several file
     * formats: postscript portrait and landscape, EPS and LaTex are supported.
     */
    private void export() {
        final JaxoTab t = getSelectedTab();
        export(t.getTabGraph(), t.getTabTitle());
    }

    // Overlaoad the previous method with the graph that needs to be saved and a title
    // (used explicitly for a selection of the current graph made by using the faint box)
    private void export(final JaxoGraph g, final String title) {
        if (exportPanel == null) {
            initExportPanel();
        }
        // TODO: watchFile mode should get dynamically set
        // this is not updated if the mode changed while the export panel is open
        exportPanel.setNewPreviewFrame(!isWatchMode());
        exportPanel.setExports(JaxoPluginInfo.getPluginInfo().getExportFormats());
        exportPanel.export(g, title);
    }

    /** Brings up the standard Java print dialog to print the current JaxoGraph directly
     * to a printer or to a postscript file.
     */
    private void print() {
        final JaxoTab t = getSelectedTab();
        final JaxoPrint p = new JaxoPrint(t.getTabGraph());

        p.print(parentComponent, t.getTabTitle());
    }

    /** Exits JaxoDraw, looping over unsaved tabs and asking for saving/discard changes*/
    private void quit() {
        boolean dontQuit = false;
        boolean saveAll = false;
        int unsavedCount = 0;

        for (int i = getTabCount() - 1; i > -1; i--) {
            final JaxoTab t = getTabAt(i);

            if (!t.isSaved()) {
                unsavedCount++;
            }
        }

        Object[] options;

        if (unsavedCount > 1) {
            options =
                new Object[]{
                    LANGUAGE.value(
                        "quit.saveAll"),
                    LANGUAGE.value("quit.save"),
                    LANGUAGE.value(
                        "quit.dontSave"),
                    LANGUAGE.value("quit.cancel")
                };
        } else {
            options =
                new Object[]{
                    LANGUAGE.value("quit.save"),
                    LANGUAGE.value(
                        "quit.dontSave"),
                    LANGUAGE.value("quit.cancel")
                };
        }

        for (int i = getTabCount() - 1; (i >= 0) && !dontQuit; --i) {
            final JaxoTab t = getTabAt(i);

            if (!t.isSaved()) {
                new Thread(new JaxoSoundsHandler(
                        "resources/sounds/unsaved_changes.wav")).start();
                setSelectedTabIndex(i);

                int choice;

                if (saveAll) {
                    choice = 1;
                } else {
                    choice =
                        showTabQuestionDialog("quit.title",
                            "quit.message%0", t, options);

                    if (options.length == 3) {
                        choice++;
                    }

                    if (choice == 0) {
                        saveAll = true;
                    }
                }

                switch (choice) {
                    case 0:
                    case 1:
                        save();
                        if (!t.isSaved()) {
                            return;
                        }
                        break;
                    case 2:
                        break;
                    case 3:
                        dontQuit = true;
                        break;
                    default:
                        break;
                }
            }
        }

        if (!dontQuit) {
            thePanel.shutdown(0);
        }
    }

    /**
     * Opens the new graph 'graph' (without opening the fileChooser dialog)
     * in a new tab, or in the current tab if it can be reused.
     *
     * @param graph The graph to open.
     */
    private void open(final JaxoGraph graph) {
        if (graph == null) {
            return;
        }

        final String shortName = JaxoIO.shortName(graph.getSaveFileName());

        addTab(new JaxoTab(theCanvas, graph), JaxoIO.baseName(shortName), false, true);

        thePanel.distributePropertyChange("Jaxo.recentFile",
                null, graph.getSaveFileName());
    }

    /**
     * Processes the right-click to show the popup menu on the canvas tab.
     * @param e The mouse event.
     */
    @Override
    public final void mousePressed(final MouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
            // Make sure it is clear (both programmatically and for the
            // user) which tab the popup refers to
            final int index = tabbedPane.indexAtLocation(e.getX(), e.getY());
            if (index != -1) {
                tabbedPane.setSelectedIndex(index);
            }
            canvasPopup.show(e.getComponent(), e.getX(), e.getY());
        }
    }

    /**
     * Indicates whether watch mode is active or not.
     *
     * @return True when watch mode is active, false otherwise.
     * @since 2.1
     */
    private boolean isWatchMode() {
        return watchMode;
    }

    /**
     * Sets the current watch mode.
     *
     * @param value True, if watch mode is to be switched on, false otherwise.
     * @since 2.1
     */
    private void setWatchMode(final boolean value) {
        if (isWatchMode() != value) {
            this.watchMode = value;

            thePanel.distributePropertyChange("Jaxo.watchMode", !value, value);
        }
    }

    /**
     * Indicates whether antialiasing is active or not.
     *
     * @return True when antialiasing is active, false otherwise.
     * @since 2.1
     */
    public boolean isAntialiasEnabled() {
        return antialiasEnabled;
    }

    /**
     * Switches antialiasing on or off.
     *
     * @param on A boolean variable that indicates whether antialising is on or not.
     * @since 2.1
     */
    public void setAntialiasEnabled(final boolean on) {
        if (on != antialiasEnabled) {
            antialiasEnabled = on;
        }
    }

    /** {@inheritDoc}
     * @param e the ActionEvent.
     */
    public void actionPerformed(final ActionEvent e) {
        if ("resetMode".equals(e.getActionCommand())) {
            resetMode();
        } else if ("commitGraphChanges".equals(e.getActionCommand())) {
            commitGraphChanges();
        } else if ("commitRepeatableGraphChanges".equals(e.getActionCommand())) {
            commitRepeatableGraphChanges();
        } else if ("commitRepeatableGraphChangesN".equals(e.getActionCommand())) {
            commitRepeatableGraphChanges(e.getID());
        } else if ("setGlassPaneVisible".equals(e.getActionCommand())) {
            thePanel.actionPerformed(e);
        } else if ("setGlassPaneInvisible".equals(e.getActionCommand())) {
            thePanel.actionPerformed(e);
        } else {
            thePanel.actionPerformed(e);
        }
    }

    /** {@inheritDoc} */
    public void propertyChange(final PropertyChangeEvent evt) {
        if ("Jaxo.canvasBackground".equals(evt.getPropertyName())) {
            theCanvas.setCanvasBackground((Color) evt.getNewValue());
            // this is an application-wide property, store in prefs
            JaxoPrefs.setStringPref(JaxoPrefs.PREF_CANVASBACKGROUND,
                    JaxoColor.getColorName((Color) evt.getNewValue(), JaxoColor.ALL_COLORS_MODE));
        } else if ("Jaxo.gridColor".equals(evt.getPropertyName())) {
            getSelectedTab().setGridColor((Color) evt.getNewValue());
        } else if ("Jaxo.gridSize".equals(evt.getPropertyName())) {
            getSelectedTab().setGridSize(((Integer) evt.getNewValue()).intValue());
        } else if (SNAP.equals(evt.getPropertyName())) {
            getSelectedTab().setSnappingToGrid(((Boolean) evt.getNewValue()).booleanValue());
        } else if ("Jaxo.gridOn".equals(evt.getPropertyName())) {
            getSelectedTab().setGridPainted(((Boolean) evt.getNewValue()).booleanValue());
        } else if ("Jaxo.zoomFactor".equals(evt.getPropertyName())) {
            theCanvas.getZoom().setZoomFactorFor(((Integer) evt.getNewValue()).intValue());
        } else if ("Jaxo.mainPanelFocused".equals(evt.getPropertyName())) {
            latexTextToolTip.setActive(((Boolean) evt.getNewValue()).booleanValue());
        }
    }

    /**
     * Updates the tab when it changes.
     * @param old The old tab.
     * @param t The new tab.
     */
    private void updateSelectedTab(final JaxoTab old, final JaxoTab t) {
        /* why?
        if (old != null) {
            old.setSnappingToGrid(isSnap());
            old.setTabMode(getMode());
        }
        */

        thePanel.distributePropertyChange("Jaxo.selectedTab", old, t);

        tabChanged(t);

        if (t == null) { // is this possible?
            //setGridOn(true);
            thePanel.distributePropertyChange("Jaxo.gridOn", false, true);
            //setSnap(false);
            thePanel.distributePropertyChange(SNAP, true, false);
            setMode(JaxoConstants.STANDBY);
        } else {
            //setGridOn(t.isGridPainted());
            thePanel.distributePropertyChange("Jaxo.gridOn", true, t.isGridPainted());
            //setSnap(t.isSnappingToGrid());
            thePanel.distributePropertyChange(SNAP, false, t.isSnappingToGrid());
            setMode(t.getTabMode());
        }
    }

    /**
     * Adjusts the main panel to a changed tab.
     * @param t The old tab.
     */
    private void tabChanged(final JaxoTab t) {
        if (t == null) {
            setCanUndo(false);
            setCanRedo(false);
        } else {
            setCanUndo(t.canUndo());
            setCanRedo(t.canRedo());
        }
    }

    private void setCanUndo(final boolean value) {
        if (value != canUndo) {
            canUndo = value;

            thePanel.distributePropertyChange("Jaxo.canUndo", !value, value);
        }
    }

    private void setCanRedo(final boolean value) {
        if (value != canRedo) {
            canRedo = value;

            thePanel.distributePropertyChange("Jaxo.canRedo", !value, value);
        }
    }

    /** Sets the current drawing/edit mode.
     * @param newMode The drawing/edit mode to be set.
     */
    private void setMode(final int newMode) {
        if (newMode != currentMode) {
            final int old = currentMode;
            currentMode = newMode;

            if (currentMode == JaxoConstants.VERTEX) {
                currentMode = vertexType;
            }

            thePanel.distributePropertyChange("Jaxo.mode", old, currentMode);
        }
    }

    /** Revert to the the defaultMode, or re-initialize the current
     * mode if the defaultMode is none.
     */
    private void resetMode() {
        int newMode = JaxoConstants.defaultActionToMode(
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_DEFAULTACTION));

        if (newMode == JaxoConstants.STANDBY) {
            newMode = currentMode;
        }

        setMode(newMode);
    }

    /** Sets the current type of vertex.
     * @param value An integer specifying the vertex type.
     */
    private void setVertexType(final int value) {
        if (value != vertexType) {
            final int old = vertexType;

            vertexType = value;

            thePanel.distributePropertyChange("Jaxo.vertexType", old, value);
        }
    }

    /** Pops a small dialog with some information about the current
     * version of JaxoDraw.
     */
    private void about() {
        final String message = JaxoInfo.about(
                JaxoPluginInfo.getPluginInfo().availablePlugins());
        final String title = JaxoDialogs.windowTitle("About");

        JaxoDialogs.showInfoDialog(parentComponent, message, title);
    }

    /** Opens the User Guide of JaxoDraw either in a custom browser
     * (if set in the Preferences) or in an internal browser.
     */
    private void usrGuide() {
        final JaxoUserGuide usrGuide = new JaxoUserGuide(parentComponent);
        usrGuide.show(preview.copy());
    }

    /** Pops up a dialog with some information about
     * the current system environment.
     */
    private void sysInfo() {
        final String message = JaxoInfo.sysInfo();
        final String title = JaxoDialogs.windowTitle("System_Info");

        JaxoDialogs.showInfoDialog(parentComponent, message, title);
    }

    /** Opens a README file for Mac OS X usage in an internal browser. */
    private void macReadme() {
        preview.copy().showURLInternally(
                Thread.currentThread().getContextClassLoader().getResource(
                "resources/MacIssues.html"));
    }

    private static class TabInfo {
        private String name;
        private int index;
        private boolean untitled;

        /** Constructor: TabInfo with null name. */
        TabInfo() {
            this(null);
        }

        /** Constructor.
         * @param nname The name of this TabInfo.
         */
        TabInfo(final String nname) {
            this.name = nname;
        }

        /** Returns the name of this TabInfo.
         * @return The name of this TabInfo.
         */
        public String getName() {
            return name;
        }

        /** Sets the name of this TabInfo.
         * @param nname The name of this TabInfo.
         */
        public void setName(final String nname) {
            this.name = nname;
        }

        /** Returns the index of this TabInfo.
         * @return The index of this TabInfo.
         */
        public int getIndex() {
            return index;
        }

        /** Sets the index of this TabInfo.
         * @return The index of this TabInfo.
         */
        public void setIndex(final int ind) {
            this.index = ind;
        }

        /** Determines whether this TabInfo is untitled.
         * @return True if this TabInfo is untitled.
         */
        public boolean isUntitled() {
            return untitled;
        }

        /** Sets this TabInfo as untitled.
         * @return True if this TabInfo should be untitled.
         */
        public void setUntitled(final boolean ut) {
            this.untitled = ut;
        }
    }
}
