/*
 * @(#)JideSplitPaneDivider.java
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.swing;


import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.plaf.basic.Painter;
import com.jidesoft.utils.PortingUtils;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;


/**
 * Divider used by JideSplitPane.
 */
public class JideSplitPaneDivider extends JPanel
        implements PropertyChangeListener {
    /**
     * Handles mouse dragging message to do the actual dragging.
     */
    protected DragController _dragger;

    /**
     * Size of the divider.
     */
    protected int _dividerSize = UIDefaultsLookup.getInt("JideSplitPane.dividerSize"); // default - SET TO 0???

    /**
     * JideSplitPane the receiver is contained in.
     */
    protected JideSplitPane _jideSplitPane;

    /**
     * Handles mouse events from both this class, and the split pane. Mouse events are handled for the JideSplitPane
     * since you want to be able to drag when clicking on the border of the divider, which is not drawn by the divider.
     */
    protected MouseHandler _mouseHandler;

    /**
     * Orientation of the JideSplitPane.
     */
    protected int _orientation;

    /**
     * Cursor used for HORIZONTAL_SPLIT split panes.
     */
    static final Cursor HORIZONTAL_CURSOR =
            JideCursors.getPredefinedCursor(JideCursors.HSPLIT_CURSOR);

    /**
     * Cursor used for VERTICAL_SPLIT split panes.
     */
    static final Cursor VERTICAL_CURSOR =
            JideCursors.getPredefinedCursor(JideCursors.VSPLIT_CURSOR);

    /**
     * Default cursor.
     */
    static final Cursor DEFAULT_CURSOR = Cursor.getDefaultCursor();

    private Painter _gripperPainter;

    /**
     * Creates an instance of BasicJideSplitPaneDivider. Registers this instance for mouse events and mouse dragged
     * events.
     *
     * @param splitPane the JideSplitPane.
     */
    public JideSplitPaneDivider(JideSplitPane splitPane) {
        setJideSplitPane(splitPane);
        _orientation = _jideSplitPane.getOrientation();

        // get divider size from JideSplitPane
        setDividerSize(splitPane.getDividerSize());
        setDefaultResizeCursor();

        setBackground(UIDefaultsLookup.getColor("JideSplitPaneDivider.background"));
        setBorder(UIDefaultsLookup.getBorder("JideSplitPaneDivider.border"));
        if (_jideSplitPane.isOneTouchExpandable()) {
            oneTouchExpandableChanged();
        }
        _gripperPainter = (Painter) UIDefaultsLookup.get("JideSplitPaneDivider.gripperPainter");
        setOpaque(false);
        setLayout(null);
    }

    public void setDefaultResizeCursor() {
        setCursor((_orientation == JideSplitPane.HORIZONTAL_SPLIT) ?
                HORIZONTAL_CURSOR : VERTICAL_CURSOR);
    }

    /**
     * Gets the <code>JideSplitPane</code>.
     *
     * @return the <code>JideSplitPane</code>
     */
    public JideSplitPane getJideSplitPane() {
        return _jideSplitPane;
    }

    /**
     * Sets the JideSplitPane that is using this divider.
     *
     * @param splitPane the JideSplitPane.
     */
    public void setJideSplitPane(JideSplitPane splitPane) {
        uninstallListeners();
        _jideSplitPane = splitPane;
        installListeners();
    }

    private void installListeners() {
        if (_jideSplitPane != null) {
            if (_mouseHandler == null) {
                _mouseHandler = createMouseHandler();
            }
            _jideSplitPane.addMouseListener(_mouseHandler);
            _jideSplitPane.addMouseMotionListener(_mouseHandler);
            addMouseListener(_mouseHandler);
            addMouseMotionListener(_mouseHandler);
            _jideSplitPane.addPropertyChangeListener(this);
        }
    }

    private void uninstallListeners() {
        if (_jideSplitPane != null) {
            _jideSplitPane.removePropertyChangeListener(this);
            if (_mouseHandler != null) {
                _jideSplitPane.removeMouseListener(_mouseHandler);
                _jideSplitPane.removeMouseMotionListener(_mouseHandler);
                removeMouseListener(_mouseHandler);
                removeMouseMotionListener(_mouseHandler);
                _mouseHandler = null;
            }
        }
    }

    protected MouseHandler createMouseHandler() {
        return new MouseHandler();
    }

    /**
     * Sets the size of the divider to <code>newSize</code>. That is the width if the split pane is
     * <code>HORIZONTAL_SPLIT</code>, or the height of <code>VERTICAL_SPLIT</code>.
     *
     * @param newSize the new divider size.
     */
    public void setDividerSize(int newSize) {
        _dividerSize = newSize;
    }


    /**
     * Returns the size of the divider, that is the width if the split pane is HORIZONTAL_SPLIT, or the height of
     * VERTICAL_SPLIT.
     *
     * @return the divider size.
     */
    public int getDividerSize() {
        return _dividerSize;
    }

    /**
     * Returns dividerSize x dividerSize
     */
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(getDividerSize(), getDividerSize());
    }

    /**
     * Returns dividerSize x dividerSize
     */
    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }


    /**
     * Property change event, presumably from the JideSplitPane, will message updateOrientation if necessary.
     */
    public void propertyChange(PropertyChangeEvent e) {
        if (e.getSource() == _jideSplitPane) {
            if (JideSplitPane.ORIENTATION_PROPERTY.equals(e.getPropertyName())) {
                _orientation = _jideSplitPane.getOrientation();
                setCursor((_orientation == JideSplitPane.HORIZONTAL_SPLIT) ?
                        HORIZONTAL_CURSOR : VERTICAL_CURSOR);
                invalidate();
                validate();
            }
            else if (JideSplitPane.ONE_TOUCH_EXPANDABLE_PROPERTY.equals(e.getPropertyName())) {
                setDividerSize(_jideSplitPane.getDividerSize());
                oneTouchExpandableChanged();
            }
            else if (JideSplitPane.DIVIDER_SIZE_PROPERTY.equals(e.getPropertyName())) {
                setDividerSize(_jideSplitPane.getDividerSize());
            }
            else if (JideSplitPane.GRIPPER_PROPERTY.equals(e.getPropertyName())) {
                repaint();
            }
        }
    }


    /**
     * Resets the UI property to a value from the current look and feel. <code>JComponent</code> subclasses must
     * override this method like this:
     * <pre>
     *   public void updateUI() {
     *      setUI((SliderUI)UIManager.getUI(this);
     *   }
     *  </pre>
     *
     * @see #setUI
     * @see UIManager#getLookAndFeel
     * @see UIManager#getUI
     */
    @Override
    public void updateUI() {
        super.updateUI();
        setBackground(UIDefaultsLookup.getColor("JideSplitPaneDivider.background"));
        setBorder(UIDefaultsLookup.getBorder("JideSplitPaneDivider.border"));
        _gripperPainter = (Painter) UIDefaultsLookup.get("JideSplitPaneDivider.gripperPainter");
    }

    /**
     * Paints the divider.
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        // Paint the border.
        Border border = getBorder();

        Dimension size = getSize();

        if (isOpaque()) {
            g.setColor(getBackground());
            g.fillRect(0, 0, size.width, size.height);
        }

        if (border != null) {
            border.paintBorder(this, g, 0, 0, size.width, size.height);
        }

        if (_jideSplitPane.isShowGripper()) {
            Rectangle rect = new Rectangle(size);
            if (_gripperPainter != null) {
                if (rect.width > rect.height) {
//                    rect.x = rect.x + rect.width / 2 - 10;
//                    rect.width = 22;
                    _gripperPainter.paint(this, g, rect, SwingConstants.VERTICAL, 0);
                }
                else {
//                    rect.y = rect.y + rect.height / 2 - 10;
//                    rect.height = 22;
                    _gripperPainter.paint(this, g, rect, SwingConstants.HORIZONTAL, 0);
                }
            }
            else {
                rect.x++;
                rect.y++;
                JideSwingUtilities.drawGrip(g, rect, 9, UIDefaultsLookup.getInt("JideSplitPane.dividerSize") / 3);
            }
        }
    }

    /**
     * Message to prepare for dragging. This messages the BasicJideSplitPaneUI with startDragging.
     */
    protected void prepareForDragging() {
        _jideSplitPane.startDragging(this);
    }

    protected void dragDividerTo(int location) {
        _jideSplitPane.dragDividerTo(this, location);

        /*
         * Update the variables used by the one-touch expand/collapse buttons.
         */
        _currentState = DEFAULT_STATE;
        int indexOfDivider = _jideSplitPane.indexOfDivider(JideSplitPaneDivider.this);
        _lastPosition = _jideSplitPane.getDividerLocation(indexOfDivider);
    }

    protected void finishDraggingTo(int location) {
        _jideSplitPane.finishDraggingTo(this, location);
    }

    protected int getPreviousDividerLocation(boolean ignoreVisibility, boolean reversed) {
        return _jideSplitPane.getPreviousDividerLocation(this, ignoreVisibility, reversed);
    }

    protected int getNextDividerLocation(boolean ignoreVisibility, boolean reversed) {
        return _jideSplitPane.getNextDividerLocation(this, ignoreVisibility, reversed);
    }

    /**
     * Gets the first component. This divider is installed between two components. The first component is usually the
     * one on the left or on the top.
     *
     * @param ignoreVisibility true to not check if the component is visible.
     * @return the first component
     */
    public Component getFirstComponent(boolean ignoreVisibility) {
        int index = _jideSplitPane.indexOf(this);
        if (index - 1 >= 0) {
            for (int i = (index - 1); i >= 0; i--) {
                if (ignoreVisibility || _jideSplitPane.getComponent(i).isVisible()) {
                    return _jideSplitPane.getComponent(i);
                }
            }
            // return an invisible component in lieu of null
            return _jideSplitPane.getComponent(index - 1);
        }
        else {
            throw new IndexOutOfBoundsException("There is no component before divider " + index);
        }
    }

    /**
     * Gets the second component. This divider is installed between two components. The second component is usually the
     * one on the right or on the bottom.
     *
     * @param ignoreVisibility true to not check if the component is visible.
     * @return the first component
     */
    public Component getSecondComponent(boolean ignoreVisibility) {
        int index = _jideSplitPane.indexOf(this);

        if (index + 1 < _jideSplitPane.getComponentCount()) {
            for (int i = (index + 1); i >= 0; i++) {
                if (ignoreVisibility || _jideSplitPane.getComponent(i).isVisible()) {
                    return _jideSplitPane.getComponent(i);
                }
            }
            // return an invisible component in lieu of null
            return _jideSplitPane.getComponent(index + 1);
        }
        else {
            throw new IndexOutOfBoundsException("There is no component before divider " + index);
        }
    }

    /**
     * MouseHandler is responsible for converting mouse events (released, dragged...) into the appropriate
     * DragController methods.
     * <p/>
     */
    protected class MouseHandler extends MouseInputAdapter {
        /**
         * Starts the dragging session by creating the appropriate instance of DragController.
         */
        @Override
        public void mousePressed(MouseEvent e) {
            if ((e.getSource() == JideSplitPaneDivider.this/*||
                    e.getSource() == _jideSplitPane*/) && _dragger == null && _jideSplitPane.isEnabled() && _jideSplitPane.isDragResizable()) {
                if (getFirstComponent(true) != null &&
                        getSecondComponent(true) != null) {
                    if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
                        _dragger = new DragController(e);
                    }
                    else {
                        _dragger = new VerticalDragController(e);
                    }
                    if (!_dragger.isValid()) {
                        _dragger = null;
                    }
                    else {
                        prepareForDragging();
                        _dragger.continueDrag(e);
                    }
                }
                e.consume();
            }
        }


        /**
         * If dragger is not null it is messaged with completeDrag.
         */
        @Override
        public void mouseReleased(MouseEvent e) {
            if (_dragger != null) {
                if (e.getSource() == _jideSplitPane) {
                    _dragger.completeDrag(e.getX(), e.getY());
                }
                else if (e.getSource() == JideSplitPaneDivider.this) {
                    Point ourLoc = getLocation();
                    _dragger.completeDrag(e.getX() + ourLoc.x, e.getY() + ourLoc.y);
                }
                _dragger = null;
                e.consume();
            }
        }

        //
        // MouseMotionListener
        //

        /**
         * If dragger is not null it is messaged with continueDrag.
         */
        @Override
        public void mouseDragged(MouseEvent e) {
            if (_dragger != null) {
                if (e.getSource() == _jideSplitPane) {
                    _dragger.continueDrag(e.getX(), e.getY());
                }
                else if (e.getSource() == JideSplitPaneDivider.this) {
                    Point ourLoc = getLocation();
                    _dragger.continueDrag(e.getX() + ourLoc.x, e.getY() + ourLoc.y);
                }
                e.consume();
            }
        }
    }


    /**
     * Handles the events during a dragging session for a HORIZONTAL_SPLIT oriented split pane. This continually
     * messages <code>dragDividerTo</code> and then when done messages <code>finishDraggingTo</code>. When an instance
     * is created it should be messaged with <code>isValid</code> to insure that dragging can happen (dragging won't be
     * allowed if the two views can not be resized).
     */
    protected class DragController {
        /**
         * Initial location of the divider.
         */
        int initialLocation;

        /**
         * Maximum and minimum positions to drag to.
         */
        int maxLocation, minLocation;

        /**
         * Initial location the mouse down happened at.
         */
        int offset;


        protected DragController(MouseEvent e) {
            ComponentOrientation o = getComponentOrientation();
            boolean ltr = o.isLeftToRight();
            boolean reversed = !ltr && _jideSplitPane.getOrientation() == JideSplitPane.HORIZONTAL_SPLIT;
            Component leftC = reversed ? getSecondComponent(false) : getFirstComponent(false);
            Component rightC = reversed ? getFirstComponent(false) : getSecondComponent(false);

            initialLocation = getLocation().x;
            if (e.getSource() == JideSplitPaneDivider.this) {
                offset = e.getX();
            }
            else { // splitPane
                offset = e.getX() - initialLocation;
            }
            if (leftC == null || rightC == null || offset < -1 ||
                    offset >= _jideSplitPane.getSize().width) {
                // Don't allow dragging.
                maxLocation = -1;
            }
            else {
                int index = _jideSplitPane.indexOf(JideSplitPaneDivider.this);
                int modelLeftWidth = 0;
                int modelRightWidth = 0;
                for (int i = 0; i < index; i++) {
                    Component component = _jideSplitPane.getComponent(i);
                    if (component instanceof JideSplitPaneDivider) {
                        modelLeftWidth += component.getSize().getWidth();
                    }
                    else if (component.isVisible()) {
                        if (((JideBoxLayout) _jideSplitPane.getLayout()).getConstraintMap().get(component) == JideBoxLayout.FIX) {
                            modelLeftWidth += component.getWidth();
                        }
                        else {
                            modelLeftWidth += component.getMinimumSize().getWidth();
                        }
                    }
                }

                for (int i = index + 1; i < _jideSplitPane.getComponentCount(); i++) {
                    Component component = _jideSplitPane.getComponent(i);
                    if (component instanceof JideSplitPaneDivider) {
                        modelRightWidth += component.getSize().getWidth();
                    }
                    else if (component.isVisible()) {
                        if (((JideBoxLayout) _jideSplitPane.getLayout()).getConstraintMap().get(component) == JideBoxLayout.FIX) {
                            modelRightWidth += component.getWidth();
                        }
                        else {
                            modelRightWidth += component.getMinimumSize().getWidth();
                        }
                    }
                }

                minLocation = reversed ? modelRightWidth : modelLeftWidth;
                maxLocation = _jideSplitPane.getWidth() - ((reversed ? modelLeftWidth : modelRightWidth) + (int) getSize().getWidth());
                if (maxLocation < minLocation) minLocation = maxLocation = 0;
            }
        }


        /**
         * Returns true if the dragging session is valid.
         *
         * @return true or false.
         */
        protected boolean isValid() {
            return (maxLocation > 0);
        }


        /**
         * Returns the new position to put the divider at based on the passed in MouseEvent.
         *
         * @param e the mouse event.
         * @return the position of the mouse event after considering the max and min size it is allowed.
         */
        protected int positionForMouseEvent(MouseEvent e) {
            int newX = (e.getSource() == JideSplitPaneDivider.this) ? (e.getX() + getLocation().x) : e.getX();
            newX = Math.min(maxLocation, Math.max(minLocation, newX - offset));
            if (_jideSplitPane.getDividerStepSize() != 0) {
                int distanceFromCurrent = newX - getX();
                newX -= (distanceFromCurrent % _jideSplitPane.getDividerStepSize());
            }

            return newX;
        }


        /**
         * Returns the x argument, since this is used for horizontal splits.
         *
         * @param x x position
         * @param y y position
         * @return the actual position after considering the max and min size it is allowed.
         */
        protected int getNeededLocation(int x, int y) {
            int newX;
            newX = Math.min(maxLocation, Math.max(minLocation, x - offset));
            if (_jideSplitPane.getDividerStepSize() != 0) {
                int distanceFromCurrent = newX - getX();
                newX -= (distanceFromCurrent % _jideSplitPane.getDividerStepSize());
            }
            return newX;
        }


        protected void continueDrag(int newX, int newY) {
            dragDividerTo(getNeededLocation(newX, newY));
        }


        /**
         * Messages dragDividerTo with the new location for the mouse event.
         *
         * @param e the mouse event.
         */
        protected void continueDrag(MouseEvent e) {
            dragDividerTo(positionForMouseEvent(e));
        }


        protected void completeDrag(int x, int y) {
            finishDraggingTo(getNeededLocation(x, y));
        }


        /**
         * Messages finishDraggingTo with the new location for the mouse event.
         *
         * @param e the mouse event.
         */
        protected void completeDrag(MouseEvent e) {
            finishDraggingTo(positionForMouseEvent(e));
        }
    } // End of BasicJideSplitPaneDivider.DragController


    /**
     * Handles the events during a dragging session for a VERTICAL_SPLIT oriented split pane. This continually messages
     * <code>dragDividerTo</code> and then when done messages <code>finishDraggingTo</code>. When an instance is created
     * it should be messaged with <code>isValid</code> to insure that dragging can happen (dragging won't be allowed if
     * the two views can not be resized).
     */
    protected class VerticalDragController extends DragController {
        /* Vertical DragControllers  are now in terms of y, not x. */

        protected VerticalDragController(MouseEvent e) {
            super(e);
            Component leftC = getFirstComponent(false);
            Component rightC = getSecondComponent(false);

            initialLocation = getLocation().y;
            if (e.getSource() == JideSplitPaneDivider.this) {
                offset = e.getY();
            }
            else { // splitPane
                offset = e.getY() - initialLocation;
            }
            if (leftC == null || rightC == null || offset < -1 ||
                    offset >= _jideSplitPane.getSize().height) {
                // Don't allow dragging.
                maxLocation = -1;
            }
            else {
                int index = _jideSplitPane.indexOf(JideSplitPaneDivider.this);
                int modelUpHeight = 0;
                int modelDownHeight = 0;
                for (int i = 0; i < index; i++) {
                    Component component = _jideSplitPane.getComponent(i);
                    if (component instanceof JideSplitPaneDivider) {
                        modelUpHeight += component.getSize().getHeight();
                    }
                    else if (component.isVisible()) {
                        if (((JideBoxLayout) _jideSplitPane.getLayout()).getConstraintMap().get(component) == JideBoxLayout.FIX) {
                            modelUpHeight += component.getHeight();
                        }
                        else {
                            modelUpHeight += component.getMinimumSize().getHeight();
                        }
                    }
                }

                for (int i = index + 1; i < _jideSplitPane.getComponentCount(); i++) {
                    Component component = _jideSplitPane.getComponent(i);
                    if (component instanceof JideSplitPaneDivider) {
                        modelDownHeight += component.getSize().getHeight();
                    }
                    else if (component.isVisible()) {
                        if (((JideBoxLayout) _jideSplitPane.getLayout()).getConstraintMap().get(component) == JideBoxLayout.FIX) {
                            modelDownHeight += component.getHeight();
                        }
                        else {
                            modelDownHeight += component.getMinimumSize().getHeight();
                        }
                    }
                }

                minLocation = modelUpHeight;
                maxLocation = _jideSplitPane.getHeight() - modelDownHeight - (int) getSize().getHeight();
                if (maxLocation < minLocation) minLocation = maxLocation = 0;
            }
        }


        /**
         * Returns the y argument, since this is used for vertical splits.
         */
        @Override
        protected int getNeededLocation(int x, int y) {
            int newY;
            newY = Math.min(maxLocation, Math.max(minLocation, y - offset));
            if (_jideSplitPane.getDividerStepSize() != 0) {
                int distanceFromCurrent = newY - getY();
                newY -= (distanceFromCurrent % _jideSplitPane.getDividerStepSize());
            }
            return newY;
        }


        /**
         * Returns the new position to put the divider at based on the passed in MouseEvent.
         */
        @Override
        protected int positionForMouseEvent(MouseEvent e) {
            int newY = (e.getSource() == JideSplitPaneDivider.this) ? (e.getY() + getLocation().y) : e.getY();
            newY = Math.min(maxLocation, Math.max(minLocation, newY - offset));
            if (_jideSplitPane.getDividerStepSize() != 0) {
                int distanceFromCurrent = newY - getY();
                newY -= (distanceFromCurrent % _jideSplitPane.getDividerStepSize());
            }
            return newY;
        }
    } // End of BasicSplitPaneDividier.VerticalDragController

    /*
     * Added on 05/14/2008 in response to http://www.jidesoft.com/forum/viewtopic.php?p=26074#26074,
     * http://www.jidesoft.com/forum/viewtopic.php?p=5148#5148 and
     * http://www.jidesoft.com/forum/viewtopic.php?p=23403#23403.
     *
     * The addition below provides the option of adding a one-touch button which is capable of expanding/collapsing the
     * split pane (in one click of the mouse).
     *
     * @see #setOneTouchExpandable(boolean)
     */

    /**
     * Indicates that the pane of the left of this component has been collapse by the one-touch button.
     */
    public static final int COLLAPSED_STATE = 0;

    /**
     * Indicates that this divider has not been expanded or collapsed.
     */
    public static final int DEFAULT_STATE = 1;

    /**
     * Indicates that the pane of the right of this component has been collapse by the one-touch button. Hence, the pane
     * on the left has been "expanded".
     */
    public static final int EXPANDED_STATE = 2;

    /**
     * Indicates the current state of this divider. Either expanded, collapsed or in its default state.
     */
    private int _currentState = DEFAULT_STATE;

    /**
     * Button for quickly toggling the left component.
     */
    protected JButton _leftButton = null;

    /**
     * Button for quickly toggling the right component.
     */
    protected JButton _rightButton = null;

    /**
     * Used to paint the triangle on the one-touch buttons.
     */
    private int _triangleSize = 5;

    /**
     * Used as the one-touch button's width.
     */
    private int _buttonWidth = 5;

    /**
     * Used as the one-touch button's height.
     */
    private int _buttonHeight = 10;

    /**
     * The last non-expanded/collapsed position of the divider. We want to keep track of the dividers last position, so
     * if a user collapses the pane on the right of this divider for example, pressing the expand button will revert the
     * divider back to its original location - the last non-expanded/collapsed position.
     *
     * @see JideSplitPaneDivider.OneTouchActionHandler#actionPerformed
     */
    private int _lastPosition;

    /**
     * Invoked when the oneTouchExpandable value of the JideSplitPane changes.<p> </p> Responsible for creating the
     * one-touch buttons and revalidating the UI.
     * <p/>
     * #see JideSplitePane#setOneTouchExpandable(boolean)
     */
    protected void oneTouchExpandableChanged() {

        if (_jideSplitPane.isOneTouchExpandable() && _leftButton == null) {
            _leftButton = createLeftOneTouchButton();
            if (_leftButton != null) {
                _leftButton.addActionListener(new OneTouchActionHandler(true));
                if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
                    _leftButton.setBounds(1, 10, _buttonWidth, _buttonHeight);
                }
                else if (_orientation == JideSplitPane.VERTICAL_SPLIT) {
                    //noinspection SuspiciousNameCombination
                    _leftButton.setBounds(10, 1, _buttonHeight, _buttonWidth);
                }
                add(_leftButton);
            }
        }

        if (_jideSplitPane.isOneTouchExpandable() && _rightButton == null) {
            _rightButton = createRightOneTouchButton();
            if (_rightButton != null) {
                _rightButton.addActionListener(new OneTouchActionHandler(false));
                if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
                    _rightButton.setBounds(1, 25, _buttonWidth, _buttonHeight);
                }
                else if (_orientation == JideSplitPane.VERTICAL_SPLIT) {
                    //noinspection SuspiciousNameCombination
                    _rightButton.setBounds(25, 1, _buttonHeight, _buttonWidth);
                }
                add(_rightButton);
            }
        }

        if (!(_jideSplitPane.isOneTouchExpandable()) && _leftButton != null) {
            remove(_leftButton);
            _leftButton = null;
        }
        if (!(_jideSplitPane.isOneTouchExpandable()) && _rightButton != null) {
            remove(_rightButton);
            _rightButton = null;
        }

        /*
         * The one-touch buttons should completely collapse the pane in question; as such, all panes should have a
         * their minimum sizes overridden whilst one-touch expandable is on.
         *
         * We fill the minimunSizes array with the current minimum size of each pane for restoration later.
         */
        int paneCount = _jideSplitPane.getPaneCount();
        if (_jideSplitPane.isOneTouchExpandable()) {
            for (int i = 0; i < paneCount; i++) {
                Component component = _jideSplitPane.getPaneAt(i);
                PortingUtils.setMinimumSize(component, new Dimension(0, 0));
            }
        }
        else {
            for (int i = 0; i < paneCount; i++) {
                Component component = _jideSplitPane.getPaneAt(i);
                PortingUtils.setMinimumSize(component, null);
            }
        }
    }

    /**
     * Builds the Button that can be used to collapse the component to the left/above this divider.
     *
     * @return a JButton instance used to collapse the component to the left/above this divider.
     */
    protected JButton createLeftOneTouchButton() {
        JButton b = new JButton() {
            @Override
            public void setBorder(Border b) {
            }

            @Override
            public void paint(Graphics g) {
                if (_jideSplitPane != null) {
                    g.setColor(JideSplitPaneDivider.this.getBackground());
                    if (isOpaque()) {
                        g.fillRect(0, 0, this.getWidth(), this.getHeight());
                    }

                    if (_jideSplitPane.getLeftOneTouchButtonImageIcon() != null) {
                        _jideSplitPane.getLeftOneTouchButtonImageIcon().paintIcon(this, g, 0, 0);
                    }
                    else if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {

                        /*
                         * If the split pane is horizontally split, paint the 'left' button.
                         */
                        g.setColor(getDarkShadowColor());
                        int size = _triangleSize;
                        for (int i = 0; i < size; i++) {
                            g.drawLine(i, size - i, i, size + i);
                        }
                    }
                    else if (_orientation == JideSplitPane.VERTICAL_SPLIT) {

                        /*
                         * If the split pane is vertically split, paint an 'up' button.
                         */
                        g.setColor(getDarkShadowColor());
                        int size = _triangleSize;
                        for (int i = 0; i < size; i++) {
                            g.drawLine(size - i, i, size + i, i);
                        }
                    }
                }
            }

            @SuppressWarnings({"deprecation"})
            @Override
            public boolean isFocusTraversable() {
                return false;
            }
        };
        b.setMinimumSize(new Dimension(_buttonWidth, _buttonHeight));
        b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        b.setFocusPainted(false);
        b.setBorderPainted(false);
        b.setRequestFocusEnabled(false);
        return b;
    }

    /**
     * Gets the current collapse/expand state.
     * <p/>
     * The default value is DEFAULT_STATE when {@link com.jidesoft.swing.JideSplitPane#isOneTouchExpandable()} returns false.
     * It could be EXPANDED_STATE or COLLAPSED_STATE when one touch expandable is activated.
     *
     * @return the current state
     * @since 3.3.8
     */
    public int getCurrentState() {
        return _currentState;
    }

    /**
     * Builds the rightButton that can be used to expand/collapse a split panes divider to the right.
     *
     * @return a JButton instance used to expand/collapse a split panes divider to the right.
     */
    protected JButton createRightOneTouchButton() {
        JButton b = new JButton() {
            @Override
            public void setBorder(Border b) {
            }

            @Override
            public void paint(Graphics g) {
                if (_jideSplitPane != null) {
                    g.setColor(JideSplitPaneDivider.this.getBackground());
                    if (isOpaque()) {
                        g.fillRect(0, 0, this.getWidth(), this.getHeight());
                    }

                    if (_jideSplitPane.getRightOneTouchButtonImageIcon() != null) {
                        _jideSplitPane.getRightOneTouchButtonImageIcon().paintIcon(this, g, 0, 0);
                    }
                    else if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {

                        /*
                         * If the split pane is horizontally split, paint the 'right' button.
                         */
                        g.setColor(getDarkShadowColor());
                        int size = _triangleSize;
                        int j = 0;
                        for (int i = size - 1; i >= 0; i--) {
                            g.drawLine(j, size - i, j, size + i);
                            j++;
                        }
                    }
                    else if (_orientation == JideSplitPane.VERTICAL_SPLIT) {

                        /*
                         * If the split pane is vertically split, paint an 'down' button.
                         */
                        g.setColor(getDarkShadowColor());
                        int size = _triangleSize;
                        int j = 0;
                        for (int i = size - 1; i >= 0; i--) {
                            g.drawLine(size - i, j, size + i, j);
                            j++;
                        }
                    }
                }
            }

            @SuppressWarnings({"deprecation"})
            @Override
            public boolean isFocusTraversable() {
                return false;
            }
        };
        b.setMinimumSize(new Dimension(_buttonWidth, _buttonHeight));
        b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        b.setFocusPainted(false);
        b.setBorderPainted(false);
        b.setRequestFocusEnabled(false);
        return b;
    }

    /**
     * Returns a dark shadow color. This color is used to paint the left and right buttons graphics. It is based on the
     * current Look and Feel. (And thus fits all look and Feels.)
     *
     * @return UIManager.getColor("controlDkShadow")
     */
    protected Color getDarkShadowColor() {
        return UIManager.getColor("controlDkShadow");
    }

    /**
     * The actionListener that will listen for button presses on either the leftButton or the rightButton. This class is
     * responsible for one-touch expanding/collapsing of the divider.
     */
    protected class OneTouchActionHandler implements ActionListener {

        /**
         * If <code>collapse</code> is true, move the divider to the left/top; otherwise, move the divider to the
         * right/bottom.
         */
        private boolean _collapse;

        /**
         * Constructs the handler responsible for expanding/collapsing the panes on either side of this divider.
         *
         * @param collapse true or false.
         */
        public OneTouchActionHandler(boolean collapse) {
            _collapse = collapse;
        }

        /**
         * Called to collapse or expand this divider.
         */
        public void actionPerformed(ActionEvent e) {

            /*
             * If _collapse is true, move the divider to the left/top; otherwise, move the divider to the right/bottom.
             */

            /*
             * If the left button is pressed. (If _collapse is true.)
             */
            if (_collapse) {
                if (_currentState == COLLAPSED_STATE) {
                    int indexOfDivider = _jideSplitPane.indexOfDivider(JideSplitPaneDivider.this);
                    int dividerPosition = _jideSplitPane.getDividerLocation(indexOfDivider);
                    int previousDividerPosition = getPreviousDividerLocation(true, false);
                    if (dividerPosition != previousDividerPosition) {
                        _currentState = DEFAULT_STATE;
                    }
                }

                if (_currentState == EXPANDED_STATE) {
                    _jideSplitPane.setDividerLocation(JideSplitPaneDivider.this, _lastPosition);
                    _currentState = DEFAULT_STATE;
                }
                else if (_currentState == DEFAULT_STATE) {
                    int indexOfDivider = _jideSplitPane.indexOfDivider(JideSplitPaneDivider.this);
                    _lastPosition = _jideSplitPane.getDividerLocation(indexOfDivider);
                    int loc = getPreviousDividerLocation(true, false);
                    _jideSplitPane.setDividerLocation(JideSplitPaneDivider.this, loc);
                    _currentState = COLLAPSED_STATE;
                }
            }
            /*
             * If the right button is pressed. (If _collapse is false.)
             */
            else {
                if (_currentState == EXPANDED_STATE) {
                    int indexOfDivider = _jideSplitPane.indexOfDivider(JideSplitPaneDivider.this);
                    int dividerPosition = _jideSplitPane.getDividerLocation(indexOfDivider);
                    int nextDividerPosition = getNextDividerLocation(true, false);
                    if (dividerPosition != nextDividerPosition) {
                        _currentState = DEFAULT_STATE;
                    }
                }

                if (_currentState == COLLAPSED_STATE) {
                    _jideSplitPane.setDividerLocation(JideSplitPaneDivider.this, _lastPosition);
                    _currentState = DEFAULT_STATE;
                }
                else if (_currentState == DEFAULT_STATE) {
                    int indexOfDivider = _jideSplitPane.indexOfDivider(JideSplitPaneDivider.this);
                    _lastPosition = _jideSplitPane.getDividerLocation(indexOfDivider);
                    int loc = getNextDividerLocation(true, false);
                    _jideSplitPane.setDividerLocation(JideSplitPaneDivider.this, loc);
                    _currentState = EXPANDED_STATE;
                }
            }
        }
    }

    /**
     * Collapses the divider to the left side (or to the top if vertically).
     */
    public void collapse() {
        if (_leftButton != null) {
            _leftButton.doClick();
        }
        else {
            new OneTouchActionHandler(true).actionPerformed(null);
        }
    }

    /**
     * Expands the divider to the right side (or to the bottom if vertically).
     */
    public void expand() {
        if (_rightButton != null) {
            _rightButton.doClick();
        }
        else {
            new OneTouchActionHandler(false).actionPerformed(null);
        }
    }

    /*
     * End of one-touch expand/collapse addition.
     *
     * Added on 05/14/2008.
     */
}
