/* -*- 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 .
*/
// 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);
/** * 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;
/** * 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();
}
// 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
// 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: elseif (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
{ constbool bSkipHidden = !GetViewOptions()->IsShowHiddenChar(); // #i107447# // To avoid loop the reset of <bInFrontOfLabel> flag is no longer // reflected in the return value <bRet>. constbool 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 );
}
}
// 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. constbool bVisuallyMarked(GetViewOptions()->IsFieldShadings()); if (bVisuallyMarked)
{ if ( !m_sMarkedListId.isEmpty() )
mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false );
if ( !sListId.isEmpty() )
mxDoc->MarkListLevel( sListId, nListLevel, true );
}
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
// 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 = SwNodes::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);
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);
SwNodes::GoNext(m_pCurrentCursor->GetPoint()); while (auto* pFoundTable = m_pCurrentCursor->GetPoint()->GetNode().FindTableNode())
{ if (pFoundTable == pTable) break; if (pTable && pTable->GetIndex() >= pFoundTable->GetIndex()) break; if (!MoveOutOfTable()) break;
}
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));
SwNodes::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);
}
statictypename 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: if (rNode.GetSectionNode()->GetSection().IsHidden()) return SwCursorShell::StartsWith::HiddenSection; 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;
}
statictypename 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 (auto pStartNode = rNode.StartOfSectionNode(); pStartNode->IsTableNode())
{ return SwCursorShell::StartsWith::Table;
} elseif (pStartNode->IsSectionNode())
{ if (pStartNode->GetSectionNode()->GetSection().IsHidden()) return SwCursorShell::StartsWith::HiddenSection;
} //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)
SwCursorShell::ExtendedSelection SwCursorShell::ExtendedSelectedAll() const
{ if (m_pTableCursor)
{ return {};
}
// 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 (autoconst ret = ::StartsWith(*pStartNode); ret != StartsWith::None)
{ return ret;
} if (autoconst ret = ::EndsWith(*pStartNode); ret != StartsWith::None)
{ return ret;
} return StartsWith::None;
}
// 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 );
// sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara staticbool 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())
{ returnfalse;
}
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 (isInHiddenFrame(pTmpCursor)
|| !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara))
{ if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara)) break;
}
int SwCursorShell::SetCursor(const Point& rLPt, bool bOnlyText, bool bBlock, bool bFieldInfo, ScrollSizeMode eScrollSizeMode)
{
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();
aTmpState.m_bFieldInfo = bFieldInfo; // always set cursor at field-start if point is over field
aTmpState.m_bPosMatchesBounds = bFieldInfo; // always set cursor at character-start if over char
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 ); elseif( !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;
} elseif( 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 );
// #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 );
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();
aTmpState.m_bPosMatchesBounds = true; // treat last half of character same as first half if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit ) returnfalse;
// 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) returntrue; // return without update
pCmp = pCmp->GetNext();
} while (m_pCurrentCursor != pCmp); returnfalse;
}
void SwCursorShell::KillPams()
{ // Does any exist for deletion? if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() ) return;
void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum, bool bAtCursorPos, constbool 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;
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();
}
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(); #if ENABLE_YRS for (autoconst& it : m_PeerCursors)
{ if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis)
{
it.second->m_pVisibleCursor->Show();
}
} #endif
}
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();
// 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 (isInHiddenFrame(pShellCursor) && !ExtendedSelectedAll())
{ // kde45196-1.html: try to get to a non-hidden paragraph, there must // be one in the document body while (isInHiddenFrame(pShellCursor))
{ if (!pShellCursor->MovePara(GoNextPara, fnParaStart))
{ break;
}
} while (isInHiddenFrame(pShellCursor))
{ if (!pShellCursor->MovePara(GoPrevPara, fnParaStart))
{ break;
}
} if (isInHiddenFrame(pShellCursor))
{
SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText);
aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
GetLayout()->GetModelPositionForViewPoint(pShellCursor->GetPoint(),
pShellCursor->GetPtPos(), &aTmpState);
pShellCursor->DeleteMark();
}
} auto* pDoc = GetDoc(); if (pDoc)
{
pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint());
pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint());
}
// #i65475# - if Point/Mark in hidden sections, move them out staticbool 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. staticvoid lcl_CheckHiddenPara( SwPosition& rPos )
{
SwNodeIndex aTmp( rPos.GetNode() );
SwTextNode* pTextNd = aTmp.GetNode().GetTextNode(); while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) )
{
SwContentNode* pContent = SwNodes::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;
if (ActionPend())
{ if ( eFlags & SwCursorShell::READONLY )
m_bIgnoreReadonly = true; return; // if not then no update
}
if (m_bNeedLayoutOnCursorUpdate)
{ // A previous spell check skipped a word that had a spelling error, because that word // had cursor. Now schedule the idle to call SwViewShell::LayoutIdle, to repeat the // spell check, in the hope that the cursor has left the word.
m_aLayoutIdle.Start();
m_bNeedLayoutOnCursorUpdate = false;
}
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 ); // TODO yrs: table selection is possible in RO mode 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() );
}
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;
// then only select inside the Box if( m_pTableCursor )
{
SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark()
? fnSectionStart
: fnSectionEnd;
// 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;
}
}
// 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. autoconst MoveIntoContent = [](SwShellCursor *const pCursor) { for (SwPaM& rCmp : pCursor->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();
}
}
}
};
MoveIntoContent(m_pCurrentCursor); #if ENABLE_YRS for (autoconst& it : m_PeerCursors)
{ if (it.second->m_pCurrentCursor)
{
MoveIntoContent(it.second->m_pCurrentCursor);
}
} #endif
SwContentFrame *pPointFrame;
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 ) returnfalse;
}
// 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 = rState.m_aCharRect;
bFirst = false;
// update cursor Points to the new Positions
pShellCursor->GetPtPos().setX(rState.m_aCharRect.Left());
pShellCursor->GetPtPos().setY(rState.m_aCharRect.Top());
if (isThisShell && !(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 (isThisShell && 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(eScrollSizeMode);
m_bSVCursorVis = bSav;
}
} while( eFlags & SwCursorShell::SCROLLWIN );
*ppFrame = pFrame; returntrue;
}; #if ENABLE_YRS for (autoconst& it : m_PeerCursors)
{ if (it.second->m_pCurrentCursor)
{
SwContentFrame *pDummy;
Impl(*it.second, &pDummy, false);
}
} #endif if (!Impl(*this, &pPointFrame, true))
{ return;
}
assert(pPointFrame);
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( m_bSVCursorVis )
m_pVisibleCursor->Show(); // show again #if ENABLE_YRS for (autoconst& it : m_PeerCursors)
{ if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis)
{
it.second->m_pVisibleCursor->Show();
}
}
GetDoc()->getIDocumentState().YrsNotifyCursorUpdate(); #endif
if (comphelper::LibreOfficeKit::isActive())
sendLOKCursorUpdates();
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;
/// 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) returnfalse;
// 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();
}
}
} returntrue;
}
/** 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();
/** Get current frame in which the cursor is positioned. */
SwContentFrame *SwCursorShell::GetCurrFrame( constbool 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 (rHint.GetId() == SfxHintId::SwPostGraphicArrived)
{ if (m_aGrfArrivedLnk.IsSet())
m_aGrfArrivedLnk.Call(*this); return;
} if (rHint.GetId() == SfxHintId::SwFormatChange
|| rHint.GetId() == SfxHintId::SwAttrSetChange
|| rHint.GetId() == SfxHintId::SwUpdateAttr)
{ if( m_bCallChgLnk ) // 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(); return;
} if (rHint.GetId() == SfxHintId::SwObjectDying)
{
EndListeningAll(); return;
} if (rHint.GetId() != SfxHintId::SwLegacyModify) return; auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); auto nWhich = pLegacy->GetWhich(); if( m_bCallChgLnk &&
( nWhich == 0 || !isFormatMessage(nWhich) )) // 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 == 0 )
{
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 elseif( 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();
} elseif (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));
/** get the nth character of the current SSelection in the same paragraph as the start/end.
@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;
@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() ) returnfalse; // no selection
sal_Int32 nPos = pPos->GetContentIndex(); if( bEnd )
{ if ((nPos + nCount) <= pTextNd->GetText().getLength())
nPos = nPos + nCount; else returnfalse; // not possible
} elseif( nPos >= nCount )
nPos = nPos - nCount; else returnfalse; // not possible anymore
SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
pPos->SetContent(nPos) ;
UpdateCursor();
returntrue;
}
/** 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, ScrollSizeMode eScrollSizeMode )
{
CurrShell aCurr( this );
Point aPt( rPt );
SwPosition aPos( *m_pCurrentCursor->GetPoint() );
SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
aTmpState.m_bRealHeight = true;
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 ) returnfalse;
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( *pStart <= *pTmpStt )
{ if( *pEnd > *pTmpStt ||
( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
pTmpDel = pTmp;
} else if( *pStart < *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
}
// 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)
, m_aLayoutIdle("SwCursorShell m_aLayoutIdle")
{
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);
// if it is not the last view then at least the field should be updated if( !unique() )
CheckTableBoxContent( m_pCurrentCursor->GetPoint() ); else
ClearTableBoxContent();
#if ENABLE_YRS for (autoconst& it : m_PeerCursors)
{ delete it.second->m_pVisibleCursor; delete it.second->m_pCurrentCursor;
} #endif
// 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();
}
/** 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 ) returntrue;
if( !pCNd ) // should *never* happen
{
rNdPos.Assign(nNdIdx); // back to old node returnfalse;
}
*m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
} elseif( bOnlyText && pCNd && pCNd->IsNoTextNode() )
{ // set to beginning of document
rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() );
SwNodes::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 = SwNodes::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 );
/// 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(boolconst 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()) returntrue;
} 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()) returntrue;
} bool bRet = false;
/// 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(constbool 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;
}
}
}
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();
}
/** 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.
*/ staticbool 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
*/ staticbool 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
SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode()); if (SwNode* pNode = SwNodes::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 = SwNodes::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 = SwNodes::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();
}
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() = std::move(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);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.95Bemerkung:
(vorverarbeitet am 2026-05-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.