/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include <tools/rc.h>
#include <tools/poly.hxx>

#include <vcl/event.hxx>
#include <vcl/split.hxx>
#include <vcl/svapp.hxx>
#include <vcl/syswin.hxx>
#include <vcl/taskpanelist.hxx>
#include <vcl/gradient.hxx>
#include <vcl/lineinfo.hxx>

#include <rtl/instance.hxx>

#include <window.h>

namespace 
{ 
    struct ImplBlackWall
        : public rtl::StaticWithInit<Wallpaper, ImplBlackWall> {
        Wallpaper operator () () {
            return Wallpaper(COL_BLACK);
        }
    };
    struct ImplWhiteWall
        : public rtl::StaticWithInit<Wallpaper, ImplWhiteWall> {
        Wallpaper operator () () {
            return Wallpaper(COL_LIGHTGRAY);
        }
    }; 
}

// =======================================================================

void Splitter::ImplInitSplitterData()
{
    ImplGetWindowImpl()->mbSplitter        = sal_True;
	mpRefWin		  = NULL;
	mnSplitPos		  = 0;
	mnLastSplitPos	  = 0;
	mnStartSplitPos   = 0;
	mbDragFull		  = sal_False;
    mbKbdSplitting    = sal_False;
    mbInKeyEvent      = 0;
    mnKeyboardStepSize = SPLITTER_DEFAULTSTEPSIZE;
}

// -----------------------------------------------------------------------

void Splitter::ImplInitHorVer(bool bNew)
{
    if(bNew != (bool)mbHorzSplit)
    {
        mbHorzSplit = bNew;

	    PointerStyle ePointerStyle;
    	const StyleSettings& rSettings = GetSettings().GetStyleSettings();

	    if ( mbHorzSplit )
	    {
		    ePointerStyle = POINTER_HSPLIT;
		    SetSizePixel( Size( rSettings.GetSplitSize(), rSettings.GetScrollBarSize() ) );
	    }
	    else
	    {
		    ePointerStyle = POINTER_VSPLIT;
		    SetSizePixel( Size( rSettings.GetScrollBarSize(), rSettings.GetSplitSize() ) );
	    }

	    SetPointer( Pointer( ePointerStyle ) );
    }
}

// -----------------------------------------------------------------------

void Splitter::ImplInit( Window* pParent, WinBits nWinStyle )
{
	Window::ImplInit( pParent, nWinStyle, NULL );

	mpRefWin = pParent;

    ImplInitHorVer(nWinStyle & WB_HSCROLL);

    if( GetSettings().GetStyleSettings().GetFaceColor().IsDark() )
	    SetBackground( ImplWhiteWall::get() );
    else 
	    SetBackground( ImplBlackWall::get() );

    TaskPaneList *pTList = GetSystemWindow()->GetTaskPaneList();
    pTList->AddWindow( this ); 
}

// -----------------------------------------------------------------------

void Splitter::ImplSplitMousePos( Point& rPos )
{
	if ( mbHorzSplit )
	{
		if ( rPos.X() > maDragRect.Right()-1 )
			rPos.X() = maDragRect.Right()-1;
		if ( rPos.X() < maDragRect.Left()+1 )
			rPos.X() = maDragRect.Left()+1;
	}
	else
	{
		if ( rPos.Y() > maDragRect.Bottom()-1 )
			rPos.Y() = maDragRect.Bottom()-1;
		if ( rPos.Y() < maDragRect.Top()+1 )
			rPos.Y() = maDragRect.Top()+1;
	}
}

// -----------------------------------------------------------------------

void Splitter::ImplDrawSplitter()
{
	Rectangle aInvRect( maDragRect );

	if ( mbHorzSplit )
	{
		aInvRect.Left() 	= maDragPos.X() - 1;
		aInvRect.Right()	= maDragPos.X() + 1;
	}
	else
	{
		aInvRect.Top()		= maDragPos.Y() - 1;
		aInvRect.Bottom()	= maDragPos.Y() + 1;
	}

	mpRefWin->InvertTracking( mpRefWin->PixelToLogic(aInvRect), SHOWTRACK_SPLIT );
}

// -----------------------------------------------------------------------

Splitter::Splitter( Window* pParent, WinBits nStyle ) :
	Window( WINDOW_SPLITTER )
{
	ImplInitSplitterData();
	ImplInit( pParent, nStyle );
}

// -----------------------------------------------------------------------

Splitter::Splitter( Window* pParent, const ResId& rResId ) :
	Window( WINDOW_SPLITTER )
{
	ImplInitSplitterData();
	rResId.SetRT( RSC_SPLITTER );
	WinBits nStyle = ImplInitRes( rResId );
	ImplInit( pParent, nStyle );
	ImplLoadRes( rResId );

	if ( !(nStyle & WB_HIDE) )
		Show();
}

// -----------------------------------------------------------------------

Splitter::~Splitter()
{
    TaskPaneList *pTList = GetSystemWindow()->GetTaskPaneList();
    pTList->RemoveWindow( this ); 
}

// -----------------------------------------------------------------------

void Splitter::SetHorizontal(bool bNew)
{
    if(bNew != (bool)mbHorzSplit)
    {
        ImplInitHorVer(bNew);
    }
}

// -----------------------------------------------------------------------

void Splitter::SetKeyboardStepSize( long nStepSize )
{
    mnKeyboardStepSize = nStepSize;
}

// -----------------------------------------------------------------------

long Splitter::GetKeyboardStepSize() const
{
    return mnKeyboardStepSize;
}
 
// -----------------------------------------------------------------------

Splitter* Splitter::ImplFindSibling()
{
    // look for another splitter with the same parent but different orientation
    Window *pWin = GetParent()->GetWindow( WINDOW_FIRSTCHILD );
    Splitter *pSplitter = NULL;
    while( pWin )
    {
        if( pWin->ImplIsSplitter() )
        {
            pSplitter = (Splitter*) pWin;
            if( pSplitter != this && IsHorizontal() != pSplitter->IsHorizontal() )
                return pSplitter;
        }
        pWin = pWin->GetWindow( WINDOW_NEXT );
    }
    return NULL;
}

// -----------------------------------------------------------------------
    
sal_Bool Splitter::ImplSplitterActive()
{
    // is splitter in document or at scrollbar handle ?

    sal_Bool bActive = sal_True;
	const StyleSettings& rSettings = GetSettings().GetStyleSettings();
	long nA = rSettings.GetScrollBarSize();
	long nB = rSettings.GetSplitSize();

    Size aSize = GetOutputSize();
	if ( mbHorzSplit )
	{
        if( aSize.Width() == nB && aSize.Height() == nA )
            bActive = sal_False;
	}
	else
	{
        if( aSize.Width() == nA && aSize.Height() == nB )
            bActive = sal_False;
	}
    return bActive;
}

// -----------------------------------------------------------------------

void Splitter::MouseButtonDown( const MouseEvent& rMEvt )
{
	if ( rMEvt.GetClicks() == 2 )
	{
		if ( mnLastSplitPos != mnSplitPos )
		{
			StartSplit();
			Point aPos = rMEvt.GetPosPixel();
			if ( mbHorzSplit )
				aPos.X() = mnLastSplitPos;
			else
				aPos.Y() = mnLastSplitPos;
			ImplSplitMousePos( aPos );
			Splitting( aPos );
			ImplSplitMousePos( aPos );
			long nTemp = mnSplitPos;
			if ( mbHorzSplit )
				SetSplitPosPixel( aPos.X() );
			else
				SetSplitPosPixel( aPos.Y() );
			mnLastSplitPos = nTemp;
			Split();
			EndSplit();
		}
	}
	else
		StartDrag();
}

// -----------------------------------------------------------------------

void Splitter::Tracking( const TrackingEvent& rTEvt )
{
	if ( rTEvt.IsTrackingEnded() )
	{
		if ( !mbDragFull )
			ImplDrawSplitter();

		if ( !rTEvt.IsTrackingCanceled() )
		{
			long nNewPos;
			if ( mbHorzSplit )
				nNewPos = maDragPos.X();
			else
				nNewPos = maDragPos.Y();
			if ( nNewPos != mnStartSplitPos )
			{
				SetSplitPosPixel( nNewPos );
				mnLastSplitPos = 0;
				Split();
			}
			EndSplit();
		}
		else if ( mbDragFull )
		{
			SetSplitPosPixel( mnStartSplitPos );
			Split();
		}
		mnStartSplitPos = 0;
	}
	else
	{
		//Point aNewPos = mpRefWin->ScreenToOutputPixel( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
		Point aNewPos = mpRefWin->NormalizedScreenToOutputPixel( OutputToNormalizedScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
		ImplSplitMousePos( aNewPos );
		Splitting( aNewPos );
		ImplSplitMousePos( aNewPos );

		if ( mbHorzSplit )
		{
			if ( aNewPos.X() == maDragPos.X() )
				return;
		}
		else
		{
			if ( aNewPos.Y() == maDragPos.Y() )
				return;
		}

		if ( mbDragFull )
		{
			maDragPos = aNewPos;
			long nNewPos;
			if ( mbHorzSplit )
				nNewPos = maDragPos.X();
			else
				nNewPos = maDragPos.Y();
			if ( nNewPos != mnSplitPos )
			{
				SetSplitPosPixel( nNewPos );
				mnLastSplitPos = 0;
				Split();
			}

			GetParent()->Update();
		}
		else
		{
			ImplDrawSplitter();
			maDragPos = aNewPos;
			ImplDrawSplitter();
		}
	}
}

// -----------------------------------------------------------------------

void Splitter::ImplKbdTracking( KeyCode aKeyCode )
{
    sal_uInt16 nCode = aKeyCode.GetCode();
	if ( nCode == KEY_ESCAPE || nCode == KEY_RETURN )
	{
        if( !mbKbdSplitting )
            return;
        else
            mbKbdSplitting = sal_False;

		if ( nCode != KEY_ESCAPE )
		{
			long nNewPos;
			if ( mbHorzSplit )
				nNewPos = maDragPos.X();
			else
				nNewPos = maDragPos.Y();
			if ( nNewPos != mnStartSplitPos )
			{
				SetSplitPosPixel( nNewPos );
				mnLastSplitPos = 0;
				Split();
			}
		}
		else
		{
			SetSplitPosPixel( mnStartSplitPos );
			Split();
			EndSplit();
		}
		mnStartSplitPos = 0;
	}
	else
	{
		Point aNewPos;
        Size aSize = mpRefWin->GetOutputSize();
        Point aPos = GetPosPixel();
        // depending on the position calc allows continuous moves or snaps to row/columns
        // continuous mode is active when position is at the origin or end of the splitter
        // otherwise snap mode is active
        // default here is snap, holding shift sets continuous mode
        if( mbHorzSplit )
            aNewPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aKeyCode.IsShift() ? 0 : aSize.Height()/2);
        else
            aNewPos = Point( aKeyCode.IsShift() ? 0 : aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );

        Point aOldWindowPos = GetPosPixel();

        int maxiter = 500;  // avoid endless loop
        int delta=0;
        int delta_step = mbHorzSplit  ? aSize.Width()/10 : aSize.Height()/10;
        
        // use the specified step size if it was set
        if( mnKeyboardStepSize != SPLITTER_DEFAULTSTEPSIZE )
            delta_step = mnKeyboardStepSize;

        while( maxiter-- && aOldWindowPos == GetPosPixel() )
        {
            // inc/dec position until application performs changes
            // thus a single key press really moves the splitter
            if( aKeyCode.IsShift() )
                delta++;
            else
                delta += delta_step;

            switch( nCode )
            {
            case KEY_LEFT:  
                aNewPos.X()-=delta;
                break;
            case KEY_RIGHT:  
                aNewPos.X()+=delta;
                break;
            case KEY_UP:  
                aNewPos.Y()-=delta;
                break;
            case KEY_DOWN:  
                aNewPos.Y()+=delta;
                break;
            default:
                maxiter = 0;    // leave loop
                break;
            }
		    ImplSplitMousePos( aNewPos );
		    Splitting( aNewPos );
		    ImplSplitMousePos( aNewPos );

		    if ( mbHorzSplit )
		    {
			    if ( aNewPos.X() == maDragPos.X() )
				    continue;
		    }
		    else
		    {
			    if ( aNewPos.Y() == maDragPos.Y() )
				    continue;
		    }
        
			maDragPos = aNewPos;
			long nNewPos;
			if ( mbHorzSplit )
				nNewPos = maDragPos.X();
			else
				nNewPos = maDragPos.Y();
			if ( nNewPos != mnSplitPos )
			{
				SetSplitPosPixel( nNewPos );
				mnLastSplitPos = 0;
				Split();
			}
			GetParent()->Update();
        }
	}
}

// -----------------------------------------------------------------------

void Splitter::StartSplit()
{
	maStartSplitHdl.Call( this );
}

// -----------------------------------------------------------------------

void Splitter::Split()
{
	maSplitHdl.Call( this );
}

// -----------------------------------------------------------------------

void Splitter::EndSplit()
{
	if ( maEndSplitHdl.IsSet() )
		maEndSplitHdl.Call( this );
}

// -----------------------------------------------------------------------

void Splitter::Splitting( Point& /* rSplitPos */ )
{
}

// -----------------------------------------------------------------------

void Splitter::SetDragRectPixel( const Rectangle& rDragRect, Window* _pRefWin )
{
	maDragRect = rDragRect;
	if ( !_pRefWin )
		mpRefWin = GetParent();
	else
		mpRefWin = _pRefWin;
}

// -----------------------------------------------------------------------

void Splitter::SetSplitPosPixel( long nNewPos )
{
	mnSplitPos = nNewPos;
}

// -----------------------------------------------------------------------

void Splitter::SetLastSplitPosPixel( long nNewPos )
{
	mnLastSplitPos = nNewPos;
}

// -----------------------------------------------------------------------

void Splitter::StartDrag()
{
	if ( IsTracking() )
		return;

	StartSplit();

	// Tracking starten
	StartTracking();

	// Start-Positon ermitteln
	maDragPos = mpRefWin->GetPointerPosPixel();
	ImplSplitMousePos( maDragPos );
	Splitting( maDragPos );
	ImplSplitMousePos( maDragPos );
	if ( mbHorzSplit )
		mnStartSplitPos = maDragPos.X();
	else
		mnStartSplitPos = maDragPos.Y();

	mbDragFull = (Application::GetSettings().GetStyleSettings().GetDragFullOptions() & DRAGFULL_OPTION_SPLIT) != 0;
	if ( !mbDragFull )
		ImplDrawSplitter();
}


// -----------------------------------------------------------------------

void Splitter::ImplStartKbdSplitting()
{
    if( mbKbdSplitting )
        return;

    mbKbdSplitting = sal_True;

	StartSplit();

	// determine start position 
    // because we have no mouse position we take either the position
    // of the splitter window or the last split position
    // the other coordinate is just the center of the reference window
    Size aSize = mpRefWin->GetOutputSize();
    Point aPos = GetPosPixel();
    if( mbHorzSplit )
        maDragPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aSize.Height()/2 );
    else
        maDragPos = Point( aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );
	ImplSplitMousePos( maDragPos );
	Splitting( maDragPos );
	ImplSplitMousePos( maDragPos );
	if ( mbHorzSplit )
		mnStartSplitPos = maDragPos.X();
	else
		mnStartSplitPos = maDragPos.Y();
}

// -----------------------------------------------------------------------

void Splitter::ImplRestoreSplitter()
{
    // set splitter in the center of the ref window
    StartSplit();
    Size aSize = mpRefWin->GetOutputSize();
    Point aPos = Point( aSize.Width()/2 , aSize.Height()/2);
    if ( mnLastSplitPos != mnSplitPos && mnLastSplitPos > 5 )
    {
        // restore last pos if it was a useful position (>5)
	    if ( mbHorzSplit )
		    aPos.X() = mnLastSplitPos;
	    else
		    aPos.Y() = mnLastSplitPos;
    }

    ImplSplitMousePos( aPos );
    Splitting( aPos );
    ImplSplitMousePos( aPos );
    long nTemp = mnSplitPos;
    if ( mbHorzSplit )
	    SetSplitPosPixel( aPos.X() );
    else
	    SetSplitPosPixel( aPos.Y() );
    mnLastSplitPos = nTemp;
    Split();
	EndSplit();
}


// -----------------------------------------------------------------------

void Splitter::GetFocus()
{
    if( !ImplSplitterActive() )
        ImplRestoreSplitter();

    Invalidate();
}

// -----------------------------------------------------------------------

void Splitter::LoseFocus()
{
    if( mbKbdSplitting )
    {
        KeyCode aReturnKey( KEY_RETURN );
        ImplKbdTracking( aReturnKey );
        mbKbdSplitting = sal_False;
    }
    Invalidate();
}

// -----------------------------------------------------------------------

void Splitter::KeyInput( const KeyEvent& rKEvt )
{
    if( mbInKeyEvent )
        return;

    mbInKeyEvent = 1;

    Splitter *pSibling = ImplFindSibling();
    KeyCode aKeyCode = rKEvt.GetKeyCode();
    sal_uInt16 nCode = aKeyCode.GetCode();
    switch ( nCode )
    {
        case KEY_UP:
        case KEY_DOWN:
            if( !mbHorzSplit )
            {
                ImplStartKbdSplitting();
                ImplKbdTracking( aKeyCode );
            }
            else
            {
                if( pSibling )
                {
                    pSibling->GrabFocus();
                    pSibling->KeyInput( rKEvt );
                }
            }
            break;
        case KEY_RIGHT:
        case KEY_LEFT:
            if( mbHorzSplit )
            {
                ImplStartKbdSplitting();
                ImplKbdTracking( aKeyCode );
            }
            else
            {
                if( pSibling )
                {
                    pSibling->GrabFocus();
                    pSibling->KeyInput( rKEvt );
                }
            }
            break;

        case KEY_DELETE:
            if( ImplSplitterActive() )
            {
                if( mbKbdSplitting )
                {
                    KeyCode aKey( KEY_ESCAPE );
                    ImplKbdTracking( aKey );
                }

			    StartSplit();
			    Point aPos;
			    if ( mbHorzSplit )
				    aPos.X() = 0;
			    else
				    aPos.Y() = 0;
			    ImplSplitMousePos( aPos );
			    Splitting( aPos );
			    ImplSplitMousePos( aPos );
			    long nTemp = mnSplitPos;
			    if ( mbHorzSplit )
				    SetSplitPosPixel( aPos.X() );
			    else
				    SetSplitPosPixel( aPos.Y() );
			    mnLastSplitPos = nTemp;
			    Split();
				EndSplit();

                // Shift-Del deletes both splitters
                if( aKeyCode.IsShift() && pSibling )
                    pSibling->KeyInput( rKEvt );

                GrabFocusToDocument();
            }
            break;

        case KEY_ESCAPE:
            if( mbKbdSplitting )
                ImplKbdTracking( aKeyCode );
            else
                GrabFocusToDocument();
            break;

        case KEY_RETURN:
            ImplKbdTracking( aKeyCode );
            GrabFocusToDocument();
            break;
        default:    // let any key input fix the splitter
            Window::KeyInput( rKEvt );
            GrabFocusToDocument();
            break;
    }
    mbInKeyEvent = 0;    
}

// -----------------------------------------------------------------------

long Splitter::Notify( NotifyEvent& rNEvt )
{
    return Window::Notify( rNEvt );
}

// -----------------------------------------------------------------------

void Splitter::DataChanged( const DataChangedEvent& rDCEvt )
{
    Window::DataChanged( rDCEvt );
    if( rDCEvt.GetType() == DATACHANGED_SETTINGS )
    {
        Color oldFaceColor = ((AllSettings *) rDCEvt.GetData())->GetStyleSettings().GetFaceColor();
        Color newFaceColor = Application::GetSettings().GetStyleSettings().GetFaceColor();
        if( oldFaceColor.IsDark() != newFaceColor.IsDark() )
        {
            if( newFaceColor.IsDark() )
	            SetBackground( ImplWhiteWall::get() );
            else 
	            SetBackground( ImplBlackWall::get() );
        }
    }
}

// -----------------------------------------------------------------------

void Splitter::Paint( const Rectangle& rPaintRect )
{
    if( HasFocus() || mbKbdSplitting )
    {   
        Color oldFillCol = GetFillColor();
        Color oldLineCol = GetLineColor();

        SetLineColor();
        SetFillColor( GetSettings().GetStyleSettings().GetFaceColor() );
        DrawRect( rPaintRect );

        Color aSelectionBorderCol( GetSettings().GetStyleSettings().GetActiveColor() );
        SetFillColor( aSelectionBorderCol );
        SetLineColor();

	    Polygon aPoly( rPaintRect );
		PolyPolygon aPolyPoly( aPoly );
		DrawTransparent( aPolyPoly, 85 );

        SetLineColor( aSelectionBorderCol );
        SetFillColor();

        if( mbKbdSplitting )
        {
            LineInfo aInfo( LINE_DASH );
            //aInfo.SetDashLen( 2 );
            //aInfo.SetDashCount( 1 );
            aInfo.SetDistance( 1 );
            aInfo.SetDotLen( 2 );
            aInfo.SetDotCount( 1 );

            DrawPolyLine( aPoly, aInfo );
        }
        else
            DrawRect( rPaintRect );

        SetFillColor( oldFillCol);
        SetLineColor( oldLineCol);
    }
    else
    {
        Window::Paint( rPaintRect );
    }
}
