/**************************************************************
 *
 * 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_sw.hxx"

#include <sfx2/bindings.hxx>
#include <wrtsh.hxx>
#ifndef _VIEW_HXX
#include <view.hxx>
#endif
#include <viewopt.hxx>
#include <crsskip.hxx>

/*	Immer:
	-	Zurücksetzen des Cursorstacks
	-	Timer nachtriggern
	-	gfs. GCAttr

	bei Selektion
	-	SttSelect()

	sonst
	-	EndSelect()
 */

const long nReadOnlyScrollOfst = 10;

class ShellMoveCrsr
{
	SwWrtShell* pSh;
	sal_Bool bAct;
public:
	inline ShellMoveCrsr( SwWrtShell* pWrtSh, sal_Bool bSel )
	{
		bAct = !pWrtSh->ActionPend() && (pWrtSh->GetFrmType(0,sal_False) & FRMTYPE_FLY_ANY);
		( pSh = pWrtSh )->MoveCrsr( sal_Bool(bSel) );
		pWrtSh->GetView().GetViewFrame()->GetBindings().Invalidate(SID_HYPERLINK_GETLINK);
	}
	inline ~ShellMoveCrsr()
	{
		if( bAct )
		{
			//Die Action wird für das Scrollen in "einabsaetzigen" Rahmen mit
			//fester Höhe gebraucht.
			pSh->StartAllAction();
			pSh->EndAllAction();
		}
	}
};

void SwWrtShell::MoveCrsr( sal_Bool bWithSelect )
{
	ResetCursorStack();
	if ( IsGCAttr() )
	{
		GCAttr();
		ClearGCAttr();
	}
	if ( bWithSelect )
		SttSelect();
	else
	{
		EndSelect();
		(this->*fnKillSel)( 0, sal_False );
	}
}

sal_Bool SwWrtShell::SimpleMove( FNSimpleMove FnSimpleMove, sal_Bool bSelect )
{
	sal_Bool nRet;
	if( bSelect )
	{
		SttCrsrMove();
		MoveCrsr( sal_True );
		nRet = (this->*FnSimpleMove)();
		EndCrsrMove();
	}
	else if( 0 != ( nRet = (this->*FnSimpleMove)() ) )
		MoveCrsr( sal_False );
	return nRet;
}


sal_Bool SwWrtShell::Left( sal_uInt16 nMode, sal_Bool bSelect,
                            sal_uInt16 nCount, sal_Bool bBasicCall, sal_Bool bVisual )
{
    if ( !bSelect && !bBasicCall && IsCrsrReadonly()  && !GetViewOptions()->IsSelectionInReadonly())
    {
        Point aTmp( VisArea().Pos() );
        aTmp.X() -= VisArea().Width() * nReadOnlyScrollOfst / 100;
        rView.SetVisArea( aTmp );
        return sal_True;
    }
    else
	{
		ShellMoveCrsr aTmp( this, bSelect );
        return SwCrsrShell::Left( nCount, nMode, bVisual );
	}
}



sal_Bool SwWrtShell::Right( sal_uInt16 nMode, sal_Bool bSelect,
                            sal_uInt16 nCount, sal_Bool bBasicCall, sal_Bool bVisual )
{
    if ( !bSelect && !bBasicCall && IsCrsrReadonly() && !GetViewOptions()->IsSelectionInReadonly() )
    {
        Point aTmp( VisArea().Pos() );
        aTmp.X() += VisArea().Width() * nReadOnlyScrollOfst / 100;
        aTmp.X() = rView.SetHScrollMax( aTmp.X() );
        rView.SetVisArea( aTmp );
        return sal_True;
    }
    else
	{
		ShellMoveCrsr aTmp( this, bSelect );
        return SwCrsrShell::Right( nCount, nMode, bVisual );
	}
}



sal_Bool SwWrtShell::Up( sal_Bool bSelect, sal_uInt16 nCount, sal_Bool bBasicCall )
{
    if ( !bSelect && !bBasicCall && IsCrsrReadonly()  && !GetViewOptions()->IsSelectionInReadonly())
    {
        Point aTmp( VisArea().Pos() );
        aTmp.Y() -= VisArea().Height() * nReadOnlyScrollOfst / 100;
        rView.SetVisArea( aTmp );
        return sal_True;
    }
    else
	{
		ShellMoveCrsr aTmp( this, bSelect );
		return SwCrsrShell::Up( nCount );
	}
}



sal_Bool SwWrtShell::Down( sal_Bool bSelect, sal_uInt16 nCount, sal_Bool bBasicCall )
{
    if ( !bSelect && !bBasicCall && IsCrsrReadonly() && !GetViewOptions()->IsSelectionInReadonly())
    {
        Point aTmp( VisArea().Pos() );
        aTmp.Y() += VisArea().Height() * nReadOnlyScrollOfst / 100;
        aTmp.Y() = rView.SetVScrollMax( aTmp.Y() );
        rView.SetVisArea( aTmp );
        return sal_True;
    }
    else
	{
		ShellMoveCrsr aTmp( this, bSelect );
		return SwCrsrShell::Down( nCount );
	}
}



sal_Bool SwWrtShell::LeftMargin( sal_Bool bSelect, sal_Bool bBasicCall )
{
	if ( !bSelect && !bBasicCall && IsCrsrReadonly() )
	{
		Point aTmp( VisArea().Pos() );
		aTmp.X() = DOCUMENTBORDER;
		rView.SetVisArea( aTmp );
		return sal_True;
	}
	else
	{
		ShellMoveCrsr aTmp( this, bSelect );
		return SwCrsrShell::LeftMargin();
	}
}



sal_Bool SwWrtShell::RightMargin( sal_Bool bSelect, sal_Bool bBasicCall  )
{
	if ( !bSelect && !bBasicCall && IsCrsrReadonly() )
	{
		Point aTmp( VisArea().Pos() );
		aTmp.X() = GetDocSize().Width() - VisArea().Width() + DOCUMENTBORDER;
		if( DOCUMENTBORDER > aTmp.X() )
			aTmp.X() = DOCUMENTBORDER;
		rView.SetVisArea( aTmp );
		return sal_True;
	}
	else
	{
		ShellMoveCrsr aTmp( this, bSelect );
		return SwCrsrShell::RightMargin(bBasicCall);
	}
}



sal_Bool SwWrtShell::GoStart( sal_Bool bKeepArea, sal_Bool *pMoveTable,
							sal_Bool bSelect, sal_Bool bDontMoveRegion )
{
	if ( IsCrsrInTbl() )
	{
		const sal_Bool bBoxSelection = HasBoxSelection();
        if( !bBlockMode )
        {
            if ( !bSelect )
                EnterStdMode();
            else
                SttSelect();
        }
			// Tabellenzelle?
		if ( !bBoxSelection && (MoveSection( fnSectionCurr, fnSectionStart)
				|| bDontMoveRegion))
		{
			if ( pMoveTable )
				*pMoveTable = sal_False;
			return sal_True;
		}
		if( MoveTable( fnTableCurr, fnTableStart ) || bDontMoveRegion )
		{
			if ( pMoveTable )
				*pMoveTable = sal_True;
			return sal_True;
		}
		else if( bBoxSelection && pMoveTable )
		{
			// JP 09.01.96: wir haben eine Boxselektion (oder leere Zelle)
			// 				und wollen selektieren (pMoveTable wird im
			//				SelAll gesetzt). Dann darf die Tabelle nicht
			//				verlassen werden; sonst ist keine Selektion der
			//				gesamten Tabelle moeglich!
			*pMoveTable = sal_True;
			return sal_True;
		}
	}

    if( !bBlockMode )
    {
        if ( !bSelect )
            EnterStdMode();
        else
            SttSelect();
    }
	const sal_uInt16 nFrmType = GetFrmType(0,sal_False);
	if ( FRMTYPE_FLY_ANY & nFrmType )
	{
		if( MoveSection( fnSectionCurr, fnSectionStart ) )
			return sal_True;
		else if ( FRMTYPE_FLY_FREE & nFrmType || bDontMoveRegion )
			return sal_False;
	}
	if(( FRMTYPE_HEADER | FRMTYPE_FOOTER | FRMTYPE_FOOTNOTE ) & nFrmType )
	{
		if ( MoveSection( fnSectionCurr, fnSectionStart ) )
			return sal_True;
		else if ( bKeepArea )
			return sal_True;
	}
	// Bereiche ???
	return SwCrsrShell::MoveRegion( fnRegionCurrAndSkip, fnRegionStart ) ||
		   SwCrsrShell::SttEndDoc(sal_True);
}



sal_Bool SwWrtShell::GoEnd(sal_Bool bKeepArea, sal_Bool *pMoveTable)
{
	if ( pMoveTable && *pMoveTable )
		return MoveTable( fnTableCurr, fnTableEnd );

	if ( IsCrsrInTbl() )
	{
		if ( MoveSection( fnSectionCurr, fnSectionEnd ) ||
			 MoveTable( fnTableCurr, fnTableEnd ) )
			return sal_True;
	}
	else
	{
		const sal_uInt16 nFrmType = GetFrmType(0,sal_False);
		if ( FRMTYPE_FLY_ANY & nFrmType )
		{
			if ( MoveSection( fnSectionCurr, fnSectionEnd ) )
				return sal_True;
			else if ( FRMTYPE_FLY_FREE & nFrmType )
				return sal_False;
		}
		if(( FRMTYPE_HEADER | FRMTYPE_FOOTER | FRMTYPE_FOOTNOTE ) & nFrmType )
		{
			if ( MoveSection( fnSectionCurr, fnSectionEnd) )
				return sal_True;
			else if ( bKeepArea )
				return sal_True;
		}
	}
	// Bereiche ???
	return SwCrsrShell::MoveRegion( fnRegionCurrAndSkip, fnRegionEnd ) ||
		   SwCrsrShell::SttEndDoc(sal_False);
}



sal_Bool SwWrtShell::SttDoc( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return GoStart(sal_False, 0, bSelect );
}



sal_Bool SwWrtShell::EndDoc( sal_Bool bSelect)
{
	ShellMoveCrsr aTmp( this, bSelect );
	return GoEnd();
}


sal_Bool SwWrtShell::SttNxtPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPageNext, fnPageStart );
}



sal_Bool SwWrtShell::SttPrvPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPagePrev, fnPageStart );
}



sal_Bool SwWrtShell::EndNxtPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPageNext, fnPageEnd );
}



sal_Bool SwWrtShell::EndPrvPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPagePrev, fnPageEnd );
}



sal_Bool SwWrtShell::SttPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPageCurr, fnPageStart );
}



sal_Bool SwWrtShell::EndPg( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePage( fnPageCurr, fnPageEnd );
}



sal_Bool SwWrtShell::SttPara( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePara( fnParaCurr, fnParaStart );
}



sal_Bool SwWrtShell::EndPara( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return MovePara(fnParaCurr,fnParaEnd);
}


/*------------------------------------------------------------------------
 Beschreibung:	Spaltenweises Springen
 Parameter: 	mit oder ohne SSelection
 Return:		Erfolg oder Misserfolg
------------------------------------------------------------------------*/



sal_Bool SwWrtShell::StartOfColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn(fnColumnCurr, fnColumnStart);
}



sal_Bool SwWrtShell::EndOfColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn(fnColumnCurr, fnColumnEnd);
}



sal_Bool SwWrtShell::StartOfNextColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn( fnColumnNext, fnColumnStart);
}



sal_Bool SwWrtShell::EndOfNextColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn(fnColumnNext, fnColumnEnd);
}



sal_Bool SwWrtShell::StartOfPrevColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn(fnColumnPrev, fnColumnStart);
}



sal_Bool SwWrtShell::EndOfPrevColumn( sal_Bool bSelect )
{
	ShellMoveCrsr aTmp( this, bSelect);
	return MoveColumn(fnColumnPrev, fnColumnEnd);
}



sal_Bool SwWrtShell::PushCrsr(SwTwips lOffset, sal_Bool bSelect)
{
	sal_Bool bDiff = sal_False;
	SwRect aOldRect( GetCharRect() ), aTmpArea( VisArea() );

	//bDestOnStack besagt, ob ich den Cursor nicht an die aktuelle Position
	//setzen konnte, da in diesem Bereich kein Inhalt vorhanden ist.
	if( !bDestOnStack )
	{
		Point aPt( aOldRect.Center() );

		if( !IsCrsrVisible() )
			// set CrsrPos to top-/bottom left pos. So the pagescroll is not
			// be dependent on the current cursor, but on the visarea.
			aPt.Y() = aTmpArea.Top() + aTmpArea.Height() / 2;

		aPt.Y() += lOffset;
		aDest = GetCntntPos(aPt,lOffset > 0);
		aDest.X() = aPt.X();
		bDestOnStack = sal_True;
	}

	//falls wir eine Rahmenselektion hatten, muss diese nach dem
	//fnSetCrsr entfernt werden und damit wir da wieder hinkommen
	//auf dem Stack gemerkt werden.
	sal_Bool bIsFrmSel = sal_False;

	sal_Bool bIsObjSel = sal_False;

	//Zielposition liegt jetzt innerhalb des sichtbaren Bereiches -->
	//Cursor an die Zielposition setzen; merken, dass keine Ziel-
	//position mehr auf dem Stack steht.
	//Der neue sichtbare Bereich wird zuvor ermittelt.
	aTmpArea.Pos().Y() += lOffset;
	if( aTmpArea.IsInside(aDest) )
	{
		if( bSelect )
			SttSelect();
		else
			EndSelect();

		bIsFrmSel = IsFrmSelected();
		bIsObjSel = 0 != IsObjSelected();

		// Rahmenselektion aufheben
		if( bIsFrmSel || bIsObjSel )
		{
			UnSelectFrm();
			LeaveSelFrmMode();
			if ( bIsObjSel )
			{
				GetView().SetDrawFuncPtr( NULL );
				GetView().LeaveDrawCreate();
			}

			CallChgLnk();
		}

		(this->*fnSetCrsr)( &aDest, sal_True );

		bDiff = aOldRect != GetCharRect();

		if( bIsFrmSel )
		{
//			CallChgLnk();
			// bei Frames immer nur die obere Ecke nehmen, damit dieser
			// wieder selektiert werden kann
			aOldRect.SSize( 5, 5 );
		}

			// Zuruecksetzen des Dest. SPoint Flags
		bDestOnStack = sal_False;
	}

	// Position auf den Stack; bDiff besagt, ob ein Unterschied zwischen
	// der alten und der neuen Cursorposition besteht.
	pCrsrStack = new CrsrStack( bDiff, bIsFrmSel, aOldRect.Center(),
								lOffset, pCrsrStack );
	return !bDestOnStack && bDiff;
}



sal_Bool SwWrtShell::PopCrsr(sal_Bool bUpdate, sal_Bool bSelect)
{
	if( 0 == pCrsrStack)
		return sal_False;

	const sal_Bool bValidPos = pCrsrStack->bValidCurPos;
	if( bUpdate && bValidPos )
	{
			// falls ein Vorgänger auf dem Stack steht, dessen Flag für eine
			// gültige Position verwenden.
		SwRect aTmpArea(VisArea());
		aTmpArea.Pos().Y() -= pCrsrStack->lOffset;
		if( aTmpArea.IsInside( pCrsrStack->aDocPos ) )
		{
			if( bSelect )
				SttSelect();
			else
				EndSelect();

			(this->*fnSetCrsr)(&pCrsrStack->aDocPos, !pCrsrStack->bIsFrmSel);
			if( pCrsrStack->bIsFrmSel && IsObjSelectable(pCrsrStack->aDocPos))
			{
				HideCrsr();
				SelectObj( pCrsrStack->aDocPos );
				EnterSelFrmMode( &pCrsrStack->aDocPos );
			}
		}
			// Falls eine Verschiebung zwischen dem sichtbaren Bereich
			// und der gemerkten Cursorpositionen auftritt, werden
			// alle gemerkten Positionen weggeschmissen
		else
		{
			_ResetCursorStack();
			return sal_False;
		}
	}
	CrsrStack *pTmp = pCrsrStack;
	pCrsrStack = pCrsrStack->pNext;
	delete pTmp;
	if( 0 == pCrsrStack )
	{
		ePageMove = MV_NO;
		bDestOnStack = sal_False;
	}
	return bValidPos;
}

/*
 * Zuruecksetzen aller gepushten Cursorpositionen; dieser werden nicht
 * zur Anzeige gebracht ( --> Kein Start-/EndAction!!)
 */



void SwWrtShell::_ResetCursorStack()
{
	CrsrStack *pTmp = pCrsrStack;
	while(pCrsrStack)
	{
		pTmp = pCrsrStack->pNext;
		delete pCrsrStack;
		pCrsrStack = pTmp;
	}
	ePageMove = MV_NO;
	bDestOnStack = sal_False;
}
/**************

	falls kein Stack existiert --> Selektionen aufheben
	falls Stack && Richtungswechsel
		--> Cursor poppen und return
	sonst
		--> Cursor pushen
			 Cursor umsetzen

***************/



sal_Bool SwWrtShell::PageCrsr(SwTwips lOffset, sal_Bool bSelect)
{
	// nichts tun, wenn ein Offset von 0 angegeben wurde
	if(!lOffset) return sal_False;
		// Diente mal dazu, eine Neuformatierung fuer das Layout
		// zu erzwingen.
		// Hat so nicht funktioniert, da der Cursor nicht gesetzt
		// wurde, da dies innerhalb einer Start- / EndActionklammerung
		// nicht geschieht.
		// Da am Ende nur ViewShell::EndAction() gerufen wird,
		// findet auch hier keine Aktualisierung der Anzeige
		// der Cursorposition statt.
		// Die CrsrShell- Actionklammerung kann nicht verwendet werden,
		// da sie immer zu einer Anzeige des Cursors fuehrt, also auch,
		// wenn nach dem Blaettern in einen Bereich ohne gueltige Position
		// geblaettert wurde.
		//	ViewShell::StartAction();
	PageMove eDir = lOffset > 0? MV_PAGE_DOWN: MV_PAGE_UP;
		// Richtungswechsel und Stack vorhanden
	if( eDir != ePageMove && ePageMove != MV_NO && PopCrsr( sal_True, bSelect ))
		return sal_True;

	const sal_Bool bRet = PushCrsr(lOffset, bSelect);
	ePageMove = eDir;
	return bRet;
}



sal_Bool SwWrtShell::GotoPage(sal_uInt16 nPage, sal_Bool bRecord)
{
	ShellMoveCrsr aTmp( this, sal_False);
	if( SwCrsrShell::GotoPage(nPage) && bRecord)
	{
		if(IsSelFrmMode())
		{
			UnSelectFrm();
			LeaveSelFrmMode();
		}
		return sal_True;
	}
	return sal_False;
}



sal_Bool SwWrtShell::GotoMark( const ::sw::mark::IMark* const pMark, sal_Bool bSelect, sal_Bool bStart )
{
	ShellMoveCrsr aTmp( this, bSelect );
	return SwCrsrShell::GotoMark( pMark, bStart );
}



sal_Bool SwWrtShell::SelectTxtAttr( sal_uInt16 nWhich, const SwTxtAttr* pAttr )
{
    sal_Bool bRet;
    {
        MV_CONTEXT(this);
        SttSelect();
        bRet = SwCrsrShell::SelectTxtAttr( nWhich, sal_False, pAttr );
    }
    EndSelect();
    return bRet;
}

/* vim: set noet sw=4 ts=4: */
