diff options
Diffstat (limited to 'sw/source/core/crsr/crsrsh.cxx')
-rw-r--r-- | sw/source/core/crsr/crsrsh.cxx | 3886 |
1 files changed, 3886 insertions, 0 deletions
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx new file mode 100644 index 000000000..4817c422a --- /dev/null +++ b/sw/source/core/crsr/crsrsh.cxx @@ -0,0 +1,3886 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +#include <config_wasm_strip.h> + +#include <com/sun/star/text/XTextRange.hpp> + +#include <hintids.hxx> +#include <svx/srchdlg.hxx> +#include <sfx2/viewsh.hxx> +#include <SwSmartTagMgr.hxx> +#include <doc.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <viewimp.hxx> +#include <pam.hxx> +#include <swselectionlist.hxx> +#include "BlockCursor.hxx" +#include <ndtxt.hxx> +#include <flyfrm.hxx> +#include <dview.hxx> +#include <viewopt.hxx> +#include <crsrsh.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <swtable.hxx> +#include "callnk.hxx" +#include <viscrs.hxx> +#include <section.hxx> +#include <docsh.hxx> +#include <scriptinfo.hxx> +#include <globdoc.hxx> +#include <pamtyp.hxx> +#include <mdiexp.hxx> +#include <fmteiro.hxx> +#include <wrong.hxx> +#include <unotextrange.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <IGrammarContact.hxx> +#include <comphelper/flagguard.hxx> +#include <strings.hrc> +#include <IDocumentLayoutAccess.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <comphelper/sequence.hxx> +#include <sfx2/lokhelper.hxx> +#include <editeng/editview.hxx> +#include <editeng/frmdir.hxx> +#include <sal/log.hxx> +#include <PostItMgr.hxx> +#include <DocumentSettingManager.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <tabcol.hxx> +#include <wrtsh.hxx> +#include <undobj.hxx> +#include <view.hxx> +#include <hints.hxx> +#include <tools/json_writer.hxx> + +using namespace com::sun::star; +using namespace util; + +/** + * Check if pCurrentCursor points into already existing ranges and delete those. + * @param Pointer to SwCursor object + */ +static void CheckRange( SwCursor* pCurrentCursor ) +{ + const SwPosition *pStt = pCurrentCursor->Start(), + *pEnd = pCurrentCursor->End(); + + SwPaM *pTmpDel = nullptr, + *pTmp = pCurrentCursor->GetNext(); + + // Search the complete ring + while( pTmp != pCurrentCursor ) + { + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->End(); + if( *pStt <= *pTmpStt ) + { + if( *pEnd > *pTmpStt || + ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) + pTmpDel = pTmp; + } + else + if( *pStt < *pTmpEnd ) + pTmpDel = pTmp; + + // If Point or Mark is within the Cursor range, we need to remove the old + // range. Take note that Point does not belong to the range anymore. + pTmp = pTmp->GetNext(); + delete pTmpDel; // Remove old range + pTmpDel = nullptr; + } +} + +// SwCursorShell + +/** + * Add a copy of current cursor, append it after current, and collapse current cursor. + * @return - Returns a newly created copy of current cursor. + */ +SwPaM * SwCursorShell::CreateCursor() +{ + // don't create new Cursor with active table Selection + assert(!IsTableMode()); + + // ensure that m_pCurrentCursor is valid; if it's invalid it would be + // copied to pNew and then pNew would be deleted in UpdateCursor() below + ClearUpCursors(); + + // New cursor as copy of current one. Add to the ring. + // Links point to previously created one, ie forward. + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + + // Hide PaM logically, to avoid undoing the inverting from + // copied PaM (#i75172#) + pNew->swapContent(*m_pCurrentCursor); + + m_pCurrentCursor->DeleteMark(); + + UpdateCursor( SwCursorShell::SCROLLWIN ); + return pNew; +} + +/** + * Delete current Cursor, making the following one the current. + * Note, this function does not delete anything if there is no other cursor. + * @return - returns true if there was another cursor and we deleted one. + */ +void SwCursorShell::DestroyCursor() +{ + // don't delete Cursor with active table Selection + assert(!IsTableMode()); + + // Is there a next one? Don't do anything if not. + if(!m_pCurrentCursor->IsMultiSelection()) + return; + + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursor* pNextCursor = static_cast<SwCursor*>(m_pCurrentCursor->GetNext()); + delete m_pCurrentCursor; + m_pCurrentCursor = dynamic_cast<SwShellCursor*>(pNextCursor); + UpdateCursor(); +} + +/** + * Create and return a new shell cursor. + * Simply returns the current shell cursor if there is no selection + * (HasSelection()). + */ +SwCursor & SwCursorShell::CreateNewShellCursor() +{ + if (HasSelection()) + { + (void) CreateCursor(); // n.b. returns old cursor + } + return *GetCursor(); +} + +/** + * Return the current shell cursor + * @return - returns current `SwPaM` shell cursor + */ +SwCursor & SwCursorShell::GetCurrentShellCursor() +{ + return *GetCursor(); +} + +/** + * Return pointer to the current shell cursor + * @return - returns pointer to current `SwCursor` shell cursor + */ +SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const +{ + if( m_pTableCursor ) + { + if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() ) + { + //don't re-create 'parked' cursors + if( m_pTableCursor->GetPoint()->nNode.GetIndex() && + m_pTableCursor->GetMark()->nNode.GetIndex() ) + { + const SwContentNode* pCNd = m_pTableCursor->GetContentNode(); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + pCNd = m_pTableCursor->GetContentNode(false); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + SwShellTableCursor* pTC = m_pTableCursor; + GetLayout()->MakeTableCursors( *pTC ); + } + } + } + } + + if( m_pTableCursor->IsChgd() ) + { + const_cast<SwCursorShell*>(this)->m_pCurrentCursor = + dynamic_cast<SwShellCursor*>(m_pTableCursor->MakeBoxSels( m_pCurrentCursor )); + } + } + return m_pCurrentCursor; +} + +void SwCursorShell::StartAction() +{ + if( !ActionPend() ) + { + // save for update of the ribbon bar + const SwNode& rNd = m_pCurrentCursor->GetPoint()->nNode.GetNode(); + m_nCurrentNode = rNd.GetIndex(); + m_nCurrentContent = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + m_nCurrentNdTyp = rNd.GetNodeType(); + if( rNd.IsTextNode() ) + m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true ); + else + m_nLeftFramePos = 0; + } + SwViewShell::StartAction(); // to the SwViewShell +} + +void SwCursorShell::EndAction( const bool bIdleEnd ) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + bool bVis = m_bSVCursorVis; + + // Idle-formatting? + if( bIdleEnd && Imp()->HasPaintRegion() ) + { + m_pCurrentCursor->Hide(); + } + + // Update all invalid numberings before the last action + if( 1 == mnStartAction ) + GetDoc()->UpdateNumRule(); + + // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call. + // Only the UpdateCursor shows the cursor. + bool bSavSVCursorVis = m_bSVCursorVis; + m_bSVCursorVis = false; + + SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first + + m_bSVCursorVis = bSavSVCursorVis; + + if( ActionPend() ) + { + if( bVis ) // display SV-Cursor again + m_pVisibleCursor->Show(); + + return; + } + + sal_uInt16 eFlags = SwCursorShell::CHKRANGE; + if ( !bIdleEnd ) + eFlags |= SwCursorShell::SCROLLWIN; + + UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes + + { + SwCallLink aLk( *this ); // Watch cursor moves, + aLk.m_nNode = m_nCurrentNode; // possibly call the link + aLk.m_nNodeType = m_nCurrentNdTyp; + aLk.m_nContent = m_nCurrentContent; + aLk.m_nLeftFramePos = m_nLeftFramePos; + + if( !m_nCursorMove || + ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) ) + // display Cursor & Selections again + ShowCursors( m_bSVCursorVis ); + } + // call ChgCall if there is still one + if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() ) + { + m_aChgLnk.Call(nullptr); + m_bChgCallFlag = false; // reset flag + } +} + +void SwCursorShell::SttCursorMove() +{ +#ifdef DBG_UTIL + OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." ); +#endif + ++m_nCursorMove; + StartAction(); +} + +void SwCursorShell::EndCursorMove( const bool bIdleEnd ) +{ +#ifdef DBG_UTIL + OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." ); +#endif + EndAction( bIdleEnd ); + --m_nCursorMove; +#ifdef DBG_UTIL + if( !m_nCursorMove ) + m_bInCMvVisportChgd = false; +#endif +} + +bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 nMode, + bool bVisualAllowed ) +{ + if( IsTableMode() ) + return bLeft ? GoPrevCell() : GoNextCell(); + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + bool bRet = false; + + // #i27615# Handle cursor in front of label. + const SwTextNode* pTextNd = nullptr; + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + // 1. CASE: Cursor is in front of label. A move to the right + // will simply reset the bInFrontOfLabel flag: + SwShellCursor* pShellCursor = getShellCursor( true ); + if ( !bLeft && pShellCursor->IsInFrontOfLabel() ) + { + SetInFrontOfLabel( false ); + bRet = true; + } + // 2. CASE: Cursor is at beginning of numbered paragraph. A move + // to the left will simply set the bInFrontOfLabel flag: + else if (bLeft + && pShellCursor->GetPoint()->nNode.GetNode().IsTextNode() + && static_cast<SwTextFrame const*>( + pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->getLayoutFrame(GetLayout()) + )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0) + && !pShellCursor->IsInFrontOfLabel() + && !pShellCursor->HasMark() + && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->nNode)) + && pTextNd->HasVisibleNumberingOrBullet()) + { + SetInFrontOfLabel( true ); + bRet = true; + } + // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag: + else + { + const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar(); + // #i107447# + // To avoid loop the reset of <bInFrontOfLabel> flag is no longer + // reflected in the return value <bRet>. + const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false ); + bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed, + bSkipHidden, !IsOverwriteCursor(), + GetLayout(), + GetViewOptions()->IsFieldName()); + if ( !bRet && bLeft && bResetOfInFrontOfLabel ) + { + // undo reset of <bInFrontOfLabel> flag + SetInFrontOfLabel( true ); + } + } + + if( bRet ) + { + UpdateCursor(); + } + + return bRet; +} + +void SwCursorShell::MarkListLevel( const OUString& sListId, + const int nListLevel ) +{ + if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel) + return; + + if ( !m_sMarkedListId.isEmpty() ) + mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false ); + + if ( !sListId.isEmpty() ) + { + mxDoc->MarkListLevel( sListId, nListLevel, true ); + } + + m_sMarkedListId = sListId; + m_nMarkedListLevel = nListLevel; +} + +void SwCursorShell::UpdateMarkedListLevel() +{ + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), + GetCursor_()->GetPoint()->nNode); + + if ( !pTextNd ) + return; + + if (!pTextNd->IsNumbered(GetLayout())) + { + m_pCurrentCursor->SetInFrontOfLabel_( false ); + MarkListLevel( OUString(), 0 ); + } + else if ( m_pCurrentCursor->IsInFrontOfLabel() ) + { + if ( pTextNd->IsInList() ) + { + assert(pTextNd->GetActualListLevel() >= 0 && + pTextNd->GetActualListLevel() < MAXLEVEL); + MarkListLevel( pTextNd->GetListId(), + pTextNd->GetActualListLevel() ); + } + } + else + { + MarkListLevel( OUString(), 0 ); + } +} + +void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FirePageChangeEvent( nOldPage, nNewPage ); +#else + (void)nOldPage; + (void)nNewPage; +#endif +} + +void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn); +#else + (void)nOldColumn; + (void)nNewColumn; +#endif +} + +void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FireSectionChangeEvent( nOldSection, nNewSection ); +#else + (void)nOldSection; + (void)nNewSection; +#endif +} + +bool SwCursorShell::bColumnChange() +{ + SwFrame* pCurrFrame = GetCurrFrame(false); + + if (pCurrFrame == nullptr) + { + return false; + } + + SwFrame* pCurrCol=pCurrFrame->FindColFrame(); + + while(pCurrCol== nullptr && pCurrFrame!=nullptr ) + { + SwLayoutFrame* pParent = pCurrFrame->GetUpper(); + if(pParent!=nullptr) + { + pCurrCol=static_cast<SwFrame*>(pParent)->FindColFrame(); + pCurrFrame = pParent; + } + else + { + break; + } + } + + if(m_oldColFrame == pCurrCol) + return false; + else + { + m_oldColFrame = pCurrCol; + return true; + } +} + +bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt ) +{ + CurrShell aCurr( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + bool bTableMode = IsTableMode(); + SwShellCursor* pTmpCursor = getShellCursor( true ); + + bool bRet = pTmpCursor->UpDown( bUp, nCnt ); + // #i40019# UpDown should always reset the bInFrontOfLabel flag: + bRet |= SetInFrontOfLabel(false); + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + if( bRet ) + { + m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint + if( !ActionPend() ) + { + CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN; + if( !bTableMode ) + eUpdateMode = static_cast<CursorFlag>(eUpdateMode + | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE); + UpdateCursor( o3tl::narrowing<sal_uInt16>(eUpdateMode) ); + } + } + return bRet; +} + +bool SwCursorShell::LRMargin( bool bLeft, bool bAPI) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + CurrShell aCurr( this ); + m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint + + const bool bTableMode = IsTableMode(); + SwShellCursor* pTmpCursor = getShellCursor( true ); + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI); + + bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI); + + if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() ) + { + const SwTextNode * pTextNd = GetCursor_()->GetNode().GetTextNode(); + assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->nNode) == pTextNd); + if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() ) + SetInFrontOfLabel( true ); + } + else if ( !bLeft ) + { + bRet = SetInFrontOfLabel( false ) || bRet; + } + + if( bRet ) + { + UpdateCursor(); + } + return bRet; +} + +bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const +{ + const SwShellCursor* pTmpCursor = getShellCursor( true ); + return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI); +} + +bool SwCursorShell::SttEndDoc( bool bStt ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor; + bool bRet = pTmpCursor->SttEndDoc( bStt ); + if( bRet ) + { + if( bStt ) + pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header) + if( m_pBlockCursor ) + { + m_pBlockCursor->clearPoints(); + RefreshBlockCursor(); + } + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +void SwCursorShell::ExtendedSelectAll(bool bFootnotes) +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwPosition* pPos = m_pCurrentCursor->GetPoint(); + pPos->nNode = bFootnotes ? rNodes.GetEndOfPostIts() : rNodes.GetEndOfAutotext(); + pPos->nContent.Assign( rNodes.GoNext( &pPos->nNode ), 0 ); + pPos = m_pCurrentCursor->GetMark(); + pPos->nNode = rNodes.GetEndOfContent(); + SwContentNode* pCNd = SwNodes::GoPrevious( &pPos->nNode ); + pPos->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); +} + +bool SwCursorShell::ExtendedSelectedAll() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode = rNodes.GetEndOfAutotext(); + SwContentNode* pStart = rNodes.GoNext(&nNode); + if (!pStart) + return false; + + nNode = rNodes.GetEndOfContent(); + SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); + if (!pEnd) + return false; + + SwPosition aStart(*pStart, 0); + SwPosition aEnd(*pEnd, pEnd->Len()); + SwShellCursor* pShellCursor = getShellCursor(false); + return aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End(); +} + +bool SwCursorShell::StartsWithTable() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode(rNodes.GetEndOfExtras()); + SwContentNode* pContentNode = rNodes.GoNext(&nNode); + return pContentNode->FindTableNode(); +} + +bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage ) +{ + bool bRet = false; + + // never jump of section borders at selection + if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + CurrShell aCurr( this ); + + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + Point& rPt = m_pCurrentCursor->GetPtPos(); + std::pair<Point, bool> tmp(rPt, false); + SwContentFrame * pFrame = m_pCurrentCursor->GetContentNode()-> + getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos )) + { + UpdateCursor(); + bRet = true; + } + } + return bRet; +} + +bool SwCursorShell::isInHiddenTextFrame(SwShellCursor* pShellCursor) +{ + SwContentNode *pCNode = pShellCursor->GetContentNode(); + std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false); + SwContentFrame *const pFrame = pCNode + ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp) + : nullptr; + return !pFrame || (pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow()); +} + +// sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara +static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell, + SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara) +{ + SwContentNode *const pCNode = pShellCursor->GetContentNode(); + assert(pCNode); // surely can't have moved otherwise? + std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false); + SwContentFrame const*const pFrame = pCNode->getLayoutFrame( + pShell->GetLayout(), pShellCursor->GetPoint(), &tmp); + if (!pFrame || !pFrame->IsTextFrame()) + { + return false; + } + SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(*pFrame)); + TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint())); + if (&fnParaStart == &fnPosPara) + { + return ix == TextFrameIndex(0); + } + else + { + assert(&fnParaEnd == &fnPosPara); + return ix == TextFrameIndex(rTextFrame.GetText().getLength()); + } +} + +bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwShellCursor* pTmpCursor = getShellCursor( true ); + bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara ); + if( bRet ) + { + //keep going until we get something visible, i.e. skip + //over hidden paragraphs, don't get stuck at the start + //which is what SwCursorShell::UpdateCursorPos will reset + //the position to if we pass it a position in an + //invisible hidden paragraph field + while (isInHiddenTextFrame(pTmpCursor) + || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara)) + { + if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara)) + break; + } + + UpdateCursor(); + } + return bRet; +} + +bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect, + SwMoveFnCollection const & fnPosSect) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursor* pTmpCursor = getShellCursor( true ); + bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect ); + if( bRet ) + UpdateCursor(); + return bRet; + +} + +// position cursor + +static SwFrame* lcl_IsInHeaderFooter( const SwNodeIndex& rIdx, Point& rPt ) +{ + SwFrame* pFrame = nullptr; + SwContentNode* pCNd = rIdx.GetNode().GetContentNode(); + if( pCNd ) + { + std::pair<Point, bool> tmp(rPt, false); + SwContentFrame *pContentFrame = pCNd->getLayoutFrame( + pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr; + while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->AnchorFrame() + : pFrame->GetUpper(); + } + return pFrame; +} + +bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const +{ + Point aPt; + SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->nNode, aPt ); + if( pFrame && pbInHeader ) + *pbInHeader = pFrame->IsHeaderFrame(); + return nullptr != pFrame; +} + +int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock ) +{ + CurrShell aCurr( this ); + + SwShellCursor* pCursor = getShellCursor( bBlock ); + SwPosition aPos( *pCursor->GetPoint() ); + Point aPt( rLPt ); + Point & rCurrentCursorPt = pCursor->GetPtPos(); + SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel : + bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + + if ( pTextNd && !IsTableMode() && + // #i37515# No bInFrontOfLabel during selection + !pCursor->HasMark() && + pTextNd->HasVisibleNumberingOrBullet() ) + { + aTmpState.m_bInFrontOfLabel = true; // #i27615# + } + else + { + aTmpState.m_bInFrontOfLabel = false; + } + + int bRet = CRSR_POSOLD | + ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) + ? 0 : CRSR_POSCHG ); + + const bool bOldInFrontOfLabel = IsInFrontOfLabel(); + const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel; + + pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel ); + + if( CursorMoveState::RightMargin == aTmpState.m_eState ) + m_eMvState = CursorMoveState::RightMargin; + // is the new position in header or footer? + SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.nNode, aPt ); + if( IsTableMode() && !pFrame && aPos.nNode.GetNode().StartOfSectionNode() == + pCursor->GetPoint()->nNode.GetNode().StartOfSectionNode() ) + // same table column and not in header/footer -> back + return bRet; + + if( m_pBlockCursor && bBlock ) + { + m_pBlockCursor->setEndPoint( rLPt ); + if( !pCursor->HasMark() ) + m_pBlockCursor->setStartPoint( rLPt ); + else if( !m_pBlockCursor->getStartPoint() ) + m_pBlockCursor->setStartPoint( pCursor->GetMkPos() ); + } + if( !pCursor->HasMark() ) + { + // is at the same position and if in header/footer -> in the same + if( aPos == *pCursor->GetPoint() && + bOldInFrontOfLabel == bNewInFrontOfLabel ) + { + if( pFrame ) + { + if( pFrame->getFrameArea().Contains( rCurrentCursorPt )) + return bRet; + } + else if( aPos.nNode.GetNode().IsContentNode() ) + { + // in the same frame? + std::pair<Point, bool> tmp(m_aCharRect.Pos(), false); + SwFrame* pOld = static_cast<SwContentNode&>(aPos.nNode.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + tmp.first = aPt; + SwFrame* pNew = static_cast<SwContentNode&>(aPos.nNode.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + if( pNew == pOld ) + return bRet; + } + } + } + else + { + // SSelection over not allowed sections or if in header/footer -> different + if( !CheckNodesRange( aPos.nNode, pCursor->GetMark()->nNode, true ) + || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) )) + return bRet; + + // is at same position but not in header/footer + if( aPos == *pCursor->GetPoint() ) + return bRet; + } + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursorSaveState aSaveState( *pCursor ); + + *pCursor->GetPoint() = aPos; + rCurrentCursorPt = aPt; + + // #i41424# Only update the marked number levels if necessary + // Force update of marked number levels if necessary. + if ( bNewInFrontOfLabel || bOldInFrontOfLabel ) + m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel ); + SetInFrontOfLabel( bNewInFrontOfLabel ); + + if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE ); + bRet &= ~CRSR_POSOLD; + } + else if( bOnlyText && !m_pCurrentCursor->HasMark() ) + { + if( FindValidContentNode( bOnlyText ) ) + { + // position cursor in a valid content + if( aPos == *pCursor->GetPoint() ) + bRet = CRSR_POSOLD; + else + { + UpdateCursor(); + bRet &= ~CRSR_POSOLD; + } + } + else + { + // there is no valid content -> hide cursor + m_pVisibleCursor->Hide(); // always hide visible cursor + m_eMvState = CursorMoveState::NONE; // status for Cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI + } + } + } + return bRet; +} + +void SwCursorShell::TableCursorToCursor() +{ + assert(m_pTableCursor); + delete m_pTableCursor; + m_pTableCursor = nullptr; +} + +void SwCursorShell::BlockCursorToCursor() +{ + assert(m_pBlockCursor); + if( m_pBlockCursor && !HasSelection() ) + { + SwPaM& rPam = m_pBlockCursor->getShellCursor(); + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetPoint() = *rPam.GetPoint(); + if( rPam.HasMark() ) + *m_pCurrentCursor->GetMark() = *rPam.GetMark(); + else + m_pCurrentCursor->DeleteMark(); + } + delete m_pBlockCursor; + m_pBlockCursor = nullptr; +} + +void SwCursorShell::CursorToBlockCursor() +{ + if( !m_pBlockCursor ) + { + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + m_pBlockCursor = new SwBlockCursor( *this, aPos ); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos(); + if( m_pCurrentCursor->HasMark() ) + { + rBlock.SetMark(); + *rBlock.GetMark() = *m_pCurrentCursor->GetMark(); + rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos(); + } + } + m_pBlockCursor->clearPoints(); + RefreshBlockCursor(); +} + +void SwCursorShell::ClearMark() +{ + // is there any GetMark? + if( m_pTableCursor ) + { + std::vector<SwPaM*> vCursors; + for(auto& rCursor : m_pCurrentCursor->GetRingContainer()) + if(&rCursor != m_pCurrentCursor) + vCursors.push_back(&rCursor); + for(auto pCursor : vCursors) + delete pCursor; + m_pTableCursor->DeleteMark(); + + m_pCurrentCursor->DeleteMark(); + + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + m_pCurrentCursor->SwSelPaintRects::Show(); + } + else + { + if( !m_pCurrentCursor->HasMark() ) + return; + m_pCurrentCursor->DeleteMark(); + if( !m_nCursorMove ) + m_pCurrentCursor->SwSelPaintRects::Show(); + } +} + +void SwCursorShell::NormalizePam(bool bPointFirst) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor->Normalize(bPointFirst); +} + +void SwCursorShell::SwapPam() +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor->Exchange(); +} + +//TODO: provide documentation +/** Search in the selected area for a Selection that covers the given point. + + It checks if a Selection exists but does + not move the current cursor. + + @param rPt The point to search at. + @param bTstHit ??? +*/ +bool SwCursorShell::TestCurrPam( + const Point & rPt, + bool bTstHit ) +{ + CurrShell aCurr( this ); + + // check if the SPoint is in a table selection + if( m_pTableCursor ) + return m_pTableCursor->Contains( rPt ); + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + // search position <rPt> in document + SwPosition aPtPos( *m_pCurrentCursor->GetPoint() ); + Point aPt( rPt ); + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit ) + return false; + + // search in all selections for this position + SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor + do + { + if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos) + return true; // return without update + pCmp = pCmp->GetNext(); + } while (m_pCurrentCursor != pCmp); + return false; +} + +void SwCursorShell::KillPams() +{ + // Does any exist for deletion? + if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() ) + return; + + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + m_pCurrentCursor->SetColumnSelection( false ); + + if( m_pTableCursor ) + { + // delete the ring of cursors + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + } + else if( m_pBlockCursor ) + { + // delete the ring of cursors + m_pCurrentCursor->DeleteMark(); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint(); + m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos(); + rBlock.DeleteMark(); + m_pBlockCursor->clearPoints(); + } + UpdateCursor( SwCursorShell::SCROLLWIN ); +} + +int SwCursorShell::CompareCursorStackMkCurrPt() const +{ + int nRet = 0; + const SwPosition *pFirst = nullptr, *pSecond = nullptr; + const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor; + // cursor on stack is needed if we compare against stack + if( pStack ) + { + pFirst = pStack->GetMark(); + pSecond = pCur->GetPoint(); + } + if( !pFirst || !pSecond ) + nRet = INT_MAX; + else if( *pFirst < *pSecond ) + nRet = -1; + else if( *pFirst == *pSecond ) + nRet = 0; + else + nRet = 1; + return nRet; +} + +bool SwCursorShell::IsSelOnePara() const +{ + if (m_pCurrentCursor->IsMultiSelection()) + { + return false; + } + if (m_pCurrentCursor->GetPoint()->nNode == m_pCurrentCursor->GetMark()->nNode) + { + return true; + } + if (GetLayout()->HasMergedParas()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + auto const n(m_pCurrentCursor->GetMark()->nNode.GetIndex()); + return FrameContainsNode(*pFrame, n); + } + return false; +} + +bool SwCursorShell::IsSttPara() const +{ + if (GetLayout()->HasMergedParas()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetTextNode()); + if (pNode) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>( + pNode->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) + == TextFrameIndex(0); + } + } + } + return m_pCurrentCursor->GetPoint()->nContent == 0; +} + +bool SwCursorShell::IsEndPara() const +{ + if (GetLayout()->HasMergedParas()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetTextNode()); + if (pNode) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>( + pNode->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) + == TextFrameIndex(pFrame->GetText().getLength()); + } + } + } + return m_pCurrentCursor->GetPoint()->nContent == m_pCurrentCursor->GetContentNode()->Len(); +} + +bool SwCursorShell::IsEndOfTable() const +{ + if (IsTableMode() || IsBlockMode() || !IsEndPara()) + { + return false; + } + SwTableNode const*const pTableNode( IsCursorInTable() ); + if (!pTableNode) + { + return false; + } + SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode()); + SwNodeIndex const lastNode(*pEndTableNode, -2); + SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core", + "text node expected"); + return (lastNode == m_pCurrentCursor->GetPoint()->nNode); +} + +bool SwCursorShell::IsCursorInFootnote() const +{ + SwStartNodeType aStartNodeType = m_pCurrentCursor->GetNode().StartOfSectionNode()->GetStartNodeType(); + return aStartNodeType == SwStartNodeType::SwFootnoteStartNode; +} + +bool SwCursorShell::IsInFrontOfLabel() const +{ + return m_pCurrentCursor->IsInFrontOfLabel(); +} + +bool SwCursorShell::SetInFrontOfLabel( bool bNew ) +{ + if ( bNew != IsInFrontOfLabel() ) + { + m_pCurrentCursor->SetInFrontOfLabel_( bNew ); + UpdateMarkedListLevel(); + return true; + } + return false; +} + +namespace { + +void collectUIInformation(const OUString& aPage) +{ + EventDescription aDescription; + aDescription.aAction = "GOTO"; + aDescription.aParameters = {{"PAGE", aPage}}; + aDescription.aID = "writer_edit"; + aDescription.aKeyWord = "SwEditWinUIObject"; + aDescription.aParent = "MainWindow"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +bool SwCursorShell::GotoPage( sal_uInt16 nPage ) +{ + CurrShell aCurr( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + + collectUIInformation(OUString::number(nPage)); + return bRet; +} + +void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos) +{ + SwContentFrame* pFrame = GetCurrFrame(); + pFrame->GetCharRect( rRect, *pPos ); +} + +void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum, + bool bAtCursorPos, const bool bCalcFrame ) +{ + CurrShell aCurr( this ); + // page number: first visible page or the one at the cursor + const SwContentFrame* pCFrame; + const SwPageFrame *pPg = nullptr; + + if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) || + nullptr == (pPg = pCFrame->FindPageFrame()) ) + { + pPg = Imp()->GetFirstVisPage(GetOut()); + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast<const SwPageFrame *>(pPg->GetNext()); + } + // pPg has to exist with a default of 1 for the special case "Writerstart" + rnPhyNum = pPg? pPg->GetPhyPageNum() : 1; + rnVirtNum = pPg? pPg->GetVirtPageNum() : 1; +} + +sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty() +{ + CurrShell aCurr(this); + // page number: first visible page or the one at the cursor + const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true); + const SwPageFrame* pPg = nullptr; + + if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame())) + { + pPg = Imp()->GetFirstVisPage(GetOut()); + while (pPg && pPg->IsEmptyPage()) + pPg = static_cast<const SwPageFrame*>(pPg->GetNext()); + } + + sal_uInt16 nPageNo = 0; + while (pPg) + { + if (!pPg->IsEmptyPage()) + ++nPageNo; + pPg = static_cast<const SwPageFrame*>(pPg->GetPrev()); + } + return nPageNo; +} + +sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext ) +{ + CurrShell aCurr( this ); + // page number: first visible page or the one at the cursor + const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut()); + if( pPg ) + { + const SwTwips nPageTop = pPg->getFrameArea().Top(); + + if( bNext ) + { + // go to next view layout row: + do + { + pPg = static_cast<const SwPageFrame *>(pPg->GetNext()); + } + while( pPg && pPg->getFrameArea().Top() == nPageTop ); + + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast<const SwPageFrame *>(pPg->GetNext()); + } + else + { + // go to previous view layout row: + do + { + pPg = static_cast<const SwPageFrame *>(pPg->GetPrev()); + } + while( pPg && pPg->getFrameArea().Top() == nPageTop ); + + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast<const SwPageFrame *>(pPg->GetPrev()); + } + } + // pPg has to exist with a default of 1 for the special case "Writerstart" + return pPg ? pPg->GetPhyPageNum() : USHRT_MAX; +} + +sal_uInt16 SwCursorShell::GetPageCnt() +{ + CurrShell aCurr( this ); + // return number of pages + return GetLayout()->GetPageNum(); +} + +OUString SwCursorShell::getPageRectangles() +{ + CurrShell aCurr(this); + SwRootFrame* pLayout = GetLayout(); + OUStringBuffer aBuf; + for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext()) + { + aBuf.append(pFrame->getFrameArea().Left()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Top()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Width()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Height()); + aBuf.append("; "); + } + if (!aBuf.isEmpty()) + aBuf.setLength( aBuf.getLength() - 2); // remove the last "; " + return aBuf.makeStringAndClear(); +} + +void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const +{ + auto pView = const_cast<SdrView*>(GetDrawView()); + if (pView->GetTextEditObject()) + { + // Blinking cursor. + EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); + rEditView.RegisterOtherShell(pOtherShell); + rEditView.ShowCursor(); + rEditView.RegisterOtherShell(nullptr); + // Text selection, if any. + rEditView.DrawSelectionXOR(pOtherShell); + + // Shape text lock. + if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView()) + { + OString sRect = pOutlinerView->GetOutputArea().toString(); + SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect); + } + } + else + { + // Cursor position. + m_pVisibleCursor->SetPosAndShow(pOtherShell); + // Cursor visibility. + if (GetSfxViewShell() != pOtherShell) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + // Text selection. + m_pCurrentCursor->Show(pOtherShell); + // Graphic selection. + pView->AdjustMarkHdl(pOtherShell); + } +} + +/// go to the next SSelection +bool SwCursorShell::GoNextCursor() +{ + if( !m_pCurrentCursor->IsMultiSelection() ) + return false; + + CurrShell aCurr( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor = m_pCurrentCursor->GetNext(); + + // #i24086#: show also all others + if( !ActionPend() ) + { + UpdateCursor(); + m_pCurrentCursor->Show(nullptr); + } + return true; +} + +/// go to the previous SSelection +bool SwCursorShell::GoPrevCursor() +{ + if( !m_pCurrentCursor->IsMultiSelection() ) + return false; + + CurrShell aCurr( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor = m_pCurrentCursor->GetPrev(); + + // #i24086#: show also all others + if( !ActionPend() ) + { + UpdateCursor(); + m_pCurrentCursor->Show(nullptr); + } + return true; +} + +void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + if( !m_pCurrentCursor->IsMultiSelection() ) + { + if( !m_pCurrentCursor->HasMark() ) + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return; + } + + if (bNext) + GoNextCursor(); + else + GoPrevCursor(); +} + +void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + CurrShell aCurr( this ); + + // always switch off all cursors when painting + SwRect aRect( rRect ); + + bool bVis = false; + // if a cursor is visible then hide the SV cursor + if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) ) + { + bVis = true; + m_pVisibleCursor->Hide(); + } + + // re-paint area + SwViewShell::Paint(rRenderContext, rRect); + + if( m_bHasFocus && !m_bBasicHideCursor ) + { + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + + if( !ActionPend() ) + { + // so that right/bottom borders will not be cropped + pCurrentCursor->Invalidate( VisArea() ); + pCurrentCursor->Show(nullptr); + } + else + pCurrentCursor->Invalidate( aRect ); + + } + + if (SwPostItMgr* pPostItMgr = GetPostItMgr()) + { + // No point in showing the cursor for Writer text when there is an + // active annotation edit. + if (bVis) + bVis = !pPostItMgr->HasActiveSidebarWin(); + } + + if( m_bSVCursorVis && bVis ) // also show SV cursor again + m_pVisibleCursor->Show(); +} + +void SwCursorShell::VisPortChgd( const SwRect & rRect ) +{ + CurrShell aCurr( this ); + bool bVis; // switch off all cursors when scrolling + + // if a cursor is visible then hide the SV cursor + bVis = m_pVisibleCursor->IsVisible(); + if( bVis ) + m_pVisibleCursor->Hide(); + + m_bVisPortChgd = true; + m_aOldRBPos.setX(VisArea().Right()); + m_aOldRBPos.setY(VisArea().Bottom()); + + // For not having problems with the SV cursor, Update() is called for the + // Window in SwViewShell::VisPo... + // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact? + SwViewShell::VisPortChgd( rRect ); // move area + + if( m_bSVCursorVis && bVis ) // show SV cursor again + m_pVisibleCursor->Show(); + + if( m_nCursorMove ) + m_bInCMvVisportChgd = true; + + m_bVisPortChgd = false; +} + +/** Set the cursor back into content. + + This should only be called if the cursor was move somewhere else (e.g. when + deleting a border). The new position is calculated from its current position + in the layout. +*/ +void SwCursorShell::UpdateCursorPos() +{ + CurrShell aCurr( this ); + ++mnStartAction; + SwShellCursor* pShellCursor = getShellCursor( true ); + Size aOldSz( GetDocSize() ); + + if( isInHiddenTextFrame(pShellCursor) ) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(), + &aTmpState ); + pShellCursor->DeleteMark(); + } + IGrammarContact *pGrammarContact = GetDoc() ? GetDoc()->getGrammarContact() : nullptr; + if( pGrammarContact ) + pGrammarContact->updateCursorPosition( *m_pCurrentCursor->GetPoint() ); + --mnStartAction; + if( aOldSz != GetDocSize() ) + SizeChgNotify(); +} + +// #i65475# - if Point/Mark in hidden sections, move them out +static bool lcl_CheckHiddenSection( SwNodeIndex& rIdx ) +{ + bool bOk = true; + const SwSectionNode* pSectNd = rIdx.GetNode().FindSectionNode(); + if( pSectNd && pSectNd->GetSection().IsHiddenFlag() ) + { + SwNodeIndex aTmp( *pSectNd ); + const SwNode* pFrameNd = + rIdx.GetNodes().FindPrvNxtFrameNode( aTmp, pSectNd->EndOfSectionNode() ); + bOk = pFrameNd != nullptr; + SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames"); + rIdx = aTmp; + } + return bOk; +} + +/// Try to set the cursor to the next visible content node. +static void lcl_CheckHiddenPara( SwPosition& rPos ) +{ + SwNodeIndex aTmp( rPos.nNode ); + SwTextNode* pTextNd = aTmp.GetNode().GetTextNode(); + while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) ) + { + SwContentNode* pContent = aTmp.GetNodes().GoNext( &aTmp ); + if ( pContent && pContent->IsTextNode() ) + pTextNd = pContent->GetTextNode(); + else + pTextNd = nullptr; + } + + if ( pTextNd ) + rPos = SwPosition( aTmp, SwIndex( pTextNd, 0 ) ); +} + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY +namespace { + +// #i27301# - helper class that notifies the accessibility about invalid text +// selections in its destructor +class SwNotifyAccAboutInvalidTextSelections +{ + private: + SwCursorShell& mrCursorSh; + + public: + explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh ) + : mrCursorSh( _rCursorSh ) + {} + + ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE + { + mrCursorSh.InvalidateAccessibleParaTextSelection(); + } +}; + +} +#endif + +void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd ) +{ + CurrShell aCurr( this ); + ClearUpCursors(); + + if (ActionPend()) + { + if ( eFlags & SwCursorShell::READONLY ) + m_bIgnoreReadonly = true; + return; // if not then no update + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this ); +#endif + + if ( m_bIgnoreReadonly ) + { + m_bIgnoreReadonly = false; + eFlags |= SwCursorShell::READONLY; + } + + if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for + CheckRange( m_pCurrentCursor ); // overlapping ranges + + if( !bIdleEnd ) + CheckTableBoxContent(); + + // If the current cursor is in a table and point/mark in different boxes, + // then the table mode is active (also if it is already active: m_pTableCursor) + SwPaM* pTstCursor = getShellCursor( true ); + if( pTstCursor->HasMark() && !m_pBlockCursor && + mxDoc->IsIdxInTable( pTstCursor->GetPoint()->nNode ) && + ( m_pTableCursor || + pTstCursor->GetNode().StartOfSectionNode() != + pTstCursor->GetNode( false ).StartOfSectionNode() ) && !mbSelectAll) + { + SwShellCursor* pITmpCursor = getShellCursor( true ); + Point aTmpPt( pITmpCursor->GetPtPos() ); + Point aTmpMk( pITmpCursor->GetMkPos() ); + SwPosition* pPos = pITmpCursor->GetPoint(); + + // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out + lcl_CheckHiddenSection( pPos->nNode ); + lcl_CheckHiddenSection( pITmpCursor->GetMark()->nNode ); + + // Move cursor out of hidden paragraphs + if ( !GetViewOptions()->IsShowHiddenChar() ) + { + lcl_CheckHiddenPara( *pPos ); + lcl_CheckHiddenPara( *pITmpCursor->GetMark() ); + } + + std::pair<Point, bool> const tmp(aTmpPt, false); + SwContentFrame *pTableFrame = pPos->nNode.GetNode().GetContentNode()-> + getLayoutFrame( GetLayout(), pPos, &tmp); + + OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" ); + + // --> Make code robust. The table cursor may point + // to a table in a currently inactive header. + SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr; + + if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 ) + { + // First check if point is in repeated headline: + bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame ); + + // Second check if mark is in repeated headline: + if ( !bInRepeatedHeadline ) + { + std::pair<Point, bool> const tmp1(aTmpMk, false); + SwContentFrame* pMarkTableFrame = pITmpCursor->GetContentNode( false )-> + getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1); + OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" ); + + if ( pMarkTableFrame ) + { + SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame(); + OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" ); + + // Make code robust: + if ( pMarkTab ) + { + bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame ); + } + } + } + + // No table cursor in repeated headlines: + if ( bInRepeatedHeadline ) + { + pTableFrame = nullptr; + + SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark() + ? fnSectionStart + : fnSectionEnd; + + // then only select inside the Box + if( m_pTableCursor ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark(); + m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos(); + m_pTableCursor->DeleteMark(); + m_pTableCursor->SwSelPaintRects::Hide(); + } + + *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark(); + GoCurrSection( *m_pCurrentCursor, fnPosSect ); + } + } + + // we really want a table selection + if( pTab && pTableFrame ) + { + if( !m_pTableCursor ) + { + m_pTableCursor = new SwShellTableCursor( *this, + *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(), + *pPos, aTmpPt ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + + CheckTableBoxContent(); + if(!m_pTableCursor) + { + SAL_WARN("sw.core", "fdo#74854: " + "this should not happen, but better lose the selection " + "rather than crashing"); + return; + } + } + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bRealHeight = true; + { + DisableCallbackAction a(*GetLayout()); + if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState)) + { + Point aCentrPt( m_aCharRect.Center() ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState); + bool const bResult = + pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint()); + OSL_ENSURE( bResult, "GetCharRect failed." ); + } + } + + m_pVisibleCursor->Hide(); // always hide visible Cursor + // scroll Cursor to visible area + if( eFlags & SwCursorShell::SCROLLWIN && + (HasSelection() || eFlags & SwCursorShell::READONLY || + !IsCursorReadonly()) ) + { + SwFrame* pBoxFrame = pTableFrame; + while( pBoxFrame && !pBoxFrame->IsCellFrame() ) + pBoxFrame = pBoxFrame->GetUpper(); + if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() ) + MakeVisible( pBoxFrame->getFrameArea() ); + else + MakeVisible( m_aCharRect ); + } + + // let Layout create the Cursors in the Boxes + if( m_pTableCursor->IsCursorMovedUpdate() ) + GetLayout()->MakeTableCursors( *m_pTableCursor ); + if( m_bHasFocus && !m_bBasicHideCursor ) + m_pTableCursor->Show(nullptr); + + // set Cursor-Points to the new Positions + m_pTableCursor->GetPtPos().setX(m_aCharRect.Left()); + m_pTableCursor->GetPtPos().setY(m_aCharRect.Top()); + + if( m_bSVCursorVis ) + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? + -m_aCharRect.Width() : m_aCharRect.Height()); + m_pVisibleCursor->Show(); // show again + } + m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents) + Imp()->InvalidateAccessibleCursorPosition( pTableFrame ); +#endif + return; + } + } + + if( m_pTableCursor ) + { + // delete Ring + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + } + + m_pVisibleCursor->Hide(); // always hide visible Cursor + + // are we perhaps in a protected / hidden Section ? + { + SwShellCursor* pShellCursor = getShellCursor( true ); + bool bChgState = true; + const SwSectionNode* pSectNd = pShellCursor->GetNode().FindSectionNode(); + if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() && + ( !mxDoc->GetDocShell() || + !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) ) + { + if( !FindValidContentNode( !HasDrawView() || + 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) + { + // everything protected/hidden -> special mode + if( m_bAllProtect && !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() ) + bChgState = false; + else + { + m_eMvState = CursorMoveState::NONE; // state for cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI! + } + return; + } + } + } + if( bChgState ) + { + bool bWasAllProtect = m_bAllProtect; + m_bAllProtect = false; + if( bWasAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI( false ); + CallChgLnk(); // notify UI! + } + } + } + + UpdateCursorPos(); + + // The cursor must always point into content; there's some code + // that relies on this. (E.g. in SwEditShell::GetScriptType, which always + // loops _behind_ the last node in the selection, which always works if you + // are in content.) To achieve this, we'll force cursor(s) to point into + // content, if UpdateCursorPos() hasn't already done so. + for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer()) + { + // start will move forwards, end will move backwards + bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() ); + + // move point; forward if it's the start, backwards if it's the end + if( ! rCmp.GetPoint()->nNode.GetNode().IsContentNode() ) + rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward, + GoInContent ); + + // move mark (if exists); forward if it's the start, else backwards + if( rCmp.HasMark() ) + { + if( ! rCmp.GetMark()->nNode.GetNode().IsContentNode() ) + { + rCmp.Exchange(); + rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward, + GoInContent ); + rCmp.Exchange(); + } + } + } + + SwRect aOld( m_aCharRect ); + bool bFirst = true; + SwContentFrame *pFrame; + int nLoopCnt = 100; + SwShellCursor* pShellCursor = getShellCursor( true ); + + do { + bool bAgainst; + do { + bAgainst = false; + std::pair<Point, bool> const tmp1(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetContentNode()->getLayoutFrame(GetLayout(), + pShellCursor->GetPoint(), &tmp1); + // if the Frame doesn't exist anymore, the complete Layout has to be + // created, because there used to be a Frame here! + if ( !pFrame ) + { + do + { + CalcLayout(); + std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetContentNode()->getLayoutFrame( + GetLayout(), pShellCursor->GetPoint(), &tmp); + } while( !pFrame ); + } + else if ( Imp()->IsIdleAction() ) + // Guarantee everything's properly formatted + pFrame->PrepareCursor(); + + // In protected Fly? but ignore in case of frame selection + if( !IsReadOnlyAvailable() && pFrame->IsProtected() && + ( !Imp()->GetDrawView() || + !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) && + (!mxDoc->GetDocShell() || + !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) ) + { + // look for a valid position + bool bChgState = true; + if( !FindValidContentNode(!HasDrawView() || + 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) + { + // everything is protected / hidden -> special Mode + if( m_bAllProtect ) + bChgState = false; + else + { + m_eMvState = CursorMoveState::NONE; // state for cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI! + } + return; + } + } + + if( bChgState ) + { + bool bWasAllProtect = m_bAllProtect; + m_bAllProtect = false; + if( bWasAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI( false ); + CallChgLnk(); // notify UI! + } + m_bAllProtect = false; + bAgainst = true; // look for the right Frame again + } + } + } while( bAgainst ); + + SwCursorMoveState aTmpState( m_eMvState ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + aTmpState.m_bRealHeight = true; + aTmpState.m_bRealWidth = IsOverwriteCursor(); + aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel(); + + // #i27615#,#i30453# + SwSpecialPos aSpecialPos; + aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE; + if (pShellCursor->IsInFrontOfLabel()) + { + aTmpState.m_pSpecialPos = &aSpecialPos; + } + + { + DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action + if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState)) + { + Point& rPt = pShellCursor->GetPtPos(); + rPt = m_aCharRect.Center(); + pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState ); + } + } + UISizeNotify(); // tdf#96256 update view size + + if( !pShellCursor->HasMark() ) + m_aCursorHeight = aTmpState.m_aRealHeight; + else + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? + -m_aCharRect.Width() : m_aCharRect.Height()); + } + + if( !bFirst && aOld == m_aCharRect ) + break; + + // if the layout says that we are after the 100th iteration still in + // flow then we should always take the current position for granted. + // (see bug: 29658) + if( !--nLoopCnt ) + { + OSL_ENSURE( false, "endless loop? CharRect != OldCharRect "); + break; + } + aOld = m_aCharRect; + bFirst = false; + + // update cursor Points to the new Positions + pShellCursor->GetPtPos().setX(m_aCharRect.Left()); + pShellCursor->GetPtPos().setY(m_aCharRect.Top()); + + if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down + { + DisableCallbackAction a(*GetLayout()); + pFrame->Calc(GetOut()); + m_nUpDownX = pFrame->IsVertical() ? + m_aCharRect.Top() - pFrame->getFrameArea().Top() : + m_aCharRect.Left() - pFrame->getFrameArea().Left(); + } + + // scroll Cursor to visible area + if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN && + (HasSelection() || eFlags & SwCursorShell::READONLY || + !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) ) + { + // in case of scrolling this EndAction doesn't show the SV cursor + // again, thus save and reset the flag here + bool bSav = m_bSVCursorVis; + m_bSVCursorVis = false; + MakeSelVisible(); + m_bSVCursorVis = bSav; + } + + } while( eFlags & SwCursorShell::SCROLLWIN ); + + assert(pFrame); + + if( m_pBlockCursor ) + RefreshBlockCursor(); + + // We should not restrict cursor update to the active view when using LOK + bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive(); + + if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor ) + { + if( m_pTableCursor ) + m_pTableCursor->SwSelPaintRects::Show(); + else + { + m_pCurrentCursor->SwSelPaintRects::Show(); + if( m_pBlockCursor ) + { + SwShellCursor* pNxt = m_pCurrentCursor->GetNext(); + while( pNxt && pNxt != m_pCurrentCursor ) + { + pNxt->SwSelPaintRects::Show(); + pNxt = pNxt->GetNext(); + } + } + } + } + + m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents) + Imp()->InvalidateAccessibleCursorPosition( pFrame ); +#endif + + // switch from blinking cursor to read-only-text-selection cursor + const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings(). + GetCursorBlinkTime(); + + if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) == + ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) ) + { + // non blinking cursor in read only - text selection mode + AllSettings aSettings = GetOut()->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ? + Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() : + STYLE_CURSOR_NOBLINKTIME; + aStyleSettings.SetCursorBlinkTime( nNewBlinkTime ); + aSettings.SetStyleSettings( aStyleSettings ); + GetOut()->SetSettings( aSettings ); + } + + if( m_bSVCursorVis ) + m_pVisibleCursor->Show(); // show again + + if (comphelper::LibreOfficeKit::isActive()) + sendLOKCursorUpdates(); + + getIDocumentMarkAccess()->NotifyCursorUpdate(*this); +} + +void SwCursorShell::sendLOKCursorUpdates() +{ + SwView* pView = static_cast<SwView*>(GetSfxViewShell()); + if (!pView || !pView->GetWrtShellPtr()) + return; + + SwWrtShell* pShell = &pView->GetWrtShell(); + + SwFrame* pCurrentFrame = GetCurrFrame(); + SelectionType eType = pShell->GetSelectionType(); + + tools::JsonWriter aJsonWriter; + + if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab()) + { + const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr); + + { + auto columnsNode = aJsonWriter.startNode("columns"); + SwTabCols aTabCols; + pShell->GetTabCols(aTabCols); + + const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left(); + + aJsonWriter.put("left", aTabCols.GetLeft()); + aJsonWriter.put("right", aTabCols.GetRight()); + aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nColumnOffset)); + + { + auto entriesNode = aJsonWriter.startArray("entries"); + for (size_t i = 0; i < aTabCols.Count(); ++i) + { + auto entryNode = aJsonWriter.startStruct(); + auto const & rEntry = aTabCols.GetEntry(i); + aJsonWriter.put("position", rEntry.nPos); + aJsonWriter.put("min", rEntry.nMin); + aJsonWriter.put("max", rEntry.nMax); + aJsonWriter.put("hidden", rEntry.bHidden); + } + } + } + + { + auto rowsNode = aJsonWriter.startNode("rows"); + SwTabCols aTabRows; + pShell->GetTabRows(aTabRows); + + const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top(); + + aJsonWriter.put("left", aTabRows.GetLeft()); + aJsonWriter.put("right", aTabRows.GetRight()); + aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nRowOffset)); + + { + auto entriesNode = aJsonWriter.startArray("entries"); + for (size_t i = 0; i < aTabRows.Count(); ++i) + { + auto entryNode = aJsonWriter.startStruct(); + auto const & rEntry = aTabRows.GetEntry(i); + aJsonWriter.put("position", rEntry.nPos); + aJsonWriter.put("min", rEntry.nMin); + aJsonWriter.put("max", rEntry.nMax); + aJsonWriter.put("hidden", rEntry.bHidden); + } + } + } + } + + char* pChar = aJsonWriter.extractData(); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar); + free(pChar); +} + +void SwCursorShell::RefreshBlockCursor() +{ + assert(m_pBlockCursor); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + Point aPt = rBlock.GetPtPos(); + std::pair<Point, bool> const tmp(aPt, false); + SwContentFrame* pFrame = rBlock.GetContentNode()->getLayoutFrame( + GetLayout(), rBlock.GetPoint(), &tmp); + Point aMk; + if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() ) + { + aPt = *m_pBlockCursor->getStartPoint(); + aMk = *m_pBlockCursor->getEndPoint(); + } + else + { + aPt = rBlock.GetPtPos(); + if( pFrame ) + { + if( pFrame->IsVertical() ) + aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX()); + else + aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX()); + } + aMk = rBlock.GetMkPos(); + } + SwRect aRect( aMk, aPt ); + aRect.Justify(); + SwSelectionList aSelList( pFrame ); + + if( !GetLayout()->FillSelection( aSelList, aRect ) ) + return; + + SwCursor* pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext()); + while( pNxt != m_pCurrentCursor ) + { + delete pNxt; + pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext()); + } + + std::list<SwPaM*>::iterator pStart = aSelList.getStart(); + std::list<SwPaM*>::iterator pPam = aSelList.getEnd(); + OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" ); + m_pCurrentCursor->SetMark(); + --pPam; + // If there is only one text portion inside the rectangle, a simple + // selection is created + if( pPam == pStart ) + { + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); + if( (*pPam)->HasMark() ) + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + else + m_pCurrentCursor->DeleteMark(); + delete *pPam; + m_pCurrentCursor->SetColumnSelection( false ); + } + else + { + // The order of the SwSelectionList has to be preserved but + // the order inside the ring created by CreateCursor() is not like + // expected => First create the selections before the last one + // downto the first selection. + // At least create the cursor for the last selection + --pPam; + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections) + if( (*pPam)->HasMark() ) + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + else + m_pCurrentCursor->DeleteMark(); + delete *pPam; + m_pCurrentCursor->SetColumnSelection( true ); + while( pPam != pStart ) + { + --pPam; + + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end()); + m_pCurrentCursor->clear(); + m_pCurrentCursor->DeleteMark(); + + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1 + if( (*pPam)->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + } + else + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetColumnSelection( true ); + delete *pPam; + } + { + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() ); + m_pCurrentCursor->clear(); + m_pCurrentCursor->DeleteMark(); + } + pPam = aSelList.getEnd(); + --pPam; + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection + if( (*pPam)->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + } + else + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetColumnSelection( true ); + delete *pPam; + } +} + +/// create a copy of the cursor and save it in the stack +void SwCursorShell::Push() +{ + // fdo#60513: if we have a table cursor, copy that; else copy current. + // This seems to work because UpdateCursor() will fix this up on Pop(), + // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring. + SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor); + m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(), + pCurrent->GetPtPos(), m_pStackCursor ); + + if (pCurrent->HasMark()) + { + m_pStackCursor->SetMark(); + *m_pStackCursor->GetMark() = *pCurrent->GetMark(); + } +} + +/** delete cursor + + @param eDelete delete from stack, or delete current + and assign the one from stack as the new current cursor. + @return <true> if there was one on the stack, <false> otherwise +*/ +bool SwCursorShell::Pop(PopMode const eDelete) +{ + ::std::unique_ptr<SwCallLink> pLink(::std::make_unique<SwCallLink>(*this)); // watch Cursor-Moves; call Link if needed + return Pop(eDelete, ::std::move(pLink)); +} + +bool SwCursorShell::Pop(PopMode const eDelete, + [[maybe_unused]] ::std::unique_ptr<SwCallLink> const pLink) +{ + assert(pLink); // parameter exists only to be deleted before return + + // are there any left? + if (nullptr == m_pStackCursor) + return false; + + SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor; + + // the successor becomes the current one + if (m_pStackCursor->GetNext() != m_pStackCursor) + { + pTmp = m_pStackCursor->GetNext(); + } + + if (PopMode::DeleteStack == eDelete) + delete m_pStackCursor; + + m_pStackCursor = pTmp; // assign new one + + if (PopMode::DeleteCurrent == eDelete) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // If the visible SSelection was not changed + const Point& rPoint = pOldStack->GetPtPos(); + if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos()) + { + // move "Selections Rectangles" + m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() ); + pOldStack->clear(); + } + + if( pOldStack->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *pOldStack->GetMark(); + m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos(); + } + else + // no selection so revoke old one and set to old position + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint(); + m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos(); + delete pOldStack; + + if( !m_pCurrentCursor->IsInProtectTable( true ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor(); // update current cursor + if (m_pTableCursor) + { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table + m_pTableCursor->SetChgd(); + } + } + } + return true; +} + +/** Combine two cursors + + Delete topmost from stack and use its GetMark in the current. +*/ +void SwCursorShell::Combine() +{ + // any others left? + if (nullptr == m_pStackCursor) + return; + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + // rhbz#689053: IsSelOvr must restore the saved stack position, not the + // current one, because current point + stack mark may be invalid PaM + SwCursorSaveState aSaveState(*m_pStackCursor); + // stack cursor & current cursor in same Section? + assert(!m_pStackCursor->HasMark() || + CheckNodesRange(m_pStackCursor->GetMark()->nNode, + m_pCurrentCursor->GetPoint()->nNode, true)); + *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint(); + m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos(); + + SwShellCursor * pTmp = nullptr; + if (m_pStackCursor->GetNext() != m_pStackCursor) + { + pTmp = m_pStackCursor->GetNext(); + } + delete m_pCurrentCursor; + m_pCurrentCursor = m_pStackCursor; + m_pStackCursor->MoveTo(nullptr); // remove from ring + m_pStackCursor = pTmp; + if( !m_pCurrentCursor->IsInProtectTable( true ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor(); // update current cursor + } +} + +void SwCursorShell::HideCursors() +{ + if( !m_bHasFocus || m_bBasicHideCursor ) + return; + + // if cursor is visible then hide SV cursor + if( m_pVisibleCursor->IsVisible() ) + { + CurrShell aCurr( this ); + m_pVisibleCursor->Hide(); + } + // revoke inversion of SSelection + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + pCurrentCursor->Hide(); +} + +void SwCursorShell::ShowCursors( bool bCursorVis ) +{ + if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor ) + return; + + CurrShell aCurr( this ); + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + pCurrentCursor->Show(nullptr); + + if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again + m_pVisibleCursor->Show(); +} + +void SwCursorShell::ShowCursor() +{ + if( m_bBasicHideCursor ) + return; + + m_bSVCursorVis = true; + m_pCurrentCursor->SetShowTextInputFieldOverlay( true ); + m_pCurrentCursor->SetShowContentControlOverlay(true); + + if (comphelper::LibreOfficeKit::isActive()) + { + const OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + + UpdateCursor(); +} + +void SwCursorShell::HideCursor() +{ + if( m_bBasicHideCursor ) + return; + + m_bSVCursorVis = false; + // possibly reverse selected areas!! + CurrShell aCurr( this ); + m_pCurrentCursor->SetShowTextInputFieldOverlay( false ); + m_pCurrentCursor->SetShowContentControlOverlay(false); + m_pVisibleCursor->Hide(); + + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } +} + +void SwCursorShell::ShellLoseFocus() +{ + if( !m_bBasicHideCursor ) + HideCursors(); + m_bHasFocus = false; +} + +void SwCursorShell::ShellGetFocus() +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + + m_bHasFocus = true; + if( !m_bBasicHideCursor && VisArea().Width() ) + { + UpdateCursor( o3tl::narrowing<sal_uInt16>( SwCursorShell::CHKRANGE ) ); + ShowCursors( m_bSVCursorVis ); + } +} + +/** Get current frame in which the cursor is positioned. */ +SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const +{ + CurrShell aCurr( const_cast<SwCursorShell*>(this) ); + SwContentFrame *pRet = nullptr; + SwContentNode *pNd = m_pCurrentCursor->GetContentNode(); + if ( pNd ) + { + if ( bCalcFrame ) + { + sal_uInt16* pST = const_cast<sal_uInt16*>(&mnStartAction); + ++(*pST); + const Size aOldSz( GetDocSize() ); + std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), true); + pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + --(*pST); + if( aOldSz != GetDocSize() ) + const_cast<SwCursorShell*>(this)->SizeChgNotify(); + } + else + { + std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), false); + pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + } + } + return pRet; +} + +//TODO: provide documentation +/** forward all attribute/format changes at the current node to the Link + + @param pOld ??? + @param pNew ??? +*/ +void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if(dynamic_cast<const sw::PostGraphicArrivedHint*>(&rHint) && m_aGrfArrivedLnk.IsSet()) + { + m_aGrfArrivedLnk.Call(*this); + return; + } + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + auto nWhich = pLegacy->GetWhich(); + if(!nWhich) + nWhich = sal::static_int_cast<sal_uInt16>(RES_MSG_BEGIN); + if( m_bCallChgLnk && + ( nWhich < RES_MSG_BEGIN || nWhich >= RES_MSG_END || + nWhich == RES_FMT_CHG || nWhich == RES_UPDATE_ATTR || + nWhich == RES_ATTRSET_CHG )) + // messages are not forwarded + // #i6681#: RES_UPDATE_ATTR is implicitly unset in + // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do + // not need to send the expensive RES_FMT_CHG in Insert. + CallChgLnk(); + switch(nWhich) + { + case RES_OBJECTDYING: + EndListeningAll(); + break; + case RES_GRAPHIC_SWAPIN: + if(m_aGrfArrivedLnk.IsSet()) + m_aGrfArrivedLnk.Call(*this); + } + +} + +/** Does the current cursor create a selection? + + This means checking if GetMark is set and if SPoint and GetMark differ. +*/ +bool SwCursorShell::HasSelection() const +{ + const SwPaM* pCursor = getShellCursor( true ); + return IsTableMode() + || (pCursor->HasMark() && + (*pCursor->GetPoint() != *pCursor->GetMark() + || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End()))); +} + +void SwCursorShell::CallChgLnk() +{ + // Do not make any call in StartAction/EndAction but just set the flag. + // This will be handled in EndAction. + if (ActionPend()) + m_bChgCallFlag = true; // remember change + else if( m_aChgLnk.IsSet() ) + { + if( m_bCallChgLnk ) + m_aChgLnk.Call(nullptr); + m_bChgCallFlag = false; // reset flag + } +} + +/// get selected text of a node at current cursor +OUString SwCursorShell::GetSelText() const +{ + OUString aText; + if (GetLayout()->HasMergedParas()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->nNode.GetIndex())) + { + OUStringBuffer buf; + SwPosition const*const pStart(m_pCurrentCursor->Start()); + SwPosition const*const pEnd(m_pCurrentCursor->End()); + for (SwNodeOffset i = pStart->nNode.GetIndex(); i <= pEnd->nNode.GetIndex(); ++i) + { + SwNode const& rNode(*pStart->nNode.GetNodes()[i]); + assert(!rNode.IsEndNode()); + if (rNode.IsStartNode()) + { + i = rNode.EndOfSectionIndex(); + } + else if (rNode.IsTextNode()) + { + sal_Int32 const nStart(i == pStart->nNode.GetIndex() + ? pStart->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(i == pEnd->nNode.GetIndex() + ? pEnd->nContent.GetIndex() + : rNode.GetTextNode()->Len()); + buf.append(rNode.GetTextNode()->GetExpandText( + GetLayout(), + nStart, nEnd - nStart, false, false, false, + ExpandMode::HideDeletions)); + + } + } + aText = buf.makeStringAndClear(); + } + } + else if( m_pCurrentCursor->GetPoint()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() ) + { + SwTextNode* pTextNd = m_pCurrentCursor->GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_Int32 nStt = m_pCurrentCursor->Start()->nContent.GetIndex(); + aText = pTextNd->GetExpandText(GetLayout(), nStt, + m_pCurrentCursor->End()->nContent.GetIndex() - nStt ); + } + } + return aText; +} + +/** get the nth character of the current SSelection + + @param bEnd Start counting from the end? From start otherwise. + @param nOffset position of the character +*/ +sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset ) +{ + if( IsTableMode() ) // not possible in table mode + return 0; + + const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint() + : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return 0; + + const sal_Int32 nPos = pPos->nContent.GetIndex(); + const OUString& rStr = pTextNd->GetText(); + sal_Unicode cCh = 0; + + if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength()) + cCh = rStr[nPos + nOffset]; + + return cCh; +} + +/** extend current SSelection by n characters + + @param bEnd Start counting from the end? From start otherwise. + @param nCount Number of characters. +*/ +bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount ) +{ + if( !m_pCurrentCursor->HasMark() || IsTableMode() ) + return false; // no selection + + SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + assert(pTextNd); + + sal_Int32 nPos = pPos->nContent.GetIndex(); + if( bEnd ) + { + if ((nPos + nCount) <= pTextNd->GetText().getLength()) + nPos = nPos + nCount; + else + return false; // not possible + } + else if( nPos >= nCount ) + nPos = nPos - nCount; + else + return false; // not possible anymore + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + pPos->nContent = nPos; + UpdateCursor(); + + return true; +} + +/** Move visible cursor to given position in document. + + @param rPt The position to move the visible cursor to. + @return <false> if SPoint was corrected by the layout. +*/ +bool SwCursorShell::SetVisibleCursor( const Point &rPt ) +{ + CurrShell aCurr( this ); + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + aTmpState.m_bRealHeight = true; + + const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ ); + + SetInFrontOfLabel( false ); // #i27615# + + // show only in TextNodes + SwTextNode* pTextNd = aPos.nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return false; + + const SwSectionNode* pSectNd = pTextNd->FindSectionNode(); + if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag())) ) + return false; + + std::pair<Point, bool> const tmp(aPt, true); + SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp); + if ( Imp()->IsIdleAction() ) + pFrame->PrepareCursor(); + SwRect aTmp( m_aCharRect ); + + pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState ); + + // #i10137# + if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() ) + return true; + + m_pVisibleCursor->Hide(); // always hide visible cursor + if( IsScrollMDI( this, m_aCharRect )) + { + MakeVisible( m_aCharRect ); + m_pCurrentCursor->Show(nullptr); + } + + { + if( aTmpState.m_bRealHeight ) + m_aCursorHeight = aTmpState.m_aRealHeight; + else + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(m_aCharRect.Height()); + } + + m_pVisibleCursor->SetDragCursor(); + m_pVisibleCursor->Show(); // show again + } + return bRet; +} + +SwVisibleCursor* SwCursorShell::GetVisibleCursor() const +{ + return m_pVisibleCursor; +} + +bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const +{ + Point aPt( rPt ); + SwPaM aPam( *m_pCurrentCursor->GetPoint() ); + GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt ); + // form view + return aPam.HasReadonlySel( GetViewOptions()->IsFormView() ); +} + +/** Get the number of elements in the ring of cursors + + @param bAll If <false> get only spanned ones (= with selections) (Basic). +*/ +sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const +{ + SwPaM* pTmp = GetCursor()->GetNext(); + sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() && + *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0; + while( pTmp != m_pCurrentCursor ) + { + if( bAll || ( pTmp->HasMark() && + *pTmp->GetPoint() != *pTmp->GetMark())) + ++n; + pTmp = pTmp->GetNext(); + } + return n; +} + +bool SwCursorShell::IsStartOfDoc() const +{ + if( m_pCurrentCursor->GetPoint()->nContent.GetIndex() ) + return false; + + // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd) + SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 ); + if( !aIdx.GetNode().IsContentNode() ) + GetDoc()->GetNodes().GoNext( &aIdx ); + return aIdx == m_pCurrentCursor->GetPoint()->nNode; +} + +bool SwCursorShell::IsEndOfDoc() const +{ + SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = SwNodes::GoPrevious( &aIdx ); + + return aIdx == m_pCurrentCursor->GetPoint()->nNode && + pCNd->Len() == m_pCurrentCursor->GetPoint()->nContent.GetIndex(); +} + +/** Invalidate cursors + + Delete all created cursors, set table crsr and last crsr to their TextNode + (or StartNode?). They will then all re-created at the next ::GetCursor() call. + + This is needed for Drag&Drop/ Clipboard-paste in tables. +*/ +bool SwCursorShell::ParkTableCursor() +{ + if( !m_pTableCursor ) + return false; + + m_pTableCursor->ParkCursor(); + + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + + // *always* move cursor's Point and Mark + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + + return true; +} + +void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing ) +{ + const SwPosition *pStt = pDelRg->Start(), + *pEnd = pDelRg->End(); + + SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing; + + // search over the whole ring + bool bGoNext; + do { + + if (!pTmp) + break; + + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->End(); + // If a SPoint or GetMark are in a cursor area then cancel the old area. + // During comparison keep in mind that End() is outside the area. + if( *pStt <= *pTmpStt ) + { + if( *pEnd > *pTmpStt || + ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) + pTmpDel = pTmp; + } + else + if( *pStt < *pTmpEnd ) + pTmpDel = pTmp; + + bGoNext = true; + if (pTmpDel) // is the pam in the range -> delete + { + bool bDelete = true; + if( *ppDelRing == pTmpDel ) + { + if( *ppDelRing == m_pCurrentCursor ) + { + bDelete = GoNextCursor(); + if( bDelete ) + { + bGoNext = false; + pTmp = pTmp->GetNext(); + } + } + else + bDelete = false; // never delete the StackCursor + } + + if( bDelete ) + { + if (pTmp == pTmpDel) + pTmp = nullptr; + delete pTmpDel; // invalidate old area + } + else + { + pTmpDel->GetPoint()->nContent.Assign(nullptr, 0); + pTmpDel->GetPoint()->nNode = SwNodeOffset(0); + pTmpDel->DeleteMark(); + } + pTmpDel = nullptr; + } + if( bGoNext && pTmp ) + pTmp = pTmp->GetNext(); + + } while( !bGoNext || *ppDelRing != pTmp ); +} + +//TODO: provide documentation +/** Remove selections and additional cursors of all shells. + + The remaining cursor of the shell is parked. + + @param rIdx ??? +*/ +void SwCursorShell::ParkCursor( const SwNodeIndex &rIdx ) +{ + SwNode *pNode = &rIdx.GetNode(); + + // create a new PaM + SwPaM aNew( *GetCursor()->GetPoint() ); + if( pNode->GetStartNode() ) + { + pNode = pNode->StartOfSectionNode(); + if( pNode->IsTableNode() ) + { + // the given node is in a table, thus park cursor to table node + // (outside of the table) + aNew.GetPoint()->nNode = *pNode->StartOfSectionNode(); + } + else + // Also on the start node itself. Then we need to request the start + // node always via its end node! (StartOfSelection of StartNode is + // the parent) + aNew.GetPoint()->nNode = *pNode->EndOfSectionNode()->StartOfSectionNode(); + } + else + aNew.GetPoint()->nNode = *pNode->StartOfSectionNode(); + aNew.SetMark(); + aNew.GetPoint()->nNode = *pNode->EndOfSectionNode(); + + // take care of all shells + for(SwViewShell& rTmp : GetRingContainer()) + { + if( auto pSh = dynamic_cast<SwCursorShell *>(&rTmp)) + { + if (pSh->m_pStackCursor) + pSh->ParkPams(&aNew, &pSh->m_pStackCursor); + + pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor ); + if( pSh->m_pTableCursor ) + { + // set table cursor always to 0 and the current one always to + // the beginning of the table + SwPaM* pTCursor = pSh->GetTableCrs(); + SwNode* pTableNd = pTCursor->GetPoint()->nNode.GetNode().FindTableNode(); + if ( pTableNd ) + { + pTCursor->GetPoint()->nContent.Assign(nullptr, 0); + pTCursor->GetPoint()->nNode = SwNodeOffset(0); + pTCursor->DeleteMark(); + pSh->m_pCurrentCursor->GetPoint()->nNode = *pTableNd; + } + } + } + } +} + +/** Copy constructor + + Copy cursor position and add it to the ring. + All views of a document are in the ring of the shell. +*/ +SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin ) + : SwViewShell( rShell, pInitWin ) + , sw::BroadcastingModify() + , m_pStackCursor( nullptr ) + , m_pBlockCursor( nullptr ) + , m_pTableCursor( nullptr ) + , m_pBoxIdx( nullptr ) + , m_pBoxPtr( nullptr ) + , m_nUpDownX(0) + , m_nLeftFramePos(0) + , m_nCurrentNode(0) + , m_nCurrentContent(0) + , m_nCurrentNdTyp(SwNodeType::NONE) + , m_nCursorMove( 0 ) + , m_eMvState( CursorMoveState::NONE ) + , m_eEnhancedTableSel(SwTable::SEARCH_NONE) + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + CurrShell aCurr( this ); + // only keep the position of the current cursor of the copy shell + m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) ); + m_pCurrentCursor->GetContentNode()->Add( this ); + + m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = + m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = + m_bOverwriteCursor = false; + m_bSendAccessibleCursorEvents = true; + m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; + m_bSVCursorVis = true; + m_bSetCursorInReadOnly = true; + m_pVisibleCursor = new SwVisibleCursor( this ); + m_bMacroExecAllowed = rShell.IsMacroExecAllowed(); +} + +/// default constructor +SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin, + const SwViewOption *pInitOpt ) + : SwViewShell( rDoc, pInitWin, pInitOpt ) + , sw::BroadcastingModify() + , m_pStackCursor( nullptr ) + , m_pBlockCursor( nullptr ) + , m_pTableCursor( nullptr ) + , m_pBoxIdx( nullptr ) + , m_pBoxPtr( nullptr ) + , m_nUpDownX(0) + , m_nLeftFramePos(0) + , m_nCurrentNode(0) + , m_nCurrentContent(0) + , m_nCurrentNdTyp(SwNodeType::NONE) + , m_nCursorMove( 0 ) + , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint + , m_eEnhancedTableSel(SwTable::SEARCH_NONE) + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + CurrShell aCurr( this ); + // create initial cursor and set it to first content position + SwNodes& rNds = rDoc.GetNodes(); + + SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() ); + SwContentNode* pCNd = rNds.GoNext( &aNodeIdx ); // go to the first ContentNode + + m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, SwIndex( pCNd, 0 ))); + + // Register shell as dependent at current node. As a result all attribute + // changes can be forwarded via the Link. + pCNd->Add( this ); + + m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = + m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = + m_bOverwriteCursor = false; + m_bSendAccessibleCursorEvents = true; + m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; + m_bSVCursorVis = true; + m_bSetCursorInReadOnly = true; + + m_pVisibleCursor = new SwVisibleCursor( this ); + m_bMacroExecAllowed = true; +} + +SwCursorShell::~SwCursorShell() +{ + // if it is not the last view then at least the field should be updated + if( !unique() ) + CheckTableBoxContent( m_pCurrentCursor->GetPoint() ); + else + ClearTableBoxContent(); + + delete m_pVisibleCursor; + delete m_pBlockCursor; + delete m_pTableCursor; + + // release cursors + while(m_pCurrentCursor->GetNext() != m_pCurrentCursor) + delete m_pCurrentCursor->GetNext(); + delete m_pCurrentCursor; + + // free stack + if (m_pStackCursor) + { + while (m_pStackCursor->GetNext() != m_pStackCursor) + delete m_pStackCursor->GetNext(); + delete m_pStackCursor; + } + + // #i54025# - do not give a HTML parser that might potentially hang as + // a client at the cursor shell the chance to hang itself on a TextNode + EndListeningAll(); +} + +SwShellCursor* SwCursorShell::getShellCursor( bool bBlock ) +{ + if( m_pTableCursor ) + return m_pTableCursor; + if( m_pBlockCursor && bBlock ) + return &m_pBlockCursor->getShellCursor(); + return m_pCurrentCursor; +} + +/** Should WaitPtr be switched on for the clipboard? + + Wait for TableMode, multiple selections and more than x selected paragraphs. +*/ +bool SwCursorShell::ShouldWait() const +{ + if ( IsTableMode() || GetCursorCnt() > 1 ) + return true; + + if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() ) + return true; + + SwPaM* pPam = GetCursor(); + return pPam->Start()->nNode.GetIndex() + SwNodeOffset(10) < + pPam->End()->nNode.GetIndex(); +} + +size_t SwCursorShell::UpdateTableSelBoxes() +{ + if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount())) + { + GetLayout()->MakeTableCursors( *m_pTableCursor ); + } + return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0; +} + +/// show the current selected "object" +void SwCursorShell::MakeSelVisible() +{ + OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" ); + if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() ) + { + SwRect aTmp( m_aCharRect ); + tools::Long nDiff = m_aCharRect.Height() - VisArea().Height(); + if( nDiff < m_aCursorHeight.getX() ) + aTmp.Top( nDiff + m_aCharRect.Top() ); + else + { + aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() ); + aTmp.Height( m_aCursorHeight.getY() ); + } + if( !aTmp.HasArea() ) + { + aTmp.AddHeight(1 ); + aTmp.AddWidth(1 ); + } + MakeVisible( aTmp ); + } + else + { + if( m_aCharRect.HasArea() ) + MakeVisible( m_aCharRect ); + else + { + SwRect aTmp( m_aCharRect ); + aTmp.AddHeight(1 ); + aTmp.AddWidth(1 ); + MakeVisible( aTmp ); + } + } +} + +/// search a valid content position (not protected/hidden) +bool SwCursorShell::FindValidContentNode( bool bOnlyText ) +{ + if( m_pTableCursor ) + { + assert(!"Did not remove table selection!"); + return false; + } + + // #i45129# - everything is allowed in UI-readonly + if( !m_bAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + return true; + + if( m_pCurrentCursor->HasMark() ) + ClearMark(); + + // first check for frames + SwNodeIndex& rNdIdx = m_pCurrentCursor->GetPoint()->nNode; + SwNodeOffset nNdIdx = rNdIdx.GetIndex(); // keep backup + SwNodes& rNds = mxDoc->GetNodes(); + SwContentNode* pCNd = rNdIdx.GetNode().GetContentNode(); + const SwContentFrame * pFrame; + + if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) && + !IsReadOnlyAvailable() && pFrame->IsProtected() && + nNdIdx < rNds.GetEndOfExtras().GetIndex() ) + { + // skip protected frame + SwPaM aPam( *m_pCurrentCursor->GetPoint() ); + aPam.SetMark(); + aPam.GetMark()->nNode = rNds.GetEndOfContent(); + aPam.GetPoint()->nNode = *pCNd->EndOfSectionNode(); + + bool bFirst = false; + if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward ))) + { + aPam.GetMark()->nNode = *rNds.GetEndOfPostIts().StartOfSectionNode(); + pCNd = ::GetNode( aPam, bFirst, fnMoveBackward ); + } + + if( !pCNd ) // should *never* happen + { + rNdIdx = nNdIdx; // back to old node + return false; + } + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + else if( bOnlyText && pCNd && pCNd->IsNoTextNode() ) + { + // set to beginning of document + rNdIdx = mxDoc->GetNodes().GetEndOfExtras(); + m_pCurrentCursor->GetPoint()->nContent.Assign( mxDoc->GetNodes().GoNext( + &rNdIdx ), 0 ); + nNdIdx = rNdIdx.GetIndex(); + } + + bool bOk = true; + + // #i9059# cursor may not stand in protected cells + // (unless cursor in protected areas is OK.) + const SwTableNode* pTableNode = rNdIdx.GetNode().FindTableNode(); + if( !IsReadOnlyAvailable() && + pTableNode != nullptr && rNdIdx.GetNode().IsProtect() ) + { + // we're in a table, and we're in a protected area, so we're + // probably in a protected cell. + + // move forward into non-protected area. + SwPaM aPam( rNdIdx.GetNode(), 0 ); + while( aPam.GetNode().IsProtect() && + aPam.Move( fnMoveForward, GoInContent ) ) + ; // nothing to do in the loop; the aPam.Move does the moving! + + // didn't work? then go backwards! + if( aPam.GetNode().IsProtect() ) + { + SwPaM aTmpPaM( rNdIdx.GetNode(), 0 ); + aPam = aTmpPaM; + while( aPam.GetNode().IsProtect() && + aPam.Move( fnMoveBackward, GoInContent ) ) + ; // nothing to do in the loop; the aPam.Move does the moving! + } + + // if we're successful, set the new position + if( ! aPam.GetNode().IsProtect() ) + { + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + } + + // in a protected frame + const SwSectionNode* pSectNd = rNdIdx.GetNode().FindSectionNode(); + if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() )) ) + { + bOk = false; + bool bGoNextSection = true; + for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt ) + { + bool bContinue; + do { + bContinue = false; + for (;;) + { + if (bGoNextSection) + pCNd = rNds.GoNextSection( &rNdIdx, + true, !IsReadOnlyAvailable() ); + else + pCNd = SwNodes::GoPrevSection( &rNdIdx, + true, !IsReadOnlyAvailable() ); + if ( pCNd == nullptr) break; + // moved inside a table -> check if it is protected + if( pCNd->FindTableNode() ) + { + SwCallLink aTmp( *this ); + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR + if( !m_pCurrentCursor->IsInProtectTable( true ) ) + { + const SwSectionNode* pSNd = pCNd->FindSectionNode(); + if( !pSNd || !pSNd->GetSection().IsHiddenFlag() + || (!IsReadOnlyAvailable() && + pSNd->GetSection().IsProtectFlag() )) + { + bOk = true; + break; // found non-protected cell + } + continue; // continue search + } + } + else + { + bOk = true; + break; // found non-protected cell + } + } + + if( bOk && rNdIdx.GetIndex() < rNds.GetEndOfExtras().GetIndex() ) + { + // also check for Fly - might be protected as well + pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr); + if (nullptr == pFrame || + ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) || + ( bOnlyText && pCNd->IsNoTextNode() ) ) + { + // continue search + bOk = false; + bContinue = true; + } + } + } while( bContinue ); + + if( !bOk ) + { + if( !nLoopCnt ) + bGoNextSection = false; + rNdIdx = nNdIdx; + } + } + } + if( bOk ) + { + pCNd = rNdIdx.GetNode().GetContentNode(); + const sal_Int32 nContent = rNdIdx.GetIndex() < nNdIdx ? pCNd->Len() : 0; + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, nContent ); + } + else + { + pCNd = rNdIdx.GetNode().GetContentNode(); + // if cursor in hidden frame, always move it + if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr)) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(), + &aTmpState ); + } + } + return bOk; +} + +bool SwCursorShell::IsCursorReadonly() const +{ + if ( GetViewOptions()->IsReadonly() || + GetViewOptions()->IsFormView() /* Formula view */ ) + { + SwFrame *pFrame = GetCurrFrame( false ); + const SwFlyFrame* pFly; + const SwSection* pSection; + + if( pFrame && pFrame->IsInFly() && + (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() && + pFly->Lower() && + !pFly->Lower()->IsNoTextFrame() && + !GetDrawView()->GetMarkedObjectList().GetMarkCount() ) + { + return false; + } + // edit in readonly sections + else if ( pFrame && pFrame->IsInSct() && + nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) && + pSection->IsEditInReadonlyFlag() ) + { + return false; + } + else if ( !IsMultiSelection() && CursorInsideInputField() ) + { + return false; + } + + return true; + } + return false; +} + +/// is the cursor allowed to enter ReadOnly sections? +void SwCursorShell::SetReadOnlyAvailable( bool bFlag ) +{ + // *never* switch in GlobalDoc + if( (!GetDoc()->GetDocShell() || + dynamic_cast<const SwGlobalDocShell*>(GetDoc()->GetDocShell()) == nullptr ) && + bFlag != m_bSetCursorInReadOnly ) + { + // If the flag is switched off then all selections need to be + // invalidated. Otherwise we would trust that nothing protected is selected. + if( !bFlag ) + { + ClearMark(); + } + m_bSetCursorInReadOnly = bFlag; + UpdateCursor(); + } +} + +bool SwCursorShell::HasReadonlySel() const +{ + if (GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + // Treat selections that span over start or end of paragraph of an outline node + // with folded outline content as read-only. + SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell(); + if (pWrtSh) + { + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint()); + aPaM.Normalize(); + SwNodeIndex aPointIdx(aPaM.GetPoint()->nNode.GetNode()); + SwNodeIndex aMarkIdx(aPaM.GetMark()->nNode.GetNode()); + if (aPointIdx == aMarkIdx) + continue; + // If any nodes in PaM are folded outline content nodes, then set read-only. + SwOutlineNodes::size_type nPos; + for (SwNodeIndex aIdx = aPointIdx; aIdx <= aMarkIdx; aIdx++) + { + if (GetDoc()->GetNodes().GetOutLineNds().Seek_Entry(&(aIdx.GetNode()), &nPos) && + !pWrtSh->GetAttrOutlineContentVisible(nPos)) + return true; + } + } + } + } + bool bRet = false; + // If protected area is to be ignored, then selections are never read-only. + if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() || + GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) && + !SwViewOption::IsIgnoreProtectedArea()) + { + if ( m_pTableCursor != nullptr ) + { + bRet = m_pTableCursor->HasReadOnlyBoxSel() + || m_pTableCursor->HasReadonlySel( GetViewOptions()->IsFormView() ); + } + else + { + for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) + { + if( rCursor.HasReadonlySel( GetViewOptions()->IsFormView() ) ) + { + bRet = true; + break; + } + } + } + } + return bRet; +} + +bool SwCursorShell::IsSelFullPara() const +{ + bool bRet = false; + + if( m_pCurrentCursor->GetPoint()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() && !m_pCurrentCursor->IsMultiSelection() ) + { + sal_Int32 nStt = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + sal_Int32 nEnd = m_pCurrentCursor->GetMark()->nContent.GetIndex(); + if( nStt > nEnd ) + { + sal_Int32 nTmp = nStt; + nStt = nEnd; + nEnd = nTmp; + } + const SwContentNode* pCNd = m_pCurrentCursor->GetContentNode(); + bRet = pCNd && !nStt && nEnd == pCNd->Len(); + } + return bRet; +} + +SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const +{ + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() ); + if( pPt ) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ); + } + + return mxDoc->GetTextDirection( aPos, &aPt ); +} + +bool SwCursorShell::IsInVerticalText( const Point* pPt ) const +{ + const SvxFrameDirection nDir = GetTextDirection( pPt ); + return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir + || nDir == SvxFrameDirection::Vertical_LR_BT; +} + +bool SwCursorShell::IsInRightToLeftText() const +{ + const SvxFrameDirection nDir = GetTextDirection(); + // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in + // vertical environment + return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir; +} + +/// If the current cursor position is inside a hidden range, the hidden range +/// is selected. +bool SwCursorShell::SelectHiddenRange() +{ + bool bRet = false; + if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() ) + { + SwPosition& rPt = *m_pCurrentCursor->GetPoint(); + const SwTextNode* pNode = rPt.nNode.GetNode().GetTextNode(); + if ( pNode ) + { + const sal_Int32 nPos = rPt.nContent.GetIndex(); + + // check if nPos is in hidden range + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd ); + if ( COMPLETE_STRING != nHiddenStart ) + { + // make selection: + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetMark()->nContent = nHiddenEnd; + bRet = true; + } + } + } + + return bRet; +} + +sal_uLong SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, + bool bSearchInNotes, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + bool bReplace ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd, + bCancel, eRng, bReplace, GetLayout()); + if( nRet || bCancel ) + UpdateCursor(); + return nRet; +} + +sal_uLong SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + const SwTextFormatColl* pReplFormat ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng, + pReplFormat ); + if( nRet ) + UpdateCursor(); + return nRet; +} + +sal_uLong SwCursorShell::FindAttrs( const SfxItemSet& rSet, + bool bNoCollections, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + const i18nutil::SearchOptions2* pSearchOpt, + const SfxItemSet* rReplSet ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd, + bCancel, eRng, pSearchOpt, rReplSet, GetLayout()); + if( nRet ) + UpdateCursor(); + return nRet; +} + +void SwCursorShell::SetSelection( const SwPaM& rCursor ) +{ + StartAction(); + SwCursor* pCursor = GetCursor(); + *pCursor->GetPoint() = *rCursor.GetPoint(); + if(rCursor.GetNext() != &rCursor) + { + const SwPaM *_pStartCursor = rCursor.GetNext(); + do + { + SwPaM* pCurrentCursor = CreateCursor(); + *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint(); + if(_pStartCursor->HasMark()) + { + pCurrentCursor->SetMark(); + *pCurrentCursor->GetMark() = *_pStartCursor->GetMark(); + } + } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor ); + } + // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current + // cursor; therefore set current cursor's mark only after creating all other cursors + if (rCursor.HasMark()) + { + pCursor->SetMark(); + *pCursor->GetMark() = *rCursor.GetMark(); + } + EndAction(); +} + +static const SwStartNode* lcl_NodeContext( const SwNode& rNode ) +{ + const SwStartNode *pRet = rNode.StartOfSectionNode(); + while( pRet->IsSectionNode() || pRet->IsTableNode() || + pRet->GetStartNodeType() == SwTableBoxStartNode ) + { + pRet = pRet->StartOfSectionNode(); + } + return pRet; +} + +/** + Checks if a position is valid. To be valid the position's node must + be a content node and the content must not be unregistered. + + @param aPos the position to check. +*/ +bool sw_PosOk(const SwPosition & aPos) +{ + return nullptr != aPos.nNode.GetNode().GetContentNode() && + aPos.nContent.GetIdxReg(); +} + +/** + Checks if a PaM is valid. For a PaM to be valid its point must be + valid. Additionally if the PaM has a mark this has to be valid, too. + + @param aPam the PaM to check +*/ +static bool lcl_CursorOk(SwPaM & aPam) +{ + return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark() + || sw_PosOk(*aPam.GetMark())); +} + +void SwCursorShell::ClearUpCursors() +{ + // start of the ring + SwPaM * pStartCursor = GetCursor(); + // start loop with second entry of the ring + SwPaM * pCursor = pStartCursor->GetNext(); + SwPaM * pTmpCursor; + bool bChanged = false; + + // For all entries in the ring except the start entry delete the entry if + // it is invalid. + while (pCursor != pStartCursor) + { + pTmpCursor = pCursor->GetNext(); + if ( ! lcl_CursorOk(*pCursor)) + { + delete pCursor; + bChanged = true; + } + pCursor = pTmpCursor; + } + + if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) ) + { + pStartCursor->DeleteMark(); + bChanged = true; + } + if (pStartCursor->GetPoint()->nNode.GetNode().IsTableNode()) + { + // tdf#106959: When cursor points to start of a table, the proper content + // node is the first one inside the table, not the previous one + SwNodes& aNodes = GetDoc()->GetNodes(); + SwNodeIndex aIdx(pStartCursor->GetPoint()->nNode); + if (SwNode* pNode = aNodes.GoNext(&aIdx)) + { + SwPaM aTmpPam(*pNode); + *pStartCursor = aTmpPam; + bChanged = true; + } + } + if( !sw_PosOk( *pStartCursor->GetPoint() ) ) + { + SwNodes & aNodes = GetDoc()->GetNodes(); + const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->nNode.GetNode() ); + SwNodeIndex aIdx( pStartCursor->GetPoint()->nNode ); + SwNode * pNode = SwNodes::GoPrevious(&aIdx); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + aNodes.GoNext( &aIdx ); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + { + // If the start entry of the ring is invalid replace it with a + // cursor pointing to the beginning of the first content node in the + // document. + aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode()); + pNode = aNodes.GoNext( &aIdx ); + } + bool bFound = (pNode != nullptr); + + assert(bFound); + + if (bFound) + { + SwPaM aTmpPam(*pNode); + *pStartCursor = aTmpPam; + } + + bChanged = true; + } + + // If at least one of the cursors in the ring have been deleted or replaced, + // remove the table cursor. + if (m_pTableCursor != nullptr && bChanged) + TableCursorToCursor(); +} + +OUString SwCursorShell::GetCursorDescr() const +{ + OUString aResult; + + if (IsMultiSelection()) + aResult += SwResId(STR_MULTISEL); + else + aResult = SwDoc::GetPaMDescr(*GetCursor()); + + return aResult; +} + +void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell")); + + SwViewShell::dumpAsXml(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor")); + for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer()) + rPaM.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes, + uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, + const SwWrongList& rSmartTagList, sal_Int32 nCurrent ) +{ + // Insert smart tag information + std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps; + + for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i ) + { + const sal_Int32 nSTPos = rSmartTagList.Pos( i ); + const sal_Int32 nSTLen = rSmartTagList.Len( i ); + + if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen ) + { + const SwWrongArea* pArea = rSmartTagList.GetElement( i ); + if ( pArea ) + { + rSmartTagTypes.push_back( pArea->maType ); + aStringKeyMaps.push_back( pArea->mxPropertyBag ); + } + } + } + + if ( !rSmartTagTypes.empty() ) + { + rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps); + } +} + +static void lcl_FillTextRange( uno::Reference<text::XTextRange>& rRange, + SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen ) +{ + // create SwPosition for nStartIndex + SwIndex aIndex( &rNode, nBegin ); + SwPosition aStartPos( rNode, aIndex ); + + // create SwPosition for nEndIndex + SwPosition aEndPos( aStartPos ); + aEndPos.nContent = nBegin + nLen; + + const uno::Reference<text::XTextRange> xRange = + SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos); + + rRange = xRange; +} + +void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes, + uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, + uno::Reference< text::XTextRange>& rRange ) const +{ + if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + return; + + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwTextNode *pNode = aPos.nNode.GetNode().GetTextNode(); + if ( !pNode || pNode->IsInProtectSect() ) + return; + + const SwWrongList *pSmartTagList = pNode->GetSmartTags(); + if ( !pSmartTagList ) + return; + + sal_Int32 nCurrent = aPos.nContent.GetIndex(); + sal_Int32 nBegin = nCurrent; + sal_Int32 nLen = 1; + + if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin)) + return; + + const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin ); + const SwWrongList* pSubList = pSmartTagList->SubList( nIndex ); + if ( pSubList ) + { + pSmartTagList = pSubList; + nCurrent = 0; + } + + lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent ); + lcl_FillTextRange( rRange, *pNode, nBegin, nLen ); +} + +// see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect ) +void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect ) +{ + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + Point aPt( rPt ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwSpecialPos aSpecialPos; + eTmpState.m_pSpecialPos = &aSpecialPos; + SwTextNode *pNode; + const SwWrongList *pSmartTagList; + + if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) ) + return; + pNode = aPos.nNode.GetNode().GetTextNode(); + if( !pNode ) + return; + pSmartTagList = pNode->GetSmartTags(); + if( !pSmartTagList ) + return; + if( pNode->IsInProtectSect() ) + return; + + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + + if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin)) + return; + + // get smarttag word + OUString aText( pNode->GetText().copy(nBegin, nLen) ); + + //save the start and end positions of the line and the starting point + Push(); + LeftMargin(); + const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex(); + RightMargin(); + const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex(); + Pop(PopMode::DeleteCurrent); + + // make sure the selection build later from the data below does not + // include "in word" character to the left and right in order to + // preserve those. Therefore count those "in words" in order to + // modify the selection accordingly. + const sal_Unicode* pChar = aText.getStr(); + sal_Int32 nLeft = 0; + while (*pChar++ == CH_TXTATR_INWORD) + ++nLeft; + pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr; + sal_Int32 nRight = 0; + while (pChar && *pChar-- == CH_TXTATR_INWORD) + ++nRight; + + aPos.nContent = nBegin + nLeft; + pCursor = GetCursor(); + *pCursor->GetPoint() = aPos; + pCursor->SetMark(); + ExtendSelection( true, nLen - nLeft - nRight ); + // do not determine the rectangle in the current line + const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft; + // take one less than the line end - otherwise the next line would + // be calculated + const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd); + Push(); + pCursor->DeleteMark(); + SwIndex& rContent = GetCursor()->GetPoint()->nContent; + rContent = nWordStart; + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetContentNode(); + std::pair<Point, bool> const tmp(rPt, false); + SwContentFrame *pContentFrame = pContentNode->getLayoutFrame( + GetLayout(), pCursor->GetPoint(), &tmp); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rContent = nWordEnd - 1; + SwRect aEndRect; + pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); + rSelectRect = aStartRect.Union( aEndRect ); + Pop(PopMode::DeleteCurrent); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |