Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / crsr / crsrsh.cxx
blob04b263cda7547af5deb218f71f1b89de5c2560e3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_wasm_strip.h>
22 #include <com/sun/star/text/XTextRange.hpp>
24 #include <hintids.hxx>
25 #include <svx/srchdlg.hxx>
26 #include <sfx2/viewsh.hxx>
27 #include <SwSmartTagMgr.hxx>
28 #include <doc.hxx>
29 #include <rootfrm.hxx>
30 #include <pagefrm.hxx>
31 #include <cntfrm.hxx>
32 #include <viewimp.hxx>
33 #include <pam.hxx>
34 #include <swselectionlist.hxx>
35 #include "BlockCursor.hxx"
36 #include <ndtxt.hxx>
37 #include <flyfrm.hxx>
38 #include <dview.hxx>
39 #include <viewopt.hxx>
40 #include <crsrsh.hxx>
41 #include <tabfrm.hxx>
42 #include <txtfrm.hxx>
43 #include <sectfrm.hxx>
44 #include <swtable.hxx>
45 #include "callnk.hxx"
46 #include <viscrs.hxx>
47 #include <section.hxx>
48 #include <docsh.hxx>
49 #include <scriptinfo.hxx>
50 #include <globdoc.hxx>
51 #include <pamtyp.hxx>
52 #include <mdiexp.hxx>
53 #include <fmteiro.hxx>
54 #include <wrong.hxx>
55 #include <unotextrange.hxx>
56 #include <vcl/svapp.hxx>
57 #include <vcl/settings.hxx>
58 #include <GrammarContact.hxx>
59 #include <OnlineAccessibilityCheck.hxx>
60 #include <comphelper/flagguard.hxx>
61 #include <strings.hrc>
62 #include <IDocumentLayoutAccess.hxx>
63 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
64 #include <comphelper/lok.hxx>
65 #include <comphelper/sequence.hxx>
66 #include <sfx2/lokhelper.hxx>
67 #include <editeng/editview.hxx>
68 #include <editeng/frmdir.hxx>
69 #include <sal/log.hxx>
70 #include <PostItMgr.hxx>
71 #include <DocumentSettingManager.hxx>
72 #include <vcl/uitest/logger.hxx>
73 #include <vcl/uitest/eventdescription.hxx>
74 #include <tabcol.hxx>
75 #include <wrtsh.hxx>
76 #include <undobj.hxx>
77 #include <view.hxx>
78 #include <hints.hxx>
79 #include <tools/json_writer.hxx>
80 #include <redline.hxx>
82 using namespace com::sun::star;
83 using namespace util;
85 /**
86 * Check if pCurrentCursor points into already existing ranges and delete those.
87 * @param Pointer to SwCursor object
89 static void CheckRange( SwCursor* pCurrentCursor )
91 auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition*
93 SwPaM *pTmpDel = nullptr,
94 *pTmp = pCurrentCursor->GetNext();
96 // Search the complete ring
97 while( pTmp != pCurrentCursor )
99 auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
100 if( *pStt <= *pTmpStt )
102 if( *pEnd > *pTmpStt ||
103 ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
104 pTmpDel = pTmp;
106 else
107 if( *pStt < *pTmpEnd )
108 pTmpDel = pTmp;
110 // If Point or Mark is within the Cursor range, we need to remove the old
111 // range. Take note that Point does not belong to the range anymore.
112 pTmp = pTmp->GetNext();
113 delete pTmpDel; // Remove old range
114 pTmpDel = nullptr;
118 // SwCursorShell
121 * Add a copy of current cursor, append it after current, and collapse current cursor.
122 * @return - Returns a newly created copy of current cursor.
124 SwPaM * SwCursorShell::CreateCursor()
126 // don't create new Cursor with active table Selection
127 assert(!IsTableMode());
129 // ensure that m_pCurrentCursor is valid; if it's invalid it would be
130 // copied to pNew and then pNew would be deleted in UpdateCursor() below
131 ClearUpCursors();
133 // New cursor as copy of current one. Add to the ring.
134 // Links point to previously created one, ie forward.
135 SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
137 // Hide PaM logically, to avoid undoing the inverting from
138 // copied PaM (#i75172#)
139 pNew->swapContent(*m_pCurrentCursor);
141 m_pCurrentCursor->DeleteMark();
143 UpdateCursor( SwCursorShell::SCROLLWIN );
144 return pNew;
148 * Delete current Cursor, making the following one the current.
149 * Note, this function does not delete anything if there is no other cursor.
150 * @return - returns true if there was another cursor and we deleted one.
152 void SwCursorShell::DestroyCursor()
154 // don't delete Cursor with active table Selection
155 assert(!IsTableMode());
157 // Is there a next one? Don't do anything if not.
158 if(!m_pCurrentCursor->IsMultiSelection())
159 return;
161 SwCallLink aLk( *this ); // watch Cursor-Moves
162 SwCursor* pNextCursor = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
163 delete m_pCurrentCursor;
164 m_pCurrentCursor = dynamic_cast<SwShellCursor*>(pNextCursor);
165 UpdateCursor();
169 * Create and return a new shell cursor.
170 * Simply returns the current shell cursor if there is no selection
171 * (HasSelection()).
173 SwCursor & SwCursorShell::CreateNewShellCursor()
175 if (HasSelection())
177 (void) CreateCursor(); // n.b. returns old cursor
179 return *GetCursor();
183 * Return the current shell cursor
184 * @return - returns current `SwPaM` shell cursor
186 SwCursor & SwCursorShell::GetCurrentShellCursor()
188 return *GetCursor();
192 * Return pointer to the current shell cursor
193 * @return - returns pointer to current `SwCursor` shell cursor
195 SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const
197 if( m_pTableCursor )
199 if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() )
201 //don't re-create 'parked' cursors
202 if( m_pTableCursor->GetPoint()->GetNodeIndex() &&
203 m_pTableCursor->GetMark()->GetNodeIndex() )
205 const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode();
206 if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
208 pCNd = m_pTableCursor->GetMarkContentNode();
209 if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
211 SwShellTableCursor* pTC = m_pTableCursor;
212 GetLayout()->MakeTableCursors( *pTC );
218 if( m_pTableCursor->IsChgd() )
220 const_cast<SwCursorShell*>(this)->m_pCurrentCursor =
221 dynamic_cast<SwShellCursor*>(m_pTableCursor->MakeBoxSels( m_pCurrentCursor ));
224 return m_pCurrentCursor;
227 void SwCursorShell::StartAction()
229 if( !ActionPend() )
231 // save for update of the ribbon bar
232 const SwNode& rNd = m_pCurrentCursor->GetPoint()->GetNode();
233 m_nCurrentNode = rNd.GetIndex();
234 m_nCurrentContent = m_pCurrentCursor->GetPoint()->GetContentIndex();
235 m_nCurrentNdTyp = rNd.GetNodeType();
236 if( rNd.IsTextNode() )
237 m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true );
238 else
239 m_nLeftFramePos = 0;
241 SwViewShell::StartAction(); // to the SwViewShell
244 void SwCursorShell::EndAction( const bool bIdleEnd )
246 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
247 bool bVis = m_bSVCursorVis;
249 // Idle-formatting?
250 if( bIdleEnd && Imp()->HasPaintRegion() )
252 m_pCurrentCursor->Hide();
255 // Update all invalid numberings before the last action
256 if( 1 == mnStartAction )
257 GetDoc()->UpdateNumRule();
259 // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call.
260 // Only the UpdateCursor shows the cursor.
261 bool bSavSVCursorVis = m_bSVCursorVis;
262 m_bSVCursorVis = false;
264 SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first
266 m_bSVCursorVis = bSavSVCursorVis;
268 if( ActionPend() )
270 if( bVis ) // display SV-Cursor again
271 m_pVisibleCursor->Show();
273 return;
276 sal_uInt16 eFlags = SwCursorShell::CHKRANGE;
277 if ( !bIdleEnd )
278 eFlags |= SwCursorShell::SCROLLWIN;
280 UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes
283 SwCallLink aLk( *this ); // Watch cursor moves,
284 aLk.m_nNode = m_nCurrentNode; // possibly call the link
285 aLk.m_nNodeType = m_nCurrentNdTyp;
286 aLk.m_nContent = m_nCurrentContent;
287 aLk.m_nLeftFramePos = m_nLeftFramePos;
289 if( !m_nCursorMove ||
290 ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) )
291 // display Cursor & Selections again
292 ShowCursors( m_bSVCursorVis );
294 // call ChgCall if there is still one
295 if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() )
297 m_aChgLnk.Call(nullptr);
298 m_bChgCallFlag = false; // reset flag
302 void SwCursorShell::SttCursorMove()
304 #ifdef DBG_UTIL
305 OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." );
306 #endif
307 ++m_nCursorMove;
308 StartAction();
311 void SwCursorShell::EndCursorMove( const bool bIdleEnd )
313 #ifdef DBG_UTIL
314 OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." );
315 #endif
316 EndAction( bIdleEnd );
317 --m_nCursorMove;
318 #ifdef DBG_UTIL
319 if( !m_nCursorMove )
320 m_bInCMvVisportChgd = false;
321 #endif
324 bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
325 bool bVisualAllowed )
327 if( IsTableMode() )
328 return bLeft ? GoPrevCell() : GoNextCell();
330 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
331 bool bRet = false;
333 // #i27615# Handle cursor in front of label.
334 const SwTextNode* pTextNd = nullptr;
336 if( m_pBlockCursor )
337 m_pBlockCursor->clearPoints();
339 // 1. CASE: Cursor is in front of label. A move to the right
340 // will simply reset the bInFrontOfLabel flag:
341 SwShellCursor* pShellCursor = getShellCursor( true );
342 if ( !bLeft && pShellCursor->IsInFrontOfLabel() )
344 SetInFrontOfLabel( false );
345 bRet = true;
347 // 2. CASE: Cursor is at beginning of numbered paragraph. A move
348 // to the left will simply set the bInFrontOfLabel flag:
349 else if (bLeft
350 && pShellCursor->GetPoint()->GetNode().IsTextNode()
351 && static_cast<SwTextFrame const*>(
352 pShellCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout())
353 )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0)
354 && !pShellCursor->IsInFrontOfLabel()
355 && !pShellCursor->HasMark()
356 && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->GetNode()))
357 && pTextNd->HasVisibleNumberingOrBullet())
359 SetInFrontOfLabel( true );
360 bRet = true;
362 // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag:
363 else
365 const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar();
366 // #i107447#
367 // To avoid loop the reset of <bInFrontOfLabel> flag is no longer
368 // reflected in the return value <bRet>.
369 const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false );
370 bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed,
371 bSkipHidden, !IsOverwriteCursor(),
372 GetLayout(),
373 GetViewOptions()->IsFieldName());
374 if ( !bRet && bLeft && bResetOfInFrontOfLabel )
376 // undo reset of <bInFrontOfLabel> flag
377 SetInFrontOfLabel( true );
381 if( bRet )
383 UpdateCursor();
386 return bRet;
389 void SwCursorShell::MarkListLevel( const OUString& sListId,
390 const int nListLevel )
392 if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel)
393 return;
395 // Writer redraws the "marked" list with the field shading, if there
396 // is no field shading then the marked list would be redrawn for no
397 // visually identifiable reason, so skip the mark if field shadings
398 // are disabled.
399 const bool bVisuallyMarked(GetViewOptions()->IsFieldShadings());
400 if (bVisuallyMarked)
402 if ( !m_sMarkedListId.isEmpty() )
403 mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false );
405 if ( !sListId.isEmpty() )
406 mxDoc->MarkListLevel( sListId, nListLevel, true );
409 m_sMarkedListId = sListId;
410 m_nMarkedListLevel = nListLevel;
413 void SwCursorShell::UpdateMarkedListLevel()
415 SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(),
416 GetCursor_()->GetPoint()->GetNode());
418 if ( !pTextNd )
419 return;
421 if (!pTextNd->IsNumbered(GetLayout()))
423 m_pCurrentCursor->SetInFrontOfLabel_( false );
424 MarkListLevel( OUString(), 0 );
426 else if ( m_pCurrentCursor->IsInFrontOfLabel() )
428 if ( pTextNd->IsInList() )
430 assert(pTextNd->GetActualListLevel() >= 0 &&
431 pTextNd->GetActualListLevel() < MAXLEVEL);
432 MarkListLevel( pTextNd->GetListId(),
433 pTextNd->GetActualListLevel() );
436 else
438 MarkListLevel( OUString(), 0 );
442 void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage)
444 #ifdef ACCESSIBLE_LAYOUT
445 if( Imp()->IsAccessible() )
446 Imp()->FirePageChangeEvent( nOldPage, nNewPage );
447 #else
448 (void)nOldPage;
449 (void)nNewPage;
450 #endif
453 void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn)
455 #ifdef ACCESSIBLE_LAYOUT
456 if( Imp()->IsAccessible() )
457 Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn);
458 #else
459 (void)nOldColumn;
460 (void)nNewColumn;
461 #endif
464 void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection)
466 #ifdef ACCESSIBLE_LAYOUT
467 if( Imp()->IsAccessible() )
468 Imp()->FireSectionChangeEvent( nOldSection, nNewSection );
469 #else
470 (void)nOldSection;
471 (void)nNewSection;
472 #endif
475 bool SwCursorShell::bColumnChange()
477 SwFrame* pCurrFrame = GetCurrFrame(false);
479 if (pCurrFrame == nullptr)
481 return false;
484 SwFrame* pCurrCol=pCurrFrame->FindColFrame();
486 while(pCurrCol== nullptr && pCurrFrame!=nullptr )
488 SwLayoutFrame* pParent = pCurrFrame->GetUpper();
489 if(pParent!=nullptr)
491 pCurrCol=static_cast<SwFrame*>(pParent)->FindColFrame();
492 pCurrFrame = pParent;
494 else
496 break;
500 if(m_oldColFrame == pCurrCol)
501 return false;
502 else
504 m_oldColFrame = pCurrCol;
505 return true;
509 bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt )
511 CurrShell aCurr( this );
512 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
514 bool bTableMode = IsTableMode();
515 SwShellCursor* pTmpCursor = getShellCursor( true );
517 bool bRet = pTmpCursor->UpDown( bUp, nCnt );
518 // #i40019# UpDown should always reset the bInFrontOfLabel flag:
519 bRet |= SetInFrontOfLabel(false);
521 if( m_pBlockCursor )
522 m_pBlockCursor->clearPoints();
524 if( bRet )
526 m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint
527 if( !ActionPend() )
529 CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN;
530 if( !bTableMode )
531 eUpdateMode = static_cast<CursorFlag>(eUpdateMode
532 | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE);
533 UpdateCursor( o3tl::narrowing<sal_uInt16>(eUpdateMode) );
536 return bRet;
539 bool SwCursorShell::LRMargin( bool bLeft, bool bAPI)
541 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
542 CurrShell aCurr( this );
543 m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint
545 const bool bTableMode = IsTableMode();
546 SwShellCursor* pTmpCursor = getShellCursor( true );
548 if( m_pBlockCursor )
549 m_pBlockCursor->clearPoints();
551 const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI);
553 bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI);
555 if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() )
557 const SwTextNode * pTextNd = GetCursor_()->GetPointNode().GetTextNode();
558 assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == pTextNd);
559 if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() )
560 SetInFrontOfLabel( true );
562 else if ( !bLeft )
564 bRet = SetInFrontOfLabel( false ) || bRet;
567 if( bRet )
569 UpdateCursor();
571 return bRet;
574 bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const
576 const SwShellCursor* pTmpCursor = getShellCursor( true );
577 return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI);
580 bool SwCursorShell::SttEndDoc( bool bStt )
582 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
584 SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor;
585 bool bRet = pTmpCursor->SttEndDoc( bStt );
586 if( bRet )
588 if( bStt )
589 pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header)
590 if( m_pBlockCursor )
592 m_pBlockCursor->clearPoints();
593 RefreshBlockCursor();
596 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
598 return bRet;
601 const SwTableNode* SwCursorShell::IsCursorInTable() const
603 if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount())
604 { // find the table that has the selected boxes
605 return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode();
607 return m_pCurrentCursor->GetPointNode().FindTableNode();
610 // fun cases to consider:
611 // * outermost table
612 // - into para => SA/ESA
613 // - into prev/next table => continue...
614 // - no prev/next => done
615 // * inner table
616 // - into containing cell => SA/ESA
617 // - into prev/next of containing cell
618 // + into para
619 // + into table nested in prev/next cell
620 // - out of table -> as above
621 // => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table
622 // - parent reached => SA/ESA depending
623 // - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable
624 // - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable
625 // => select-all cannot select a sequence of table with no para at same level; only 1 table
626 // - no parent, no prev/next => TrySelectOuterTable
628 bool SwCursorShell::MoveOutOfTable()
630 SwPosition const point(*getShellCursor(false)->GetPoint());
631 SwPosition const mark(*getShellCursor(false)->GetMark());
633 for (auto const fnMove : {&fnMoveBackward, &fnMoveForward})
635 Push();
636 SwCursor *const pCursor(getShellCursor(false));
638 pCursor->Normalize(fnMove == &fnMoveBackward);
639 pCursor->DeleteMark();
640 SwTableNode const*const pTable(pCursor->GetPoint()->GetNode().FindTableNode());
641 assert(pTable);
642 while (MovePara(GoInContent, *fnMove))
644 SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode());
645 if (!pBox)
647 Pop(SwCursorShell::PopMode::DeleteStack);
648 return true; // moved to paragraph at top-level of text
650 if (pBox->GetIndex() < pTable->GetIndex()
651 && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex())
653 Pop(SwCursorShell::PopMode::DeleteStack);
654 return true; // pBox contains start position (pTable)
658 Pop(SwCursorShell::PopMode::DeleteCurrent);
659 // FIXME: Pop doesn't restore original cursor if nested tables
660 *getShellCursor(false)->GetPoint() = point;
661 getShellCursor(false)->SetMark();
662 *getShellCursor(false)->GetMark() = mark;
664 return false;
667 bool SwCursorShell::TrySelectOuterTable()
669 assert(m_pTableCursor);
670 SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->GetNode().FindTableNode());
671 SwNodes const& rNodes(rInnerTable.GetNodes());
672 SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode());
673 if (!pOuterTable)
675 return false;
678 // manually select boxes of pOuterTable
679 SwNodeIndex firstCell(*pOuterTable, +1);
680 SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode());
681 SwSelBoxes aNew;
682 pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(),
683 aNew, SwTable::SEARCH_NONE, false);
684 // set table cursor to 1st / last content which may be in inner table
685 SwContentNode *const pStart = rNodes.GoNext(&firstCell);
686 assert(pStart); // must at least find the previous point node
687 lastCell = *lastCell.GetNode().EndOfSectionNode();
688 SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell);
689 assert(pEnd); // must at least find the previous point node
690 delete m_pTableCursor;
691 m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(),
692 SwPosition(*pEnd, 0), Point());
693 m_pTableCursor->ActualizeSelection( aNew );
694 m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes
696 // this will update m_pCurrentCursor based on m_pTableCursor
697 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
699 return true;
702 /// find XText start node
703 static SwStartNode const* FindTextStart(SwPosition const& rPos)
705 SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode());
706 while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode()))
708 pStartNode = pStartNode->StartOfSectionNode();
710 return pStartNode;
713 static SwStartNode const* FindParentText(SwShellCursor const& rCursor)
715 // find closest section containing both start and end - ignore Sections
716 SwStartNode const* pStartNode(FindTextStart(*rCursor.Start()));
717 SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode());
718 while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex())
720 pStartNode = pStartNode->StartOfSectionNode();
722 while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex())
724 pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode();
726 assert(pStartNode->EndOfSectionNode() == pEndNode);
728 return (pStartNode->IsSectionNode() || pStartNode->IsTableNode())
729 ? FindTextStart(SwPosition(*pStartNode))
730 : pStartNode;
733 bool SwCursorShell::MoveStartText()
735 SwPosition const old(*m_pCurrentCursor->GetPoint());
736 SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
737 assert(pStartNode);
738 SwTableNode const*const pTable(pStartNode->FindTableNode());
739 m_pCurrentCursor->GetPoint()->Assign(*pStartNode);
740 GetDoc()->GetNodes().GoNext(m_pCurrentCursor->GetPoint());
741 while (m_pCurrentCursor->GetPoint()->GetNode().FindTableNode() != pTable
742 && (!pTable || pTable->GetIndex() < m_pCurrentCursor->GetPoint()->GetNode().FindTableNode()->GetIndex())
743 && MoveOutOfTable());
744 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
745 return old != *m_pCurrentCursor->GetPoint();
748 // select all inside the current XText, with table or hidden para at start/end
749 void SwCursorShell::ExtendedSelectAll(bool bFootnotes)
751 // find common ancestor node of both ends of cursor
752 SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
753 assert(pStartNode);
754 if (IsTableMode())
755 { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode
756 TableCursorToCursor();
758 SwNodes& rNodes = GetDoc()->GetNodes();
759 m_pCurrentCursor->Normalize(true);
760 SwPosition* pPos = m_pCurrentCursor->GetPoint();
761 pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode));
762 rNodes.GoNext( pPos );
763 pPos = m_pCurrentCursor->GetMark();
764 pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode()));
765 SwContentNode* pCNd = SwNodes::GoPrevious( pPos );
766 if (pCNd)
767 pPos->AssignEndIndex(*pCNd);
770 static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart)
772 for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i)
774 SwNode const& rNode(*rStart.GetNodes()[i]);
775 switch (rNode.GetNodeType())
777 case SwNodeType::Section:
778 continue;
779 case SwNodeType::Table:
780 return SwCursorShell::StartsWith::Table;
781 case SwNodeType::Text:
782 if (rNode.GetTextNode()->IsHidden())
784 return SwCursorShell::StartsWith::HiddenPara;
786 return SwCursorShell::StartsWith::None;
787 default:
788 return SwCursorShell::StartsWith::None;
791 return SwCursorShell::StartsWith::None;
794 static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart)
796 for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i)
798 SwNode const& rNode(*rStart.GetNodes()[i]);
799 switch (rNode.GetNodeType())
801 case SwNodeType::End:
802 if (rNode.StartOfSectionNode()->IsTableNode())
804 return SwCursorShell::StartsWith::Table;
806 //TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode());
807 break;
808 case SwNodeType::Text:
809 if (rNode.GetTextNode()->IsHidden())
811 return SwCursorShell::StartsWith::HiddenPara;
813 return SwCursorShell::StartsWith::None;
814 default:
815 return SwCursorShell::StartsWith::None;
818 return SwCursorShell::StartsWith::None;
821 // return the node that is the start of the extended selection (to include table
822 // or section start nodes; looks like extending for end nodes is not required)
823 ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>
824 SwCursorShell::ExtendedSelectedAll() const
826 if (m_pTableCursor)
828 return {};
831 SwNodes& rNodes = GetDoc()->GetNodes();
832 SwShellCursor const*const pShellCursor = getShellCursor(false);
833 SwStartNode const* pStartNode(FindParentText(*pShellCursor));
835 SwNodeIndex nNode(*pStartNode);
836 SwContentNode* pStart = rNodes.GoNext(&nNode);
837 if (!pStart)
839 return {};
842 nNode = *pStartNode->EndOfSectionNode();
843 SwContentNode* pEnd = SwNodes::GoPrevious(&nNode);
844 if (!pEnd)
846 return {};
849 SwPosition aStart(*pStart, 0);
850 SwPosition aEnd(*pEnd, pEnd->Len());
851 if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End()))
853 return {};
856 auto const ends(::EndsWith(*pStartNode));
857 if (::StartsWith(*pStartNode) == StartsWith::None
858 && ends == StartsWith::None)
860 return {}; // "ordinary" selection will work
863 ::std::vector<SwTableNode*> tablesAtEnd;
864 if (ends == StartsWith::Table)
866 SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]);
867 while (pLastNode->IsEndNode())
869 SwNode *const pNode(pLastNode->StartOfSectionNode());
870 if (pNode->IsTableNode())
872 tablesAtEnd.push_back(pNode->GetTableNode());
873 pLastNode = rNodes[pNode->GetIndex() - 1];
875 else if (pNode->IsSectionNode())
877 pLastNode = rNodes[pLastNode->GetIndex() - 1];
880 assert(!tablesAtEnd.empty());
883 // tdf#133990 ensure directly containing section is included in SwUndoDelete
884 while (pStartNode->IsSectionNode()
885 && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1
886 && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex())
888 pStartNode = pStartNode->StartOfSectionNode();
891 // pStartNode is the node that fully contains the selection - the first
892 // node of the selection is the first node inside pStartNode
893 return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd);
896 typename SwCursorShell::StartsWith SwCursorShell::StartsWith_()
898 SwShellCursor const*const pShellCursor = getShellCursor(false);
899 // first, check if this is invalid; ExtendedSelectAll(true) may result in
900 // a) an ordinary selection that is valid
901 // b) a selection that is extended
902 // c) a selection that is invalid and will cause FindParentText to loop
903 SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras());
904 if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex()
905 && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex())
907 return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling!
909 SwStartNode const*const pStartNode(FindParentText(*pShellCursor));
910 if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None)
912 return ret;
914 if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None)
916 return ret;
918 return StartsWith::None;
921 bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage )
923 bool bRet = false;
925 // never jump of section borders at selection
926 if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() )
928 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
929 CurrShell aCurr( this );
931 SwCursorSaveState aSaveState( *m_pCurrentCursor );
932 Point& rPt = m_pCurrentCursor->GetPtPos();
933 std::pair<Point, bool> tmp(rPt, false);
934 SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()->
935 getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
936 if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) &&
937 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
938 SwCursorSelOverFlags::ChangePos ))
940 UpdateCursor();
941 bRet = true;
944 return bRet;
947 bool SwCursorShell::isInHiddenTextFrame(SwShellCursor* pShellCursor)
949 SwContentNode *pCNode = pShellCursor->GetPointContentNode();
950 std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
951 SwContentFrame *const pFrame = pCNode
952 ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp)
953 : nullptr;
954 return !pFrame || (pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow());
957 // sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara
958 static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell,
959 SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara)
961 SwContentNode *const pCNode = pShellCursor->GetPointContentNode();
962 assert(pCNode); // surely can't have moved otherwise?
963 std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
964 SwContentFrame const*const pFrame = pCNode->getLayoutFrame(
965 pShell->GetLayout(), pShellCursor->GetPoint(), &tmp);
966 if (!pFrame || !pFrame->IsTextFrame())
968 return false;
970 SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(*pFrame));
971 TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint()));
972 if (&fnParaStart == &fnPosPara)
974 return ix == TextFrameIndex(0);
976 else
978 assert(&fnParaEnd == &fnPosPara);
979 return ix == TextFrameIndex(rTextFrame.GetText().getLength());
983 bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
985 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
986 SwShellCursor* pTmpCursor = getShellCursor( true );
987 bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara );
988 if( bRet )
990 //keep going until we get something visible, i.e. skip
991 //over hidden paragraphs, don't get stuck at the start
992 //which is what SwCursorShell::UpdateCursorPos will reset
993 //the position to if we pass it a position in an
994 //invisible hidden paragraph field
995 while (isInHiddenTextFrame(pTmpCursor)
996 || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara))
998 if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara))
999 break;
1002 UpdateCursor();
1004 return bRet;
1007 bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect,
1008 SwMoveFnCollection const & fnPosSect)
1010 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1011 SwCursor* pTmpCursor = getShellCursor( true );
1012 bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect );
1013 if( bRet )
1014 UpdateCursor();
1015 return bRet;
1019 // position cursor
1021 static SwFrame* lcl_IsInHeaderFooter( SwNode& rNd, Point& rPt )
1023 SwFrame* pFrame = nullptr;
1024 SwContentNode* pCNd = rNd.GetContentNode();
1025 if( pCNd )
1027 std::pair<Point, bool> tmp(rPt, false);
1028 SwContentFrame *pContentFrame = pCNd->getLayoutFrame(
1029 pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
1030 nullptr, &tmp);
1031 pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr;
1032 while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() )
1033 pFrame = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->AnchorFrame()
1034 : pFrame->GetUpper();
1036 return pFrame;
1039 bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const
1041 Point aPt;
1042 SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->GetNode(), aPt );
1043 if( pFrame && pbInHeader )
1044 *pbInHeader = pFrame->IsHeaderFrame();
1045 return nullptr != pFrame;
1048 int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock )
1050 CurrShell aCurr( this );
1052 SwShellCursor* pCursor = getShellCursor( bBlock );
1053 SwPosition aPos( *pCursor->GetPoint() );
1054 Point aPt( rLPt );
1055 Point & rCurrentCursorPt = pCursor->GetPtPos();
1056 SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel :
1057 bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE );
1058 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1060 SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
1062 if ( pTextNd && !IsTableMode() &&
1063 // #i37515# No bInFrontOfLabel during selection
1064 !pCursor->HasMark() &&
1065 pTextNd->HasVisibleNumberingOrBullet() )
1067 aTmpState.m_bInFrontOfLabel = true; // #i27615#
1069 else
1071 aTmpState.m_bInFrontOfLabel = false;
1074 int bRet = CRSR_POSOLD |
1075 ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState )
1076 ? 0 : CRSR_POSCHG );
1078 const bool bOldInFrontOfLabel = IsInFrontOfLabel();
1079 const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel;
1081 pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel );
1083 if( CursorMoveState::RightMargin == aTmpState.m_eState )
1084 m_eMvState = CursorMoveState::RightMargin;
1085 // is the new position in header or footer?
1086 SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.GetNode(), aPt );
1087 if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() ==
1088 pCursor->GetPoint()->GetNode().StartOfSectionNode() )
1089 // same table column and not in header/footer -> back
1090 return bRet;
1092 if( m_pBlockCursor && bBlock )
1094 m_pBlockCursor->setEndPoint( rLPt );
1095 if( !pCursor->HasMark() )
1096 m_pBlockCursor->setStartPoint( rLPt );
1097 else if( !m_pBlockCursor->getStartPoint() )
1098 m_pBlockCursor->setStartPoint( pCursor->GetMkPos() );
1100 if( !pCursor->HasMark() )
1102 // is at the same position and if in header/footer -> in the same
1103 if( aPos == *pCursor->GetPoint() &&
1104 bOldInFrontOfLabel == bNewInFrontOfLabel )
1106 if( pFrame )
1108 if( pFrame->getFrameArea().Contains( rCurrentCursorPt ))
1109 return bRet;
1111 else if( aPos.GetNode().IsContentNode() )
1113 // in the same frame?
1114 std::pair<Point, bool> tmp(m_aCharRect.Pos(), false);
1115 SwFrame* pOld = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
1116 GetLayout(), nullptr, &tmp);
1117 tmp.first = aPt;
1118 SwFrame* pNew = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
1119 GetLayout(), nullptr, &tmp);
1120 if( pNew == pOld )
1121 return bRet;
1125 else
1127 // SSelection over not allowed sections or if in header/footer -> different
1128 if( !CheckNodesRange( aPos.GetNode(), pCursor->GetMark()->GetNode(), true )
1129 || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) ))
1130 return bRet;
1132 // is at same position but not in header/footer
1133 if( aPos == *pCursor->GetPoint() )
1134 return bRet;
1137 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1138 SwCursorSaveState aSaveState( *pCursor );
1140 *pCursor->GetPoint() = aPos;
1141 rCurrentCursorPt = aPt;
1143 // #i41424# Only update the marked number levels if necessary
1144 // Force update of marked number levels if necessary.
1145 if ( bNewInFrontOfLabel || bOldInFrontOfLabel )
1146 m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel );
1147 SetInFrontOfLabel( bNewInFrontOfLabel );
1149 if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) )
1151 UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE );
1152 bRet &= ~CRSR_POSOLD;
1154 else if( bOnlyText && !m_pCurrentCursor->HasMark() )
1156 if( FindValidContentNode( bOnlyText ) )
1158 // position cursor in a valid content
1159 if( aPos == *pCursor->GetPoint() )
1160 bRet = CRSR_POSOLD;
1161 else
1163 UpdateCursor();
1164 bRet &= ~CRSR_POSOLD;
1167 else
1169 // there is no valid content -> hide cursor
1170 m_pVisibleCursor->Hide(); // always hide visible cursor
1171 m_eMvState = CursorMoveState::NONE; // status for Cursor travelling
1172 m_bAllProtect = true;
1173 if( GetDoc()->GetDocShell() )
1175 GetDoc()->GetDocShell()->SetReadOnlyUI();
1176 CallChgLnk(); // notify UI
1180 return bRet;
1183 void SwCursorShell::TableCursorToCursor()
1185 assert(m_pTableCursor);
1186 delete m_pTableCursor;
1187 m_pTableCursor = nullptr;
1190 void SwCursorShell::BlockCursorToCursor()
1192 assert(m_pBlockCursor);
1193 if( m_pBlockCursor && !HasSelection() )
1195 SwPaM& rPam = m_pBlockCursor->getShellCursor();
1196 m_pCurrentCursor->SetMark();
1197 *m_pCurrentCursor->GetPoint() = *rPam.GetPoint();
1198 if( rPam.HasMark() )
1199 *m_pCurrentCursor->GetMark() = *rPam.GetMark();
1200 else
1201 m_pCurrentCursor->DeleteMark();
1203 delete m_pBlockCursor;
1204 m_pBlockCursor = nullptr;
1207 void SwCursorShell::CursorToBlockCursor()
1209 if( !m_pBlockCursor )
1211 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
1212 m_pBlockCursor = new SwBlockCursor( *this, aPos );
1213 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
1214 rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos();
1215 if( m_pCurrentCursor->HasMark() )
1217 rBlock.SetMark();
1218 *rBlock.GetMark() = *m_pCurrentCursor->GetMark();
1219 rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos();
1222 m_pBlockCursor->clearPoints();
1223 RefreshBlockCursor();
1226 void SwCursorShell::ClearMark()
1228 // is there any GetMark?
1229 if( m_pTableCursor )
1231 std::vector<SwPaM*> vCursors;
1232 for(auto& rCursor : m_pCurrentCursor->GetRingContainer())
1233 if(&rCursor != m_pCurrentCursor)
1234 vCursors.push_back(&rCursor);
1235 for(auto pCursor : vCursors)
1236 delete pCursor;
1237 m_pTableCursor->DeleteMark();
1239 m_pCurrentCursor->DeleteMark();
1241 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
1242 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
1243 delete m_pTableCursor;
1244 m_pTableCursor = nullptr;
1245 m_pCurrentCursor->SwSelPaintRects::Show();
1247 else
1249 if( !m_pCurrentCursor->HasMark() )
1250 return;
1251 m_pCurrentCursor->DeleteMark();
1252 if( !m_nCursorMove )
1253 m_pCurrentCursor->SwSelPaintRects::Show();
1257 void SwCursorShell::NormalizePam(bool bPointFirst)
1259 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1260 m_pCurrentCursor->Normalize(bPointFirst);
1263 void SwCursorShell::SwapPam()
1265 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1266 m_pCurrentCursor->Exchange();
1269 //TODO: provide documentation
1270 /** Search in the selected area for a Selection that covers the given point.
1272 It checks if a Selection exists but does
1273 not move the current cursor.
1275 @param rPt The point to search at.
1276 @param bTstHit ???
1278 bool SwCursorShell::TestCurrPam(
1279 const Point & rPt,
1280 bool bTstHit )
1282 CurrShell aCurr( this );
1284 // check if the SPoint is in a table selection
1285 if( m_pTableCursor )
1286 return m_pTableCursor->Contains( rPt );
1288 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1289 // search position <rPt> in document
1290 SwPosition aPtPos( *m_pCurrentCursor->GetPoint() );
1291 Point aPt( rPt );
1293 SwCursorMoveState aTmpState( CursorMoveState::NONE );
1294 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1295 if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit )
1296 return false;
1298 // search in all selections for this position
1299 SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor
1302 if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos)
1303 return true; // return without update
1304 pCmp = pCmp->GetNext();
1305 } while (m_pCurrentCursor != pCmp);
1306 return false;
1309 void SwCursorShell::KillPams()
1311 // Does any exist for deletion?
1312 if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() )
1313 return;
1315 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
1316 delete m_pCurrentCursor->GetNext();
1317 m_pCurrentCursor->SetColumnSelection( false );
1319 if( m_pTableCursor )
1321 // delete the ring of cursors
1322 m_pCurrentCursor->DeleteMark();
1323 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
1324 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
1325 delete m_pTableCursor;
1326 m_pTableCursor = nullptr;
1328 else if( m_pBlockCursor )
1330 // delete the ring of cursors
1331 m_pCurrentCursor->DeleteMark();
1332 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
1333 *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint();
1334 m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos();
1335 rBlock.DeleteMark();
1336 m_pBlockCursor->clearPoints();
1338 UpdateCursor( SwCursorShell::SCROLLWIN );
1341 int SwCursorShell::CompareCursorStackMkCurrPt() const
1343 int nRet = 0;
1344 const SwPosition *pFirst = nullptr, *pSecond = nullptr;
1345 const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor;
1346 // cursor on stack is needed if we compare against stack
1347 if( pStack )
1349 pFirst = pStack->GetMark();
1350 pSecond = pCur->GetPoint();
1352 if( !pFirst || !pSecond )
1353 nRet = INT_MAX;
1354 else if( *pFirst < *pSecond )
1355 nRet = -1;
1356 else if( *pFirst == *pSecond )
1357 nRet = 0;
1358 else
1359 nRet = 1;
1360 return nRet;
1363 bool SwCursorShell::IsSelOnePara() const
1365 if (m_pCurrentCursor->IsMultiSelection())
1367 return false;
1369 if (m_pCurrentCursor->GetPoint()->GetNode() == m_pCurrentCursor->GetMark()->GetNode())
1371 return true;
1373 if (GetLayout()->HasMergedParas())
1375 SwContentFrame const*const pFrame(GetCurrFrame(false));
1376 auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex());
1377 return FrameContainsNode(*pFrame, n);
1379 return false;
1382 bool SwCursorShell::IsSttPara() const
1384 if (GetLayout()->HasMergedParas())
1386 SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
1387 if (pNode)
1389 SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
1390 pNode->getLayoutFrame(GetLayout())));
1391 if (pFrame)
1393 return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
1394 == TextFrameIndex(0);
1398 return m_pCurrentCursor->GetPoint()->GetContentIndex() == 0;
1401 bool SwCursorShell::IsEndPara() const
1403 if (GetLayout()->HasMergedParas())
1405 SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
1406 if (pNode)
1408 SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
1409 pNode->getLayoutFrame(GetLayout())));
1410 if (pFrame)
1412 return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
1413 == TextFrameIndex(pFrame->GetText().getLength());
1417 return m_pCurrentCursor->GetPoint()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->Len();
1420 bool SwCursorShell::IsEndOfTable() const
1422 if (IsTableMode() || IsBlockMode() || !IsEndPara())
1424 return false;
1426 SwTableNode const*const pTableNode( IsCursorInTable() );
1427 if (!pTableNode)
1429 return false;
1431 SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode());
1432 SwNodeIndex const lastNode(*pEndTableNode, -2);
1433 SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core",
1434 "text node expected");
1435 return (lastNode == m_pCurrentCursor->GetPoint()->GetNode());
1438 bool SwCursorShell::IsCursorInFootnote() const
1440 SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType();
1441 return aStartNodeType == SwStartNodeType::SwFootnoteStartNode;
1444 Point SwCursorShell::GetCursorPagePos() const
1446 Point aRet(-1, -1);
1447 if (SwFrame *pFrame = GetCurrFrame())
1449 if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame())
1451 const Point& rDocPos = GetCursorDocPos();
1452 aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft();
1455 return aRet;
1458 bool SwCursorShell::IsInFrontOfLabel() const
1460 return m_pCurrentCursor->IsInFrontOfLabel();
1463 bool SwCursorShell::SetInFrontOfLabel( bool bNew )
1465 if ( bNew != IsInFrontOfLabel() )
1467 m_pCurrentCursor->SetInFrontOfLabel_( bNew );
1468 UpdateMarkedListLevel();
1469 return true;
1471 return false;
1474 namespace {
1476 void collectUIInformation(const OUString& aPage)
1478 EventDescription aDescription;
1479 aDescription.aAction = "GOTO";
1480 aDescription.aParameters = {{"PAGE", aPage}};
1481 aDescription.aID = "writer_edit";
1482 aDescription.aKeyWord = "SwEditWinUIObject";
1483 aDescription.aParent = "MainWindow";
1484 UITestLogger::getInstance().logEvent(aDescription);
1489 bool SwCursorShell::GotoPage( sal_uInt16 nPage )
1491 CurrShell aCurr( this );
1492 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1493 SwCursorSaveState aSaveState( *m_pCurrentCursor );
1494 bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) &&
1495 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
1496 SwCursorSelOverFlags::ChangePos );
1497 if( bRet )
1498 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
1500 collectUIInformation(OUString::number(nPage));
1501 return bRet;
1504 void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos)
1506 SwContentFrame* pFrame = GetCurrFrame();
1507 pFrame->GetCharRect( rRect, *pPos );
1510 void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum,
1511 bool bAtCursorPos, const bool bCalcFrame )
1513 CurrShell aCurr( this );
1514 // page number: first visible page or the one at the cursor
1515 const SwContentFrame* pCFrame;
1516 const SwPageFrame *pPg = nullptr;
1518 if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) ||
1519 nullptr == (pPg = pCFrame->FindPageFrame()) )
1521 pPg = Imp()->GetFirstVisPage(GetOut());
1522 while( pPg && pPg->IsEmptyPage() )
1523 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1525 // pPg has to exist with a default of 1 for the special case "Writerstart"
1526 rnPhyNum = pPg? pPg->GetPhyPageNum() : 1;
1527 rnVirtNum = pPg? pPg->GetVirtPageNum() : 1;
1530 sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty()
1532 CurrShell aCurr(this);
1533 // page number: first visible page or the one at the cursor
1534 const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true);
1535 const SwPageFrame* pPg = nullptr;
1537 if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame()))
1539 pPg = Imp()->GetFirstVisPage(GetOut());
1540 while (pPg && pPg->IsEmptyPage())
1541 pPg = static_cast<const SwPageFrame*>(pPg->GetNext());
1544 sal_uInt16 nPageNo = 0;
1545 while (pPg)
1547 if (!pPg->IsEmptyPage())
1548 ++nPageNo;
1549 pPg = static_cast<const SwPageFrame*>(pPg->GetPrev());
1551 return nPageNo;
1554 sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext )
1556 CurrShell aCurr( this );
1557 // page number: first visible page or the one at the cursor
1558 const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut());
1559 if( pPg )
1561 const SwTwips nPageTop = pPg->getFrameArea().Top();
1563 if( bNext )
1565 // go to next view layout row:
1568 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1570 while( pPg && pPg->getFrameArea().Top() == nPageTop );
1572 while( pPg && pPg->IsEmptyPage() )
1573 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1575 else
1577 // go to previous view layout row:
1580 pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
1582 while( pPg && pPg->getFrameArea().Top() == nPageTop );
1584 while( pPg && pPg->IsEmptyPage() )
1585 pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
1588 // pPg has to exist with a default of 1 for the special case "Writerstart"
1589 return pPg ? pPg->GetPhyPageNum() : USHRT_MAX;
1592 sal_uInt16 SwCursorShell::GetPageCnt()
1594 CurrShell aCurr( this );
1595 // return number of pages
1596 return GetLayout()->GetPageNum();
1599 OUString SwCursorShell::getPageRectangles()
1601 CurrShell aCurr(this);
1602 SwRootFrame* pLayout = GetLayout();
1603 OUStringBuffer aBuf;
1604 for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
1606 aBuf.append(OUString::number(pFrame->getFrameArea().Left())
1607 + ", "
1608 + OUString::number(pFrame->getFrameArea().Top())
1609 + ", "
1610 + OUString::number(pFrame->getFrameArea().Width())
1611 + ", "
1612 + OUString::number(pFrame->getFrameArea().Height())
1613 + "; ");
1615 if (!aBuf.isEmpty())
1616 aBuf.setLength( aBuf.getLength() - 2); // remove the last "; "
1617 return aBuf.makeStringAndClear();
1620 void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const
1622 auto pView = const_cast<SdrView*>(GetDrawView());
1623 if (pView->GetTextEditObject())
1625 // Blinking cursor.
1626 EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
1627 rEditView.RegisterOtherShell(pOtherShell);
1628 rEditView.ShowCursor();
1629 rEditView.RegisterOtherShell(nullptr);
1630 // Text selection, if any.
1631 rEditView.DrawSelectionXOR(pOtherShell);
1633 // Shape text lock.
1634 if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView())
1636 OString sRect = pOutlinerView->GetOutputArea().toString();
1637 SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect);
1640 else
1642 // Cursor position.
1643 m_pVisibleCursor->SetPosAndShow(pOtherShell);
1644 // Cursor visibility.
1645 if (GetSfxViewShell() != pOtherShell)
1647 OString aPayload = OString::boolean(m_bSVCursorVis);
1648 SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
1650 // Text selection.
1651 m_pCurrentCursor->Show(pOtherShell);
1652 // Graphic selection.
1653 pView->AdjustMarkHdl(pOtherShell);
1657 /// go to the next SSelection
1658 bool SwCursorShell::GoNextCursor()
1660 if( !m_pCurrentCursor->IsMultiSelection() )
1661 return false;
1663 CurrShell aCurr( this );
1664 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1665 m_pCurrentCursor = m_pCurrentCursor->GetNext();
1667 // #i24086#: show also all others
1668 if( !ActionPend() )
1670 UpdateCursor();
1671 m_pCurrentCursor->Show(nullptr);
1673 return true;
1676 /// go to the previous SSelection
1677 bool SwCursorShell::GoPrevCursor()
1679 if( !m_pCurrentCursor->IsMultiSelection() )
1680 return false;
1682 CurrShell aCurr( this );
1683 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1684 m_pCurrentCursor = m_pCurrentCursor->GetPrev();
1686 // #i24086#: show also all others
1687 if( !ActionPend() )
1689 UpdateCursor();
1690 m_pCurrentCursor->Show(nullptr);
1692 return true;
1695 void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext)
1697 SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
1699 if( !m_pCurrentCursor->IsMultiSelection() )
1701 if( !m_pCurrentCursor->HasMark() )
1702 SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
1703 return;
1706 if (bNext)
1707 GoNextCursor();
1708 else
1709 GoPrevCursor();
1712 void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect)
1714 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
1715 CurrShell aCurr( this );
1717 // always switch off all cursors when painting
1718 SwRect aRect( rRect );
1720 bool bVis = false;
1721 // if a cursor is visible then hide the SV cursor
1722 if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) )
1724 bVis = true;
1725 m_pVisibleCursor->Hide();
1728 // re-paint area
1729 SwViewShell::Paint(rRenderContext, rRect);
1731 if( m_bHasFocus && !m_bBasicHideCursor )
1733 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
1735 if( !ActionPend() )
1737 // so that right/bottom borders will not be cropped
1738 pCurrentCursor->Invalidate( VisArea() );
1739 pCurrentCursor->Show(nullptr);
1741 else
1742 pCurrentCursor->Invalidate( aRect );
1746 if (SwPostItMgr* pPostItMgr = GetPostItMgr())
1748 // No point in showing the cursor for Writer text when there is an
1749 // active annotation edit.
1750 if (bVis)
1751 bVis = !pPostItMgr->HasActiveSidebarWin();
1754 if( m_bSVCursorVis && bVis ) // also show SV cursor again
1755 m_pVisibleCursor->Show();
1758 void SwCursorShell::VisPortChgd( const SwRect & rRect )
1760 CurrShell aCurr( this );
1761 bool bVis; // switch off all cursors when scrolling
1763 // if a cursor is visible then hide the SV cursor
1764 bVis = m_pVisibleCursor->IsVisible();
1765 if( bVis )
1766 m_pVisibleCursor->Hide();
1768 m_bVisPortChgd = true;
1769 m_aOldRBPos.setX(VisArea().Right());
1770 m_aOldRBPos.setY(VisArea().Bottom());
1772 // For not having problems with the SV cursor, Update() is called for the
1773 // Window in SwViewShell::VisPo...
1774 // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact?
1775 SwViewShell::VisPortChgd( rRect ); // move area
1777 if( m_bSVCursorVis && bVis ) // show SV cursor again
1778 m_pVisibleCursor->Show();
1780 if( m_nCursorMove )
1781 m_bInCMvVisportChgd = true;
1783 m_bVisPortChgd = false;
1786 /** Set the cursor back into content.
1788 This should only be called if the cursor was moved (e.g. when deleting a
1789 text frame). The new position is calculated from its current position
1790 in the layout.
1792 void SwCursorShell::UpdateCursorPos()
1794 CurrShell aCurr( this );
1795 ++mnStartAction;
1796 SwShellCursor* pShellCursor = getShellCursor( true );
1797 Size aOldSz( GetDocSize() );
1799 if (isInHiddenTextFrame(pShellCursor) && !ExtendedSelectedAll())
1801 SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText);
1802 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1803 GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(),
1804 &aTmpState );
1805 pShellCursor->DeleteMark();
1806 // kde45196-1.html: try to get to a non-hidden paragraph, there must
1807 // be one in the document body
1808 while (isInHiddenTextFrame(pShellCursor))
1810 if (!pShellCursor->MovePara(GoNextPara, fnParaStart))
1812 break;
1815 while (isInHiddenTextFrame(pShellCursor))
1817 if (!pShellCursor->MovePara(GoPrevPara, fnParaStart))
1819 break;
1823 auto* pDoc = GetDoc();
1824 if (pDoc)
1826 pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint());
1827 pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint());
1830 --mnStartAction;
1831 if( aOldSz != GetDocSize() )
1832 SizeChgNotify();
1835 // #i65475# - if Point/Mark in hidden sections, move them out
1836 static bool lcl_CheckHiddenSection( SwPosition& rPos )
1838 bool bOk = true;
1839 const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode();
1840 if( pSectNd && pSectNd->GetSection().IsHiddenFlag() )
1842 const SwNode* pFrameNd =
1843 rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() );
1844 bOk = pFrameNd != nullptr;
1845 SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames");
1846 rPos.Assign( *(bOk ? pFrameNd : pSectNd) );
1848 return bOk;
1851 /// Try to set the cursor to the next visible content node.
1852 static void lcl_CheckHiddenPara( SwPosition& rPos )
1854 SwNodeIndex aTmp( rPos.GetNode() );
1855 SwTextNode* pTextNd = aTmp.GetNode().GetTextNode();
1856 while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) )
1858 SwContentNode* pContent = aTmp.GetNodes().GoNext( &aTmp );
1859 if ( pContent && pContent->IsTextNode() )
1860 pTextNd = pContent->GetTextNode();
1861 else
1862 pTextNd = nullptr;
1865 if ( pTextNd )
1866 rPos.Assign( *pTextNd, 0 );
1869 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
1870 namespace {
1872 // #i27301# - helper class that notifies the accessibility about invalid text
1873 // selections in its destructor
1874 class SwNotifyAccAboutInvalidTextSelections
1876 private:
1877 SwCursorShell& mrCursorSh;
1879 public:
1880 explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh )
1881 : mrCursorSh( _rCursorSh )
1884 ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE
1886 mrCursorSh.InvalidateAccessibleParaTextSelection();
1891 #endif
1893 void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd )
1895 CurrShell aCurr( this );
1896 ClearUpCursors();
1898 if (ActionPend())
1900 if ( eFlags & SwCursorShell::READONLY )
1901 m_bIgnoreReadonly = true;
1902 return; // if not then no update
1905 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
1906 SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this );
1907 #endif
1909 if ( m_bIgnoreReadonly )
1911 m_bIgnoreReadonly = false;
1912 eFlags |= SwCursorShell::READONLY;
1915 if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for
1916 CheckRange( m_pCurrentCursor ); // overlapping ranges
1918 if( !bIdleEnd )
1919 CheckTableBoxContent();
1921 // If the current cursor is in a table and point/mark in different boxes,
1922 // then the table mode is active (also if it is already active: m_pTableCursor)
1923 SwPaM* pTstCursor = getShellCursor( true );
1924 if( pTstCursor->HasMark() && !m_pBlockCursor &&
1925 SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) &&
1926 ( m_pTableCursor ||
1927 pTstCursor->GetPointNode().StartOfSectionNode() !=
1928 pTstCursor->GetMarkNode().StartOfSectionNode() ) && !mbSelectAll)
1930 SwShellCursor* pITmpCursor = getShellCursor( true );
1931 Point aTmpPt( pITmpCursor->GetPtPos() );
1932 Point aTmpMk( pITmpCursor->GetMkPos() );
1933 SwPosition* pPos = pITmpCursor->GetPoint();
1935 // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out
1936 lcl_CheckHiddenSection( *pPos );
1937 lcl_CheckHiddenSection( *pITmpCursor->GetMark() );
1939 // Move cursor out of hidden paragraphs
1940 if ( !GetViewOptions()->IsShowHiddenChar() )
1942 lcl_CheckHiddenPara( *pPos );
1943 lcl_CheckHiddenPara( *pITmpCursor->GetMark() );
1946 std::pair<Point, bool> const tmp(aTmpPt, false);
1947 SwContentFrame *pTableFrame = pPos->GetNode().GetContentNode()->
1948 getLayoutFrame( GetLayout(), pPos, &tmp);
1950 OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" );
1952 // --> Make code robust. The table cursor may point
1953 // to a table in a currently inactive header.
1954 SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr;
1956 if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 )
1958 // First check if point is in repeated headline:
1959 bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame );
1961 // Second check if mark is in repeated headline:
1962 if ( !bInRepeatedHeadline )
1964 std::pair<Point, bool> const tmp1(aTmpMk, false);
1965 SwContentFrame* pMarkTableFrame = pITmpCursor->GetMarkContentNode()->
1966 getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1);
1967 OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" );
1969 if ( pMarkTableFrame )
1971 SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame();
1972 OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" );
1974 // Make code robust:
1975 if ( pMarkTab )
1977 bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame );
1982 // No table cursor in repeated headlines:
1983 if ( bInRepeatedHeadline )
1985 pTableFrame = nullptr;
1987 SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark()
1988 ? fnSectionStart
1989 : fnSectionEnd;
1991 // then only select inside the Box
1992 if( m_pTableCursor )
1994 m_pCurrentCursor->SetMark();
1995 *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark();
1996 m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos();
1997 m_pTableCursor->DeleteMark();
1998 m_pTableCursor->SwSelPaintRects::Hide();
2001 *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
2002 GoCurrSection( *m_pCurrentCursor, fnPosSect );
2006 // we really want a table selection
2007 if( pTab && pTableFrame )
2009 if( !m_pTableCursor )
2011 m_pTableCursor = new SwShellTableCursor( *this,
2012 *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(),
2013 *pPos, aTmpPt );
2014 m_pCurrentCursor->DeleteMark();
2015 m_pCurrentCursor->SwSelPaintRects::Hide();
2017 CheckTableBoxContent();
2018 if(!m_pTableCursor)
2020 SAL_WARN("sw.core", "fdo#74854: "
2021 "this should not happen, but better lose the selection "
2022 "rather than crashing");
2023 return;
2027 SwCursorMoveState aTmpState( CursorMoveState::NONE );
2028 aTmpState.m_bRealHeight = true;
2030 DisableCallbackAction a(*GetLayout());
2031 if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState))
2033 Point aCentrPt( m_aCharRect.Center() );
2034 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
2035 pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState);
2036 bool const bResult =
2037 pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint());
2038 OSL_ENSURE( bResult, "GetCharRect failed." );
2042 m_pVisibleCursor->Hide(); // always hide visible Cursor
2043 // scroll Cursor to visible area
2044 if( eFlags & SwCursorShell::SCROLLWIN &&
2045 (HasSelection() || eFlags & SwCursorShell::READONLY ||
2046 !IsCursorReadonly()) )
2048 SwFrame* pBoxFrame = pTableFrame;
2049 while( pBoxFrame && !pBoxFrame->IsCellFrame() )
2050 pBoxFrame = pBoxFrame->GetUpper();
2051 if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() )
2052 MakeVisible( pBoxFrame->getFrameArea() );
2053 else
2054 MakeVisible( m_aCharRect );
2057 // let Layout create the Cursors in the Boxes
2058 if( m_pTableCursor->IsCursorMovedUpdate() )
2059 GetLayout()->MakeTableCursors( *m_pTableCursor );
2060 if( m_bHasFocus && !m_bBasicHideCursor )
2061 m_pTableCursor->Show(nullptr);
2063 // set Cursor-Points to the new Positions
2064 m_pTableCursor->GetPtPos().setX(m_aCharRect.Left());
2065 m_pTableCursor->GetPtPos().setY(m_aCharRect.Top());
2067 if( m_bSVCursorVis )
2069 m_aCursorHeight.setX(0);
2070 m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
2071 -m_aCharRect.Width() : m_aCharRect.Height());
2072 m_pVisibleCursor->Show(); // show again
2074 m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
2075 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2076 if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
2077 Imp()->InvalidateAccessibleCursorPosition( pTableFrame );
2078 #endif
2079 return;
2083 if( m_pTableCursor )
2085 // delete Ring
2086 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
2087 delete m_pCurrentCursor->GetNext();
2088 m_pCurrentCursor->DeleteMark();
2089 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
2090 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
2091 delete m_pTableCursor;
2092 m_pTableCursor = nullptr;
2095 m_pVisibleCursor->Hide(); // always hide visible Cursor
2097 // are we perhaps in a protected / hidden Section ?
2099 SwShellCursor* pShellCursor = getShellCursor( true );
2100 bool bChgState = true;
2101 const SwSectionNode* pSectNd = pShellCursor->GetPointNode().FindSectionNode();
2102 if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
2103 ( !IsReadOnlyAvailable() &&
2104 pSectNd->GetSection().IsProtectFlag() &&
2105 ( !mxDoc->GetDocShell() ||
2106 !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) )
2108 if( !FindValidContentNode( !HasDrawView() ||
2109 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
2111 // everything protected/hidden -> special mode
2112 if( m_bAllProtect && !IsReadOnlyAvailable() &&
2113 pSectNd->GetSection().IsProtectFlag() )
2114 bChgState = false;
2115 else
2117 m_eMvState = CursorMoveState::NONE; // state for cursor travelling
2118 m_bAllProtect = true;
2119 if( GetDoc()->GetDocShell() )
2121 GetDoc()->GetDocShell()->SetReadOnlyUI();
2122 CallChgLnk(); // notify UI!
2124 return;
2128 if( bChgState )
2130 bool bWasAllProtect = m_bAllProtect;
2131 m_bAllProtect = false;
2132 if( bWasAllProtect && GetDoc()->GetDocShell() &&
2133 GetDoc()->GetDocShell()->IsReadOnlyUI() )
2135 GetDoc()->GetDocShell()->SetReadOnlyUI( false );
2136 CallChgLnk(); // notify UI!
2141 UpdateCursorPos();
2143 // The cursor must always point into content; there's some code
2144 // that relies on this. (E.g. in SwEditShell::GetScriptType, which always
2145 // loops _behind_ the last node in the selection, which always works if you
2146 // are in content.) To achieve this, we'll force cursor(s) to point into
2147 // content, if UpdateCursorPos() hasn't already done so.
2148 for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer())
2150 // start will move forwards, end will move backwards
2151 bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() );
2153 // move point; forward if it's the start, backwards if it's the end
2154 if( ! rCmp.GetPoint()->GetNode().IsContentNode() )
2155 rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward,
2156 GoInContent );
2158 // move mark (if exists); forward if it's the start, else backwards
2159 if( rCmp.HasMark() )
2161 if( ! rCmp.GetMark()->GetNode().IsContentNode() )
2163 rCmp.Exchange();
2164 rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward,
2165 GoInContent );
2166 rCmp.Exchange();
2171 SwRect aOld( m_aCharRect );
2172 bool bFirst = true;
2173 SwContentFrame *pFrame;
2174 int nLoopCnt = 100;
2175 SwShellCursor* pShellCursor = getShellCursor( true );
2177 do {
2178 bool bAgainst;
2179 do {
2180 bAgainst = false;
2181 std::pair<Point, bool> const tmp1(pShellCursor->GetPtPos(), false);
2182 pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(GetLayout(),
2183 pShellCursor->GetPoint(), &tmp1);
2184 // if the Frame doesn't exist anymore, the complete Layout has to be
2185 // created, because there used to be a Frame here!
2186 if ( !pFrame )
2188 // skip, if it is a hidden deleted cell without frame
2189 if ( GetLayout()->IsHideRedlines() )
2191 const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode();
2192 if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete )
2193 return;
2198 CalcLayout();
2199 std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false);
2200 pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(
2201 GetLayout(), pShellCursor->GetPoint(), &tmp);
2202 } while( !pFrame );
2204 else if ( Imp()->IsIdleAction() )
2205 // Guarantee everything's properly formatted
2206 pFrame->PrepareCursor();
2208 // In protected Fly? but ignore in case of frame selection
2209 if( !IsReadOnlyAvailable() && pFrame->IsProtected() &&
2210 ( !Imp()->GetDrawView() ||
2211 !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) &&
2212 (!mxDoc->GetDocShell() ||
2213 !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) )
2215 // look for a valid position
2216 bool bChgState = true;
2217 if( !FindValidContentNode(!HasDrawView() ||
2218 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
2220 // everything is protected / hidden -> special Mode
2221 if( m_bAllProtect )
2222 bChgState = false;
2223 else
2225 m_eMvState = CursorMoveState::NONE; // state for cursor travelling
2226 m_bAllProtect = true;
2227 if( GetDoc()->GetDocShell() )
2229 GetDoc()->GetDocShell()->SetReadOnlyUI();
2230 CallChgLnk(); // notify UI!
2232 return;
2236 if( bChgState )
2238 bool bWasAllProtect = m_bAllProtect;
2239 m_bAllProtect = false;
2240 if( bWasAllProtect && GetDoc()->GetDocShell() &&
2241 GetDoc()->GetDocShell()->IsReadOnlyUI() )
2243 GetDoc()->GetDocShell()->SetReadOnlyUI( false );
2244 CallChgLnk(); // notify UI!
2246 m_bAllProtect = false;
2247 bAgainst = true; // look for the right Frame again
2250 } while( bAgainst );
2252 SwCursorMoveState aTmpState( m_eMvState );
2253 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
2254 aTmpState.m_bRealHeight = true;
2255 aTmpState.m_bRealWidth = IsOverwriteCursor();
2256 aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel();
2258 // #i27615#,#i30453#
2259 SwSpecialPos aSpecialPos;
2260 aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE;
2261 if (pShellCursor->IsInFrontOfLabel())
2263 aTmpState.m_pSpecialPos = &aSpecialPos;
2267 DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action
2268 if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState))
2270 Point& rPt = pShellCursor->GetPtPos();
2271 rPt = m_aCharRect.Center();
2272 pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState );
2275 UISizeNotify(); // tdf#96256 update view size
2277 if( !pShellCursor->HasMark() )
2278 m_aCursorHeight = aTmpState.m_aRealHeight;
2279 else
2281 m_aCursorHeight.setX(0);
2282 m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
2283 -m_aCharRect.Width() : m_aCharRect.Height());
2286 if( !bFirst && aOld == m_aCharRect )
2287 break;
2289 // if the layout says that we are after the 100th iteration still in
2290 // flow then we should always take the current position for granted.
2291 // (see bug: 29658)
2292 if( !--nLoopCnt )
2294 OSL_ENSURE( false, "endless loop? CharRect != OldCharRect ");
2295 break;
2297 aOld = m_aCharRect;
2298 bFirst = false;
2300 // update cursor Points to the new Positions
2301 pShellCursor->GetPtPos().setX(m_aCharRect.Left());
2302 pShellCursor->GetPtPos().setY(m_aCharRect.Top());
2304 if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down
2306 DisableCallbackAction a(*GetLayout());
2307 pFrame->Calc(GetOut());
2308 m_nUpDownX = pFrame->IsVertical() ?
2309 m_aCharRect.Top() - pFrame->getFrameArea().Top() :
2310 m_aCharRect.Left() - pFrame->getFrameArea().Left();
2313 // scroll Cursor to visible area
2314 if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
2315 (HasSelection() || eFlags & SwCursorShell::READONLY ||
2316 !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) )
2318 // in case of scrolling this EndAction doesn't show the SV cursor
2319 // again, thus save and reset the flag here
2320 bool bSav = m_bSVCursorVis;
2321 m_bSVCursorVis = false;
2322 MakeSelVisible();
2323 m_bSVCursorVis = bSav;
2326 } while( eFlags & SwCursorShell::SCROLLWIN );
2328 assert(pFrame);
2330 if( m_pBlockCursor )
2331 RefreshBlockCursor();
2333 // We should not restrict cursor update to the active view when using LOK
2334 bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive();
2336 if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor )
2338 if( m_pTableCursor )
2339 m_pTableCursor->SwSelPaintRects::Show();
2340 else
2342 m_pCurrentCursor->SwSelPaintRects::Show();
2343 if( m_pBlockCursor )
2345 SwShellCursor* pNxt = m_pCurrentCursor->GetNext();
2346 while( pNxt && pNxt != m_pCurrentCursor )
2348 pNxt->SwSelPaintRects::Show();
2349 pNxt = pNxt->GetNext();
2355 m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
2357 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2358 if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
2359 Imp()->InvalidateAccessibleCursorPosition( pFrame );
2360 #endif
2362 // switch from blinking cursor to read-only-text-selection cursor
2363 const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings().
2364 GetCursorBlinkTime();
2366 if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) ==
2367 ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) )
2369 // non blinking cursor in read only - text selection mode
2370 AllSettings aSettings = GetOut()->GetSettings();
2371 StyleSettings aStyleSettings = aSettings.GetStyleSettings();
2372 const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ?
2373 Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() :
2374 STYLE_CURSOR_NOBLINKTIME;
2375 aStyleSettings.SetCursorBlinkTime( nNewBlinkTime );
2376 aSettings.SetStyleSettings( aStyleSettings );
2377 GetOut()->SetSettings( aSettings );
2380 if( m_bSVCursorVis )
2381 m_pVisibleCursor->Show(); // show again
2383 if (comphelper::LibreOfficeKit::isActive())
2384 sendLOKCursorUpdates();
2386 getIDocumentMarkAccess()->NotifyCursorUpdate(*this);
2389 void SwCursorShell::sendLOKCursorUpdates()
2391 SwView* pView = static_cast<SwView*>(GetSfxViewShell());
2392 if (!pView || !pView->GetWrtShellPtr())
2393 return;
2395 SwWrtShell* pShell = &pView->GetWrtShell();
2397 SwFrame* pCurrentFrame = GetCurrFrame();
2398 SelectionType eType = pShell->GetSelectionType();
2400 tools::JsonWriter aJsonWriter;
2402 if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab())
2404 const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr);
2407 auto columnsNode = aJsonWriter.startNode("columns");
2408 SwTabCols aTabCols;
2409 pShell->GetTabCols(aTabCols);
2411 const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left();
2413 aJsonWriter.put("left", aTabCols.GetLeft());
2414 aJsonWriter.put("right", aTabCols.GetRight());
2415 aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nColumnOffset));
2418 auto entriesNode = aJsonWriter.startArray("entries");
2419 for (size_t i = 0; i < aTabCols.Count(); ++i)
2421 auto entryNode = aJsonWriter.startStruct();
2422 auto const & rEntry = aTabCols.GetEntry(i);
2423 aJsonWriter.put("position", rEntry.nPos);
2424 aJsonWriter.put("min", rEntry.nMin);
2425 aJsonWriter.put("max", rEntry.nMax);
2426 aJsonWriter.put("hidden", rEntry.bHidden);
2432 auto rowsNode = aJsonWriter.startNode("rows");
2433 SwTabCols aTabRows;
2434 pShell->GetTabRows(aTabRows);
2436 const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top();
2438 aJsonWriter.put("left", aTabRows.GetLeft());
2439 aJsonWriter.put("right", aTabRows.GetRight());
2440 aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nRowOffset));
2443 auto entriesNode = aJsonWriter.startArray("entries");
2444 for (size_t i = 0; i < aTabRows.Count(); ++i)
2446 auto entryNode = aJsonWriter.startStruct();
2447 auto const & rEntry = aTabRows.GetEntry(i);
2448 aJsonWriter.put("position", rEntry.nPos);
2449 aJsonWriter.put("min", rEntry.nMin);
2450 aJsonWriter.put("max", rEntry.nMax);
2451 aJsonWriter.put("hidden", rEntry.bHidden);
2457 OString pChar = aJsonWriter.finishAndGetAsOString();
2458 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar);
2461 void SwCursorShell::RefreshBlockCursor()
2463 assert(m_pBlockCursor);
2464 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
2465 Point aPt = rBlock.GetPtPos();
2466 std::pair<Point, bool> const tmp(aPt, false);
2467 SwContentFrame* pFrame = rBlock.GetPointContentNode()->getLayoutFrame(
2468 GetLayout(), rBlock.GetPoint(), &tmp);
2469 Point aMk;
2470 if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() )
2472 aPt = *m_pBlockCursor->getStartPoint();
2473 aMk = *m_pBlockCursor->getEndPoint();
2475 else
2477 aPt = rBlock.GetPtPos();
2478 if( pFrame )
2480 if( pFrame->IsVertical() )
2481 aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX());
2482 else
2483 aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX());
2485 aMk = rBlock.GetMkPos();
2487 SwRect aRect( aMk, aPt );
2488 aRect.Justify();
2489 SwSelectionList aSelList( pFrame );
2491 if( !GetLayout()->FillSelection( aSelList, aRect ) )
2492 return;
2494 SwCursor* pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
2495 while( pNxt != m_pCurrentCursor )
2497 delete pNxt;
2498 pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
2501 std::list<SwPaM*>::iterator pStart = aSelList.getStart();
2502 std::list<SwPaM*>::iterator pPam = aSelList.getEnd();
2503 OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" );
2504 m_pCurrentCursor->SetMark();
2505 --pPam;
2506 // If there is only one text portion inside the rectangle, a simple
2507 // selection is created
2508 if( pPam == pStart )
2510 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint();
2511 if( (*pPam)->HasMark() )
2512 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2513 else
2514 m_pCurrentCursor->DeleteMark();
2515 delete *pPam;
2516 m_pCurrentCursor->SetColumnSelection( false );
2518 else
2520 // The order of the SwSelectionList has to be preserved but
2521 // the order inside the ring created by CreateCursor() is not like
2522 // expected => First create the selections before the last one
2523 // downto the first selection.
2524 // At least create the cursor for the last selection
2525 --pPam;
2526 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections)
2527 if( (*pPam)->HasMark() )
2528 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2529 else
2530 m_pCurrentCursor->DeleteMark();
2531 delete *pPam;
2532 m_pCurrentCursor->SetColumnSelection( true );
2533 while( pPam != pStart )
2535 --pPam;
2537 SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
2538 pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end());
2539 m_pCurrentCursor->clear();
2540 m_pCurrentCursor->DeleteMark();
2542 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1
2543 if( (*pPam)->HasMark() )
2545 m_pCurrentCursor->SetMark();
2546 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2548 else
2549 m_pCurrentCursor->DeleteMark();
2550 m_pCurrentCursor->SetColumnSelection( true );
2551 delete *pPam;
2554 SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
2555 pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() );
2556 m_pCurrentCursor->clear();
2557 m_pCurrentCursor->DeleteMark();
2559 pPam = aSelList.getEnd();
2560 --pPam;
2561 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection
2562 if( (*pPam)->HasMark() )
2564 m_pCurrentCursor->SetMark();
2565 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2567 else
2568 m_pCurrentCursor->DeleteMark();
2569 m_pCurrentCursor->SetColumnSelection( true );
2570 delete *pPam;
2574 /// create a copy of the cursor and save it in the stack
2575 void SwCursorShell::Push()
2577 // fdo#60513: if we have a table cursor, copy that; else copy current.
2578 // This seems to work because UpdateCursor() will fix this up on Pop(),
2579 // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring.
2580 SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor);
2581 m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(),
2582 pCurrent->GetPtPos(), m_pStackCursor );
2584 if (pCurrent->HasMark())
2586 m_pStackCursor->SetMark();
2587 *m_pStackCursor->GetMark() = *pCurrent->GetMark();
2591 /** delete cursor
2593 @param eDelete delete from stack, or delete current
2594 and assign the one from stack as the new current cursor.
2595 @return <true> if there was one on the stack, <false> otherwise
2597 bool SwCursorShell::Pop(PopMode const eDelete)
2599 std::optional<SwCallLink> aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed
2600 return Pop(eDelete, aLink);
2603 bool SwCursorShell::Pop(PopMode const eDelete,
2604 [[maybe_unused]] std::optional<SwCallLink>& roLink)
2606 // parameter exists only to be deleted before return
2607 assert(roLink);
2608 comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } );
2610 // are there any left?
2611 if (nullptr == m_pStackCursor)
2612 return false;
2614 SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor;
2616 // the successor becomes the current one
2617 if (m_pStackCursor->GetNext() != m_pStackCursor)
2619 pTmp = m_pStackCursor->GetNext();
2622 if (PopMode::DeleteStack == eDelete)
2623 delete m_pStackCursor;
2625 m_pStackCursor = pTmp; // assign new one
2627 if (PopMode::DeleteCurrent == eDelete)
2629 ::std::optional<SwCursorSaveState> oSaveState( *m_pCurrentCursor );
2631 // If the visible SSelection was not changed
2632 const Point& rPoint = pOldStack->GetPtPos();
2633 if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos())
2635 // move "Selections Rectangles"
2636 m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() );
2637 pOldStack->clear();
2640 if( pOldStack->HasMark() )
2642 m_pCurrentCursor->SetMark();
2643 *m_pCurrentCursor->GetMark() = *pOldStack->GetMark();
2644 m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos();
2646 else
2647 // no selection so revoke old one and set to old position
2648 m_pCurrentCursor->DeleteMark();
2649 *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint();
2650 m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos();
2651 delete pOldStack;
2653 if( !m_pCurrentCursor->IsInProtectTable( true ) &&
2654 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
2655 SwCursorSelOverFlags::ChangePos ) )
2657 oSaveState.reset(); // prevent UAF
2658 UpdateCursor(); // update current cursor
2659 if (m_pTableCursor)
2660 { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table
2661 m_pTableCursor->SetChgd();
2665 return true;
2668 /** Combine two cursors
2670 Delete topmost from stack and use its GetMark in the current.
2672 void SwCursorShell::Combine()
2674 // any others left?
2675 if (nullptr == m_pStackCursor)
2676 return;
2678 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
2679 // rhbz#689053: IsSelOvr must restore the saved stack position, not the
2680 // current one, because current point + stack mark may be invalid PaM
2681 SwCursorSaveState aSaveState(*m_pStackCursor);
2682 // stack cursor & current cursor in same Section?
2683 assert(!m_pStackCursor->HasMark() ||
2684 CheckNodesRange(m_pStackCursor->GetMark()->GetNode(),
2685 m_pCurrentCursor->GetPoint()->GetNode(), true));
2686 *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint();
2687 m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos();
2689 SwShellCursor * pTmp = nullptr;
2690 if (m_pStackCursor->GetNext() != m_pStackCursor)
2692 pTmp = m_pStackCursor->GetNext();
2694 delete m_pCurrentCursor;
2695 m_pCurrentCursor = m_pStackCursor;
2696 m_pStackCursor->MoveTo(nullptr); // remove from ring
2697 m_pStackCursor = pTmp;
2698 if( !m_pCurrentCursor->IsInProtectTable( true ) &&
2699 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
2700 SwCursorSelOverFlags::ChangePos ) )
2702 UpdateCursor(); // update current cursor
2706 void SwCursorShell::HideCursors()
2708 if( !m_bHasFocus || m_bBasicHideCursor )
2709 return;
2711 // if cursor is visible then hide SV cursor
2712 if( m_pVisibleCursor->IsVisible() )
2714 CurrShell aCurr( this );
2715 m_pVisibleCursor->Hide();
2717 // revoke inversion of SSelection
2718 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
2719 pCurrentCursor->Hide();
2722 void SwCursorShell::ShowCursors( bool bCursorVis )
2724 if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor )
2725 return;
2727 CurrShell aCurr( this );
2728 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
2729 pCurrentCursor->Show(nullptr);
2731 if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again
2732 m_pVisibleCursor->Show();
2735 void SwCursorShell::ShowCursor()
2737 if( m_bBasicHideCursor )
2738 return;
2740 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
2742 m_bSVCursorVis = true;
2743 m_pCurrentCursor->SetShowTextInputFieldOverlay( true );
2744 m_pCurrentCursor->SetShowContentControlOverlay(true);
2746 if (comphelper::LibreOfficeKit::isActive())
2748 const OString aPayload = OString::boolean(m_bSVCursorVis);
2749 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
2750 SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
2753 UpdateCursor();
2756 void SwCursorShell::HideCursor()
2758 if( m_bBasicHideCursor )
2759 return;
2761 m_bSVCursorVis = false;
2762 // possibly reverse selected areas!!
2763 CurrShell aCurr( this );
2764 m_pCurrentCursor->SetShowTextInputFieldOverlay( false );
2765 m_pCurrentCursor->SetShowContentControlOverlay(false);
2766 m_pVisibleCursor->Hide();
2768 if (comphelper::LibreOfficeKit::isActive())
2770 OString aPayload = OString::boolean(m_bSVCursorVis);
2771 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
2772 SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
2776 void SwCursorShell::ShellLoseFocus()
2778 if( !m_bBasicHideCursor )
2779 HideCursors();
2780 m_bHasFocus = false;
2783 void SwCursorShell::ShellGetFocus()
2785 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
2787 m_bHasFocus = true;
2788 if( !m_bBasicHideCursor && VisArea().Width() )
2790 UpdateCursor( o3tl::narrowing<sal_uInt16>( SwCursorShell::CHKRANGE ) );
2791 ShowCursors( m_bSVCursorVis );
2795 /** Get current frame in which the cursor is positioned. */
2796 SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const
2798 CurrShell aCurr( const_cast<SwCursorShell*>(this) );
2799 SwContentFrame *pRet = nullptr;
2800 SwContentNode *pNd = m_pCurrentCursor->GetPointContentNode();
2801 if ( pNd )
2803 if ( bCalcFrame )
2805 sal_uInt16* pST = const_cast<sal_uInt16*>(&mnStartAction);
2806 ++(*pST);
2807 const Size aOldSz( GetDocSize() );
2808 std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), true);
2809 pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
2810 --(*pST);
2811 if( aOldSz != GetDocSize() )
2812 const_cast<SwCursorShell*>(this)->SizeChgNotify();
2814 else
2816 std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), false);
2817 pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
2820 return pRet;
2823 //TODO: provide documentation
2824 /** forward all attribute/format changes at the current node to the Link
2826 @param pOld ???
2827 @param pNew ???
2829 void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint)
2831 if(dynamic_cast<const sw::PostGraphicArrivedHint*>(&rHint) && m_aGrfArrivedLnk.IsSet())
2833 m_aGrfArrivedLnk.Call(*this);
2834 return;
2836 if (rHint.GetId() != SfxHintId::SwLegacyModify)
2837 return;
2838 auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
2839 auto nWhich = pLegacy->GetWhich();
2840 if(!nWhich)
2841 nWhich = RES_OBJECTDYING;
2842 if( m_bCallChgLnk &&
2843 ( !isFormatMessage(nWhich)
2844 || nWhich == RES_FMT_CHG
2845 || nWhich == RES_UPDATE_ATTR
2846 || nWhich == RES_ATTRSET_CHG ))
2847 // messages are not forwarded
2848 // #i6681#: RES_UPDATE_ATTR is implicitly unset in
2849 // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do
2850 // not need to send the expensive RES_FMT_CHG in Insert.
2851 CallChgLnk();
2852 if( nWhich == RES_OBJECTDYING )
2854 EndListeningAll();
2859 /** Does the current cursor create a selection?
2861 This means checking if GetMark is set and if SPoint and GetMark differ.
2863 bool SwCursorShell::HasSelection() const
2865 const SwPaM* pCursor = getShellCursor( true );
2866 return IsTableMode()
2867 || (pCursor->HasMark() &&
2868 (*pCursor->GetPoint() != *pCursor->GetMark()
2869 || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End())));
2872 void SwCursorShell::CallChgLnk()
2874 // Do not make any call in StartAction/EndAction but just set the flag.
2875 // This will be handled in EndAction.
2876 if (ActionPend())
2877 m_bChgCallFlag = true; // remember change
2878 else if( m_aChgLnk.IsSet() )
2880 if( m_bCallChgLnk )
2881 m_aChgLnk.Call(nullptr);
2882 m_bChgCallFlag = false; // reset flag
2886 /// get selected text of a node at current cursor
2887 OUString SwCursorShell::GetSelText() const
2889 OUString aText;
2890 if (GetLayout()->HasMergedParas())
2892 SwContentFrame const*const pFrame(GetCurrFrame(false));
2893 if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->GetNodeIndex()))
2895 OUStringBuffer buf;
2896 SwPosition const*const pStart(m_pCurrentCursor->Start());
2897 SwPosition const*const pEnd(m_pCurrentCursor->End());
2898 for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i)
2900 SwNode const& rNode(*pStart->GetNodes()[i]);
2901 assert(!rNode.IsEndNode());
2902 if (rNode.IsStartNode())
2904 i = rNode.EndOfSectionIndex();
2906 else if (rNode.IsTextNode())
2908 sal_Int32 const nStart(i == pStart->GetNodeIndex()
2909 ? pStart->GetContentIndex()
2910 : 0);
2911 sal_Int32 const nEnd(i == pEnd->GetNodeIndex()
2912 ? pEnd->GetContentIndex()
2913 : rNode.GetTextNode()->Len());
2914 buf.append(rNode.GetTextNode()->GetExpandText(
2915 GetLayout(),
2916 nStart, nEnd - nStart, false, false, false,
2917 ExpandMode::HideDeletions));
2921 aText = buf.makeStringAndClear();
2924 else if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
2925 m_pCurrentCursor->GetMark()->GetNodeIndex() )
2927 SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode();
2928 if( pTextNd )
2930 const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex();
2931 aText = pTextNd->GetExpandText(GetLayout(), nStt,
2932 m_pCurrentCursor->End()->GetContentIndex() - nStt );
2935 return aText;
2938 /** get the nth character of the current SSelection
2940 @param bEnd Start counting from the end? From start otherwise.
2941 @param nOffset position of the character
2943 sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset )
2945 if( IsTableMode() ) // not possible in table mode
2946 return 0;
2948 const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint()
2949 : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
2950 SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
2951 if( !pTextNd )
2952 return 0;
2954 const sal_Int32 nPos = pPos->GetContentIndex();
2955 const OUString& rStr = pTextNd->GetText();
2956 sal_Unicode cCh = 0;
2958 if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength())
2959 cCh = rStr[nPos + nOffset];
2961 return cCh;
2964 /** extend current SSelection by n characters
2966 @param bEnd Start counting from the end? From start otherwise.
2967 @param nCount Number of characters.
2969 bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount )
2971 if( !m_pCurrentCursor->HasMark() || IsTableMode() )
2972 return false; // no selection
2974 SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
2975 SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
2976 assert(pTextNd);
2978 sal_Int32 nPos = pPos->GetContentIndex();
2979 if( bEnd )
2981 if ((nPos + nCount) <= pTextNd->GetText().getLength())
2982 nPos = nPos + nCount;
2983 else
2984 return false; // not possible
2986 else if( nPos >= nCount )
2987 nPos = nPos - nCount;
2988 else
2989 return false; // not possible anymore
2991 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
2993 pPos->SetContent(nPos) ;
2994 UpdateCursor();
2996 return true;
2999 /** Move visible cursor to given position in document.
3001 @param rPt The position to move the visible cursor to.
3002 @return <false> if SPoint was corrected by the layout.
3004 bool SwCursorShell::SetVisibleCursor( const Point &rPt )
3006 CurrShell aCurr( this );
3007 Point aPt( rPt );
3008 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
3009 SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
3010 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3011 aTmpState.m_bRealHeight = true;
3013 const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ );
3015 SetInFrontOfLabel( false ); // #i27615#
3017 // show only in TextNodes
3018 SwTextNode* pTextNd = aPos.GetNode().GetTextNode();
3019 if( !pTextNd )
3020 return false;
3022 const SwSectionNode* pSectNd = pTextNd->FindSectionNode();
3023 if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() ||
3024 ( !IsReadOnlyAvailable() &&
3025 pSectNd->GetSection().IsProtectFlag())) )
3026 return false;
3028 std::pair<Point, bool> const tmp(aPt, true);
3029 SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp);
3030 if ( Imp()->IsIdleAction() )
3031 pFrame->PrepareCursor();
3032 SwRect aTmp( m_aCharRect );
3034 pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState );
3036 // #i10137#
3037 if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() )
3038 return true;
3040 m_pVisibleCursor->Hide(); // always hide visible cursor
3041 if( IsScrollMDI( this, m_aCharRect ))
3043 MakeVisible( m_aCharRect );
3044 m_pCurrentCursor->Show(nullptr);
3048 if( aTmpState.m_bRealHeight )
3049 m_aCursorHeight = aTmpState.m_aRealHeight;
3050 else
3052 m_aCursorHeight.setX(0);
3053 m_aCursorHeight.setY(m_aCharRect.Height());
3056 m_pVisibleCursor->SetDragCursor();
3057 m_pVisibleCursor->Show(); // show again
3059 return bRet;
3062 SwVisibleCursor* SwCursorShell::GetVisibleCursor() const
3064 return m_pVisibleCursor;
3067 bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const
3069 Point aPt( rPt );
3070 SwPaM aPam( *m_pCurrentCursor->GetPoint() );
3071 GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt );
3072 // form view
3073 return aPam.HasReadonlySel(GetViewOptions()->IsFormView(), false);
3076 /** Get the number of elements in the ring of cursors
3078 @param bAll If <false> get only spanned ones (= with selections) (Basic).
3080 sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const
3082 SwPaM* pTmp = GetCursor()->GetNext();
3083 sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() &&
3084 *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0;
3085 while( pTmp != m_pCurrentCursor )
3087 if( bAll || ( pTmp->HasMark() &&
3088 *pTmp->GetPoint() != *pTmp->GetMark()))
3089 ++n;
3090 pTmp = pTmp->GetNext();
3092 return n;
3095 bool SwCursorShell::IsStartOfDoc() const
3097 if( m_pCurrentCursor->GetPoint()->GetContentIndex() )
3098 return false;
3100 // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd)
3101 SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 );
3102 if( !aIdx.GetNode().IsContentNode() )
3103 GetDoc()->GetNodes().GoNext( &aIdx );
3104 return aIdx == m_pCurrentCursor->GetPoint()->GetNode();
3107 bool SwCursorShell::IsEndOfDoc() const
3109 SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 );
3110 SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
3111 if( !pCNd )
3112 pCNd = SwNodes::GoPrevious( &aIdx );
3114 return aIdx == m_pCurrentCursor->GetPoint()->GetNode() && pCNd &&
3115 pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex();
3118 /** Invalidate cursors
3120 Delete all created cursors, set table crsr and last crsr to their TextNode
3121 (or StartNode?). They will then all re-created at the next ::GetCursor() call.
3123 This is needed for Drag&Drop/ Clipboard-paste in tables.
3125 bool SwCursorShell::ParkTableCursor()
3127 if( !m_pTableCursor )
3128 return false;
3130 m_pTableCursor->ParkCursor();
3132 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
3133 delete m_pCurrentCursor->GetNext();
3135 // *always* move cursor's Point and Mark
3136 m_pCurrentCursor->DeleteMark();
3137 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
3139 return true;
3142 void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing )
3144 auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition*
3146 SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing;
3148 // search over the whole ring
3149 bool bGoNext;
3150 do {
3152 if (!pTmp)
3153 break;
3155 auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
3156 // If a SPoint or GetMark are in a cursor area then cancel the old area.
3157 // During comparison keep in mind that End() is outside the area.
3158 if( *pStt <= *pTmpStt )
3160 if( *pEnd > *pTmpStt ||
3161 ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
3162 pTmpDel = pTmp;
3164 else
3165 if( *pStt < *pTmpEnd )
3166 pTmpDel = pTmp;
3168 bGoNext = true;
3169 if (pTmpDel) // is the pam in the range -> delete
3171 bool bDelete = true;
3172 if( *ppDelRing == pTmpDel )
3174 if( *ppDelRing == m_pCurrentCursor )
3176 bDelete = GoNextCursor();
3177 if( bDelete )
3179 bGoNext = false;
3180 pTmp = pTmp->GetNext();
3183 else
3184 bDelete = false; // never delete the StackCursor
3187 if( bDelete )
3189 if (pTmp == pTmpDel)
3190 pTmp = nullptr;
3191 delete pTmpDel; // invalidate old area
3193 else
3195 pTmpDel->GetPoint()->Assign(SwNodeOffset(0));
3196 pTmpDel->DeleteMark();
3198 pTmpDel = nullptr;
3200 if( bGoNext && pTmp )
3201 pTmp = pTmp->GetNext();
3203 } while( !bGoNext || *ppDelRing != pTmp );
3206 //TODO: provide documentation
3207 /** Remove selections and additional cursors of all shells.
3209 The remaining cursor of the shell is parked.
3211 @param rIdx ???
3213 void SwCursorShell::ParkCursor( const SwNode &rIdx )
3215 const SwNode *pNode = &rIdx;
3217 // create a new PaM
3218 SwPaM aNew( *GetCursor()->GetPoint() );
3219 if( pNode->GetStartNode() )
3221 pNode = pNode->StartOfSectionNode();
3222 if( pNode->IsTableNode() )
3224 // the given node is in a table, thus park cursor to table node
3225 // (outside of the table)
3226 aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
3228 else
3229 // Also on the start node itself. Then we need to request the start
3230 // node always via its end node! (StartOfSelection of StartNode is
3231 // the parent)
3232 aNew.GetPoint()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() );
3234 else
3235 aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
3236 aNew.SetMark();
3237 aNew.GetPoint()->Assign(*pNode->EndOfSectionNode());
3239 // take care of all shells
3240 for(SwViewShell& rTmp : GetRingContainer())
3242 if( auto pSh = dynamic_cast<SwCursorShell *>(&rTmp))
3244 if (pSh->m_pStackCursor)
3245 pSh->ParkPams(&aNew, &pSh->m_pStackCursor);
3247 pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor );
3248 if( pSh->m_pTableCursor )
3250 // set table cursor always to 0 and the current one always to
3251 // the beginning of the table
3252 SwPaM* pTCursor = pSh->GetTableCrs();
3253 SwNode* pTableNd = pTCursor->GetPoint()->GetNode().FindTableNode();
3254 if ( pTableNd )
3256 pTCursor->GetPoint()->Assign(SwNodeOffset(0));
3257 pTCursor->DeleteMark();
3258 pSh->m_pCurrentCursor->GetPoint()->Assign( *pTableNd );
3265 /** Copy constructor
3267 Copy cursor position and add it to the ring.
3268 All views of a document are in the ring of the shell.
3270 SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin )
3271 : SwViewShell( rShell, pInitWin )
3272 , sw::BroadcastingModify()
3273 , m_pStackCursor( nullptr )
3274 , m_pBlockCursor( nullptr )
3275 , m_pTableCursor( nullptr )
3276 , m_pBoxIdx( nullptr )
3277 , m_pBoxPtr( nullptr )
3278 , m_nUpDownX(0)
3279 , m_nLeftFramePos(0)
3280 , m_nCurrentNode(0)
3281 , m_nCurrentContent(0)
3282 , m_nCurrentNdTyp(SwNodeType::NONE)
3283 , m_nCursorMove( 0 )
3284 , m_eMvState( CursorMoveState::NONE )
3285 , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
3286 , m_nMarkedListLevel( 0 )
3287 , m_oldColFrame(nullptr)
3289 CurrShell aCurr( this );
3290 // only keep the position of the current cursor of the copy shell
3291 m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) );
3292 m_pCurrentCursor->GetPointContentNode()->Add( this );
3294 m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
3295 m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
3296 m_bOverwriteCursor = false;
3297 m_bSendAccessibleCursorEvents = true;
3298 m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
3299 m_bSVCursorVis = true;
3300 m_bSetCursorInReadOnly = true;
3301 m_pVisibleCursor = new SwVisibleCursor( this );
3302 m_bMacroExecAllowed = rShell.IsMacroExecAllowed();
3305 /// default constructor
3306 SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin,
3307 const SwViewOption *pInitOpt )
3308 : SwViewShell( rDoc, pInitWin, pInitOpt )
3309 , sw::BroadcastingModify()
3310 , m_pStackCursor( nullptr )
3311 , m_pBlockCursor( nullptr )
3312 , m_pTableCursor( nullptr )
3313 , m_pBoxIdx( nullptr )
3314 , m_pBoxPtr( nullptr )
3315 , m_nUpDownX(0)
3316 , m_nLeftFramePos(0)
3317 , m_nCurrentNode(0)
3318 , m_nCurrentContent(0)
3319 , m_nCurrentNdTyp(SwNodeType::NONE)
3320 , m_nCursorMove( 0 )
3321 , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint
3322 , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
3323 , m_nMarkedListLevel( 0 )
3324 , m_oldColFrame(nullptr)
3326 CurrShell aCurr( this );
3327 // create initial cursor and set it to first content position
3328 SwNodes& rNds = rDoc.GetNodes();
3330 SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() );
3331 SwContentNode* pCNd = rNds.GoNext( &aNodeIdx ); // go to the first ContentNode
3333 m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, pCNd, 0 ) );
3335 // Register shell as dependent at current node. As a result all attribute
3336 // changes can be forwarded via the Link.
3337 pCNd->Add( this );
3339 m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
3340 m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
3341 m_bOverwriteCursor = false;
3342 m_bSendAccessibleCursorEvents = true;
3343 m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
3344 m_bSVCursorVis = true;
3345 m_bSetCursorInReadOnly = true;
3347 m_pVisibleCursor = new SwVisibleCursor( this );
3348 m_bMacroExecAllowed = true;
3351 SwCursorShell::~SwCursorShell()
3353 // if it is not the last view then at least the field should be updated
3354 if( !unique() )
3355 CheckTableBoxContent( m_pCurrentCursor->GetPoint() );
3356 else
3357 ClearTableBoxContent();
3359 delete m_pVisibleCursor;
3360 delete m_pBlockCursor;
3361 delete m_pTableCursor;
3363 // release cursors
3364 while(m_pCurrentCursor->GetNext() != m_pCurrentCursor)
3365 delete m_pCurrentCursor->GetNext();
3366 delete m_pCurrentCursor;
3368 // free stack
3369 if (m_pStackCursor)
3371 while (m_pStackCursor->GetNext() != m_pStackCursor)
3372 delete m_pStackCursor->GetNext();
3373 delete m_pStackCursor;
3376 // #i54025# - do not give a HTML parser that might potentially hang as
3377 // a client at the cursor shell the chance to hang itself on a TextNode
3378 EndListeningAll();
3381 SwShellCursor* SwCursorShell::getShellCursor( bool bBlock )
3383 if( m_pTableCursor )
3384 return m_pTableCursor;
3385 if( m_pBlockCursor && bBlock )
3386 return &m_pBlockCursor->getShellCursor();
3387 return m_pCurrentCursor;
3390 /** Should WaitPtr be switched on for the clipboard?
3392 Wait for TableMode, multiple selections and more than x selected paragraphs.
3394 bool SwCursorShell::ShouldWait() const
3396 if ( IsTableMode() || GetCursorCnt() > 1 )
3397 return true;
3399 if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() )
3400 return true;
3402 SwPaM* pPam = GetCursor();
3403 return pPam->Start()->GetNodeIndex() + SwNodeOffset(10) <
3404 pPam->End()->GetNodeIndex();
3407 size_t SwCursorShell::UpdateTableSelBoxes()
3409 if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount()))
3411 GetLayout()->MakeTableCursors( *m_pTableCursor );
3413 return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0;
3416 /// show the current selected "object"
3417 void SwCursorShell::MakeSelVisible()
3419 OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" );
3420 if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() )
3422 SwRect aTmp( m_aCharRect );
3423 tools::Long nDiff = m_aCharRect.Height() - VisArea().Height();
3424 if( nDiff < m_aCursorHeight.getX() )
3425 aTmp.Top( nDiff + m_aCharRect.Top() );
3426 else
3428 aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() );
3429 aTmp.Height( m_aCursorHeight.getY() );
3431 if( !aTmp.HasArea() )
3433 aTmp.AddHeight(1 );
3434 aTmp.AddWidth(1 );
3436 MakeVisible( aTmp );
3438 else
3440 if( m_aCharRect.HasArea() )
3441 MakeVisible( m_aCharRect );
3442 else
3444 SwRect aTmp( m_aCharRect );
3445 aTmp.AddHeight(1 );
3446 aTmp.AddWidth(1 );
3447 MakeVisible( aTmp );
3452 /// search a valid content position (not protected/hidden)
3453 bool SwCursorShell::FindValidContentNode( bool bOnlyText )
3455 if( m_pTableCursor )
3457 assert(!"Did not remove table selection!");
3458 return false;
3461 // #i45129# - everything is allowed in UI-readonly
3462 if( !m_bAllProtect && GetDoc()->GetDocShell() &&
3463 GetDoc()->GetDocShell()->IsReadOnlyUI() )
3464 return true;
3466 if( m_pCurrentCursor->HasMark() )
3467 ClearMark();
3469 // first check for frames
3470 SwPosition& rNdPos = *m_pCurrentCursor->GetPoint();
3471 SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup
3472 SwNodes& rNds = mxDoc->GetNodes();
3473 SwContentNode* pCNd = rNdPos.GetNode().GetContentNode();
3474 const SwContentFrame * pFrame;
3476 if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) &&
3477 !IsReadOnlyAvailable() && pFrame->IsProtected() &&
3478 nNdIdx < rNds.GetEndOfExtras().GetIndex() )
3480 // skip protected frame
3481 SwPaM aPam( *m_pCurrentCursor->GetPoint() );
3482 aPam.SetMark();
3483 aPam.GetMark()->Assign( rNds.GetEndOfContent() );
3484 aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() );
3486 bool bFirst = false;
3487 if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward )))
3489 aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() );
3490 pCNd = ::GetNode( aPam, bFirst, fnMoveBackward );
3493 if( !pCNd ) // should *never* happen
3495 rNdPos.Assign(nNdIdx); // back to old node
3496 return false;
3498 *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
3500 else if( bOnlyText && pCNd && pCNd->IsNoTextNode() )
3502 // set to beginning of document
3503 rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() );
3504 mxDoc->GetNodes().GoNext( &rNdPos );
3505 nNdIdx = rNdPos.GetNodeIndex();
3508 bool bOk = true;
3510 // #i9059# cursor may not stand in protected cells
3511 // (unless cursor in protected areas is OK.)
3512 const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode();
3513 if( !IsReadOnlyAvailable() &&
3514 pTableNode != nullptr && rNdPos.GetNode().IsProtect() )
3516 // we're in a table, and we're in a protected area, so we're
3517 // probably in a protected cell.
3519 // move forward into non-protected area.
3520 SwPaM aPam( rNdPos.GetNode(), 0 );
3521 while( aPam.GetPointNode().IsProtect() &&
3522 aPam.Move( fnMoveForward, GoInContent ) )
3523 ; // nothing to do in the loop; the aPam.Move does the moving!
3525 // didn't work? then go backwards!
3526 if( aPam.GetPointNode().IsProtect() )
3528 SwPaM aTmpPaM( rNdPos.GetNode(), 0 );
3529 aPam = aTmpPaM;
3530 while( aPam.GetPointNode().IsProtect() &&
3531 aPam.Move( fnMoveBackward, GoInContent ) )
3532 ; // nothing to do in the loop; the aPam.Move does the moving!
3535 // if we're successful, set the new position
3536 if( ! aPam.GetPointNode().IsProtect() )
3538 *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
3542 // in a protected frame
3543 const SwSectionNode* pSectNd = rNdPos.GetNode().FindSectionNode();
3544 if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
3545 ( !IsReadOnlyAvailable() &&
3546 pSectNd->GetSection().IsProtectFlag() )) )
3548 bOk = false;
3549 bool bGoNextSection = true;
3550 for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt )
3552 bool bContinue;
3553 do {
3554 bContinue = false;
3555 for (;;)
3557 if (bGoNextSection)
3558 pCNd = rNds.GoNextSection( &rNdPos,
3559 true, !IsReadOnlyAvailable() );
3560 else
3561 pCNd = SwNodes::GoPrevSection( &rNdPos,
3562 true, !IsReadOnlyAvailable() );
3563 if ( pCNd == nullptr) break;
3564 // moved inside a table -> check if it is protected
3565 if( pCNd->FindTableNode() )
3567 SwCallLink aTmp( *this );
3568 SwCursorSaveState aSaveState( *m_pCurrentCursor );
3569 aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR
3570 if( !m_pCurrentCursor->IsInProtectTable( true ) )
3572 const SwSectionNode* pSNd = pCNd->FindSectionNode();
3573 if( !pSNd || !pSNd->GetSection().IsHiddenFlag()
3574 || (!IsReadOnlyAvailable() &&
3575 pSNd->GetSection().IsProtectFlag() ))
3577 bOk = true;
3578 break; // found non-protected cell
3580 continue; // continue search
3583 else
3585 bOk = true;
3586 break; // found non-protected cell
3590 if( bOk && rNdPos.GetNodeIndex() < rNds.GetEndOfExtras().GetIndex() )
3592 // also check for Fly - might be protected as well
3593 pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr);
3594 if (nullptr == pFrame ||
3595 ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) ||
3596 ( bOnlyText && pCNd->IsNoTextNode() ) )
3598 // continue search
3599 bOk = false;
3600 bContinue = true;
3603 } while( bContinue );
3605 if( !bOk )
3607 if( !nLoopCnt )
3608 bGoNextSection = false;
3609 rNdPos.Assign( nNdIdx );
3613 if( bOk )
3615 pCNd = rNdPos.GetNode().GetContentNode();
3616 const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0;
3617 m_pCurrentCursor->GetPoint()->SetContent( nContent );
3619 else
3621 pCNd = rNdPos.GetNode().GetContentNode();
3622 // if cursor in hidden frame, always move it
3623 if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr))
3625 SwCursorMoveState aTmpState( CursorMoveState::NONE );
3626 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3627 GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(),
3628 &aTmpState );
3631 return bOk;
3634 bool SwCursorShell::IsCursorReadonly() const
3636 if ( GetViewOptions()->IsReadonly() ||
3637 GetViewOptions()->IsFormView() /* Formula view */ )
3639 SwFrame *pFrame = GetCurrFrame( false );
3640 const SwFlyFrame* pFly;
3641 const SwSection* pSection;
3643 if( pFrame && pFrame->IsInFly() &&
3644 (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
3645 pFly->Lower() &&
3646 !pFly->Lower()->IsNoTextFrame() &&
3647 !GetDrawView()->GetMarkedObjectList().GetMarkCount() )
3649 return false;
3651 // edit in readonly sections
3652 else if ( pFrame && pFrame->IsInSct() &&
3653 nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) &&
3654 pSection->IsEditInReadonlyFlag() )
3656 return false;
3658 else if ( !IsMultiSelection() && CursorInsideInputField() )
3660 return false;
3663 return true;
3665 return false;
3668 /// is the cursor allowed to enter ReadOnly sections?
3669 void SwCursorShell::SetReadOnlyAvailable( bool bFlag )
3671 // *never* switch in GlobalDoc
3672 if( (!GetDoc()->GetDocShell() ||
3673 dynamic_cast<const SwGlobalDocShell*>(GetDoc()->GetDocShell()) == nullptr ) &&
3674 bFlag != m_bSetCursorInReadOnly )
3676 // If the flag is switched off then all selections need to be
3677 // invalidated. Otherwise we would trust that nothing protected is selected.
3678 if( !bFlag )
3680 ClearMark();
3682 m_bSetCursorInReadOnly = bFlag;
3683 UpdateCursor();
3687 bool SwCursorShell::HasReadonlySel(bool const isReplace) const
3689 // Treat selections that span over start or end of paragraph of an outline node
3690 // with folded outline content as read-only.
3691 if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
3693 SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
3694 if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
3695 return true;
3697 bool bRet = false;
3698 // If protected area is to be ignored, then selections are never read-only.
3699 if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() ||
3700 GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) &&
3701 !SwViewOption::IsIgnoreProtectedArea())
3703 if ( m_pTableCursor != nullptr )
3705 // TODO: handling when a table cell (cells) is selected
3706 bRet = m_pTableCursor->HasReadOnlyBoxSel()
3707 || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace);
3709 else
3711 for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
3713 if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace))
3715 bRet = true;
3716 break;
3721 return bRet;
3724 bool SwCursorShell::HasHiddenSections() const
3726 // Treat selections that span over start or end of paragraph of an outline node
3727 // with folded outline content as read-only.
3728 if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
3730 SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
3731 if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
3732 return true;
3734 bool bRet = false;
3736 if ( m_pTableCursor != nullptr )
3738 bRet = m_pTableCursor->HasHiddenBoxSel()
3739 || m_pTableCursor->HasHiddenSections();
3741 else
3743 for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
3745 if (rCursor.HasHiddenSections())
3747 bRet = true;
3748 break;
3753 return bRet;
3756 bool SwCursorShell::IsSelFullPara() const
3758 bool bRet = false;
3760 if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
3761 m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() )
3763 sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex();
3764 sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex();
3765 if( nStt > nEnd )
3766 std::swap( nStt, nEnd );
3767 const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode();
3768 bRet = pCNd && !nStt && nEnd == pCNd->Len();
3770 return bRet;
3773 SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const
3775 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
3776 Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() );
3777 if( pPt )
3779 SwCursorMoveState aTmpState( CursorMoveState::NONE );
3780 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3782 GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState );
3785 return mxDoc->GetTextDirection( aPos, &aPt );
3788 bool SwCursorShell::IsInVerticalText( const Point* pPt ) const
3790 const SvxFrameDirection nDir = GetTextDirection( pPt );
3791 return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir
3792 || nDir == SvxFrameDirection::Vertical_LR_BT;
3795 bool SwCursorShell::IsInRightToLeftText() const
3797 const SvxFrameDirection nDir = GetTextDirection();
3798 // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in
3799 // vertical environment
3800 return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir;
3803 /// If the current cursor position is inside a hidden range true is returned. If bSelect is
3804 /// true, the hidden range is selected. If bSelect is false, the hidden range is not selected.
3805 bool SwCursorShell::IsInHiddenRange(const bool bSelect)
3807 bool bRet = false;
3808 if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() )
3810 SwPosition& rPt = *m_pCurrentCursor->GetPoint();
3811 const SwTextNode* pNode = rPt.GetNode().GetTextNode();
3812 if ( pNode )
3814 const sal_Int32 nPos = rPt.GetContentIndex();
3816 // check if nPos is in hidden range
3817 sal_Int32 nHiddenStart;
3818 sal_Int32 nHiddenEnd;
3819 SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd );
3820 if ( COMPLETE_STRING != nHiddenStart )
3822 if (bSelect)
3824 // make selection:
3825 m_pCurrentCursor->SetMark();
3826 m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd);
3828 bRet = true;
3833 return bRet;
3836 sal_Int32 SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt,
3837 bool bSearchInNotes,
3838 SwDocPositions eStart, SwDocPositions eEnd,
3839 bool& bCancel,
3840 FindRanges eRng,
3841 bool bReplace )
3843 if( m_pTableCursor )
3844 GetCursor();
3845 delete m_pTableCursor;
3846 m_pTableCursor = nullptr;
3847 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3848 sal_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd,
3849 bCancel, eRng, bReplace, GetLayout());
3850 if( nRet || bCancel )
3851 UpdateCursor();
3852 return nRet;
3855 sal_Int32 SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl,
3856 SwDocPositions eStart, SwDocPositions eEnd,
3857 bool& bCancel,
3858 FindRanges eRng,
3859 const SwTextFormatColl* pReplFormat )
3861 if( m_pTableCursor )
3862 GetCursor();
3863 delete m_pTableCursor;
3864 m_pTableCursor = nullptr;
3865 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3866 sal_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng,
3867 pReplFormat );
3868 if( nRet )
3869 UpdateCursor();
3870 return nRet;
3873 sal_Int32 SwCursorShell::FindAttrs( const SfxItemSet& rSet,
3874 bool bNoCollections,
3875 SwDocPositions eStart, SwDocPositions eEnd,
3876 bool& bCancel,
3877 FindRanges eRng,
3878 const i18nutil::SearchOptions2* pSearchOpt,
3879 const SfxItemSet* rReplSet )
3881 if( m_pTableCursor )
3882 GetCursor();
3883 delete m_pTableCursor;
3884 m_pTableCursor = nullptr;
3885 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3886 sal_Int32 nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd,
3887 bCancel, eRng, pSearchOpt, rReplSet, GetLayout());
3888 if( nRet )
3889 UpdateCursor();
3890 return nRet;
3893 void SwCursorShell::SetSelection( const SwPaM& rCursor )
3895 StartAction();
3896 SwCursor* pCursor = GetCursor();
3897 *pCursor->GetPoint() = *rCursor.GetPoint();
3898 if(rCursor.GetNext() != &rCursor)
3900 const SwPaM *_pStartCursor = rCursor.GetNext();
3903 SwPaM* pCurrentCursor = CreateCursor();
3904 *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint();
3905 if(_pStartCursor->HasMark())
3907 pCurrentCursor->SetMark();
3908 *pCurrentCursor->GetMark() = *_pStartCursor->GetMark();
3910 } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor );
3912 // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current
3913 // cursor; therefore set current cursor's mark only after creating all other cursors
3914 if (rCursor.HasMark())
3916 pCursor->SetMark();
3917 *pCursor->GetMark() = *rCursor.GetMark();
3919 EndAction();
3922 static const SwStartNode* lcl_NodeContext( const SwNode& rNode )
3924 const SwStartNode *pRet = rNode.StartOfSectionNode();
3925 while( pRet->IsSectionNode() || pRet->IsTableNode() ||
3926 pRet->GetStartNodeType() == SwTableBoxStartNode )
3928 pRet = pRet->StartOfSectionNode();
3930 return pRet;
3934 Checks if a position is valid. To be valid the position's node must
3935 be a content node and the content must not be unregistered.
3937 @param aPos the position to check.
3939 static bool sw_PosOk(const SwPosition & aPos)
3941 return nullptr != aPos.GetNode().GetContentNode() &&
3942 aPos.GetContentNode();
3946 Checks if a PaM is valid. For a PaM to be valid its point must be
3947 valid. Additionally if the PaM has a mark this has to be valid, too.
3949 @param aPam the PaM to check
3951 static bool lcl_CursorOk(SwPaM & aPam)
3953 return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark()
3954 || sw_PosOk(*aPam.GetMark()));
3957 void SwCursorShell::ClearUpCursors()
3959 // start of the ring
3960 SwPaM * pStartCursor = GetCursor();
3961 // start loop with second entry of the ring
3962 SwPaM * pCursor = pStartCursor->GetNext();
3963 SwPaM * pTmpCursor;
3964 bool bChanged = false;
3966 // For all entries in the ring except the start entry delete the entry if
3967 // it is invalid.
3968 while (pCursor != pStartCursor)
3970 pTmpCursor = pCursor->GetNext();
3971 if ( ! lcl_CursorOk(*pCursor))
3973 delete pCursor;
3974 bChanged = true;
3976 pCursor = pTmpCursor;
3979 if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) )
3981 pStartCursor->DeleteMark();
3982 bChanged = true;
3984 if (pStartCursor->GetPoint()->GetNode().IsTableNode())
3986 // tdf#106959: When cursor points to start of a table, the proper content
3987 // node is the first one inside the table, not the previous one
3988 SwNodes& aNodes = GetDoc()->GetNodes();
3989 SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode());
3990 if (SwNode* pNode = aNodes.GoNext(&aIdx))
3992 SwPaM aTmpPam(*pNode);
3993 *pStartCursor = aTmpPam;
3994 bChanged = true;
3997 if( !sw_PosOk( *pStartCursor->GetPoint() ) )
3999 SwNodes & aNodes = GetDoc()->GetNodes();
4000 const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->GetNode() );
4001 SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() );
4002 SwNode * pNode = SwNodes::GoPrevious(&aIdx);
4003 if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
4005 pNode = aNodes.GoNext( &aIdx );
4006 if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
4008 // If the start entry of the ring is invalid replace it with a
4009 // cursor pointing to the beginning of the first content node in the
4010 // document.
4011 aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode());
4012 pNode = aNodes.GoNext( &aIdx );
4015 bool bFound = (pNode != nullptr);
4017 assert(bFound);
4019 if (bFound)
4021 SwPaM aTmpPam(*pNode);
4022 *pStartCursor = aTmpPam;
4025 bChanged = true;
4028 // If at least one of the cursors in the ring have been deleted or replaced,
4029 // remove the table cursor.
4030 if (m_pTableCursor != nullptr && bChanged)
4031 TableCursorToCursor();
4034 OUString SwCursorShell::GetCursorDescr() const
4036 OUString aResult;
4038 if (IsMultiSelection())
4039 aResult += SwResId(STR_MULTISEL);
4040 else
4041 aResult = SwDoc::GetPaMDescr(*GetCursor());
4043 return aResult;
4046 void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const
4048 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell"));
4050 SwViewShell::dumpAsXml(pWriter);
4052 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor"));
4053 for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer())
4054 rPaM.dumpAsXml(pWriter);
4055 (void)xmlTextWriterEndElement(pWriter);
4057 (void)xmlTextWriterEndElement(pWriter);
4060 static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes,
4061 uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
4062 const SwWrongList& rSmartTagList, sal_Int32 nCurrent )
4064 // Insert smart tag information
4065 std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps;
4067 for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i )
4069 const sal_Int32 nSTPos = rSmartTagList.Pos( i );
4070 const sal_Int32 nSTLen = rSmartTagList.Len( i );
4072 if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen )
4074 const SwWrongArea* pArea = rSmartTagList.GetElement( i );
4075 if ( pArea )
4077 rSmartTagTypes.push_back( pArea->maType );
4078 aStringKeyMaps.push_back( pArea->mxPropertyBag );
4083 if ( !rSmartTagTypes.empty() )
4085 rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps);
4089 static void lcl_FillTextRange( uno::Reference<text::XTextRange>& rRange,
4090 SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen )
4092 // create SwPosition for nStartIndex
4093 SwPosition aStartPos( rNode, nBegin );
4094 // create SwPosition for nEndIndex
4095 SwPosition aEndPos( rNode, nBegin + nLen );
4097 const rtl::Reference<SwXTextRange> xRange =
4098 SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos);
4100 rRange = xRange;
4103 void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes,
4104 uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
4105 uno::Reference< text::XTextRange>& rRange ) const
4107 if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() )
4108 return;
4110 SwPaM* pCursor = GetCursor();
4111 SwPosition aPos( *pCursor->GetPoint() );
4112 SwTextNode *pNode = aPos.GetNode().GetTextNode();
4113 if ( !pNode || pNode->IsInProtectSect() )
4114 return;
4116 const SwWrongList *pSmartTagList = pNode->GetSmartTags();
4117 if ( !pSmartTagList )
4118 return;
4120 sal_Int32 nCurrent = aPos.GetContentIndex();
4121 sal_Int32 nBegin = nCurrent;
4122 sal_Int32 nLen = 1;
4124 if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
4125 return;
4127 const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin );
4128 const SwWrongList* pSubList = pSmartTagList->SubList( nIndex );
4129 if ( pSubList )
4131 pSmartTagList = pSubList;
4132 nCurrent = 0;
4135 lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent );
4136 lcl_FillTextRange( rRange, *pNode, nBegin, nLen );
4139 // see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
4140 void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect )
4142 SwPaM* pCursor = GetCursor();
4143 SwPosition aPos( *pCursor->GetPoint() );
4144 Point aPt( rPt );
4145 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
4146 SwSpecialPos aSpecialPos;
4147 eTmpState.m_pSpecialPos = &aSpecialPos;
4148 SwTextNode *pNode;
4149 const SwWrongList *pSmartTagList;
4151 if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) )
4152 return;
4153 pNode = aPos.GetNode().GetTextNode();
4154 if( !pNode )
4155 return;
4156 pSmartTagList = pNode->GetSmartTags();
4157 if( !pSmartTagList )
4158 return;
4159 if( pNode->IsInProtectSect() )
4160 return;
4162 sal_Int32 nBegin = aPos.GetContentIndex();
4163 sal_Int32 nLen = 1;
4165 if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
4166 return;
4168 // get smarttag word
4169 OUString aText( pNode->GetText().copy(nBegin, nLen) );
4171 //save the start and end positions of the line and the starting point
4172 Push();
4173 LeftMargin();
4174 const sal_Int32 nLineStart = GetCursor()->GetPoint()->GetContentIndex();
4175 RightMargin();
4176 const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex();
4177 Pop(PopMode::DeleteCurrent);
4179 // make sure the selection build later from the data below does not
4180 // include "in word" character to the left and right in order to
4181 // preserve those. Therefore count those "in words" in order to
4182 // modify the selection accordingly.
4183 const sal_Unicode* pChar = aText.getStr();
4184 sal_Int32 nLeft = 0;
4185 while (*pChar++ == CH_TXTATR_INWORD)
4186 ++nLeft;
4187 pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
4188 sal_Int32 nRight = 0;
4189 while (pChar && *pChar-- == CH_TXTATR_INWORD)
4190 ++nRight;
4192 aPos.SetContent( nBegin + nLeft );
4193 pCursor = GetCursor();
4194 *pCursor->GetPoint() = aPos;
4195 pCursor->SetMark();
4196 ExtendSelection( true, nLen - nLeft - nRight );
4197 // do not determine the rectangle in the current line
4198 const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
4199 // take one less than the line end - otherwise the next line would
4200 // be calculated
4201 const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd);
4202 Push();
4203 pCursor->DeleteMark();
4204 SwPosition& rPos = *GetCursor()->GetPoint();
4205 rPos.SetContent( nWordStart );
4206 SwRect aStartRect;
4207 SwCursorMoveState aState;
4208 aState.m_bRealWidth = true;
4209 SwContentNode* pContentNode = pCursor->GetPointContentNode();
4210 std::pair<Point, bool> const tmp(rPt, false);
4211 SwContentFrame *pContentFrame = pContentNode->getLayoutFrame(
4212 GetLayout(), pCursor->GetPoint(), &tmp);
4214 pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
4215 rPos.SetContent( nWordEnd - 1 );
4216 SwRect aEndRect;
4217 pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
4218 rSelectRect = aStartRect.Union( aEndRect );
4219 Pop(PopMode::DeleteCurrent);
4222 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */