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

package com.mucommander.ui.layout;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;

import com.mucommander.core.desktop.DesktopManager;

/**
 * ProportionalSplitPane is a JSplitPane that is able to maintain the divider's location constant proportionally when
 * the window it is attached to is resized, or when its orientation is changed.
 *
 * <p>Another added feature is the ability to restore a split ratio of 0.5f (same size for both panels) when
 * the divider component is double-clicked.
 *
 * @author Maxence Bernard
 */
public class ProportionalSplitPane extends JSplitPane implements ComponentListener, MouseListener {
    /** Last known width of the window this split pane is attached to. */
    private int windowWidth;

    /** Last known absolute divider location */
    private int lastDividerLocation = -1;

    /** Current proportional divider location, initially 0.5f (same size for both panels) */
    private float splitRatio = 0.5f;

    /** Window this split pane is attached to */
    private Window window;


    public ProportionalSplitPane(Window window) {
        super();
        init(window, null, null);
    }

    public ProportionalSplitPane(Window window, int orientation) {
        super(orientation);
        init(window, null, null);
    }

    public ProportionalSplitPane(Window window, int orientation, boolean continuousLayout) {
        super(orientation, continuousLayout);
        init(window, null, null);
    }

    public ProportionalSplitPane(Window window, int orientation, JComponent leftComponent, JComponent rightComponent) {
        super(orientation, leftComponent, rightComponent);
        init(window, leftComponent, rightComponent);
    }

    public ProportionalSplitPane(Window window, int orientation, boolean continuousLayout, JComponent leftComponent, JComponent rightComponent) {
        super(orientation, continuousLayout, leftComponent, rightComponent);
        init(window, leftComponent, rightComponent);
    }

    private void init(Window window, JComponent leftComponent, JComponent rightComponent) {
        this.window = window;
        window.addComponentListener(this);

        BasicSplitPaneDivider divider = getDividerComponent();
        divider.addComponentListener(this);
        divider.addMouseListener(this);

        // Set null minimum size for both components so that divider can be moved all the way left/up and right/down
        Dimension nullDimension = new Dimension(0,0);
        if(leftComponent!=null)
            leftComponent.setMinimumSize(nullDimension);
        if(rightComponent!=null)
            rightComponent.setMinimumSize(nullDimension);
    }


    /**
     * Updates the divider component's location to keep the current proportional divider location. 
     */
    public void updateDividerLocation() {
        if(!window.isVisible())
            return;

        // Reset the last divider location to make sure that the next call to moveComponent doesn't
        // needlessly recalculate the split ratio.
        lastDividerLocation = -1;
        //setDividerLocation((int)(splitRatio*(getOrientation()==HORIZONTAL_SPLIT?getWidth():getHeight())));
        setDividerLocation(splitRatio);
    }


    /**
     * Sets the constant, proportional divider's location. The given float but be comprised between 0 and 1, 0 meaning
     * completely left (or top), 1 right completely (or bottom).   
     *
     * @param splitRatio the proportional divider's location, comprised between 0 and 1.
     */
    public void setSplitRatio(float splitRatio) {
        this.splitRatio = splitRatio;
        updateDividerLocation();
    }


    /**
     * returns current pane split ratio.
     */
    public float getSplitRatio() {
        return this.splitRatio;
    }


    /**
     * Returns the split pane divider component.
     */
    public BasicSplitPaneDivider getDividerComponent() {
        return ((BasicSplitPaneUI)getUI()).getDivider();
    }


    /**
     * Disables all the JSPlitPane accessibility shortcuts that are registered by default:
     * <ul>
     * <li>Navigate in - Tab
     * <li>Navigate out - Ctrl+Tab, Ctrl+Shift+Tab
     * <li>Navigate between - Tab, F6
     * <li>Give focus spliter bar - F8
     * <li>Change size - Arrow keys, home, and end (moves the pane splitter appropriately)
     * </ul>
     */
    public void disableAccessibilityShortcuts() {
        InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.clear();
        inputMap.setParent(null);
    }


    ////////////////////////
    // Overridden methods //
    ////////////////////////

    @Override
    public void setOrientation(int orientation) {
        super.setOrientation(orientation);
        // Maintain the divider's proportional location
        updateDividerLocation();
    }


    //////////////////////////////////////
    // ComponentListener implementation //
    //////////////////////////////////////

    /**
     * Sets the divider location when the ContentPane has been resized so that it stays at the
     * same proportional (not absolute) location.
     */
    public void componentResized(ComponentEvent e) {
        Object source = e.getSource();


        if(source==window) {
            // Note: the window/split pane may not be visible when this method is called for the first time

            // Makes sure that windowWidth is never 0 in #componentMoved.
            windowWidth = window.getWidth();
            updateDividerLocation();
        }
    }

    public void componentMoved(ComponentEvent e) {
        if(e.getSource() == getDividerComponent()) {
            // Ignore this event if the divider's location hasn't changed, or if the initial divider's location
            // hasn't been set yet
            if(lastDividerLocation == -1) {
                lastDividerLocation = getDividerLocation();
                return;
            }
            else if(lastDividerLocation==getDividerLocation())
                return;

            // This is a bit tricky: we want to ignore events triggered by the divider moving because the window was
            // resized in such a way that it didn't have a choice (window width smaller than current divider location).
            // Such events are managed by the componentResized method.
            if(windowWidth != window.getWidth()) {
                windowWidth = window.getWidth();
                return;
            }

            // Divider has been moved, calculate new split ratio
            lastDividerLocation = getDividerLocation();
            splitRatio = lastDividerLocation / (float)(getOrientation()==HORIZONTAL_SPLIT?getWidth():getHeight());
        }
    }

    public void componentShown(ComponentEvent e) {
        // Called when the window is made visible
        if(e.getSource()==window) {
            // Set initial divider's location
            updateDividerLocation();
        }
    }

    public void componentHidden(ComponentEvent e) {
    }


    ///////////////////////////
    // MouseListener methods //
    ///////////////////////////

    public void mouseClicked(MouseEvent mouseEvent) {
        if(DesktopManager.isLeftMouseButton(mouseEvent) && mouseEvent.getClickCount()==2)
            setSplitRatio(0.5f);
    }

    public void mousePressed(MouseEvent mouseEvent) {
    }

    public void mouseReleased(MouseEvent mouseEvent) {
    }

    public void mouseEntered(MouseEvent mouseEvent) {
    }

    public void mouseExited(MouseEvent mouseEvent) {
    }
}
