summaryrefslogtreecommitdiffstats
path: root/sw/source/core/crsr/crsrsh.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/crsr/crsrsh.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/crsr/crsrsh.cxx')
-rw-r--r--sw/source/core/crsr/crsrsh.cxx4222
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: */