/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  EikEdwin physics handler
 *
 */

#include <aknphysics.h>
#include <aknphysicsobserveriface.h>
#include <babitflags.h>

#include "eikedwin.h"
#include "aknedwinphysicshandler.h"


const TInt KMaxWorldSize( 100000000 );  // Maximum world size

// ======== MEMBER FUNCTIONS ========

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::NewL
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler* CAknEdwinPhysicsHandler::NewL( CEikEdwin& aEdwin )
    {
    CAknEdwinPhysicsHandler* self = CAknEdwinPhysicsHandler::NewLC( aEdwin );
    CleanupStack::Pop( self );
    return self;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::NewLC
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler* CAknEdwinPhysicsHandler::NewLC( CEikEdwin& aEdwin )
    {
    CAknEdwinPhysicsHandler* self = new ( ELeave ) CAknEdwinPhysicsHandler(
            aEdwin );
    CleanupStack::PushL( self );
    self->ConstructL();
    return self;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::NewL
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler* CAknEdwinPhysicsHandler::NewL( CEikEdwin& aEdwin, CAknPhysics* aPhysics )
    {
    CAknEdwinPhysicsHandler* self = CAknEdwinPhysicsHandler::NewLC( aEdwin, aPhysics );
    CleanupStack::Pop( self );
    return self;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::NewLC
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler* CAknEdwinPhysicsHandler::NewLC( CEikEdwin& aEdwin, CAknPhysics* aPhysics )
    {
    CAknEdwinPhysicsHandler* self = new ( ELeave ) CAknEdwinPhysicsHandler(
            aEdwin, aPhysics );
    CleanupStack::PushL( self );
    self->ConstructL();
    return self;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::~CAknEdwinPhysicsHandler
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler::~CAknEdwinPhysicsHandler()
    {
    if (iOwnsPhysics)
        {
        delete iPhysics;
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::HandlePointerEvent
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::HandlePointerEvent(
    const TPointerEvent& aPointerEvent )
    {   
    if (iOwnsPhysics)
        {
        if ( aPointerEvent.iType == TPointerEvent::EButton1Down &&
            iViewRect.Contains( aPointerEvent.iPosition ) &&
            iEventUnblockingAllowed )
            {
            BlockEvents( EFalse );
            iPreviousYCoordinate = - KMaxWorldSize;
            }
        if ( iEventsBlocked )
            {
            return;
            }
    
        // Dragging/flicking logic    
        
        if ( aPointerEvent.iType == TPointerEvent::EButton1Down &&
            iViewRect.Contains( aPointerEvent.iPosition ) )
            {
            // Pointer down inside view rect, set flag to allow dragging 
            // and initialize values
            iFlags.Set( EFlagDraggingAllowed );
            iStartPosition = aPointerEvent.iPosition;
            StopPhysics();
    
            iStartTime.HomeTime();
            iDragThresholdExceeded = EFalse;
            iFlags.Clear( EFlagBouncingUp );
            iFlags.Clear( EFlagBouncingDown );
            return;
            }
        if ( iFlags.IsClear( EFlagDraggingAllowed ) )
            {
            return;
            }
    
        if ( aPointerEvent.iType == TPointerEvent::EDrag )
            {
            if ( !iDragThresholdExceeded )
                {
                TInt drag( iStartPosition.iY - aPointerEvent.iPosition.iY );
                if ( Abs( drag ) > DragThreshold() )
                    {
                    iDragThresholdExceeded = ETrue;
                    }
                }
            else
                {
                if ( !iEdwin.ContentFitsToViewRect() )
                    {
                    TInt deltaY( iPrevPosition.iY - aPointerEvent.iPosition.iY );
                    TPoint deltaPoint( 0, deltaY );
                    iPhysics->RegisterPanningPosition( deltaPoint );
                    }
                }
            iPrevPosition = aPointerEvent.iPosition;
            }
        else if ( aPointerEvent.iType == TPointerEvent::EButton1Up )
            {
            if ( iFlags.IsClear( EFlagDraggingAllowed ) )
                {
                return;
                }
            
            iFlags.Clear( EFlagDraggingAllowed );

            if ( !iEdwin.ContentFitsToViewRect() )
                {
                TPoint drag( 0, iStartPosition.iY - aPointerEvent.iPosition.iY );
                iPhysics->StartPhysics( drag, iStartTime );
                }
            }
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::SetViewRect
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::SetViewRect( const TRect& aRect )
    {
    iViewRect = aRect;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::InitPhysicsL
// ---------------------------------------------------------------------------
//   
void CAknEdwinPhysicsHandler::InitPhysicsL()
    {
    if (iOwnsPhysics)
        {
        TSize totalSize( iViewRect.Width(), iViewRect.Height()
                + KMaxWorldSize );
        TSize viewSize( iViewRect.Width(), iViewRect.Height() );
        
        CTextLayout* layout = iEdwin.TextLayout();
        
        if ( layout )
            {
            TInt pixelsAbove = layout->PixelsAboveBand();
            if ( pixelsAbove > 0 )
                {
                MoveScrollIndex( -pixelsAbove );
                }
            } 
        
        iPhysics->InitPhysicsL( totalSize, viewSize, EFalse );
        }
	}

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::MoveScrollIndex
// ---------------------------------------------------------------------------
//     
void CAknEdwinPhysicsHandler::MoveScrollIndex( TInt aPixelsToMove )
    {
    // This is called when user moves content using scrollbar
    
    // Do nothing if handler itself is updating the view
    if ( iFlags.IsSet( EFlagUpdatingView ) )
        {
        return;
        }
    if ( !IsBouncing() )
        {
        iFlags.Clear( EFlagBouncingDown ); 
        iFlags.Clear( EFlagBouncingUp ); 
        }
    iWorldIndex += aPixelsToMove;
    ScrollView( EFalse );
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::EmulationOngoing
// ---------------------------------------------------------------------------
//     
TBool CAknEdwinPhysicsHandler::EmulationOngoing()
    {
    return IsFlicking() || IsBouncing();
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::DragThreshold
// ---------------------------------------------------------------------------
//     
TInt CAknEdwinPhysicsHandler::DragThreshold() const
    {
    return iPhysics->DragThreshold();
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ViewPositionChanged
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::ViewPositionChanged( const TPoint& aNewPosition,
        TBool /*aDrawNow*/, TUint /*aFlags*/)
    {        
    // Skip equal sequential values.
    if ( aNewPosition.iY == iPreviousYCoordinate )
        {
        return;
        }
    iPreviousYCoordinate = aNewPosition.iY;
    
    TInt y( 0 );
    if ( iFlags.IsSet( EFlagBouncingDown ) )
        {
        // We are at the beginning of world
        y = aNewPosition.iY + iBounceDownIndex;
        }
    else if ( iFlags.IsSet( EFlagBouncingUp ) )
        {
        // We are at the end of world
        y = aNewPosition.iY + iBounceUpIndex - KMaxWorldSize;
        }
    else
        {
        // We are at the middle of world
        y = aNewPosition.iY - KMaxWorldSize / 2;
        }

    iWorldIndex = y - iViewRect.Height() / 2;

    // If bounce is ongoing, block all pointer events.
    if ( IsBouncing() )
        {
        BlockEvents( ETrue );
        }
    
    // Check also world indexes here because those might still equal although
    // y coordinates differ due to crossing world boundaries.
    if ( iPreviousWorldIndex != iWorldIndex )
        {
        // Disable background drawing while panning or flicking. Instead,
        // background is drawn in one frame.
        iEdwin.DrawViewBackground( ETrue );
        ScrollView( ETrue );
        iEdwin.DrawViewBackground( EFalse );
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::PhysicEmulationEnded
// ---------------------------------------------------------------------------
//    
void CAknEdwinPhysicsHandler::PhysicEmulationEnded()
    {
    TInt offset = iEdwin.PixelsOutOfBorder();
    if ( offset != 0 )
        {
        // If physics emulation ends and view position is not moved
        // back to original position (to position where bounce started)
        // we adjust it here. This is because aknphysics won't return
        // always to original position.
     
        TPoint newPosition( 0, iPreviousYCoordinate + offset );
        ViewPositionChanged( newPosition, ETrue, 0 );
        }
    
    // Enable pointer events when next pointer down received
    iEventUnblockingAllowed = ETrue;
    
    // Show cursor again if needed   
    iEdwin.RestoreCursorState();
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ViewPosition
// ---------------------------------------------------------------------------
//    
TPoint CAknEdwinPhysicsHandler::ViewPosition() const
    {
    // Aknphysics calls this function to get current view position.
    // We adjust the position based on information if we
    // are are bouncing up or down (in other words we
    // have exceeded end- or beginning of real editor content).
    // In these situations we adjust returned value so that
    // from aknphysic's point of view we seem to be at the beginning
    // or end of almost infinite large world. If we have not exceeded
    // borders of editor content, we are at the middle of this world.
    // So, when we are at the middle of this almost infinite large world,
    // we can in practice add and remove content to editor
    // and we must not tell correct position to aknphysics (we also can't because
    // it seems to be impossible to calculate excatly correct position and size
    // of world if we haven't formatted yet the whole editor content).  

    TInt y( 0 );

    if ( iFlags.IsClear( EFlagBouncingDown )
            && iFlags.IsClear( EFlagBouncingUp ) )
        {
        // We are at the middle of world
        y = iWorldIndex + KMaxWorldSize / 2;
        }
    else if ( iFlags.IsSet( EFlagBouncingDown ) )
        {
        // We are at the beginning of world
        y = iWorldIndex - iBounceDownIndex;
        }
    else if ( iFlags.IsSet( EFlagBouncingUp ) )
        {
        // We are at the end of world
        y = iWorldIndex - iBounceUpIndex +  KMaxWorldSize;
        }

    return TPoint( iViewRect.Width() / 2, y + iViewRect.Height() / 2 );
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::CAknEdwinPhysicsHandler
// ---------------------------------------------------------------------------
//
CAknEdwinPhysicsHandler::CAknEdwinPhysicsHandler( CEikEdwin& aEdwin,
    CAknPhysics* aPhysics ) :
    iWorldIndex( 0 ),
    iPreviousWorldIndex( 0 ),
    iPhysics( aPhysics ),
    iEdwin( aEdwin ),
    iOwnsPhysics( aPhysics == NULL )
    {
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ConstructL
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::ConstructL()
    {
    if (iOwnsPhysics)
        {
        iPhysics = CAknPhysics::NewL( *this, &iEdwin );    
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ScrollView
// ---------------------------------------------------------------------------
//     
void CAknEdwinPhysicsHandler::ScrollView( TBool aAllowBounce )
    {
    iFlags.Set( EFlagUpdatingView );
    if ( iPreviousWorldIndex != iWorldIndex )
        {
        // Calculate amount of pixels to scroll based on previous position
        TInt pixelsToScroll( iPreviousWorldIndex - iWorldIndex );
        if ( pixelsToScroll != 0 )
            {
            if ( iFlags.IsSet( EFlagBouncingDown ) ||
                    iFlags.IsSet( EFlagBouncingUp ) )
                {
                // Bounce ongoing, use function that supports scrolling
                // over the content borders (there can be empty space above or
                // below the editor content)
                TInt restOfPixels( 0 );
                ScrollStepWithBounce( pixelsToScroll, restOfPixels );
                
                // If restOfPixels is not zero, we were bouncing back
                // and exceeded the border again. In this situation
                // we must scroll rest of pixels using function
                // that stops if we again exceed the border. This is needed
                // because we need to know if we exceed the border again
                // (for example if length of content is short) to adjust
                // position on aknphysics world correctly.

                if ( restOfPixels != 0)
                    {
                    ScrollStep( restOfPixels, ETrue );
                    }
                }
            else
                {
                // Bounce not ongoing, use function that stops if we exceed the border
                ScrollStep( pixelsToScroll, aAllowBounce );
                }
            }
        }
    iFlags.Clear( EFlagUpdatingView );
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ScrollStep
// ---------------------------------------------------------------------------
//   
void CAknEdwinPhysicsHandler::ScrollStep( TInt aPixelsToScroll,
    TBool aAllowBounce )
    {
    TInt restOfPixels( 0 );
    TBool borderExceeded( EFalse );

    TInt scrolledPixels = iEdwin.ScrollView( aPixelsToScroll, borderExceeded,
        restOfPixels );
           
    if ( borderExceeded && aAllowBounce )
        {
        if ( aPixelsToScroll != 0 )
            {
            MoveWorldIndex( scrolledPixels );
            
            if ( IsFlicking() )
                {
                // Flicking is ongoing and end of content is arrived.
                // In this situation we stop physics, adjust the view position in the
                // aknphysics (in function ViewPosition) and restart physics with
                // current force and velocity.
                
                iPhysics->SuspendPhysics();
                InitBounce( aPixelsToScroll > 0 );
                ScrollRestOfPixels( restOfPixels );
                iPhysics->ResumePhysics();
                }
            else
                {
                InitBounce( aPixelsToScroll > 0 );
                ScrollRestOfPixels( restOfPixels );
                }
            }
        return;
        }

    MoveWorldIndex( scrolledPixels );
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ScrollStepWithBounce
// ---------------------------------------------------------------------------
// 
void CAknEdwinPhysicsHandler::ScrollStepWithBounce( TInt aPixelsToScroll,
    TInt& aRestOfPixels )
    {
    TBool endOfBounce( EFalse );
    TInt scrolledPixels = iEdwin.ScrollViewWithBounce(
        aPixelsToScroll, endOfBounce, aRestOfPixels );

    MoveWorldIndex( scrolledPixels );

    if ( endOfBounce )
        {
        iFlags.Clear( EFlagBouncingDown );  
        iFlags.Clear( EFlagBouncingUp );
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::MoveWorldIndex
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::MoveWorldIndex( TInt aDelta )
    {
    iWorldIndex = iPreviousWorldIndex - aDelta;
    iPreviousWorldIndex = iWorldIndex;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::ScrollRestOfPixels
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::ScrollRestOfPixels( TInt aRestOfPixels )
    {
    // Scroll rest of pixels
    TBool endOfBounce;
    TInt restOfPixels( 0 );
    
    TInt scrolledPixels = iEdwin.ScrollViewWithBounce( aRestOfPixels,
            endOfBounce, restOfPixels );
    
    MoveWorldIndex( scrolledPixels );
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::StopPhysics
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::StopPhysics()
    {
    iPhysics->StopPhysics();
    iPhysics->ResetFriction();
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::BlockEvents
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::BlockEvents( TBool aBlocked )
    {
    iEventsBlocked = aBlocked;
    iEventUnblockingAllowed = EFalse;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::IsFlicking
// ---------------------------------------------------------------------------
//
TBool CAknEdwinPhysicsHandler::IsFlicking() const
    {
    return iPhysics->OngoingPhysicsAction()
            == CAknPhysics::EAknPhysicsActionFlicking;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::IsBouncing
// ---------------------------------------------------------------------------
//
TBool CAknEdwinPhysicsHandler::IsBouncing() const
    {
    return iPhysics->OngoingPhysicsAction()
            == CAknPhysics::EAknPhysicsActionBouncing;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::InitBounce
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::InitBounce( TBool aBouncingDown )
    {
    if ( aBouncingDown )
        {
        iFlags.Clear( EFlagBouncingUp );
        iFlags.Set( EFlagBouncingDown );
        iBounceDownIndex = iWorldIndex;
        }
    else
        {
        iFlags.Set( EFlagBouncingUp );
        iFlags.Clear( EFlagBouncingDown );
        iBounceUpIndex = iWorldIndex;
        }   
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::InitKineticScrolling
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::InitKineticScrolling(
    const TPoint& aStartPosition )
    {
    iSelectionStarted = EFalse;
    iDraggingStarted = EFalse;
    iStartPosition = aStartPosition;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::HandleKineticScrolling
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::HandleKineticScrolling(
    const TPointerEvent& aPointerEvent, TBool& aShouldReturn )
    {
    aShouldReturn = EFalse;
    
    // If editor content is empty, it is not possible to drag,
    // flick or select text and we return here. Other events
    // are passed because they are handled later.
    if ( aPointerEvent.iType == TPointerEvent::EDrag
        && iEdwin.TextLength() == 0 )
        {
        aShouldReturn = ETrue;
        return;
        }
    
    // If dragging or selection not started,
    // check if it should be started
    if ( aPointerEvent.iType == TPointerEvent::EDrag && !iSelectionStarted
        && !iDraggingStarted )
        {
        // Get delta values for pointer movement
        TInt deltaX( aPointerEvent.iPosition.iX - iStartPosition.iX );
        TInt deltaY( aPointerEvent.iPosition.iY - iStartPosition.iY );
            
        // If drag threshold not exceeded, return.
        if ( Abs( deltaX ) < DragThreshold()
            && Abs( deltaY ) < DragThreshold() )
            {
            HandlePointerEvent( aPointerEvent );
            aShouldReturn = ETrue;
            return;
            }
                  
        // If dragged horizontally more than vertically,
        // text selection started. Otherwise dragging started.
        if ( Abs( deltaX ) > Abs( deltaY ) )
            {
            iSelectionStarted = ETrue;
            }
        else
            {
            iDraggingStarted = ETrue;
                
            // If cursor is visible, disable it and store
            // cursor visibility info when dragging starts.
            iEdwin.StoreCursorState();
            }
        }

    // Forward all events to physics handler, except:
    // If text selection started, don't forward drag and up events
    // because kinetic scrolling can't be used when selecting text.
    // However, fast scrolling can be used when we are selecting text.

    if ( !( iSelectionStarted &&
            ( aPointerEvent.iType == TPointerEvent::EDrag ||
                aPointerEvent.iType == TPointerEvent::EButton1Up ) ) )
        {
        HandlePointerEvent( aPointerEvent );
        }
    
    if ( aPointerEvent.iType == TPointerEvent::EButton1Up )
        {
        // If flick or bounce effect was started, check if there
        // is need to hide cursor.
        // If flick or bounce was not started, show cursor again if needed.
        if ( EmulationOngoing() )
            {
            iEdwin.StoreCursorState();
            }
        else
            {
            iEdwin.RestoreCursorState();
            }
        }

    // If selection not started, don't handle drag events but return
    // All other events must be handled anyway.
    if ( !iSelectionStarted && aPointerEvent.iType
            == TPointerEvent::EDrag )
        {
        aShouldReturn = ETrue;                     
        return;
        }
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::SelectionStarted
// ---------------------------------------------------------------------------
//
TBool CAknEdwinPhysicsHandler::SelectionStarted() const
    {
    return iSelectionStarted;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::DraggingStarted
// ---------------------------------------------------------------------------
//
TBool CAknEdwinPhysicsHandler::DraggingStarted() const
    {
    return iDraggingStarted;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::DragThresholdExceeded
// ---------------------------------------------------------------------------
//
TBool CAknEdwinPhysicsHandler::DragThresholdExceeded(
    const TPoint& aCurrentPosition ) const
    {
    // Get delta values for pointer movement
    TInt deltaX( aCurrentPosition.iX - iStartPosition.iX );
    TInt deltaY( aCurrentPosition.iY - iStartPosition.iY );

    if ( Abs( deltaX ) >= DragThreshold() || Abs( deltaY ) >= DragThreshold() )
        {
        return ETrue;
        }
    return EFalse;
    }

// ---------------------------------------------------------------------------
// CAknEdwinPhysicsHandler::DisableDragging
// ---------------------------------------------------------------------------
//
void CAknEdwinPhysicsHandler::DisableDragging()
    {
    iFlags.Clear( EFlagDraggingAllowed );
    }
    
