diff options
Diffstat (limited to 'sw/source/core/crsr/crsrsh.cxx')
-rw-r--r-- | sw/source/core/crsr/crsrsh.cxx | 4222 |
1 files changed, 4222 insertions, 0 deletions
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx new file mode 100644 index 0000000000..04b263cda7 --- /dev/null +++ b/sw/source/core/crsr/crsrsh.cxx @@ -0,0 +1,4222 @@ +/* -*- 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 <GrammarContact.hxx> +#include <OnlineAccessibilityCheck.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> +#include <redline.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 ) +{ + auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition* + + SwPaM *pTmpDel = nullptr, + *pTmp = pCurrentCursor->GetNext(); + + // Search the complete ring + while( pTmp != pCurrentCursor ) + { + auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition* + 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()->GetNodeIndex() && + m_pTableCursor->GetMark()->GetNodeIndex() ) + { + const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode(); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + pCNd = m_pTableCursor->GetMarkContentNode(); + 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()->GetNode(); + m_nCurrentNode = rNd.GetIndex(); + m_nCurrentContent = m_pCurrentCursor->GetPoint()->GetContentIndex(); + 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, StartsWith_() != StartsWith::None && 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, SwCursorSkipMode 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()->GetNode().IsTextNode() + && static_cast<SwTextFrame const*>( + pShellCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout()) + )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0) + && !pShellCursor->IsInFrontOfLabel() + && !pShellCursor->HasMark() + && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->GetNode())) + && 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; + + // Writer redraws the "marked" list with the field shading, if there + // is no field shading then the marked list would be redrawn for no + // visually identifiable reason, so skip the mark if field shadings + // are disabled. + const bool bVisuallyMarked(GetViewOptions()->IsFieldShadings()); + if (bVisuallyMarked) + { + 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()->GetNode()); + + 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_()->GetPointNode().GetTextNode(); + assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == 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; +} + +const SwTableNode* SwCursorShell::IsCursorInTable() const +{ + if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount()) + { // find the table that has the selected boxes + return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode(); + } + return m_pCurrentCursor->GetPointNode().FindTableNode(); +} + +// fun cases to consider: +// * outermost table +// - into para => SA/ESA +// - into prev/next table => continue... +// - no prev/next => done +// * inner table +// - into containing cell => SA/ESA +// - into prev/next of containing cell +// + into para +// + into table nested in prev/next cell +// - out of table -> as above +// => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table +// - parent reached => SA/ESA depending +// - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable +// - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable +// => select-all cannot select a sequence of table with no para at same level; only 1 table +// - no parent, no prev/next => TrySelectOuterTable + +bool SwCursorShell::MoveOutOfTable() +{ + SwPosition const point(*getShellCursor(false)->GetPoint()); + SwPosition const mark(*getShellCursor(false)->GetMark()); + + for (auto const fnMove : {&fnMoveBackward, &fnMoveForward}) + { + Push(); + SwCursor *const pCursor(getShellCursor(false)); + + pCursor->Normalize(fnMove == &fnMoveBackward); + pCursor->DeleteMark(); + SwTableNode const*const pTable(pCursor->GetPoint()->GetNode().FindTableNode()); + assert(pTable); + while (MovePara(GoInContent, *fnMove)) + { + SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode()); + if (!pBox) + { + Pop(SwCursorShell::PopMode::DeleteStack); + return true; // moved to paragraph at top-level of text + } + if (pBox->GetIndex() < pTable->GetIndex() + && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex()) + { + Pop(SwCursorShell::PopMode::DeleteStack); + return true; // pBox contains start position (pTable) + } + } + + Pop(SwCursorShell::PopMode::DeleteCurrent); + // FIXME: Pop doesn't restore original cursor if nested tables + *getShellCursor(false)->GetPoint() = point; + getShellCursor(false)->SetMark(); + *getShellCursor(false)->GetMark() = mark; + } + return false; +} + +bool SwCursorShell::TrySelectOuterTable() +{ + assert(m_pTableCursor); + SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->GetNode().FindTableNode()); + SwNodes const& rNodes(rInnerTable.GetNodes()); + SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode()); + if (!pOuterTable) + { + return false; + } + + // manually select boxes of pOuterTable + SwNodeIndex firstCell(*pOuterTable, +1); + SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode()); + SwSelBoxes aNew; + pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(), + aNew, SwTable::SEARCH_NONE, false); + // set table cursor to 1st / last content which may be in inner table + SwContentNode *const pStart = rNodes.GoNext(&firstCell); + assert(pStart); // must at least find the previous point node + lastCell = *lastCell.GetNode().EndOfSectionNode(); + SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell); + assert(pEnd); // must at least find the previous point node + delete m_pTableCursor; + m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(), + SwPosition(*pEnd, 0), Point()); + m_pTableCursor->ActualizeSelection( aNew ); + m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes + + // this will update m_pCurrentCursor based on m_pTableCursor + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + + return true; +} + +/// find XText start node +static SwStartNode const* FindTextStart(SwPosition const& rPos) +{ + SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode()); + while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode())) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + return pStartNode; +} + +static SwStartNode const* FindParentText(SwShellCursor const& rCursor) +{ + // find closest section containing both start and end - ignore Sections + SwStartNode const* pStartNode(FindTextStart(*rCursor.Start())); + SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode()); + while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex()) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex()) + { + pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode(); + } + assert(pStartNode->EndOfSectionNode() == pEndNode); + + return (pStartNode->IsSectionNode() || pStartNode->IsTableNode()) + ? FindTextStart(SwPosition(*pStartNode)) + : pStartNode; +} + +bool SwCursorShell::MoveStartText() +{ + SwPosition const old(*m_pCurrentCursor->GetPoint()); + SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); + assert(pStartNode); + SwTableNode const*const pTable(pStartNode->FindTableNode()); + m_pCurrentCursor->GetPoint()->Assign(*pStartNode); + GetDoc()->GetNodes().GoNext(m_pCurrentCursor->GetPoint()); + while (m_pCurrentCursor->GetPoint()->GetNode().FindTableNode() != pTable + && (!pTable || pTable->GetIndex() < m_pCurrentCursor->GetPoint()->GetNode().FindTableNode()->GetIndex()) + && MoveOutOfTable()); + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return old != *m_pCurrentCursor->GetPoint(); +} + +// select all inside the current XText, with table or hidden para at start/end +void SwCursorShell::ExtendedSelectAll(bool bFootnotes) +{ + // find common ancestor node of both ends of cursor + SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); + assert(pStartNode); + if (IsTableMode()) + { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode + TableCursorToCursor(); + } + SwNodes& rNodes = GetDoc()->GetNodes(); + m_pCurrentCursor->Normalize(true); + SwPosition* pPos = m_pCurrentCursor->GetPoint(); + pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode)); + rNodes.GoNext( pPos ); + pPos = m_pCurrentCursor->GetMark(); + pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode())); + SwContentNode* pCNd = SwNodes::GoPrevious( pPos ); + if (pCNd) + pPos->AssignEndIndex(*pCNd); +} + +static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart) +{ + for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i) + { + SwNode const& rNode(*rStart.GetNodes()[i]); + switch (rNode.GetNodeType()) + { + case SwNodeType::Section: + continue; + case SwNodeType::Table: + return SwCursorShell::StartsWith::Table; + case SwNodeType::Text: + if (rNode.GetTextNode()->IsHidden()) + { + return SwCursorShell::StartsWith::HiddenPara; + } + return SwCursorShell::StartsWith::None; + default: + return SwCursorShell::StartsWith::None; + } + } + return SwCursorShell::StartsWith::None; +} + +static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart) +{ + for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i) + { + SwNode const& rNode(*rStart.GetNodes()[i]); + switch (rNode.GetNodeType()) + { + case SwNodeType::End: + if (rNode.StartOfSectionNode()->IsTableNode()) + { + return SwCursorShell::StartsWith::Table; + } +//TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode()); + break; + case SwNodeType::Text: + if (rNode.GetTextNode()->IsHidden()) + { + return SwCursorShell::StartsWith::HiddenPara; + } + return SwCursorShell::StartsWith::None; + default: + return SwCursorShell::StartsWith::None; + } + } + return SwCursorShell::StartsWith::None; +} + +// return the node that is the start of the extended selection (to include table +// or section start nodes; looks like extending for end nodes is not required) +::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>> +SwCursorShell::ExtendedSelectedAll() const +{ + if (m_pTableCursor) + { + return {}; + } + + SwNodes& rNodes = GetDoc()->GetNodes(); + SwShellCursor const*const pShellCursor = getShellCursor(false); + SwStartNode const* pStartNode(FindParentText(*pShellCursor)); + + SwNodeIndex nNode(*pStartNode); + SwContentNode* pStart = rNodes.GoNext(&nNode); + if (!pStart) + { + return {}; + } + + nNode = *pStartNode->EndOfSectionNode(); + SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); + if (!pEnd) + { + return {}; + } + + SwPosition aStart(*pStart, 0); + SwPosition aEnd(*pEnd, pEnd->Len()); + if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End())) + { + return {}; + } + + auto const ends(::EndsWith(*pStartNode)); + if (::StartsWith(*pStartNode) == StartsWith::None + && ends == StartsWith::None) + { + return {}; // "ordinary" selection will work + } + + ::std::vector<SwTableNode*> tablesAtEnd; + if (ends == StartsWith::Table) + { + SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]); + while (pLastNode->IsEndNode()) + { + SwNode *const pNode(pLastNode->StartOfSectionNode()); + if (pNode->IsTableNode()) + { + tablesAtEnd.push_back(pNode->GetTableNode()); + pLastNode = rNodes[pNode->GetIndex() - 1]; + } + else if (pNode->IsSectionNode()) + { + pLastNode = rNodes[pLastNode->GetIndex() - 1]; + } + } + assert(!tablesAtEnd.empty()); + } + + // tdf#133990 ensure directly containing section is included in SwUndoDelete + while (pStartNode->IsSectionNode() + && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1 + && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex()) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + + // pStartNode is the node that fully contains the selection - the first + // node of the selection is the first node inside pStartNode + return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd); +} + +typename SwCursorShell::StartsWith SwCursorShell::StartsWith_() +{ + SwShellCursor const*const pShellCursor = getShellCursor(false); + // first, check if this is invalid; ExtendedSelectAll(true) may result in + // a) an ordinary selection that is valid + // b) a selection that is extended + // c) a selection that is invalid and will cause FindParentText to loop + SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras()); + if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex() + && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex()) + { + return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling! + } + SwStartNode const*const pStartNode(FindParentText(*pShellCursor)); + if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None) + { + return ret; + } + if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None) + { + return ret; + } + return StartsWith::None; +} + +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->GetPointContentNode()-> + 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->GetPointContentNode(); + 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->GetPointContentNode(); + 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( SwNode& rNd, Point& rPt ) +{ + SwFrame* pFrame = nullptr; + SwContentNode* pCNd = rNd.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()->GetNode(), 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()->GetNode()); + + 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.GetNode(), aPt ); + if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() == + pCursor->GetPoint()->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.GetNode().IsContentNode() ) + { + // in the same frame? + std::pair<Point, bool> tmp(m_aCharRect.Pos(), false); + SwFrame* pOld = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + tmp.first = aPt; + SwFrame* pNew = static_cast<SwContentNode&>(aPos.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.GetNode(), pCursor->GetMark()->GetNode(), 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()->GetNode() == m_pCurrentCursor->GetMark()->GetNode()) + { + return true; + } + if (GetLayout()->HasMergedParas()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex()); + return FrameContainsNode(*pFrame, n); + } + return false; +} + +bool SwCursorShell::IsSttPara() const +{ + if (GetLayout()->HasMergedParas()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->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()->GetContentIndex() == 0; +} + +bool SwCursorShell::IsEndPara() const +{ + if (GetLayout()->HasMergedParas()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->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()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->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()->GetNode()); +} + +bool SwCursorShell::IsCursorInFootnote() const +{ + SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType(); + return aStartNodeType == SwStartNodeType::SwFootnoteStartNode; +} + +Point SwCursorShell::GetCursorPagePos() const +{ + Point aRet(-1, -1); + if (SwFrame *pFrame = GetCurrFrame()) + { + if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame()) + { + const Point& rDocPos = GetCursorDocPos(); + aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft(); + } + } + return aRet; +} + +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(OUString::number(pFrame->getFrameArea().Left()) + + ", " + + OUString::number(pFrame->getFrameArea().Top()) + + ", " + + OUString::number(pFrame->getFrameArea().Width()) + + ", " + + OUString::number(pFrame->getFrameArea().Height()) + + "; "); + } + 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, StartsWith_() != StartsWith::None && 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 moved (e.g. when deleting a + text frame). 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) && !ExtendedSelectedAll()) + { + SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(), + &aTmpState ); + pShellCursor->DeleteMark(); + // kde45196-1.html: try to get to a non-hidden paragraph, there must + // be one in the document body + while (isInHiddenTextFrame(pShellCursor)) + { + if (!pShellCursor->MovePara(GoNextPara, fnParaStart)) + { + break; + } + } + while (isInHiddenTextFrame(pShellCursor)) + { + if (!pShellCursor->MovePara(GoPrevPara, fnParaStart)) + { + break; + } + } + } + auto* pDoc = GetDoc(); + if (pDoc) + { + pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint()); + pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint()); + } + + --mnStartAction; + if( aOldSz != GetDocSize() ) + SizeChgNotify(); +} + +// #i65475# - if Point/Mark in hidden sections, move them out +static bool lcl_CheckHiddenSection( SwPosition& rPos ) +{ + bool bOk = true; + const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode(); + if( pSectNd && pSectNd->GetSection().IsHiddenFlag() ) + { + const SwNode* pFrameNd = + rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() ); + bOk = pFrameNd != nullptr; + SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames"); + rPos.Assign( *(bOk ? pFrameNd : pSectNd) ); + } + return bOk; +} + +/// Try to set the cursor to the next visible content node. +static void lcl_CheckHiddenPara( SwPosition& rPos ) +{ + SwNodeIndex aTmp( rPos.GetNode() ); + 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.Assign( *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 && + SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) && + ( m_pTableCursor || + pTstCursor->GetPointNode().StartOfSectionNode() != + pTstCursor->GetMarkNode().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 ); + lcl_CheckHiddenSection( *pITmpCursor->GetMark() ); + + // 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->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->GetMarkContentNode()-> + 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->GetPointNode().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()->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()->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->GetPointContentNode()->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 ) + { + // skip, if it is a hidden deleted cell without frame + if ( GetLayout()->IsHideRedlines() ) + { + const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode(); + if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete ) + return; + } + + do + { + CalcLayout(); + std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetPointContentNode()->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); + } + } + } + } + + OString pChar = aJsonWriter.finishAndGetAsOString(); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, 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.GetPointContentNode()->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::optional<SwCallLink> aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed + return Pop(eDelete, aLink); +} + +bool SwCursorShell::Pop(PopMode const eDelete, + [[maybe_unused]] std::optional<SwCallLink>& roLink) +{ + // parameter exists only to be deleted before return + assert(roLink); + comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } ); + + // 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) + { + ::std::optional<SwCursorSaveState> oSaveState( *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 ) ) + { + oSaveState.reset(); // prevent UAF + 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()->GetNode(), + m_pCurrentCursor->GetPoint()->GetNode(), 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; + + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); + + 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); + 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); + 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, StartsWith_() != StartsWith::None && 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->GetPointContentNode(); + 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 = RES_OBJECTDYING; + if( m_bCallChgLnk && + ( !isFormatMessage(nWhich) + || 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(); + if( nWhich == RES_OBJECTDYING ) + { + EndListeningAll(); + } + +} + +/** 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()->GetNodeIndex())) + { + OUStringBuffer buf; + SwPosition const*const pStart(m_pCurrentCursor->Start()); + SwPosition const*const pEnd(m_pCurrentCursor->End()); + for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i) + { + SwNode const& rNode(*pStart->GetNodes()[i]); + assert(!rNode.IsEndNode()); + if (rNode.IsStartNode()) + { + i = rNode.EndOfSectionIndex(); + } + else if (rNode.IsTextNode()) + { + sal_Int32 const nStart(i == pStart->GetNodeIndex() + ? pStart->GetContentIndex() + : 0); + sal_Int32 const nEnd(i == pEnd->GetNodeIndex() + ? pEnd->GetContentIndex() + : 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()->GetNodeIndex() == + m_pCurrentCursor->GetMark()->GetNodeIndex() ) + { + SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode(); + if( pTextNd ) + { + const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex(); + aText = pTextNd->GetExpandText(GetLayout(), nStt, + m_pCurrentCursor->End()->GetContentIndex() - 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->GetNode().GetTextNode(); + if( !pTextNd ) + return 0; + + const sal_Int32 nPos = pPos->GetContentIndex(); + 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->GetNode().GetTextNode(); + assert(pTextNd); + + sal_Int32 nPos = pPos->GetContentIndex(); + 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->SetContent(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.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(), false); +} + +/** 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()->GetContentIndex() ) + 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()->GetNode(); +} + +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()->GetNode() && pCNd && + pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex(); +} + +/** 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 ) +{ + auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition* + + SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing; + + // search over the whole ring + bool bGoNext; + do { + + if (!pTmp) + break; + + auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition* + // 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()->Assign(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 SwNode &rIdx ) +{ + const SwNode *pNode = &rIdx; + + // 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()->Assign( *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()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() ); + } + else + aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() ); + aNew.SetMark(); + aNew.GetPoint()->Assign(*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()->GetNode().FindTableNode(); + if ( pTableNd ) + { + pTCursor->GetPoint()->Assign(SwNodeOffset(0)); + pTCursor->DeleteMark(); + pSh->m_pCurrentCursor->GetPoint()->Assign( *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->GetPointContentNode()->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, 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()->GetNodeIndex() + SwNodeOffset(10) < + pPam->End()->GetNodeIndex(); +} + +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 + SwPosition& rNdPos = *m_pCurrentCursor->GetPoint(); + SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup + SwNodes& rNds = mxDoc->GetNodes(); + SwContentNode* pCNd = rNdPos.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()->Assign( rNds.GetEndOfContent() ); + aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() ); + + bool bFirst = false; + if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward ))) + { + aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() ); + pCNd = ::GetNode( aPam, bFirst, fnMoveBackward ); + } + + if( !pCNd ) // should *never* happen + { + rNdPos.Assign(nNdIdx); // back to old node + return false; + } + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + else if( bOnlyText && pCNd && pCNd->IsNoTextNode() ) + { + // set to beginning of document + rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() ); + mxDoc->GetNodes().GoNext( &rNdPos ); + nNdIdx = rNdPos.GetNodeIndex(); + } + + bool bOk = true; + + // #i9059# cursor may not stand in protected cells + // (unless cursor in protected areas is OK.) + const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode(); + if( !IsReadOnlyAvailable() && + pTableNode != nullptr && rNdPos.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( rNdPos.GetNode(), 0 ); + while( aPam.GetPointNode().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.GetPointNode().IsProtect() ) + { + SwPaM aTmpPaM( rNdPos.GetNode(), 0 ); + aPam = aTmpPaM; + while( aPam.GetPointNode().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.GetPointNode().IsProtect() ) + { + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + } + + // in a protected frame + const SwSectionNode* pSectNd = rNdPos.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( &rNdPos, + true, !IsReadOnlyAvailable() ); + else + pCNd = SwNodes::GoPrevSection( &rNdPos, + 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 && rNdPos.GetNodeIndex() < 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; + rNdPos.Assign( nNdIdx ); + } + } + } + if( bOk ) + { + pCNd = rNdPos.GetNode().GetContentNode(); + const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0; + m_pCurrentCursor->GetPoint()->SetContent( nContent ); + } + else + { + pCNd = rNdPos.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(bool const isReplace) const +{ + // Treat selections that span over start or end of paragraph of an outline node + // with folded outline content as read-only. + if (GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell(); + if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected()) + 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 ) + { + // TODO: handling when a table cell (cells) is selected + bRet = m_pTableCursor->HasReadOnlyBoxSel() + || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace); + } + else + { + for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) + { + if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace)) + { + bRet = true; + break; + } + } + } + } + return bRet; +} + +bool SwCursorShell::HasHiddenSections() const +{ + // Treat selections that span over start or end of paragraph of an outline node + // with folded outline content as read-only. + if (GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell(); + if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected()) + return true; + } + bool bRet = false; + + if ( m_pTableCursor != nullptr ) + { + bRet = m_pTableCursor->HasHiddenBoxSel() + || m_pTableCursor->HasHiddenSections(); + } + else + { + for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) + { + if (rCursor.HasHiddenSections()) + { + bRet = true; + break; + } + } + } + + return bRet; +} + +bool SwCursorShell::IsSelFullPara() const +{ + bool bRet = false; + + if( m_pCurrentCursor->GetPoint()->GetNodeIndex() == + m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() ) + { + sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex(); + sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex(); + if( nStt > nEnd ) + std::swap( nStt, nEnd ); + const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode(); + 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 true is returned. If bSelect is +/// true, the hidden range is selected. If bSelect is false, the hidden range is not selected. +bool SwCursorShell::IsInHiddenRange(const bool bSelect) +{ + bool bRet = false; + if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() ) + { + SwPosition& rPt = *m_pCurrentCursor->GetPoint(); + const SwTextNode* pNode = rPt.GetNode().GetTextNode(); + if ( pNode ) + { + const sal_Int32 nPos = rPt.GetContentIndex(); + + // check if nPos is in hidden range + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd ); + if ( COMPLETE_STRING != nHiddenStart ) + { + if (bSelect) + { + // make selection: + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd); + } + bRet = true; + } + } + } + + return bRet; +} + +sal_Int32 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_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd, + bCancel, eRng, bReplace, GetLayout()); + if( nRet || bCancel ) + UpdateCursor(); + return nRet; +} + +sal_Int32 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_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng, + pReplFormat ); + if( nRet ) + UpdateCursor(); + return nRet; +} + +sal_Int32 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_Int32 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. +*/ +static bool sw_PosOk(const SwPosition & aPos) +{ + return nullptr != aPos.GetNode().GetContentNode() && + aPos.GetContentNode(); +} + +/** + 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()->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()->GetNode()); + 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()->GetNode() ); + SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() ); + SwNode * pNode = SwNodes::GoPrevious(&aIdx); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + { + pNode = 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 + SwPosition aStartPos( rNode, nBegin ); + // create SwPosition for nEndIndex + SwPosition aEndPos( rNode, nBegin + nLen ); + + const rtl::Reference<SwXTextRange> 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.GetNode().GetTextNode(); + if ( !pNode || pNode->IsInProtectSect() ) + return; + + const SwWrongList *pSmartTagList = pNode->GetSmartTags(); + if ( !pSmartTagList ) + return; + + sal_Int32 nCurrent = aPos.GetContentIndex(); + 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.GetNode().GetTextNode(); + if( !pNode ) + return; + pSmartTagList = pNode->GetSmartTags(); + if( !pSmartTagList ) + return; + if( pNode->IsInProtectSect() ) + return; + + sal_Int32 nBegin = aPos.GetContentIndex(); + 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()->GetContentIndex(); + RightMargin(); + const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex(); + 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.SetContent( 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(); + SwPosition& rPos = *GetCursor()->GetPoint(); + rPos.SetContent( nWordStart ); + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetPointContentNode(); + std::pair<Point, bool> const tmp(rPt, false); + SwContentFrame *pContentFrame = pContentNode->getLayoutFrame( + GetLayout(), pCursor->GetPoint(), &tmp); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rPos.SetContent( nWordEnd - 1 ); + SwRect aEndRect; + pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); + rSelectRect = aStartRect.Union( aEndRect ); + Pop(PopMode::DeleteCurrent); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |