1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <config_wasm_strip.h>
21
22 #include <com/sun/star/text/XTextRange.hpp>
23
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>
81 #include <boost/property_tree/ptree.hpp>
82
83 using namespace com::sun::star;
84
85 /**
86 * Check if pCurrentCursor points into already existing ranges and delete those.
87 * @param Pointer to SwCursor object
88 */
CheckRange(SwCursor * pCurrentCursor)89 static void CheckRange( SwCursor* pCurrentCursor )
90 {
91 auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition*
92
93 SwPaM *pTmpDel = nullptr,
94 *pTmp = pCurrentCursor->GetNext();
95
96 // Search the complete ring
97 while( pTmp != pCurrentCursor )
98 {
99 auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
100 if( *pStt <= *pTmpStt )
101 {
102 if( *pEnd > *pTmpStt ||
103 ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
104 pTmpDel = pTmp;
105 }
106 else
107 if( *pStt < *pTmpEnd )
108 pTmpDel = pTmp;
109
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;
115 }
116 }
117
118 // SwCursorShell
119
120 /**
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.
123 */
CreateCursor()124 SwPaM * SwCursorShell::CreateCursor()
125 {
126 // don't create new Cursor with active table Selection
127 assert(!IsTableMode());
128
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();
132
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 );
136
137 // Hide PaM logically, to avoid undoing the inverting from
138 // copied PaM (#i75172#)
139 pNew->swapContent(*m_pCurrentCursor);
140
141 m_pCurrentCursor->DeleteMark();
142
143 UpdateCursor( SwCursorShell::SCROLLWIN );
144 return pNew;
145 }
146
147 /**
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.
151 */
DestroyCursor()152 void SwCursorShell::DestroyCursor()
153 {
154 // don't delete Cursor with active table Selection
155 assert(!IsTableMode());
156
157 // Is there a next one? Don't do anything if not.
158 if(!m_pCurrentCursor->IsMultiSelection())
159 return;
160
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();
166 }
167
168 /**
169 * Create and return a new shell cursor.
170 * Simply returns the current shell cursor if there is no selection
171 * (HasSelection()).
172 */
CreateNewShellCursor()173 SwCursor & SwCursorShell::CreateNewShellCursor()
174 {
175 if (HasSelection())
176 {
177 (void) CreateCursor(); // n.b. returns old cursor
178 }
179 return *GetCursor();
180 }
181
182 /**
183 * Return the current shell cursor
184 * @return - returns current `SwPaM` shell cursor
185 */
GetCurrentShellCursor()186 SwCursor & SwCursorShell::GetCurrentShellCursor()
187 {
188 return *GetCursor();
189 }
190
191 /**
192 * Return pointer to the current shell cursor
193 * @return - returns pointer to current `SwCursor` shell cursor
194 */
GetCursor(bool bMakeTableCursor) const195 SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const
196 {
197 if( m_pTableCursor )
198 {
199 if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() )
200 {
201 //don't re-create 'parked' cursors
202 if( m_pTableCursor->GetPoint()->GetNodeIndex() &&
203 m_pTableCursor->GetMark()->GetNodeIndex() )
204 {
205 const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode();
206 if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
207 {
208 pCNd = m_pTableCursor->GetMarkContentNode();
209 if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
210 {
211 SwShellTableCursor* pTC = m_pTableCursor;
212 GetLayout()->MakeTableCursors( *pTC );
213 }
214 }
215 }
216 }
217
218 if( m_pTableCursor->IsChgd() )
219 {
220 const_cast<SwCursorShell*>(this)->m_pCurrentCursor =
221 dynamic_cast<SwShellCursor*>(m_pTableCursor->MakeBoxSels( m_pCurrentCursor ));
222 }
223 }
224 return m_pCurrentCursor;
225 }
226
StartAction()227 void SwCursorShell::StartAction()
228 {
229 if( !ActionPend() )
230 {
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;
240 }
241 SwViewShell::StartAction(); // to the SwViewShell
242 }
243
EndAction(const bool bIdleEnd)244 void SwCursorShell::EndAction( const bool bIdleEnd )
245 {
246 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
247 bool bVis = m_bSVCursorVis;
248
249 // Idle-formatting?
250 if( bIdleEnd && Imp()->HasPaintRegion() )
251 {
252 m_pCurrentCursor->Hide();
253 }
254
255 // Update all invalid numberings before the last action
256 if( 1 == mnStartAction )
257 GetDoc()->UpdateNumRule();
258
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;
263
264 SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first
265
266 m_bSVCursorVis = bSavSVCursorVis;
267
268 if( ActionPend() )
269 {
270 if( bVis ) // display SV-Cursor again
271 m_pVisibleCursor->Show();
272
273 return;
274 }
275
276 sal_uInt16 eFlags = SwCursorShell::CHKRANGE;
277 if ( !bIdleEnd )
278 eFlags |= SwCursorShell::SCROLLWIN;
279
280 UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes
281
282 {
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;
288
289 if( !m_nCursorMove ||
290 ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) )
291 // display Cursor & Selections again
292 ShowCursors( m_bSVCursorVis );
293 }
294 // call ChgCall if there is still one
295 if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() )
296 {
297 m_aChgLnk.Call(nullptr);
298 m_bChgCallFlag = false; // reset flag
299 }
300 }
301
SttCursorMove()302 void SwCursorShell::SttCursorMove()
303 {
304 #ifdef DBG_UTIL
305 OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." );
306 #endif
307 ++m_nCursorMove;
308 StartAction();
309 }
310
EndCursorMove(const bool bIdleEnd)311 void SwCursorShell::EndCursorMove( const bool bIdleEnd )
312 {
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
322 }
323
LeftRight(bool bLeft,sal_uInt16 nCnt,SwCursorSkipMode nMode,bool bVisualAllowed)324 bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
325 bool bVisualAllowed )
326 {
327 if( IsTableMode() )
328 return bLeft ? GoPrevCell() : GoNextCell();
329
330 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
331 bool bRet = false;
332
333 // #i27615# Handle cursor in front of label.
334 const SwTextNode* pTextNd = nullptr;
335
336 if( m_pBlockCursor )
337 m_pBlockCursor->clearPoints();
338
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() )
343 {
344 SetInFrontOfLabel( false );
345 bRet = true;
346 }
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())
358 {
359 SetInFrontOfLabel( true );
360 bRet = true;
361 }
362 // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag:
363 else
364 {
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 )
375 {
376 // undo reset of <bInFrontOfLabel> flag
377 SetInFrontOfLabel( true );
378 }
379 }
380
381 if( bRet )
382 {
383 UpdateCursor();
384 }
385
386 return bRet;
387 }
388
MarkListLevel(const OUString & sListId,const int nListLevel)389 void SwCursorShell::MarkListLevel( const OUString& sListId,
390 const int nListLevel )
391 {
392 if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel)
393 return;
394
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)
401 {
402 if ( !m_sMarkedListId.isEmpty() )
403 mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false );
404
405 if ( !sListId.isEmpty() )
406 mxDoc->MarkListLevel( sListId, nListLevel, true );
407 }
408
409 m_sMarkedListId = sListId;
410 m_nMarkedListLevel = nListLevel;
411 }
412
UpdateMarkedListLevel()413 void SwCursorShell::UpdateMarkedListLevel()
414 {
415 SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(),
416 GetCursor_()->GetPoint()->GetNode());
417
418 if ( !pTextNd )
419 return;
420
421 if (!pTextNd->IsNumbered(GetLayout()))
422 {
423 m_pCurrentCursor->SetInFrontOfLabel_( false );
424 MarkListLevel( OUString(), 0 );
425 }
426 else if ( m_pCurrentCursor->IsInFrontOfLabel() )
427 {
428 if ( pTextNd->IsInList() )
429 {
430 assert(pTextNd->GetActualListLevel() >= 0 &&
431 pTextNd->GetActualListLevel() < MAXLEVEL);
432 MarkListLevel( pTextNd->GetListId(),
433 pTextNd->GetActualListLevel() );
434 }
435 }
436 else
437 {
438 MarkListLevel( OUString(), 0 );
439 }
440 }
441
FirePageChangeEvent(sal_uInt16 nOldPage,sal_uInt16 nNewPage)442 void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage)
443 {
444 #ifdef ACCESSIBLE_LAYOUT
445 if( Imp()->IsAccessible() )
446 Imp()->FirePageChangeEvent( nOldPage, nNewPage );
447 #else
448 (void)nOldPage;
449 (void)nNewPage;
450 #endif
451 }
452
FireColumnChangeEvent(sal_uInt16 nOldColumn,sal_uInt16 nNewColumn)453 void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn)
454 {
455 #ifdef ACCESSIBLE_LAYOUT
456 if( Imp()->IsAccessible() )
457 Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn);
458 #else
459 (void)nOldColumn;
460 (void)nNewColumn;
461 #endif
462 }
463
FireSectionChangeEvent(sal_uInt16 nOldSection,sal_uInt16 nNewSection)464 void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection)
465 {
466 #ifdef ACCESSIBLE_LAYOUT
467 if( Imp()->IsAccessible() )
468 Imp()->FireSectionChangeEvent( nOldSection, nNewSection );
469 #else
470 (void)nOldSection;
471 (void)nNewSection;
472 #endif
473 }
474
bColumnChange()475 bool SwCursorShell::bColumnChange()
476 {
477 SwFrame* pCurrFrame = GetCurrFrame(false);
478
479 if (pCurrFrame == nullptr)
480 {
481 return false;
482 }
483
484 SwFrame* pCurrCol=pCurrFrame->FindColFrame();
485
486 while(pCurrCol== nullptr && pCurrFrame!=nullptr )
487 {
488 SwLayoutFrame* pParent = pCurrFrame->GetUpper();
489 if(pParent!=nullptr)
490 {
491 pCurrCol=static_cast<SwFrame*>(pParent)->FindColFrame();
492 pCurrFrame = pParent;
493 }
494 else
495 {
496 break;
497 }
498 }
499
500 if(m_oldColFrame == pCurrCol)
501 return false;
502 else
503 {
504 m_oldColFrame = pCurrCol;
505 return true;
506 }
507 }
508
UpDown(bool bUp,sal_uInt16 nCnt)509 bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt )
510 {
511 CurrShell aCurr( this );
512 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
513
514 bool bTableMode = IsTableMode();
515 SwShellCursor* pTmpCursor = getShellCursor( true );
516
517 bool bRet = pTmpCursor->UpDown( bUp, nCnt );
518 // #i40019# UpDown should always reset the bInFrontOfLabel flag:
519 bRet |= SetInFrontOfLabel(false);
520
521 if( m_pBlockCursor )
522 m_pBlockCursor->clearPoints();
523
524 if( bRet )
525 {
526 m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint
527 if( !ActionPend() )
528 {
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) );
534 }
535 }
536 return bRet;
537 }
538
LRMargin(bool bLeft,bool bAPI)539 bool SwCursorShell::LRMargin( bool bLeft, bool bAPI)
540 {
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
544
545 const bool bTableMode = IsTableMode();
546 SwShellCursor* pTmpCursor = getShellCursor( true );
547
548 if( m_pBlockCursor )
549 m_pBlockCursor->clearPoints();
550
551 const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI);
552
553 bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI);
554
555 if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() )
556 {
557 const SwTextNode * pTextNd = GetCursor_()->GetPointNode().GetTextNode();
558 assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == pTextNd);
559 if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() )
560 SetInFrontOfLabel( true );
561 }
562 else if ( !bLeft )
563 {
564 bRet = SetInFrontOfLabel( false ) || bRet;
565 }
566
567 if( bRet )
568 {
569 UpdateCursor();
570 }
571 return bRet;
572 }
573
IsAtLRMargin(bool bLeft,bool bAPI) const574 bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const
575 {
576 const SwShellCursor* pTmpCursor = getShellCursor( true );
577 return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI);
578 }
579
SttEndDoc(bool bStt)580 bool SwCursorShell::SttEndDoc( bool bStt )
581 {
582 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
583
584 SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor;
585 bool bRet = pTmpCursor->SttEndDoc( bStt );
586 if( bRet )
587 {
588 if( bStt )
589 pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header)
590 if( m_pBlockCursor )
591 {
592 m_pBlockCursor->clearPoints();
593 RefreshBlockCursor();
594 }
595
596 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
597 }
598 return bRet;
599 }
600
IsCursorInTable() const601 const SwTableNode* SwCursorShell::IsCursorInTable() const
602 {
603 if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount())
604 { // find the table that has the selected boxes
605 return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode();
606 }
607 return m_pCurrentCursor->GetPointNode().FindTableNode();
608 }
609
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
627
MoveOutOfTable()628 bool SwCursorShell::MoveOutOfTable()
629 {
630 SwPosition const point(*getShellCursor(false)->GetPoint());
631 SwPosition const mark(*getShellCursor(false)->GetMark());
632
633 for (auto const fnMove : {&fnMoveBackward, &fnMoveForward})
634 {
635 Push();
636 SwCursor *const pCursor(getShellCursor(false));
637
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))
643 {
644 SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode());
645 if (!pBox)
646 {
647 Pop(SwCursorShell::PopMode::DeleteStack);
648 return true; // moved to paragraph at top-level of text
649 }
650 if (pBox->GetIndex() < pTable->GetIndex()
651 && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex())
652 {
653 Pop(SwCursorShell::PopMode::DeleteStack);
654 return true; // pBox contains start position (pTable)
655 }
656 }
657
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;
663 }
664 return false;
665 }
666
TrySelectOuterTable()667 bool SwCursorShell::TrySelectOuterTable()
668 {
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)
674 {
675 return false;
676 }
677
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 = SwNodes::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
695
696 // this will update m_pCurrentCursor based on m_pTableCursor
697 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
698
699 return true;
700 }
701
702 /// find XText start node
FindTextStart(SwPosition const & rPos)703 static SwStartNode const* FindTextStart(SwPosition const& rPos)
704 {
705 SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode());
706 while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode()))
707 {
708 pStartNode = pStartNode->StartOfSectionNode();
709 }
710 return pStartNode;
711 }
712
FindParentText(SwShellCursor const & rCursor)713 static SwStartNode const* FindParentText(SwShellCursor const& rCursor)
714 {
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())
719 {
720 pStartNode = pStartNode->StartOfSectionNode();
721 }
722 while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex())
723 {
724 pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode();
725 }
726 assert(pStartNode->EndOfSectionNode() == pEndNode);
727
728 return (pStartNode->IsSectionNode() || pStartNode->IsTableNode())
729 ? FindTextStart(SwPosition(*pStartNode))
730 : pStartNode;
731 }
732
MoveStartText()733 bool SwCursorShell::MoveStartText()
734 {
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 SwNodes::GoNext(m_pCurrentCursor->GetPoint());
741 while (auto* pFoundTable = m_pCurrentCursor->GetPoint()->GetNode().FindTableNode())
742 {
743 if (pFoundTable == pTable)
744 break;
745 if (pTable && pTable->GetIndex() >= pFoundTable->GetIndex())
746 break;
747 if (!MoveOutOfTable())
748 break;
749 }
750 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
751 return old != *m_pCurrentCursor->GetPoint();
752 }
753
754 // select all inside the current XText, with table or hidden para at start/end
ExtendedSelectAll(bool bFootnotes)755 void SwCursorShell::ExtendedSelectAll(bool bFootnotes)
756 {
757 // find common ancestor node of both ends of cursor
758 SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
759 assert(pStartNode);
760 if (IsTableMode())
761 { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode
762 TableCursorToCursor();
763 }
764 SwNodes& rNodes = GetDoc()->GetNodes();
765 m_pCurrentCursor->Normalize(true);
766 SwPosition* pPos = m_pCurrentCursor->GetPoint();
767 pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode));
768 SwNodes::GoNext(pPos);
769 pPos = m_pCurrentCursor->GetMark();
770 pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode()));
771 SwContentNode* pCNd = SwNodes::GoPrevious( pPos );
772 if (pCNd)
773 pPos->AssignEndIndex(*pCNd);
774 }
775
StartsWith(SwStartNode const & rStart)776 static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart)
777 {
778 for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i)
779 {
780 SwNode const& rNode(*rStart.GetNodes()[i]);
781 switch (rNode.GetNodeType())
782 {
783 case SwNodeType::Section:
784 if (rNode.GetSectionNode()->GetSection().IsHidden())
785 return SwCursorShell::StartsWith::HiddenSection;
786 continue;
787 case SwNodeType::Table:
788 return SwCursorShell::StartsWith::Table;
789 case SwNodeType::Text:
790 if (rNode.GetTextNode()->IsHidden())
791 {
792 return SwCursorShell::StartsWith::HiddenPara;
793 }
794 return SwCursorShell::StartsWith::None;
795 default:
796 return SwCursorShell::StartsWith::None;
797 }
798 }
799 return SwCursorShell::StartsWith::None;
800 }
801
EndsWith(SwStartNode const & rStart)802 static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart)
803 {
804 for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i)
805 {
806 SwNode const& rNode(*rStart.GetNodes()[i]);
807 switch (rNode.GetNodeType())
808 {
809 case SwNodeType::End:
810 if (auto pStartNode = rNode.StartOfSectionNode(); pStartNode->IsTableNode())
811 {
812 return SwCursorShell::StartsWith::Table;
813 }
814 else if (pStartNode->IsSectionNode())
815 {
816 if (pStartNode->GetSectionNode()->GetSection().IsHidden())
817 return SwCursorShell::StartsWith::HiddenSection;
818 }
819 //TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode());
820 break;
821 case SwNodeType::Text:
822 if (rNode.GetTextNode()->IsHidden())
823 {
824 return SwCursorShell::StartsWith::HiddenPara;
825 }
826 return SwCursorShell::StartsWith::None;
827 default:
828 return SwCursorShell::StartsWith::None;
829 }
830 }
831 return SwCursorShell::StartsWith::None;
832 }
833
834 // return the node that is the start of the extended selection (to include table
835 // or section start nodes; looks like extending for end nodes is not required)
836 ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>
ExtendedSelectedAll() const837 SwCursorShell::ExtendedSelectedAll() const
838 {
839 if (m_pTableCursor)
840 {
841 return {};
842 }
843
844 SwNodes& rNodes = GetDoc()->GetNodes();
845 SwShellCursor const*const pShellCursor = getShellCursor(false);
846 SwStartNode const* pStartNode(FindParentText(*pShellCursor));
847
848 SwNodeIndex nNode(*pStartNode);
849 SwContentNode* pStart = SwNodes::GoNext(&nNode);
850 if (!pStart)
851 {
852 return {};
853 }
854
855 nNode = *pStartNode->EndOfSectionNode();
856 SwContentNode* pEnd = SwNodes::GoPrevious(&nNode);
857 if (!pEnd)
858 {
859 return {};
860 }
861
862 SwPosition aStart(*pStart, 0);
863 SwPosition aEnd(*pEnd, pEnd->Len());
864 if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End()))
865 {
866 return {};
867 }
868
869 auto const ends(::EndsWith(*pStartNode));
870 if (::StartsWith(*pStartNode) == StartsWith::None
871 && ends == StartsWith::None)
872 {
873 return {}; // "ordinary" selection will work
874 }
875
876 ::std::vector<SwTableNode*> tablesAtEnd;
877 if (ends == StartsWith::Table)
878 {
879 SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]);
880 while (pLastNode->IsEndNode())
881 {
882 SwNode *const pNode(pLastNode->StartOfSectionNode());
883 if (pNode->IsTableNode())
884 {
885 tablesAtEnd.push_back(pNode->GetTableNode());
886 pLastNode = rNodes[pNode->GetIndex() - 1];
887 }
888 else if (pNode->IsSectionNode())
889 {
890 pLastNode = rNodes[pLastNode->GetIndex() - 1];
891 }
892 }
893 assert(!tablesAtEnd.empty());
894 }
895
896 // tdf#133990 ensure directly containing section is included in SwUndoDelete
897 while (pStartNode->IsSectionNode()
898 && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1
899 && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex())
900 {
901 pStartNode = pStartNode->StartOfSectionNode();
902 }
903
904 // pStartNode is the node that fully contains the selection - the first
905 // node of the selection is the first node inside pStartNode
906 return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd);
907 }
908
StartsWith_()909 typename SwCursorShell::StartsWith SwCursorShell::StartsWith_()
910 {
911 SwShellCursor const*const pShellCursor = getShellCursor(false);
912 // first, check if this is invalid; ExtendedSelectAll(true) may result in
913 // a) an ordinary selection that is valid
914 // b) a selection that is extended
915 // c) a selection that is invalid and will cause FindParentText to loop
916 SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras());
917 if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex()
918 && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex())
919 {
920 return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling!
921 }
922 SwStartNode const*const pStartNode(FindParentText(*pShellCursor));
923 if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None)
924 {
925 return ret;
926 }
927 if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None)
928 {
929 return ret;
930 }
931 return StartsWith::None;
932 }
933
MovePage(SwWhichPage fnWhichPage,SwPosPage fnPosPage)934 bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage )
935 {
936 bool bRet = false;
937
938 // never jump of section borders at selection
939 if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() )
940 {
941 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
942 CurrShell aCurr( this );
943
944 SwCursorSaveState aSaveState( *m_pCurrentCursor );
945 Point& rPt = m_pCurrentCursor->GetPtPos();
946 std::pair<Point, bool> tmp(rPt, false);
947 SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()->
948 getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
949 if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) &&
950 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
951 SwCursorSelOverFlags::ChangePos ))
952 {
953 UpdateCursor();
954 bRet = true;
955 }
956 }
957 return bRet;
958 }
959
isInHiddenFrame(SwShellCursor * pShellCursor)960 bool SwCursorShell::isInHiddenFrame(SwShellCursor* pShellCursor)
961 {
962 SwContentNode *pCNode = pShellCursor->GetPointContentNode();
963 std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
964 SwContentFrame *const pFrame = pCNode
965 ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp)
966 : nullptr;
967 return !pFrame || pFrame->IsHiddenNow();
968 }
969
970 // sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara
IsAtStartOrEndOfFrame(SwCursorShell const * const pShell,SwShellCursor const * const pShellCursor,SwMoveFnCollection const & fnPosPara)971 static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell,
972 SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara)
973 {
974 SwContentNode *const pCNode = pShellCursor->GetPointContentNode();
975 assert(pCNode); // surely can't have moved otherwise?
976 std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
977 SwContentFrame const*const pFrame = pCNode->getLayoutFrame(
978 pShell->GetLayout(), pShellCursor->GetPoint(), &tmp);
979 if (!pFrame || !pFrame->IsTextFrame())
980 {
981 return false;
982 }
983 SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(*pFrame));
984 TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint()));
985 if (&fnParaStart == &fnPosPara)
986 {
987 return ix == TextFrameIndex(0);
988 }
989 else
990 {
991 assert(&fnParaEnd == &fnPosPara);
992 return ix == TextFrameIndex(rTextFrame.GetText().getLength());
993 }
994 }
995
MovePara(SwWhichPara fnWhichPara,SwMoveFnCollection const & fnPosPara)996 bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
997 {
998 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
999 SwShellCursor* pTmpCursor = getShellCursor( true );
1000 bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara );
1001 if( bRet )
1002 {
1003 //keep going until we get something visible, i.e. skip
1004 //over hidden paragraphs, don't get stuck at the start
1005 //which is what SwCursorShell::UpdateCursorPos will reset
1006 //the position to if we pass it a position in an
1007 //invisible hidden paragraph field
1008 while (isInHiddenFrame(pTmpCursor)
1009 || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara))
1010 {
1011 if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara))
1012 break;
1013 }
1014
1015 UpdateCursor();
1016 }
1017 return bRet;
1018 }
1019
MoveSection(SwWhichSection fnWhichSect,SwMoveFnCollection const & fnPosSect)1020 bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect,
1021 SwMoveFnCollection const & fnPosSect)
1022 {
1023 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1024 SwCursor* pTmpCursor = getShellCursor( true );
1025 bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect );
1026 if( bRet )
1027 UpdateCursor();
1028 return bRet;
1029
1030 }
1031
1032 // position cursor
1033
lcl_IsInHeaderFooter(SwNode & rNd,Point & rPt)1034 static SwFrame* lcl_IsInHeaderFooter( SwNode& rNd, Point& rPt )
1035 {
1036 SwFrame* pFrame = nullptr;
1037 SwContentNode* pCNd = rNd.GetContentNode();
1038 if( pCNd )
1039 {
1040 std::pair<Point, bool> tmp(rPt, false);
1041 SwContentFrame *pContentFrame = pCNd->getLayoutFrame(
1042 pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
1043 nullptr, &tmp);
1044 pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr;
1045 while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() )
1046 pFrame = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->AnchorFrame()
1047 : pFrame->GetUpper();
1048 }
1049 return pFrame;
1050 }
1051
IsInHeaderFooter(bool * pbInHeader) const1052 bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const
1053 {
1054 Point aPt;
1055 SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->GetNode(), aPt );
1056 if( pFrame && pbInHeader )
1057 *pbInHeader = pFrame->IsHeaderFrame();
1058 return nullptr != pFrame;
1059 }
1060
SetCursor(const Point & rLPt,bool bOnlyText,bool bBlock,bool bFieldInfo)1061 int SwCursorShell::SetCursor(const Point& rLPt, bool bOnlyText, bool bBlock, bool bFieldInfo)
1062 {
1063 CurrShell aCurr( this );
1064
1065 SwShellCursor* pCursor = getShellCursor( bBlock );
1066 SwPosition aPos( *pCursor->GetPoint() );
1067 Point aPt( rLPt );
1068 Point & rCurrentCursorPt = pCursor->GetPtPos();
1069 SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel :
1070 bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE );
1071 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1072 aTmpState.m_bFieldInfo = bFieldInfo; // always set cursor at field-start if point is over field
1073 aTmpState.m_bPosMatchesBounds = bFieldInfo; // always set cursor at character-start if over char
1074
1075 SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
1076
1077 if ( pTextNd && !IsTableMode() &&
1078 // #i37515# No bInFrontOfLabel during selection
1079 !pCursor->HasMark() &&
1080 pTextNd->HasVisibleNumberingOrBullet() )
1081 {
1082 aTmpState.m_bInFrontOfLabel = true; // #i27615#
1083 }
1084 else
1085 {
1086 aTmpState.m_bInFrontOfLabel = false;
1087 }
1088
1089 int bRet = CRSR_POSOLD |
1090 ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState )
1091 ? 0 : CRSR_POSCHG );
1092
1093 const bool bOldInFrontOfLabel = IsInFrontOfLabel();
1094 const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel;
1095
1096 pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel );
1097
1098 if( CursorMoveState::RightMargin == aTmpState.m_eState )
1099 m_eMvState = CursorMoveState::RightMargin;
1100 // is the new position in header or footer?
1101 SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.GetNode(), aPt );
1102 if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() ==
1103 pCursor->GetPoint()->GetNode().StartOfSectionNode() )
1104 // same table column and not in header/footer -> back
1105 return bRet;
1106
1107 if( m_pBlockCursor && bBlock )
1108 {
1109 m_pBlockCursor->setEndPoint( rLPt );
1110 if( !pCursor->HasMark() )
1111 m_pBlockCursor->setStartPoint( rLPt );
1112 else if( !m_pBlockCursor->getStartPoint() )
1113 m_pBlockCursor->setStartPoint( pCursor->GetMkPos() );
1114 }
1115 if( !pCursor->HasMark() )
1116 {
1117 // is at the same position and if in header/footer -> in the same
1118 if( aPos == *pCursor->GetPoint() &&
1119 bOldInFrontOfLabel == bNewInFrontOfLabel )
1120 {
1121 if( pFrame )
1122 {
1123 if( pFrame->getFrameArea().Contains( rCurrentCursorPt ))
1124 return bRet;
1125 }
1126 else if( aPos.GetNode().IsContentNode() )
1127 {
1128 // in the same frame?
1129 std::pair<Point, bool> tmp(m_aCharRect.Pos(), false);
1130 SwFrame* pOld = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
1131 GetLayout(), nullptr, &tmp);
1132 tmp.first = aPt;
1133 SwFrame* pNew = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
1134 GetLayout(), nullptr, &tmp);
1135 if( pNew == pOld )
1136 return bRet;
1137 }
1138 }
1139 }
1140 else
1141 {
1142 // SSelection over not allowed sections or if in header/footer -> different
1143 if( !CheckNodesRange( aPos.GetNode(), pCursor->GetMark()->GetNode(), true )
1144 || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) ))
1145 return bRet;
1146
1147 // is at same position but not in header/footer
1148 if( aPos == *pCursor->GetPoint() )
1149 return bRet;
1150 }
1151
1152 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1153 SwCursorSaveState aSaveState( *pCursor );
1154
1155 *pCursor->GetPoint() = aPos;
1156 rCurrentCursorPt = aPt;
1157
1158 // #i41424# Only update the marked number levels if necessary
1159 // Force update of marked number levels if necessary.
1160 if ( bNewInFrontOfLabel || bOldInFrontOfLabel )
1161 m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel );
1162 SetInFrontOfLabel( bNewInFrontOfLabel );
1163
1164 if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) )
1165 {
1166 UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE );
1167 bRet &= ~CRSR_POSOLD;
1168 }
1169 else if( bOnlyText && !m_pCurrentCursor->HasMark() )
1170 {
1171 if( FindValidContentNode( bOnlyText ) )
1172 {
1173 // position cursor in a valid content
1174 if( aPos == *pCursor->GetPoint() )
1175 bRet = CRSR_POSOLD;
1176 else
1177 {
1178 UpdateCursor();
1179 bRet &= ~CRSR_POSOLD;
1180 }
1181 }
1182 else
1183 {
1184 // there is no valid content -> hide cursor
1185 m_pVisibleCursor->Hide(); // always hide visible cursor
1186 m_eMvState = CursorMoveState::NONE; // status for Cursor travelling
1187 m_bAllProtect = true;
1188 if( GetDoc()->GetDocShell() )
1189 {
1190 GetDoc()->GetDocShell()->SetReadOnlyUI();
1191 CallChgLnk(); // notify UI
1192 }
1193 }
1194 }
1195 return bRet;
1196 }
1197
TableCursorToCursor()1198 void SwCursorShell::TableCursorToCursor()
1199 {
1200 assert(m_pTableCursor);
1201 delete m_pTableCursor;
1202 m_pTableCursor = nullptr;
1203 }
1204
BlockCursorToCursor()1205 void SwCursorShell::BlockCursorToCursor()
1206 {
1207 assert(m_pBlockCursor);
1208 if( m_pBlockCursor && !HasSelection() )
1209 {
1210 SwPaM& rPam = m_pBlockCursor->getShellCursor();
1211 m_pCurrentCursor->SetMark();
1212 *m_pCurrentCursor->GetPoint() = *rPam.GetPoint();
1213 if( rPam.HasMark() )
1214 *m_pCurrentCursor->GetMark() = *rPam.GetMark();
1215 else
1216 m_pCurrentCursor->DeleteMark();
1217 }
1218 delete m_pBlockCursor;
1219 m_pBlockCursor = nullptr;
1220 }
1221
CursorToBlockCursor()1222 void SwCursorShell::CursorToBlockCursor()
1223 {
1224 if( !m_pBlockCursor )
1225 {
1226 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
1227 m_pBlockCursor = new SwBlockCursor( *this, aPos );
1228 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
1229 rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos();
1230 if( m_pCurrentCursor->HasMark() )
1231 {
1232 rBlock.SetMark();
1233 *rBlock.GetMark() = *m_pCurrentCursor->GetMark();
1234 rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos();
1235 }
1236 }
1237 m_pBlockCursor->clearPoints();
1238 RefreshBlockCursor();
1239 }
1240
ClearMark()1241 void SwCursorShell::ClearMark()
1242 {
1243 // is there any GetMark?
1244 if( m_pTableCursor )
1245 {
1246 std::vector<SwPaM*> vCursors;
1247 for(auto& rCursor : m_pCurrentCursor->GetRingContainer())
1248 if(&rCursor != m_pCurrentCursor)
1249 vCursors.push_back(&rCursor);
1250 for(auto pCursor : vCursors)
1251 delete pCursor;
1252 m_pTableCursor->DeleteMark();
1253
1254 m_pCurrentCursor->DeleteMark();
1255
1256 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
1257 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
1258 delete m_pTableCursor;
1259 m_pTableCursor = nullptr;
1260 m_pCurrentCursor->SwSelPaintRects::Show();
1261 }
1262 else
1263 {
1264 if( !m_pCurrentCursor->HasMark() )
1265 return;
1266 m_pCurrentCursor->DeleteMark();
1267 if( !m_nCursorMove )
1268 m_pCurrentCursor->SwSelPaintRects::Show();
1269 }
1270 }
1271
NormalizePam(bool bPointFirst)1272 void SwCursorShell::NormalizePam(bool bPointFirst)
1273 {
1274 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1275 m_pCurrentCursor->Normalize(bPointFirst);
1276 }
1277
SwapPam()1278 void SwCursorShell::SwapPam()
1279 {
1280 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1281 m_pCurrentCursor->Exchange();
1282 }
1283
1284 //TODO: provide documentation
1285 /** Search in the selected area for a Selection that covers the given point.
1286
1287 It checks if a Selection exists but does
1288 not move the current cursor.
1289
1290 @param rPt The point to search at.
1291 @param bTstHit ???
1292 */
TestCurrPam(const Point & rPt,bool bTstHit)1293 bool SwCursorShell::TestCurrPam(
1294 const Point & rPt,
1295 bool bTstHit )
1296 {
1297 CurrShell aCurr( this );
1298
1299 // check if the SPoint is in a table selection
1300 if( m_pTableCursor )
1301 return m_pTableCursor->Contains( rPt );
1302
1303 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1304 // search position <rPt> in document
1305 SwPosition aPtPos( *m_pCurrentCursor->GetPoint() );
1306 Point aPt( rPt );
1307
1308 SwCursorMoveState aTmpState( CursorMoveState::NONE );
1309 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1310 aTmpState.m_bPosMatchesBounds = true; // treat last half of character same as first half
1311 if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit )
1312 return false;
1313
1314 // search in all selections for this position
1315 SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor
1316 do
1317 {
1318 if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos)
1319 return true; // return without update
1320 pCmp = pCmp->GetNext();
1321 } while (m_pCurrentCursor != pCmp);
1322 return false;
1323 }
1324
KillPams()1325 void SwCursorShell::KillPams()
1326 {
1327 // Does any exist for deletion?
1328 if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() )
1329 return;
1330
1331 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
1332 delete m_pCurrentCursor->GetNext();
1333 m_pCurrentCursor->SetColumnSelection( false );
1334
1335 if( m_pTableCursor )
1336 {
1337 // delete the ring of cursors
1338 m_pCurrentCursor->DeleteMark();
1339 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
1340 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
1341 delete m_pTableCursor;
1342 m_pTableCursor = nullptr;
1343 }
1344 else if( m_pBlockCursor )
1345 {
1346 // delete the ring of cursors
1347 m_pCurrentCursor->DeleteMark();
1348 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
1349 *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint();
1350 m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos();
1351 rBlock.DeleteMark();
1352 m_pBlockCursor->clearPoints();
1353 }
1354 UpdateCursor( SwCursorShell::SCROLLWIN );
1355 }
1356
CompareCursorStackMkCurrPt() const1357 int SwCursorShell::CompareCursorStackMkCurrPt() const
1358 {
1359 int nRet = 0;
1360 const SwPosition *pFirst = nullptr, *pSecond = nullptr;
1361 const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor;
1362 // cursor on stack is needed if we compare against stack
1363 if( pStack )
1364 {
1365 pFirst = pStack->GetMark();
1366 pSecond = pCur->GetPoint();
1367 }
1368 if( !pFirst || !pSecond )
1369 nRet = INT_MAX;
1370 else if( *pFirst < *pSecond )
1371 nRet = -1;
1372 else if( *pFirst == *pSecond )
1373 nRet = 0;
1374 else
1375 nRet = 1;
1376 return nRet;
1377 }
1378
IsSelOnePara() const1379 bool SwCursorShell::IsSelOnePara() const
1380 {
1381 if (m_pCurrentCursor->IsMultiSelection())
1382 {
1383 return false;
1384 }
1385 if (m_pCurrentCursor->GetPoint()->GetNode() == m_pCurrentCursor->GetMark()->GetNode())
1386 {
1387 return true;
1388 }
1389 if (GetLayout()->HasMergedParas())
1390 {
1391 SwContentFrame const*const pFrame(GetCurrFrame(false));
1392 auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex());
1393 return FrameContainsNode(*pFrame, n);
1394 }
1395 return false;
1396 }
1397
IsSttPara() const1398 bool SwCursorShell::IsSttPara() const
1399 {
1400 if (GetLayout()->HasMergedParas())
1401 {
1402 SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
1403 if (pNode)
1404 {
1405 SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
1406 pNode->getLayoutFrame(GetLayout())));
1407 if (pFrame)
1408 {
1409 return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
1410 == TextFrameIndex(0);
1411 }
1412 }
1413 }
1414 return m_pCurrentCursor->GetPoint()->GetContentIndex() == 0;
1415 }
1416
IsEndPara() const1417 bool SwCursorShell::IsEndPara() const
1418 {
1419 if (GetLayout()->HasMergedParas())
1420 {
1421 SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
1422 if (pNode)
1423 {
1424 SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
1425 pNode->getLayoutFrame(GetLayout())));
1426 if (pFrame)
1427 {
1428 return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
1429 == TextFrameIndex(pFrame->GetText().getLength());
1430 }
1431 }
1432 }
1433 return m_pCurrentCursor->GetPoint()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->Len();
1434 }
1435
IsEndOfTable() const1436 bool SwCursorShell::IsEndOfTable() const
1437 {
1438 if (IsTableMode() || IsBlockMode() || !IsEndPara())
1439 {
1440 return false;
1441 }
1442 SwTableNode const*const pTableNode( IsCursorInTable() );
1443 if (!pTableNode)
1444 {
1445 return false;
1446 }
1447 SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode());
1448 SwNodeIndex const lastNode(*pEndTableNode, -2);
1449 SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core",
1450 "text node expected");
1451 return (lastNode == m_pCurrentCursor->GetPoint()->GetNode());
1452 }
1453
IsCursorInFootnote() const1454 bool SwCursorShell::IsCursorInFootnote() const
1455 {
1456 SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType();
1457 return aStartNodeType == SwStartNodeType::SwFootnoteStartNode;
1458 }
1459
GetCursorPagePos() const1460 Point SwCursorShell::GetCursorPagePos() const
1461 {
1462 Point aRet(-1, -1);
1463 if (SwFrame *pFrame = GetCurrFrame())
1464 {
1465 if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame())
1466 {
1467 const Point& rDocPos = GetCursorDocPos();
1468 aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft();
1469 }
1470 }
1471 return aRet;
1472 }
1473
IsInFrontOfLabel() const1474 bool SwCursorShell::IsInFrontOfLabel() const
1475 {
1476 return m_pCurrentCursor->IsInFrontOfLabel();
1477 }
1478
SetInFrontOfLabel(bool bNew)1479 bool SwCursorShell::SetInFrontOfLabel( bool bNew )
1480 {
1481 if ( bNew != IsInFrontOfLabel() )
1482 {
1483 m_pCurrentCursor->SetInFrontOfLabel_( bNew );
1484 UpdateMarkedListLevel();
1485 return true;
1486 }
1487 return false;
1488 }
1489
1490 namespace {
1491
collectUIInformation(const OUString & aPage)1492 void collectUIInformation(const OUString& aPage)
1493 {
1494 EventDescription aDescription;
1495 aDescription.aAction = "GOTO";
1496 aDescription.aParameters = {{"PAGE", aPage}};
1497 aDescription.aID = "writer_edit";
1498 aDescription.aKeyWord = "SwEditWinUIObject";
1499 aDescription.aParent = "MainWindow";
1500 UITestLogger::getInstance().logEvent(aDescription);
1501 }
1502
1503 }
1504
GotoPage(sal_uInt16 nPage)1505 bool SwCursorShell::GotoPage( sal_uInt16 nPage )
1506 {
1507 CurrShell aCurr( this );
1508 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1509 SwCursorSaveState aSaveState( *m_pCurrentCursor );
1510 bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) &&
1511 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
1512 SwCursorSelOverFlags::ChangePos );
1513 if( bRet )
1514 UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
1515
1516 collectUIInformation(OUString::number(nPage));
1517 return bRet;
1518 }
1519
GetCharRectAt(SwRect & rRect,const SwPosition * pPos)1520 void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos)
1521 {
1522 SwContentFrame* pFrame = GetCurrFrame();
1523 pFrame->GetCharRect( rRect, *pPos );
1524 }
1525
GetPageNum(sal_uInt16 & rnPhyNum,sal_uInt16 & rnVirtNum,bool bAtCursorPos,const bool bCalcFrame)1526 void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum,
1527 bool bAtCursorPos, const bool bCalcFrame )
1528 {
1529 CurrShell aCurr( this );
1530 // page number: first visible page or the one at the cursor
1531 const SwContentFrame* pCFrame;
1532 const SwPageFrame *pPg = nullptr;
1533
1534 if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) ||
1535 nullptr == (pPg = pCFrame->FindPageFrame()) )
1536 {
1537 pPg = Imp()->GetFirstVisPage(GetOut());
1538 while( pPg && pPg->IsEmptyPage() )
1539 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1540 }
1541 // pPg has to exist with a default of 1 for the special case "Writerstart"
1542 rnPhyNum = pPg? pPg->GetPhyPageNum() : 1;
1543 rnVirtNum = pPg? pPg->GetVirtPageNum() : 1;
1544 }
1545
GetPageNumSeqNonEmpty()1546 sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty()
1547 {
1548 CurrShell aCurr(this);
1549 // page number: first visible page or the one at the cursor
1550 const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true);
1551 const SwPageFrame* pPg = nullptr;
1552
1553 if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame()))
1554 {
1555 pPg = Imp()->GetFirstVisPage(GetOut());
1556 while (pPg && pPg->IsEmptyPage())
1557 pPg = static_cast<const SwPageFrame*>(pPg->GetNext());
1558 }
1559
1560 sal_uInt16 nPageNo = 0;
1561 while (pPg)
1562 {
1563 if (!pPg->IsEmptyPage())
1564 ++nPageNo;
1565 pPg = static_cast<const SwPageFrame*>(pPg->GetPrev());
1566 }
1567 return nPageNo;
1568 }
1569
GetNextPrevPageNum(bool bNext)1570 sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext )
1571 {
1572 CurrShell aCurr( this );
1573 // page number: first visible page or the one at the cursor
1574 const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut());
1575 if( pPg )
1576 {
1577 const SwTwips nPageTop = pPg->getFrameArea().Top();
1578
1579 if( bNext )
1580 {
1581 // go to next view layout row:
1582 do
1583 {
1584 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1585 }
1586 while( pPg && pPg->getFrameArea().Top() == nPageTop );
1587
1588 while( pPg && pPg->IsEmptyPage() )
1589 pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
1590 }
1591 else
1592 {
1593 // go to previous view layout row:
1594 do
1595 {
1596 pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
1597 }
1598 while( pPg && pPg->getFrameArea().Top() == nPageTop );
1599
1600 while( pPg && pPg->IsEmptyPage() )
1601 pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
1602 }
1603 }
1604 // pPg has to exist with a default of 1 for the special case "Writerstart"
1605 return pPg ? pPg->GetPhyPageNum() : USHRT_MAX;
1606 }
1607
GetPageCnt()1608 sal_uInt16 SwCursorShell::GetPageCnt()
1609 {
1610 CurrShell aCurr( this );
1611 // return number of pages
1612 return GetLayout()->GetPageNum();
1613 }
1614
getPageRectangles()1615 OUString SwCursorShell::getPageRectangles()
1616 {
1617 CurrShell aCurr(this);
1618 SwRootFrame* pLayout = GetLayout();
1619 OUStringBuffer aBuf;
1620 for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
1621 {
1622 aBuf.append(OUString::number(pFrame->getFrameArea().Left())
1623 + ", "
1624 + OUString::number(pFrame->getFrameArea().Top())
1625 + ", "
1626 + OUString::number(pFrame->getFrameArea().Width())
1627 + ", "
1628 + OUString::number(pFrame->getFrameArea().Height())
1629 + "; ");
1630 }
1631 if (!aBuf.isEmpty())
1632 aBuf.setLength( aBuf.getLength() - 2); // remove the last "; "
1633 return aBuf.makeStringAndClear();
1634 }
1635
NotifyCursor(SfxViewShell * pOtherShell) const1636 void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const
1637 {
1638 auto pView = const_cast<SdrView*>(GetDrawView());
1639 if (pView->GetTextEditObject())
1640 {
1641 // Blinking cursor.
1642 EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
1643 rEditView.RegisterOtherShell(pOtherShell);
1644 rEditView.ShowCursor();
1645 rEditView.RegisterOtherShell(nullptr);
1646 // Text selection, if any.
1647 rEditView.DrawSelectionXOR(pOtherShell);
1648
1649 // Shape text lock.
1650 if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView())
1651 {
1652 OString sRect = pOutlinerView->GetOutputArea().toString();
1653 SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect);
1654 }
1655 }
1656 else
1657 {
1658 // Cursor position.
1659 m_pVisibleCursor->SetPosAndShow(pOtherShell);
1660 // Cursor visibility.
1661 if (GetSfxViewShell() != pOtherShell)
1662 {
1663 OString aPayload = OString::boolean(m_bSVCursorVis);
1664 SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
1665 }
1666 // Text selection.
1667 m_pCurrentCursor->Show(pOtherShell);
1668 // Graphic selection.
1669 pView->AdjustMarkHdl(pOtherShell);
1670 }
1671 }
1672
1673 /// go to the next SSelection
GoNextCursor()1674 bool SwCursorShell::GoNextCursor()
1675 {
1676 if( !m_pCurrentCursor->IsMultiSelection() )
1677 return false;
1678
1679 CurrShell aCurr( this );
1680 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1681 m_pCurrentCursor = m_pCurrentCursor->GetNext();
1682
1683 // #i24086#: show also all others
1684 if( !ActionPend() )
1685 {
1686 UpdateCursor();
1687 m_pCurrentCursor->Show(nullptr);
1688 }
1689 return true;
1690 }
1691
1692 /// go to the previous SSelection
GoPrevCursor()1693 bool SwCursorShell::GoPrevCursor()
1694 {
1695 if( !m_pCurrentCursor->IsMultiSelection() )
1696 return false;
1697
1698 CurrShell aCurr( this );
1699 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
1700 m_pCurrentCursor = m_pCurrentCursor->GetPrev();
1701
1702 // #i24086#: show also all others
1703 if( !ActionPend() )
1704 {
1705 UpdateCursor();
1706 m_pCurrentCursor->Show(nullptr);
1707 }
1708 return true;
1709 }
1710
GoNextPrevCursorSetSearchLabel(const bool bNext)1711 void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext)
1712 {
1713 SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
1714
1715 if( !m_pCurrentCursor->IsMultiSelection() )
1716 {
1717 if( !m_pCurrentCursor->HasMark() )
1718 SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
1719 return;
1720 }
1721
1722 if (bNext)
1723 GoNextCursor();
1724 else
1725 GoPrevCursor();
1726 }
1727
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)1728 void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect)
1729 {
1730 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
1731 CurrShell aCurr( this );
1732
1733 // always switch off all cursors when painting
1734 SwRect aRect( rRect );
1735
1736 bool bVis = false;
1737 // if a cursor is visible then hide the SV cursor
1738 if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) )
1739 {
1740 bVis = true;
1741 m_pVisibleCursor->Hide();
1742 }
1743
1744 // re-paint area
1745 SwViewShell::Paint(rRenderContext, rRect);
1746
1747 if( m_bHasFocus && !m_bBasicHideCursor )
1748 {
1749 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
1750
1751 if( !ActionPend() )
1752 {
1753 // so that right/bottom borders will not be cropped
1754 pCurrentCursor->Invalidate( VisArea() );
1755 pCurrentCursor->Show(nullptr);
1756 }
1757 else
1758 pCurrentCursor->Invalidate( aRect );
1759
1760 }
1761
1762 if (SwPostItMgr* pPostItMgr = GetPostItMgr())
1763 {
1764 // No point in showing the cursor for Writer text when there is an
1765 // active annotation edit.
1766 if (bVis)
1767 bVis = !pPostItMgr->HasActiveSidebarWin();
1768 }
1769
1770 if( m_bSVCursorVis && bVis ) // also show SV cursor again
1771 m_pVisibleCursor->Show();
1772 }
1773
VisPortChgd(const SwRect & rRect)1774 void SwCursorShell::VisPortChgd( const SwRect & rRect )
1775 {
1776 CurrShell aCurr( this );
1777 bool bVis; // switch off all cursors when scrolling
1778
1779 // if a cursor is visible then hide the SV cursor
1780 bVis = m_pVisibleCursor->IsVisible();
1781 if( bVis )
1782 m_pVisibleCursor->Hide();
1783
1784 m_bVisPortChgd = true;
1785 m_aOldRBPos.setX(VisArea().Right());
1786 m_aOldRBPos.setY(VisArea().Bottom());
1787
1788 // For not having problems with the SV cursor, Update() is called for the
1789 // Window in SwViewShell::VisPo...
1790 // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact?
1791 SwViewShell::VisPortChgd( rRect ); // move area
1792
1793 if( m_bSVCursorVis && bVis ) // show SV cursor again
1794 m_pVisibleCursor->Show();
1795
1796 if( comphelper::LibreOfficeKit::isActive() && !rRect.Overlaps( m_aCharRect ))
1797 {
1798 boost::property_tree::ptree aParams;
1799 tools::Rectangle aRect(rRect.TopLeft(), Size(1, 1));
1800
1801 aParams.put("rectangle", aRect.toString());
1802 aParams.put("scroll", true);
1803 aParams.put("hyperlink", "");
1804
1805 SfxLokHelper::notifyOtherView(GetSfxViewShell(),
1806 GetSfxViewShell(),
1807 LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
1808 aParams);
1809 }
1810
1811 if( m_nCursorMove )
1812 m_bInCMvVisportChgd = true;
1813
1814 m_bVisPortChgd = false;
1815 }
1816
1817 /** Set the cursor back into content.
1818
1819 This should only be called if the cursor was moved (e.g. when deleting a
1820 text frame). The new position is calculated from its current position
1821 in the layout.
1822 */
UpdateCursorPos()1823 void SwCursorShell::UpdateCursorPos()
1824 {
1825 CurrShell aCurr( this );
1826 ++mnStartAction;
1827 SwShellCursor* pShellCursor = getShellCursor( true );
1828 Size aOldSz( GetDocSize() );
1829
1830 if (isInHiddenFrame(pShellCursor) && !ExtendedSelectedAll())
1831 {
1832 SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText);
1833 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
1834 GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(),
1835 &aTmpState );
1836 pShellCursor->DeleteMark();
1837 // kde45196-1.html: try to get to a non-hidden paragraph, there must
1838 // be one in the document body
1839 while (isInHiddenFrame(pShellCursor))
1840 {
1841 if (!pShellCursor->MovePara(GoNextPara, fnParaStart))
1842 {
1843 break;
1844 }
1845 }
1846 while (isInHiddenFrame(pShellCursor))
1847 {
1848 if (!pShellCursor->MovePara(GoPrevPara, fnParaStart))
1849 {
1850 break;
1851 }
1852 }
1853 }
1854 auto* pDoc = GetDoc();
1855 if (pDoc)
1856 {
1857 pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint());
1858 pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint());
1859 }
1860
1861 --mnStartAction;
1862 if( aOldSz != GetDocSize() )
1863 SizeChgNotify();
1864 }
1865
1866 // #i65475# - if Point/Mark in hidden sections, move them out
lcl_CheckHiddenSection(SwPosition & rPos)1867 static bool lcl_CheckHiddenSection( SwPosition& rPos )
1868 {
1869 bool bOk = true;
1870 const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode();
1871 if( pSectNd && pSectNd->GetSection().IsHiddenFlag() )
1872 {
1873 const SwNode* pFrameNd =
1874 rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() );
1875 bOk = pFrameNd != nullptr;
1876 SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames");
1877 rPos.Assign( *(bOk ? pFrameNd : pSectNd) );
1878 }
1879 return bOk;
1880 }
1881
1882 /// Try to set the cursor to the next visible content node.
lcl_CheckHiddenPara(SwPosition & rPos)1883 static void lcl_CheckHiddenPara( SwPosition& rPos )
1884 {
1885 SwNodeIndex aTmp( rPos.GetNode() );
1886 SwTextNode* pTextNd = aTmp.GetNode().GetTextNode();
1887 while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) )
1888 {
1889 SwContentNode* pContent = SwNodes::GoNext(&aTmp);
1890 if ( pContent && pContent->IsTextNode() )
1891 pTextNd = pContent->GetTextNode();
1892 else
1893 pTextNd = nullptr;
1894 }
1895
1896 if ( pTextNd )
1897 rPos.Assign( *pTextNd, 0 );
1898 }
1899
1900 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
1901 namespace {
1902
1903 // #i27301# - helper class that notifies the accessibility about invalid text
1904 // selections in its destructor
1905 class SwNotifyAccAboutInvalidTextSelections
1906 {
1907 private:
1908 SwCursorShell& mrCursorSh;
1909
1910 public:
SwNotifyAccAboutInvalidTextSelections(SwCursorShell & _rCursorSh)1911 explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh )
1912 : mrCursorSh( _rCursorSh )
1913 {}
1914
~SwNotifyAccAboutInvalidTextSelections()1915 ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE
1916 {
1917 mrCursorSh.InvalidateAccessibleParaTextSelection();
1918 }
1919 };
1920
1921 }
1922 #endif
1923
UpdateCursor(sal_uInt16 eFlags,bool bIdleEnd)1924 void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd )
1925 {
1926 CurrShell aCurr( this );
1927 ClearUpCursors();
1928
1929 if (ActionPend())
1930 {
1931 if ( eFlags & SwCursorShell::READONLY )
1932 m_bIgnoreReadonly = true;
1933 return; // if not then no update
1934 }
1935
1936 if (m_bNeedLayoutOnCursorUpdate)
1937 {
1938 // A previous spell check skipped a word that had a spelling error, because that word
1939 // had cursor. Now schedule the idle to call SwViewShell::LayoutIdle, to repeat the
1940 // spell check, in the hope that the cursor has left the word.
1941 m_aLayoutIdle.Start();
1942 m_bNeedLayoutOnCursorUpdate = false;
1943 }
1944
1945 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
1946 SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this );
1947 #endif
1948
1949 if ( m_bIgnoreReadonly )
1950 {
1951 m_bIgnoreReadonly = false;
1952 eFlags |= SwCursorShell::READONLY;
1953 }
1954
1955 if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for
1956 CheckRange( m_pCurrentCursor ); // overlapping ranges
1957
1958 if( !bIdleEnd )
1959 CheckTableBoxContent();
1960
1961 // If the current cursor is in a table and point/mark in different boxes,
1962 // then the table mode is active (also if it is already active: m_pTableCursor)
1963 SwPaM* pTstCursor = getShellCursor( true );
1964 if( pTstCursor->HasMark() && !m_pBlockCursor &&
1965 SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) &&
1966 ( m_pTableCursor ||
1967 pTstCursor->GetPointNode().StartOfSectionNode() !=
1968 pTstCursor->GetMarkNode().StartOfSectionNode() ) && !mbSelectAll)
1969 {
1970 SwShellCursor* pITmpCursor = getShellCursor( true );
1971 Point aTmpPt( pITmpCursor->GetPtPos() );
1972 Point aTmpMk( pITmpCursor->GetMkPos() );
1973 SwPosition* pPos = pITmpCursor->GetPoint();
1974
1975 // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out
1976 lcl_CheckHiddenSection( *pPos );
1977 lcl_CheckHiddenSection( *pITmpCursor->GetMark() );
1978
1979 // Move cursor out of hidden paragraphs
1980 if ( !GetViewOptions()->IsShowHiddenChar() )
1981 {
1982 lcl_CheckHiddenPara( *pPos );
1983 lcl_CheckHiddenPara( *pITmpCursor->GetMark() );
1984 }
1985
1986 std::pair<Point, bool> const tmp(aTmpPt, false);
1987 SwContentFrame *pTableFrame = pPos->GetNode().GetContentNode()->
1988 getLayoutFrame( GetLayout(), pPos, &tmp);
1989
1990 OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" );
1991
1992 // --> Make code robust. The table cursor may point
1993 // to a table in a currently inactive header.
1994 SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr;
1995
1996 if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 )
1997 {
1998 // First check if point is in repeated headline:
1999 bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame );
2000
2001 // Second check if mark is in repeated headline:
2002 if ( !bInRepeatedHeadline )
2003 {
2004 std::pair<Point, bool> const tmp1(aTmpMk, false);
2005 SwContentFrame* pMarkTableFrame = pITmpCursor->GetMarkContentNode()->
2006 getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1);
2007 OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" );
2008
2009 if ( pMarkTableFrame )
2010 {
2011 SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame();
2012 OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" );
2013
2014 // Make code robust:
2015 if ( pMarkTab )
2016 {
2017 bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame );
2018 }
2019 }
2020 }
2021
2022 // No table cursor in repeated headlines:
2023 if ( bInRepeatedHeadline )
2024 {
2025 pTableFrame = nullptr;
2026
2027 // then only select inside the Box
2028 if( m_pTableCursor )
2029 {
2030 SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark()
2031 ? fnSectionStart
2032 : fnSectionEnd;
2033
2034 m_pCurrentCursor->SetMark();
2035 *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark();
2036 m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos();
2037 m_pTableCursor->DeleteMark();
2038 m_pTableCursor->SwSelPaintRects::Hide();
2039
2040 *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
2041 GoCurrSection( *m_pCurrentCursor, fnPosSect );
2042 }
2043 else
2044 {
2045 eFlags &= SwCursorShell::UPDOWN;
2046 *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
2047 }
2048 }
2049 }
2050
2051 // we really want a table selection
2052 if( pTab && pTableFrame )
2053 {
2054 if( !m_pTableCursor )
2055 {
2056 m_pTableCursor = new SwShellTableCursor( *this,
2057 *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(),
2058 *pPos, aTmpPt );
2059 m_pCurrentCursor->DeleteMark();
2060 m_pCurrentCursor->SwSelPaintRects::Hide();
2061
2062 CheckTableBoxContent();
2063 if(!m_pTableCursor)
2064 {
2065 SAL_WARN("sw.core", "fdo#74854: "
2066 "this should not happen, but better lose the selection "
2067 "rather than crashing");
2068 return;
2069 }
2070 }
2071
2072 SwCursorMoveState aTmpState( CursorMoveState::NONE );
2073 aTmpState.m_bRealHeight = true;
2074 {
2075 DisableCallbackAction a(*GetLayout());
2076 if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState))
2077 {
2078 Point aCentrPt( m_aCharRect.Center() );
2079 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
2080 pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState);
2081 bool const bResult =
2082 pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint());
2083 OSL_ENSURE( bResult, "GetCharRect failed." );
2084 }
2085 }
2086
2087 m_pVisibleCursor->Hide(); // always hide visible Cursor
2088 // scroll Cursor to visible area
2089 if( eFlags & SwCursorShell::SCROLLWIN &&
2090 (HasSelection() || eFlags & SwCursorShell::READONLY ||
2091 !IsCursorReadonly()) )
2092 {
2093 SwFrame* pBoxFrame = pTableFrame;
2094 while( pBoxFrame && !pBoxFrame->IsCellFrame() )
2095 pBoxFrame = pBoxFrame->GetUpper();
2096 if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() )
2097 MakeVisible( pBoxFrame->getFrameArea() );
2098 else
2099 MakeVisible( m_aCharRect );
2100 }
2101
2102 // let Layout create the Cursors in the Boxes
2103 if( m_pTableCursor->IsCursorMovedUpdate() )
2104 GetLayout()->MakeTableCursors( *m_pTableCursor );
2105 if( m_bHasFocus && !m_bBasicHideCursor )
2106 m_pTableCursor->Show(nullptr);
2107
2108 // set Cursor-Points to the new Positions
2109 m_pTableCursor->GetPtPos().setX(m_aCharRect.Left());
2110 m_pTableCursor->GetPtPos().setY(m_aCharRect.Top());
2111
2112 if( m_bSVCursorVis )
2113 {
2114 m_aCursorHeight.setX(0);
2115 m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
2116 -m_aCharRect.Width() : m_aCharRect.Height());
2117 m_pVisibleCursor->Show(); // show again
2118 }
2119 m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
2120 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2121 if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
2122 Imp()->InvalidateAccessibleCursorPosition( pTableFrame );
2123 #endif
2124 return;
2125 }
2126 }
2127
2128 if( m_pTableCursor )
2129 {
2130 // delete Ring
2131 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
2132 delete m_pCurrentCursor->GetNext();
2133 m_pCurrentCursor->DeleteMark();
2134 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
2135 m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
2136 delete m_pTableCursor;
2137 m_pTableCursor = nullptr;
2138 }
2139
2140 m_pVisibleCursor->Hide(); // always hide visible Cursor
2141
2142 // are we perhaps in a protected / hidden Section ?
2143 {
2144 SwShellCursor* pShellCursor = getShellCursor( true );
2145 bool bChgState = true;
2146 const SwSectionNode* pSectNd = pShellCursor->GetPointNode().FindSectionNode();
2147 if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
2148 ( !IsReadOnlyAvailable() &&
2149 pSectNd->GetSection().IsProtectFlag() &&
2150 ( !mxDoc->GetDocShell() ||
2151 !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) )
2152 {
2153 if( !FindValidContentNode( !HasDrawView() ||
2154 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
2155 {
2156 // everything protected/hidden -> special mode
2157 if( m_bAllProtect && !IsReadOnlyAvailable() &&
2158 pSectNd->GetSection().IsProtectFlag() )
2159 bChgState = false;
2160 else
2161 {
2162 m_eMvState = CursorMoveState::NONE; // state for cursor travelling
2163 m_bAllProtect = true;
2164 if( GetDoc()->GetDocShell() )
2165 {
2166 GetDoc()->GetDocShell()->SetReadOnlyUI();
2167 CallChgLnk(); // notify UI!
2168 }
2169 return;
2170 }
2171 }
2172 }
2173 if( bChgState )
2174 {
2175 bool bWasAllProtect = m_bAllProtect;
2176 m_bAllProtect = false;
2177 if( bWasAllProtect && GetDoc()->GetDocShell() &&
2178 GetDoc()->GetDocShell()->IsReadOnlyUI() )
2179 {
2180 GetDoc()->GetDocShell()->SetReadOnlyUI( false );
2181 CallChgLnk(); // notify UI!
2182 }
2183 }
2184 }
2185
2186 UpdateCursorPos();
2187
2188 // The cursor must always point into content; there's some code
2189 // that relies on this. (E.g. in SwEditShell::GetScriptType, which always
2190 // loops _behind_ the last node in the selection, which always works if you
2191 // are in content.) To achieve this, we'll force cursor(s) to point into
2192 // content, if UpdateCursorPos() hasn't already done so.
2193 for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer())
2194 {
2195 // start will move forwards, end will move backwards
2196 bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() );
2197
2198 // move point; forward if it's the start, backwards if it's the end
2199 if( ! rCmp.GetPoint()->GetNode().IsContentNode() )
2200 rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward,
2201 GoInContent );
2202
2203 // move mark (if exists); forward if it's the start, else backwards
2204 if( rCmp.HasMark() )
2205 {
2206 if( ! rCmp.GetMark()->GetNode().IsContentNode() )
2207 {
2208 rCmp.Exchange();
2209 rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward,
2210 GoInContent );
2211 rCmp.Exchange();
2212 }
2213 }
2214 }
2215
2216 SwRect aOld( m_aCharRect );
2217 bool bFirst = true;
2218 SwContentFrame *pFrame;
2219 int nLoopCnt = 100;
2220 SwShellCursor* pShellCursor = getShellCursor( true );
2221
2222 do {
2223 bool bAgainst;
2224 do {
2225 bAgainst = false;
2226 std::pair<Point, bool> const tmp1(pShellCursor->GetPtPos(), false);
2227 pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(GetLayout(),
2228 pShellCursor->GetPoint(), &tmp1);
2229 // if the Frame doesn't exist anymore, the complete Layout has to be
2230 // created, because there used to be a Frame here!
2231 if ( !pFrame )
2232 {
2233 // skip, if it is a hidden deleted cell without frame
2234 if ( GetLayout()->IsHideRedlines() )
2235 {
2236 const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode();
2237 if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete )
2238 return;
2239 }
2240
2241 do
2242 {
2243 CalcLayout();
2244 std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false);
2245 pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(
2246 GetLayout(), pShellCursor->GetPoint(), &tmp);
2247 } while( !pFrame );
2248 }
2249 else if ( Imp()->IsIdleAction() )
2250 // Guarantee everything's properly formatted
2251 pFrame->PrepareCursor();
2252
2253 // In protected Fly? but ignore in case of frame selection
2254 if( !IsReadOnlyAvailable() && pFrame->IsProtected() &&
2255 ( !Imp()->GetDrawView() ||
2256 !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) &&
2257 (!mxDoc->GetDocShell() ||
2258 !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) )
2259 {
2260 // look for a valid position
2261 bool bChgState = true;
2262 if( !FindValidContentNode(!HasDrawView() ||
2263 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
2264 {
2265 // everything is protected / hidden -> special Mode
2266 if( m_bAllProtect )
2267 bChgState = false;
2268 else
2269 {
2270 m_eMvState = CursorMoveState::NONE; // state for cursor travelling
2271 m_bAllProtect = true;
2272 if( GetDoc()->GetDocShell() )
2273 {
2274 GetDoc()->GetDocShell()->SetReadOnlyUI();
2275 CallChgLnk(); // notify UI!
2276 }
2277 return;
2278 }
2279 }
2280
2281 if( bChgState )
2282 {
2283 bool bWasAllProtect = m_bAllProtect;
2284 m_bAllProtect = false;
2285 if( bWasAllProtect && GetDoc()->GetDocShell() &&
2286 GetDoc()->GetDocShell()->IsReadOnlyUI() )
2287 {
2288 GetDoc()->GetDocShell()->SetReadOnlyUI( false );
2289 CallChgLnk(); // notify UI!
2290 }
2291 m_bAllProtect = false;
2292 bAgainst = true; // look for the right Frame again
2293 }
2294 }
2295 } while( bAgainst );
2296
2297 SwCursorMoveState aTmpState( m_eMvState );
2298 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
2299 aTmpState.m_bRealHeight = true;
2300 aTmpState.m_bRealWidth = IsOverwriteCursor();
2301 aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel();
2302
2303 // #i27615#,#i30453#
2304 SwSpecialPos aSpecialPos;
2305 aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE;
2306 if (pShellCursor->IsInFrontOfLabel())
2307 {
2308 aTmpState.m_pSpecialPos = &aSpecialPos;
2309 }
2310
2311 {
2312 DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action
2313 if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState))
2314 {
2315 Point& rPt = pShellCursor->GetPtPos();
2316 rPt = m_aCharRect.Center();
2317 pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState );
2318 }
2319 }
2320 UISizeNotify(); // tdf#96256 update view size
2321
2322 if( !pShellCursor->HasMark() )
2323 m_aCursorHeight = aTmpState.m_aRealHeight;
2324 else
2325 {
2326 m_aCursorHeight.setX(0);
2327 m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
2328 -m_aCharRect.Width() : m_aCharRect.Height());
2329 }
2330
2331 if( !bFirst && aOld == m_aCharRect )
2332 break;
2333
2334 // if the layout says that we are after the 100th iteration still in
2335 // flow then we should always take the current position for granted.
2336 // (see bug: 29658)
2337 if( !--nLoopCnt )
2338 {
2339 OSL_ENSURE( false, "endless loop? CharRect != OldCharRect ");
2340 break;
2341 }
2342 aOld = m_aCharRect;
2343 bFirst = false;
2344
2345 // update cursor Points to the new Positions
2346 pShellCursor->GetPtPos().setX(m_aCharRect.Left());
2347 pShellCursor->GetPtPos().setY(m_aCharRect.Top());
2348
2349 if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down
2350 {
2351 DisableCallbackAction a(*GetLayout());
2352 pFrame->Calc(GetOut());
2353 m_nUpDownX = pFrame->IsVertical() ?
2354 m_aCharRect.Top() - pFrame->getFrameArea().Top() :
2355 m_aCharRect.Left() - pFrame->getFrameArea().Left();
2356 }
2357
2358 // scroll Cursor to visible area
2359 if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
2360 (HasSelection() || eFlags & SwCursorShell::READONLY ||
2361 !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) )
2362 {
2363 // in case of scrolling this EndAction doesn't show the SV cursor
2364 // again, thus save and reset the flag here
2365 bool bSav = m_bSVCursorVis;
2366 m_bSVCursorVis = false;
2367 MakeSelVisible();
2368 m_bSVCursorVis = bSav;
2369 }
2370
2371 } while( eFlags & SwCursorShell::SCROLLWIN );
2372
2373 assert(pFrame);
2374
2375 if( m_pBlockCursor )
2376 RefreshBlockCursor();
2377
2378 // We should not restrict cursor update to the active view when using LOK
2379 bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive();
2380
2381 if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor )
2382 {
2383 if( m_pTableCursor )
2384 m_pTableCursor->SwSelPaintRects::Show();
2385 else
2386 {
2387 m_pCurrentCursor->SwSelPaintRects::Show();
2388 if( m_pBlockCursor )
2389 {
2390 SwShellCursor* pNxt = m_pCurrentCursor->GetNext();
2391 while( pNxt && pNxt != m_pCurrentCursor )
2392 {
2393 pNxt->SwSelPaintRects::Show();
2394 pNxt = pNxt->GetNext();
2395 }
2396 }
2397 }
2398 }
2399
2400 m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
2401
2402 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2403 if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
2404 Imp()->InvalidateAccessibleCursorPosition( pFrame );
2405 #endif
2406
2407 // switch from blinking cursor to read-only-text-selection cursor
2408 const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings().
2409 GetCursorBlinkTime();
2410
2411 if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) ==
2412 ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) )
2413 {
2414 // non blinking cursor in read only - text selection mode
2415 AllSettings aSettings = GetOut()->GetSettings();
2416 StyleSettings aStyleSettings = aSettings.GetStyleSettings();
2417 const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ?
2418 Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() :
2419 STYLE_CURSOR_NOBLINKTIME;
2420 aStyleSettings.SetCursorBlinkTime( nNewBlinkTime );
2421 aSettings.SetStyleSettings( aStyleSettings );
2422 GetOut()->SetSettings( aSettings );
2423 }
2424
2425 if( m_bSVCursorVis )
2426 m_pVisibleCursor->Show(); // show again
2427
2428 if (comphelper::LibreOfficeKit::isActive())
2429 sendLOKCursorUpdates();
2430
2431 getIDocumentMarkAccess()->NotifyCursorUpdate(*this);
2432 }
2433
sendLOKCursorUpdates()2434 void SwCursorShell::sendLOKCursorUpdates()
2435 {
2436 SwView* pView = static_cast<SwView*>(GetSfxViewShell());
2437 if (!pView || !pView->GetWrtShellPtr())
2438 return;
2439
2440 SwWrtShell* pShell = &pView->GetWrtShell();
2441
2442 SwFrame* pCurrentFrame = GetCurrFrame();
2443 SelectionType eType = pShell->GetSelectionType();
2444
2445 tools::JsonWriter aJsonWriter;
2446
2447 if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab())
2448 {
2449 const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr);
2450
2451 {
2452 auto columnsNode = aJsonWriter.startNode("columns");
2453 SwTabCols aTabCols;
2454 pShell->GetTabCols(aTabCols);
2455
2456 const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left();
2457
2458 aJsonWriter.put("left", aTabCols.GetLeft());
2459 aJsonWriter.put("right", aTabCols.GetRight());
2460 aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nColumnOffset));
2461
2462 {
2463 auto entriesNode = aJsonWriter.startArray("entries");
2464 for (size_t i = 0; i < aTabCols.Count(); ++i)
2465 {
2466 auto entryNode = aJsonWriter.startStruct();
2467 auto const & rEntry = aTabCols.GetEntry(i);
2468 aJsonWriter.put("position", rEntry.nPos);
2469 aJsonWriter.put("min", rEntry.nMin);
2470 aJsonWriter.put("max", rEntry.nMax);
2471 aJsonWriter.put("hidden", rEntry.bHidden);
2472 }
2473 }
2474 }
2475
2476 {
2477 auto rowsNode = aJsonWriter.startNode("rows");
2478 SwTabCols aTabRows;
2479 pShell->GetTabRows(aTabRows);
2480
2481 const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top();
2482
2483 aJsonWriter.put("left", aTabRows.GetLeft());
2484 aJsonWriter.put("right", aTabRows.GetRight());
2485 aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nRowOffset));
2486
2487 {
2488 auto entriesNode = aJsonWriter.startArray("entries");
2489 for (size_t i = 0; i < aTabRows.Count(); ++i)
2490 {
2491 auto entryNode = aJsonWriter.startStruct();
2492 auto const & rEntry = aTabRows.GetEntry(i);
2493 aJsonWriter.put("position", rEntry.nPos);
2494 aJsonWriter.put("min", rEntry.nMin);
2495 aJsonWriter.put("max", rEntry.nMax);
2496 aJsonWriter.put("hidden", rEntry.bHidden);
2497 }
2498 }
2499 }
2500 }
2501
2502 OString pChar = aJsonWriter.finishAndGetAsOString();
2503 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar);
2504 }
2505
RefreshBlockCursor()2506 void SwCursorShell::RefreshBlockCursor()
2507 {
2508 assert(m_pBlockCursor);
2509 SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
2510 Point aPt = rBlock.GetPtPos();
2511 std::pair<Point, bool> const tmp(aPt, false);
2512 SwContentFrame* pFrame = rBlock.GetPointContentNode()->getLayoutFrame(
2513 GetLayout(), rBlock.GetPoint(), &tmp);
2514 Point aMk;
2515 if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() )
2516 {
2517 aPt = *m_pBlockCursor->getStartPoint();
2518 aMk = *m_pBlockCursor->getEndPoint();
2519 }
2520 else
2521 {
2522 aPt = rBlock.GetPtPos();
2523 if( pFrame )
2524 {
2525 if( pFrame->IsVertical() )
2526 aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX());
2527 else
2528 aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX());
2529 }
2530 aMk = rBlock.GetMkPos();
2531 }
2532 SwRect aRect( aMk, aPt );
2533 aRect.Justify();
2534 SwSelectionList aSelList( pFrame );
2535
2536 if( !GetLayout()->FillSelection( aSelList, aRect ) )
2537 return;
2538
2539 SwCursor* pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
2540 while( pNxt != m_pCurrentCursor )
2541 {
2542 delete pNxt;
2543 pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
2544 }
2545
2546 std::list<SwPaM*>::iterator pStart = aSelList.getStart();
2547 std::list<SwPaM*>::iterator pPam = aSelList.getEnd();
2548 OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" );
2549 m_pCurrentCursor->SetMark();
2550 --pPam;
2551 // If there is only one text portion inside the rectangle, a simple
2552 // selection is created
2553 if( pPam == pStart )
2554 {
2555 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint();
2556 if( (*pPam)->HasMark() )
2557 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2558 else
2559 m_pCurrentCursor->DeleteMark();
2560 delete *pPam;
2561 m_pCurrentCursor->SetColumnSelection( false );
2562 }
2563 else
2564 {
2565 // The order of the SwSelectionList has to be preserved but
2566 // the order inside the ring created by CreateCursor() is not like
2567 // expected => First create the selections before the last one
2568 // downto the first selection.
2569 // At least create the cursor for the last selection
2570 --pPam;
2571 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections)
2572 if( (*pPam)->HasMark() )
2573 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2574 else
2575 m_pCurrentCursor->DeleteMark();
2576 delete *pPam;
2577 m_pCurrentCursor->SetColumnSelection( true );
2578 while( pPam != pStart )
2579 {
2580 --pPam;
2581
2582 SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
2583 pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end());
2584 m_pCurrentCursor->clear();
2585 m_pCurrentCursor->DeleteMark();
2586
2587 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1
2588 if( (*pPam)->HasMark() )
2589 {
2590 m_pCurrentCursor->SetMark();
2591 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2592 }
2593 else
2594 m_pCurrentCursor->DeleteMark();
2595 m_pCurrentCursor->SetColumnSelection( true );
2596 delete *pPam;
2597 }
2598 {
2599 SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
2600 pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() );
2601 m_pCurrentCursor->clear();
2602 m_pCurrentCursor->DeleteMark();
2603 }
2604 pPam = aSelList.getEnd();
2605 --pPam;
2606 *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection
2607 if( (*pPam)->HasMark() )
2608 {
2609 m_pCurrentCursor->SetMark();
2610 *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
2611 }
2612 else
2613 m_pCurrentCursor->DeleteMark();
2614 m_pCurrentCursor->SetColumnSelection( true );
2615 delete *pPam;
2616 }
2617 }
2618
2619 /// create a copy of the cursor and save it in the stack
Push()2620 void SwCursorShell::Push()
2621 {
2622 // fdo#60513: if we have a table cursor, copy that; else copy current.
2623 // This seems to work because UpdateCursor() will fix this up on Pop(),
2624 // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring.
2625 SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor);
2626 m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(),
2627 pCurrent->GetPtPos(), m_pStackCursor );
2628
2629 if (pCurrent->HasMark())
2630 {
2631 m_pStackCursor->SetMark();
2632 *m_pStackCursor->GetMark() = *pCurrent->GetMark();
2633 }
2634 }
2635
2636 /** delete cursor
2637
2638 @param eDelete delete from stack, or delete current
2639 and assign the one from stack as the new current cursor.
2640 @return <true> if there was one on the stack, <false> otherwise
2641 */
Pop(PopMode const eDelete)2642 bool SwCursorShell::Pop(PopMode const eDelete)
2643 {
2644 std::optional<SwCallLink> aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed
2645 return Pop(eDelete, aLink);
2646 }
2647
Pop(PopMode const eDelete,std::optional<SwCallLink> & roLink)2648 bool SwCursorShell::Pop(PopMode const eDelete,
2649 [[maybe_unused]] std::optional<SwCallLink>& roLink)
2650 {
2651 // parameter exists only to be deleted before return
2652 assert(roLink);
2653 comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } );
2654
2655 // are there any left?
2656 if (nullptr == m_pStackCursor)
2657 return false;
2658
2659 SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor;
2660
2661 // the successor becomes the current one
2662 if (m_pStackCursor->GetNext() != m_pStackCursor)
2663 {
2664 pTmp = m_pStackCursor->GetNext();
2665 }
2666
2667 if (PopMode::DeleteStack == eDelete)
2668 delete m_pStackCursor;
2669
2670 m_pStackCursor = pTmp; // assign new one
2671
2672 if (PopMode::DeleteCurrent == eDelete)
2673 {
2674 ::std::optional<SwCursorSaveState> oSaveState( *m_pCurrentCursor );
2675
2676 // If the visible SSelection was not changed
2677 const Point& rPoint = pOldStack->GetPtPos();
2678 if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos())
2679 {
2680 // move "Selections Rectangles"
2681 m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() );
2682 pOldStack->clear();
2683 }
2684
2685 if( pOldStack->HasMark() )
2686 {
2687 m_pCurrentCursor->SetMark();
2688 *m_pCurrentCursor->GetMark() = *pOldStack->GetMark();
2689 m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos();
2690 }
2691 else
2692 // no selection so revoke old one and set to old position
2693 m_pCurrentCursor->DeleteMark();
2694 *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint();
2695 m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos();
2696 delete pOldStack;
2697
2698 if( !m_pCurrentCursor->IsInProtectTable( true ) &&
2699 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
2700 SwCursorSelOverFlags::ChangePos ) )
2701 {
2702 oSaveState.reset(); // prevent UAF
2703 UpdateCursor(); // update current cursor
2704 if (m_pTableCursor)
2705 { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table
2706 m_pTableCursor->SetChgd();
2707 }
2708 }
2709 }
2710 return true;
2711 }
2712
2713 /** Combine two cursors
2714
2715 Delete topmost from stack and use its GetMark in the current.
2716 */
Combine()2717 void SwCursorShell::Combine()
2718 {
2719 // any others left?
2720 if (nullptr == m_pStackCursor)
2721 return;
2722
2723 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
2724 // rhbz#689053: IsSelOvr must restore the saved stack position, not the
2725 // current one, because current point + stack mark may be invalid PaM
2726 SwCursorSaveState aSaveState(*m_pStackCursor);
2727 // stack cursor & current cursor in same Section?
2728 assert(!m_pStackCursor->HasMark() ||
2729 CheckNodesRange(m_pStackCursor->GetMark()->GetNode(),
2730 m_pCurrentCursor->GetPoint()->GetNode(), true));
2731 *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint();
2732 m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos();
2733
2734 SwShellCursor * pTmp = nullptr;
2735 if (m_pStackCursor->GetNext() != m_pStackCursor)
2736 {
2737 pTmp = m_pStackCursor->GetNext();
2738 }
2739 delete m_pCurrentCursor;
2740 m_pCurrentCursor = m_pStackCursor;
2741 m_pStackCursor->MoveTo(nullptr); // remove from ring
2742 m_pStackCursor = pTmp;
2743 if( !m_pCurrentCursor->IsInProtectTable( true ) &&
2744 !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
2745 SwCursorSelOverFlags::ChangePos ) )
2746 {
2747 UpdateCursor(); // update current cursor
2748 }
2749 }
2750
HideCursors()2751 void SwCursorShell::HideCursors()
2752 {
2753 if( !m_bHasFocus || m_bBasicHideCursor )
2754 return;
2755
2756 // if cursor is visible then hide SV cursor
2757 if( m_pVisibleCursor->IsVisible() )
2758 {
2759 CurrShell aCurr( this );
2760 m_pVisibleCursor->Hide();
2761 }
2762 // revoke inversion of SSelection
2763 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
2764 pCurrentCursor->Hide();
2765 }
2766
ShowCursors(bool bCursorVis)2767 void SwCursorShell::ShowCursors( bool bCursorVis )
2768 {
2769 if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor )
2770 return;
2771
2772 CurrShell aCurr( this );
2773 SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
2774 pCurrentCursor->Show(nullptr);
2775
2776 if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again
2777 m_pVisibleCursor->Show();
2778 }
2779
ShowCursor()2780 void SwCursorShell::ShowCursor()
2781 {
2782 if( m_bBasicHideCursor )
2783 return;
2784
2785 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
2786
2787 m_bSVCursorVis = true;
2788 m_pCurrentCursor->SetShowTextInputFieldOverlay( true );
2789 m_pCurrentCursor->SetShowContentControlOverlay(true);
2790
2791 if (comphelper::LibreOfficeKit::isActive())
2792 {
2793 const OString aPayload = OString::boolean(m_bSVCursorVis);
2794 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
2795 SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
2796 }
2797
2798 UpdateCursor();
2799 }
2800
HideCursor()2801 void SwCursorShell::HideCursor()
2802 {
2803 if( m_bBasicHideCursor )
2804 return;
2805
2806 m_bSVCursorVis = false;
2807 // possibly reverse selected areas!!
2808 CurrShell aCurr( this );
2809 m_pCurrentCursor->SetShowTextInputFieldOverlay( false );
2810 m_pCurrentCursor->SetShowContentControlOverlay(false);
2811 m_pVisibleCursor->Hide();
2812
2813 if (comphelper::LibreOfficeKit::isActive())
2814 {
2815 OString aPayload = OString::boolean(m_bSVCursorVis);
2816 GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
2817 SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
2818 }
2819 }
2820
ShellLoseFocus()2821 void SwCursorShell::ShellLoseFocus()
2822 {
2823 if( !m_bBasicHideCursor )
2824 HideCursors();
2825 m_bHasFocus = false;
2826 }
2827
ShellGetFocus()2828 void SwCursorShell::ShellGetFocus()
2829 {
2830 comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
2831
2832 m_bHasFocus = true;
2833 if( !m_bBasicHideCursor && VisArea().Width() )
2834 {
2835 UpdateCursor( o3tl::narrowing<sal_uInt16>( SwCursorShell::CHKRANGE ) );
2836 ShowCursors( m_bSVCursorVis );
2837 }
2838 }
2839
2840 /** Get current frame in which the cursor is positioned. */
GetCurrFrame(const bool bCalcFrame) const2841 SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const
2842 {
2843 CurrShell aCurr( const_cast<SwCursorShell*>(this) );
2844 SwContentFrame *pRet = nullptr;
2845 SwContentNode *pNd = m_pCurrentCursor->GetPointContentNode();
2846 if ( pNd )
2847 {
2848 if ( bCalcFrame )
2849 {
2850 sal_uInt16* pST = const_cast<sal_uInt16*>(&mnStartAction);
2851 ++(*pST);
2852 const Size aOldSz( GetDocSize() );
2853 std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), true);
2854 pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
2855 --(*pST);
2856 if( aOldSz != GetDocSize() )
2857 const_cast<SwCursorShell*>(this)->SizeChgNotify();
2858 }
2859 else
2860 {
2861 std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), false);
2862 pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
2863 }
2864 }
2865 return pRet;
2866 }
2867
2868 //TODO: provide documentation
2869 /** forward all attribute/format changes at the current node to the Link
2870
2871 @param pOld ???
2872 @param pNew ???
2873 */
SwClientNotify(const SwModify &,const SfxHint & rHint)2874 void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint)
2875 {
2876 if (rHint.GetId() == SfxHintId::SwPostGraphicArrived && m_aGrfArrivedLnk.IsSet())
2877 {
2878 m_aGrfArrivedLnk.Call(*this);
2879 return;
2880 }
2881 if (rHint.GetId() != SfxHintId::SwLegacyModify)
2882 return;
2883 auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
2884 auto nWhich = pLegacy->GetWhich();
2885 if(!nWhich)
2886 nWhich = RES_OBJECTDYING;
2887 if( m_bCallChgLnk &&
2888 ( !isFormatMessage(nWhich)
2889 || nWhich == RES_FMT_CHG
2890 || nWhich == RES_UPDATE_ATTR
2891 || nWhich == RES_ATTRSET_CHG ))
2892 // messages are not forwarded
2893 // #i6681#: RES_UPDATE_ATTR is implicitly unset in
2894 // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do
2895 // not need to send the expensive RES_FMT_CHG in Insert.
2896 CallChgLnk();
2897 if( nWhich == RES_OBJECTDYING )
2898 {
2899 EndListeningAll();
2900 }
2901
2902 }
2903
2904 /** Does the current cursor create a selection?
2905
2906 This means checking if GetMark is set and if SPoint and GetMark differ.
2907 */
HasSelection() const2908 bool SwCursorShell::HasSelection() const
2909 {
2910 const SwPaM* pCursor = getShellCursor( true );
2911 return IsTableMode()
2912 || (pCursor->HasMark() &&
2913 (*pCursor->GetPoint() != *pCursor->GetMark()
2914 || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End())));
2915 }
2916
CallChgLnk()2917 void SwCursorShell::CallChgLnk()
2918 {
2919 // Do not make any call in StartAction/EndAction but just set the flag.
2920 // This will be handled in EndAction.
2921 if (ActionPend())
2922 m_bChgCallFlag = true; // remember change
2923 else if( m_aChgLnk.IsSet() )
2924 {
2925 if( m_bCallChgLnk )
2926 m_aChgLnk.Call(nullptr);
2927 m_bChgCallFlag = false; // reset flag
2928 }
2929 }
2930
2931 /// get selected text of a node at current cursor
GetSelText() const2932 OUString SwCursorShell::GetSelText() const
2933 {
2934 OUString aText;
2935 if (GetLayout()->HasMergedParas())
2936 {
2937 SwContentFrame const*const pFrame(GetCurrFrame(false));
2938 if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->GetNodeIndex()))
2939 {
2940 OUStringBuffer buf;
2941 SwPosition const*const pStart(m_pCurrentCursor->Start());
2942 SwPosition const*const pEnd(m_pCurrentCursor->End());
2943 for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i)
2944 {
2945 SwNode const& rNode(*pStart->GetNodes()[i]);
2946 assert(!rNode.IsEndNode());
2947 if (rNode.IsStartNode())
2948 {
2949 i = rNode.EndOfSectionIndex();
2950 }
2951 else if (rNode.IsTextNode())
2952 {
2953 sal_Int32 const nStart(i == pStart->GetNodeIndex()
2954 ? pStart->GetContentIndex()
2955 : 0);
2956 sal_Int32 const nEnd(i == pEnd->GetNodeIndex()
2957 ? pEnd->GetContentIndex()
2958 : rNode.GetTextNode()->Len());
2959 buf.append(rNode.GetTextNode()->GetExpandText(
2960 GetLayout(),
2961 nStart, nEnd - nStart, false, false, false,
2962 ExpandMode::HideDeletions));
2963
2964 }
2965 }
2966 aText = buf.makeStringAndClear();
2967 }
2968 }
2969 else if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
2970 m_pCurrentCursor->GetMark()->GetNodeIndex() )
2971 {
2972 SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode();
2973 if( pTextNd )
2974 {
2975 const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex();
2976 aText = pTextNd->GetExpandText(GetLayout(), nStt,
2977 m_pCurrentCursor->End()->GetContentIndex() - nStt );
2978 }
2979 }
2980 return aText;
2981 }
2982
2983 /** get the nth character of the current SSelection
2984 in the same paragraph as the start/end.
2985
2986 @param bEnd Start counting from the end? From start otherwise.
2987 @param nOffset position of the character
2988 */
GetChar(bool bEnd,tools::Long nOffset)2989 sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset )
2990 {
2991 if( IsTableMode() ) // not possible in table mode
2992 return 0;
2993
2994 const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint()
2995 : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
2996 SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
2997 if( !pTextNd )
2998 return 0;
2999
3000 SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTextNd->getLayoutFrame(GetLayout())));
3001 if (!pFrame)
3002 {
3003 return 0;
3004 }
3005
3006 const sal_Int32 nPos(sal_Int32(pFrame->MapModelToViewPos(*pPos)));
3007 const OUString& rStr(pFrame->GetText());
3008 sal_Unicode cCh = 0;
3009
3010 if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength())
3011 cCh = rStr[nPos + nOffset];
3012
3013 return cCh;
3014 }
3015
3016 /** extend current SSelection by n characters
3017
3018 @param bEnd Start counting from the end? From start otherwise.
3019 @param nCount Number of characters.
3020 */
ExtendSelection(bool bEnd,sal_Int32 nCount)3021 bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount )
3022 {
3023 if( !m_pCurrentCursor->HasMark() || IsTableMode() )
3024 return false; // no selection
3025
3026 SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
3027 SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
3028 assert(pTextNd);
3029
3030 sal_Int32 nPos = pPos->GetContentIndex();
3031 if( bEnd )
3032 {
3033 if ((nPos + nCount) <= pTextNd->GetText().getLength())
3034 nPos = nPos + nCount;
3035 else
3036 return false; // not possible
3037 }
3038 else if( nPos >= nCount )
3039 nPos = nPos - nCount;
3040 else
3041 return false; // not possible anymore
3042
3043 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3044
3045 pPos->SetContent(nPos) ;
3046 UpdateCursor();
3047
3048 return true;
3049 }
3050
3051 /** Move visible cursor to given position in document.
3052
3053 @param rPt The position to move the visible cursor to.
3054 @return <false> if SPoint was corrected by the layout.
3055 */
SetVisibleCursor(const Point & rPt)3056 bool SwCursorShell::SetVisibleCursor( const Point &rPt )
3057 {
3058 CurrShell aCurr( this );
3059 Point aPt( rPt );
3060 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
3061 SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
3062 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3063 aTmpState.m_bRealHeight = true;
3064
3065 const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ );
3066
3067 SetInFrontOfLabel( false ); // #i27615#
3068
3069 // show only in TextNodes
3070 SwTextNode* pTextNd = aPos.GetNode().GetTextNode();
3071 if( !pTextNd )
3072 return false;
3073
3074 const SwSectionNode* pSectNd = pTextNd->FindSectionNode();
3075 if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() ||
3076 ( !IsReadOnlyAvailable() &&
3077 pSectNd->GetSection().IsProtectFlag())) )
3078 return false;
3079
3080 std::pair<Point, bool> const tmp(aPt, true);
3081 SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp);
3082 if ( Imp()->IsIdleAction() )
3083 pFrame->PrepareCursor();
3084 SwRect aTmp( m_aCharRect );
3085
3086 pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState );
3087
3088 // #i10137#
3089 if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() )
3090 return true;
3091
3092 m_pVisibleCursor->Hide(); // always hide visible cursor
3093 if( IsScrollMDI( this, m_aCharRect ))
3094 {
3095 MakeVisible( m_aCharRect );
3096 m_pCurrentCursor->Show(nullptr);
3097 }
3098
3099 {
3100 if( aTmpState.m_bRealHeight )
3101 m_aCursorHeight = aTmpState.m_aRealHeight;
3102 else
3103 {
3104 m_aCursorHeight.setX(0);
3105 m_aCursorHeight.setY(m_aCharRect.Height());
3106 }
3107
3108 m_pVisibleCursor->SetDragCursor();
3109 m_pVisibleCursor->Show(); // show again
3110 }
3111 return bRet;
3112 }
3113
GetVisibleCursor() const3114 SwVisibleCursor* SwCursorShell::GetVisibleCursor() const
3115 {
3116 return m_pVisibleCursor;
3117 }
3118
IsOverReadOnlyPos(const Point & rPt) const3119 bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const
3120 {
3121 Point aPt( rPt );
3122 SwPaM aPam( *m_pCurrentCursor->GetPoint() );
3123 GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt );
3124 // form view
3125 return aPam.HasReadonlySel(GetViewOptions()->IsFormView(), false);
3126 }
3127
3128 /** Get the number of elements in the ring of cursors
3129
3130 @param bAll If <false> get only spanned ones (= with selections) (Basic).
3131 */
GetCursorCnt(bool bAll) const3132 sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const
3133 {
3134 SwPaM* pTmp = GetCursor()->GetNext();
3135 sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() &&
3136 *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0;
3137 while( pTmp != m_pCurrentCursor )
3138 {
3139 if( bAll || ( pTmp->HasMark() &&
3140 *pTmp->GetPoint() != *pTmp->GetMark()))
3141 ++n;
3142 pTmp = pTmp->GetNext();
3143 }
3144 return n;
3145 }
3146
IsStartOfDoc() const3147 bool SwCursorShell::IsStartOfDoc() const
3148 {
3149 if( m_pCurrentCursor->GetPoint()->GetContentIndex() )
3150 return false;
3151
3152 // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd)
3153 SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 );
3154 if( !aIdx.GetNode().IsContentNode() )
3155 SwNodes::GoNext(&aIdx);
3156 return aIdx == m_pCurrentCursor->GetPoint()->GetNode();
3157 }
3158
IsEndOfDoc() const3159 bool SwCursorShell::IsEndOfDoc() const
3160 {
3161 SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 );
3162 SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
3163 if( !pCNd )
3164 pCNd = SwNodes::GoPrevious( &aIdx );
3165
3166 return aIdx == m_pCurrentCursor->GetPoint()->GetNode() && pCNd &&
3167 pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex();
3168 }
3169
3170 /** Invalidate cursors
3171
3172 Delete all created cursors, set table crsr and last crsr to their TextNode
3173 (or StartNode?). They will then all re-created at the next ::GetCursor() call.
3174
3175 This is needed for Drag&Drop/ Clipboard-paste in tables.
3176 */
ParkTableCursor()3177 bool SwCursorShell::ParkTableCursor()
3178 {
3179 if( !m_pTableCursor )
3180 return false;
3181
3182 m_pTableCursor->ParkCursor();
3183
3184 while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
3185 delete m_pCurrentCursor->GetNext();
3186
3187 // *always* move cursor's Point and Mark
3188 m_pCurrentCursor->DeleteMark();
3189 *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
3190
3191 return true;
3192 }
3193
ParkPams(SwPaM * pDelRg,SwShellCursor ** ppDelRing)3194 void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing )
3195 {
3196 auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition*
3197
3198 SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing;
3199
3200 // search over the whole ring
3201 bool bGoNext;
3202 do {
3203
3204 if (!pTmp)
3205 break;
3206
3207 auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
3208 // If a SPoint or GetMark are in a cursor area then cancel the old area.
3209 // During comparison keep in mind that End() is outside the area.
3210 if( *pStt <= *pTmpStt )
3211 {
3212 if( *pEnd > *pTmpStt ||
3213 ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
3214 pTmpDel = pTmp;
3215 }
3216 else
3217 if( *pStt < *pTmpEnd )
3218 pTmpDel = pTmp;
3219
3220 bGoNext = true;
3221 if (pTmpDel) // is the pam in the range -> delete
3222 {
3223 bool bDelete = true;
3224 if( *ppDelRing == pTmpDel )
3225 {
3226 if( *ppDelRing == m_pCurrentCursor )
3227 {
3228 bDelete = GoNextCursor();
3229 if( bDelete )
3230 {
3231 bGoNext = false;
3232 pTmp = pTmp->GetNext();
3233 }
3234 }
3235 else
3236 bDelete = false; // never delete the StackCursor
3237 }
3238
3239 if( bDelete )
3240 {
3241 if (pTmp == pTmpDel)
3242 pTmp = nullptr;
3243 delete pTmpDel; // invalidate old area
3244 }
3245 else
3246 {
3247 pTmpDel->GetPoint()->Assign(SwNodeOffset(0));
3248 pTmpDel->DeleteMark();
3249 }
3250 pTmpDel = nullptr;
3251 }
3252 if( bGoNext && pTmp )
3253 pTmp = pTmp->GetNext();
3254
3255 } while( !bGoNext || *ppDelRing != pTmp );
3256 }
3257
3258 //TODO: provide documentation
3259 /** Remove selections and additional cursors of all shells.
3260
3261 The remaining cursor of the shell is parked.
3262
3263 @param rIdx ???
3264 */
ParkCursor(const SwNode & rIdx)3265 void SwCursorShell::ParkCursor( const SwNode &rIdx )
3266 {
3267 const SwNode *pNode = &rIdx;
3268
3269 // create a new PaM
3270 SwPaM aNew( *GetCursor()->GetPoint() );
3271 if( pNode->GetStartNode() )
3272 {
3273 pNode = pNode->StartOfSectionNode();
3274 if( pNode->IsTableNode() )
3275 {
3276 // the given node is in a table, thus park cursor to table node
3277 // (outside of the table)
3278 aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
3279 }
3280 else
3281 // Also on the start node itself. Then we need to request the start
3282 // node always via its end node! (StartOfSelection of StartNode is
3283 // the parent)
3284 aNew.GetPoint()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() );
3285 }
3286 else
3287 aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
3288 aNew.SetMark();
3289 aNew.GetPoint()->Assign(*pNode->EndOfSectionNode());
3290
3291 // take care of all shells
3292 for(SwViewShell& rTmp : GetRingContainer())
3293 {
3294 if( auto pSh = dynamic_cast<SwCursorShell *>(&rTmp))
3295 {
3296 if (pSh->m_pStackCursor)
3297 pSh->ParkPams(&aNew, &pSh->m_pStackCursor);
3298
3299 pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor );
3300 if( pSh->m_pTableCursor )
3301 {
3302 // set table cursor always to 0 and the current one always to
3303 // the beginning of the table
3304 SwPaM* pTCursor = pSh->GetTableCrs();
3305 SwNode* pTableNd = pTCursor->GetPoint()->GetNode().FindTableNode();
3306 if ( pTableNd )
3307 {
3308 pTCursor->GetPoint()->Assign(SwNodeOffset(0));
3309 pTCursor->DeleteMark();
3310 pSh->m_pCurrentCursor->GetPoint()->Assign( *pTableNd );
3311 }
3312 }
3313 }
3314 }
3315 }
3316
3317 /** Copy constructor
3318
3319 Copy cursor position and add it to the ring.
3320 All views of a document are in the ring of the shell.
3321 */
SwCursorShell(SwCursorShell & rShell,vcl::Window * pInitWin)3322 SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin )
3323 : SwViewShell( rShell, pInitWin )
3324 , sw::BroadcastingModify()
3325 , m_pStackCursor( nullptr )
3326 , m_pBlockCursor( nullptr )
3327 , m_pTableCursor( nullptr )
3328 , m_pBoxIdx( nullptr )
3329 , m_pBoxPtr( nullptr )
3330 , m_nUpDownX(0)
3331 , m_nLeftFramePos(0)
3332 , m_nCurrentNode(0)
3333 , m_nCurrentContent(0)
3334 , m_nCurrentNdTyp(SwNodeType::NONE)
3335 , m_nCursorMove( 0 )
3336 , m_eMvState( CursorMoveState::NONE )
3337 , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
3338 , m_nMarkedListLevel( 0 )
3339 , m_oldColFrame(nullptr)
3340 , m_aLayoutIdle("SwCursorShell m_aLayoutIdle")
3341 {
3342 CurrShell aCurr( this );
3343 // only keep the position of the current cursor of the copy shell
3344 m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) );
3345 m_pCurrentCursor->GetPointContentNode()->Add(*this);
3346
3347 m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
3348 m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
3349 m_bOverwriteCursor = false;
3350 m_bSendAccessibleCursorEvents = true;
3351 m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
3352 m_bSVCursorVis = true;
3353 m_bSetCursorInReadOnly = true;
3354 m_pVisibleCursor = new SwVisibleCursor( this );
3355 m_bMacroExecAllowed = rShell.IsMacroExecAllowed();
3356
3357 m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
3358 m_aLayoutIdle.SetInvokeHandler(LINK(this, SwCursorShell, DoLayoutIdle));
3359 }
3360
3361 /// default constructor
SwCursorShell(SwDoc & rDoc,vcl::Window * pInitWin,const SwViewOption * pInitOpt)3362 SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin,
3363 const SwViewOption *pInitOpt )
3364 : SwViewShell( rDoc, pInitWin, pInitOpt )
3365 , sw::BroadcastingModify()
3366 , m_pStackCursor( nullptr )
3367 , m_pBlockCursor( nullptr )
3368 , m_pTableCursor( nullptr )
3369 , m_pBoxIdx( nullptr )
3370 , m_pBoxPtr( nullptr )
3371 , m_nUpDownX(0)
3372 , m_nLeftFramePos(0)
3373 , m_nCurrentNode(0)
3374 , m_nCurrentContent(0)
3375 , m_nCurrentNdTyp(SwNodeType::NONE)
3376 , m_nCursorMove( 0 )
3377 , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint
3378 , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
3379 , m_nMarkedListLevel( 0 )
3380 , m_oldColFrame(nullptr)
3381 , m_aLayoutIdle("SwCursorShell m_aLayoutIdle")
3382 {
3383 CurrShell aCurr( this );
3384 // create initial cursor and set it to first content position
3385 SwNodes& rNds = rDoc.GetNodes();
3386
3387 SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() );
3388 SwContentNode* pCNd = SwNodes::GoNext(&aNodeIdx); // go to the first ContentNode
3389
3390 m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, pCNd, 0 ) );
3391
3392 // Register shell as dependent at current node. As a result all attribute
3393 // changes can be forwarded via the Link.
3394 pCNd->Add(*this);
3395
3396 m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
3397 m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
3398 m_bOverwriteCursor = false;
3399 m_bSendAccessibleCursorEvents = true;
3400 m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
3401 m_bSVCursorVis = true;
3402 m_bSetCursorInReadOnly = true;
3403
3404 m_pVisibleCursor = new SwVisibleCursor( this );
3405 m_bMacroExecAllowed = true;
3406
3407 m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
3408 m_aLayoutIdle.SetInvokeHandler(LINK(this, SwCursorShell, DoLayoutIdle));
3409 }
3410
~SwCursorShell()3411 SwCursorShell::~SwCursorShell()
3412 {
3413 m_aLayoutIdle.Stop();
3414
3415 // if it is not the last view then at least the field should be updated
3416 if( !unique() )
3417 CheckTableBoxContent( m_pCurrentCursor->GetPoint() );
3418 else
3419 ClearTableBoxContent();
3420
3421 delete m_pVisibleCursor;
3422 delete m_pBlockCursor;
3423 delete m_pTableCursor;
3424
3425 // release cursors
3426 while(m_pCurrentCursor->GetNext() != m_pCurrentCursor)
3427 delete m_pCurrentCursor->GetNext();
3428 delete m_pCurrentCursor;
3429
3430 // free stack
3431 if (m_pStackCursor)
3432 {
3433 while (m_pStackCursor->GetNext() != m_pStackCursor)
3434 delete m_pStackCursor->GetNext();
3435 delete m_pStackCursor;
3436 }
3437
3438 // #i54025# - do not give a HTML parser that might potentially hang as
3439 // a client at the cursor shell the chance to hang itself on a TextNode
3440 EndListeningAll();
3441 }
3442
IMPL_LINK_NOARG(SwCursorShell,DoLayoutIdle,Timer *,void)3443 IMPL_LINK_NOARG(SwCursorShell, DoLayoutIdle, Timer*, void) { LayoutIdle(); }
3444
getShellCursor(bool bBlock)3445 SwShellCursor* SwCursorShell::getShellCursor( bool bBlock )
3446 {
3447 if( m_pTableCursor )
3448 return m_pTableCursor;
3449 if( m_pBlockCursor && bBlock )
3450 return &m_pBlockCursor->getShellCursor();
3451 return m_pCurrentCursor;
3452 }
3453
3454 /** Should WaitPtr be switched on for the clipboard?
3455
3456 Wait for TableMode, multiple selections and more than x selected paragraphs.
3457 */
ShouldWait() const3458 bool SwCursorShell::ShouldWait() const
3459 {
3460 if ( IsTableMode() || GetCursorCnt() > 1 )
3461 return true;
3462
3463 if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() )
3464 return true;
3465
3466 SwPaM* pPam = GetCursor();
3467 return pPam->Start()->GetNodeIndex() + SwNodeOffset(10) <
3468 pPam->End()->GetNodeIndex();
3469 }
3470
UpdateTableSelBoxes()3471 size_t SwCursorShell::UpdateTableSelBoxes()
3472 {
3473 if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount()))
3474 {
3475 GetLayout()->MakeTableCursors( *m_pTableCursor );
3476 }
3477 return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0;
3478 }
3479
3480 /// show the current selected "object"
MakeSelVisible()3481 void SwCursorShell::MakeSelVisible()
3482 {
3483 OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" );
3484 if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() )
3485 {
3486 SwRect aTmp( m_aCharRect );
3487 tools::Long nDiff = m_aCharRect.Height() - VisArea().Height();
3488 if( nDiff < m_aCursorHeight.getX() )
3489 aTmp.Top( nDiff + m_aCharRect.Top() );
3490 else
3491 {
3492 aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() );
3493 aTmp.Height( m_aCursorHeight.getY() );
3494 }
3495 if( !aTmp.HasArea() )
3496 {
3497 aTmp.AddHeight(1 );
3498 aTmp.AddWidth(1 );
3499 }
3500 MakeVisible( aTmp );
3501 }
3502 else
3503 {
3504 if( m_aCharRect.HasArea() )
3505 MakeVisible( m_aCharRect );
3506 else
3507 {
3508 SwRect aTmp( m_aCharRect );
3509 aTmp.AddHeight(1 );
3510 aTmp.AddWidth(1 );
3511 MakeVisible( aTmp );
3512 }
3513 }
3514 }
3515
3516 /// search a valid content position (not protected/hidden)
FindValidContentNode(bool bOnlyText)3517 bool SwCursorShell::FindValidContentNode( bool bOnlyText )
3518 {
3519 if( m_pTableCursor )
3520 {
3521 assert(!"Did not remove table selection!");
3522 return false;
3523 }
3524
3525 // #i45129# - everything is allowed in UI-readonly
3526 if( !m_bAllProtect && GetDoc()->GetDocShell() &&
3527 GetDoc()->GetDocShell()->IsReadOnlyUI() )
3528 return true;
3529
3530 if( m_pCurrentCursor->HasMark() && !mbSelectAll )
3531 ClearMark();
3532
3533 // first check for frames
3534 SwPosition& rNdPos = *m_pCurrentCursor->GetPoint();
3535 SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup
3536 SwNodes& rNds = mxDoc->GetNodes();
3537 SwContentNode* pCNd = rNdPos.GetNode().GetContentNode();
3538 const SwContentFrame * pFrame;
3539
3540 if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) &&
3541 !IsReadOnlyAvailable() && pFrame->IsProtected() &&
3542 nNdIdx < rNds.GetEndOfExtras().GetIndex() )
3543 {
3544 // skip protected frame
3545 SwPaM aPam( *m_pCurrentCursor->GetPoint() );
3546 aPam.SetMark();
3547 aPam.GetMark()->Assign( rNds.GetEndOfContent() );
3548 aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() );
3549
3550 bool bFirst = false;
3551 if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward )))
3552 {
3553 aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() );
3554 pCNd = ::GetNode( aPam, bFirst, fnMoveBackward );
3555 }
3556
3557 if( !pCNd ) // should *never* happen
3558 {
3559 rNdPos.Assign(nNdIdx); // back to old node
3560 return false;
3561 }
3562 *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
3563 }
3564 else if( bOnlyText && pCNd && pCNd->IsNoTextNode() )
3565 {
3566 // set to beginning of document
3567 rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() );
3568 SwNodes::GoNext(&rNdPos);
3569 nNdIdx = rNdPos.GetNodeIndex();
3570 }
3571
3572 bool bOk = true;
3573
3574 // #i9059# cursor may not stand in protected cells
3575 // (unless cursor in protected areas is OK.)
3576 const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode();
3577 if( !IsReadOnlyAvailable() &&
3578 pTableNode != nullptr && rNdPos.GetNode().IsProtect() )
3579 {
3580 // we're in a table, and we're in a protected area, so we're
3581 // probably in a protected cell.
3582
3583 // move forward into non-protected area.
3584 SwPaM aPam( rNdPos.GetNode(), 0 );
3585 while( aPam.GetPointNode().IsProtect() &&
3586 aPam.Move( fnMoveForward, GoInContent ) )
3587 ; // nothing to do in the loop; the aPam.Move does the moving!
3588
3589 // didn't work? then go backwards!
3590 if( aPam.GetPointNode().IsProtect() )
3591 {
3592 SwPaM aTmpPaM( rNdPos.GetNode(), 0 );
3593 aPam = aTmpPaM;
3594 while( aPam.GetPointNode().IsProtect() &&
3595 aPam.Move( fnMoveBackward, GoInContent ) )
3596 ; // nothing to do in the loop; the aPam.Move does the moving!
3597 }
3598
3599 // if we're successful, set the new position
3600 if( ! aPam.GetPointNode().IsProtect() )
3601 {
3602 *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
3603 }
3604 }
3605
3606 // in a protected frame
3607 const SwSectionNode* pSectNd = rNdPos.GetNode().FindSectionNode();
3608 if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
3609 ( !IsReadOnlyAvailable() &&
3610 pSectNd->GetSection().IsProtectFlag() )) )
3611 {
3612 bOk = false;
3613 bool bGoNextSection = true;
3614 for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt )
3615 {
3616 bool bContinue;
3617 do {
3618 bContinue = false;
3619 for (;;)
3620 {
3621 if (bGoNextSection)
3622 pCNd = SwNodes::GoNextSection( &rNdPos,
3623 true, !IsReadOnlyAvailable() );
3624 else
3625 pCNd = SwNodes::GoPrevSection( &rNdPos,
3626 true, !IsReadOnlyAvailable() );
3627 if ( pCNd == nullptr) break;
3628 // moved inside a table -> check if it is protected
3629 if( pCNd->FindTableNode() )
3630 {
3631 SwCallLink aTmp( *this );
3632 SwCursorSaveState aSaveState( *m_pCurrentCursor );
3633 aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR
3634 if( !m_pCurrentCursor->IsInProtectTable( true ) )
3635 {
3636 const SwSectionNode* pSNd = pCNd->FindSectionNode();
3637 if( !pSNd || !pSNd->GetSection().IsHiddenFlag()
3638 || (!IsReadOnlyAvailable() &&
3639 pSNd->GetSection().IsProtectFlag() ))
3640 {
3641 bOk = true;
3642 break; // found non-protected cell
3643 }
3644 continue; // continue search
3645 }
3646 }
3647 else
3648 {
3649 bOk = true;
3650 break; // found non-protected cell
3651 }
3652 }
3653
3654 if( bOk && rNdPos.GetNodeIndex() < rNds.GetEndOfExtras().GetIndex() )
3655 {
3656 // also check for Fly - might be protected as well
3657 pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr);
3658 if (nullptr == pFrame ||
3659 ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) ||
3660 ( bOnlyText && pCNd->IsNoTextNode() ) )
3661 {
3662 // continue search
3663 bOk = false;
3664 bContinue = true;
3665 }
3666 }
3667 } while( bContinue );
3668
3669 if( !bOk )
3670 {
3671 if( !nLoopCnt )
3672 bGoNextSection = false;
3673 rNdPos.Assign( nNdIdx );
3674 }
3675 }
3676 }
3677 if( bOk )
3678 {
3679 pCNd = rNdPos.GetNode().GetContentNode();
3680 const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0;
3681 m_pCurrentCursor->GetPoint()->SetContent( nContent );
3682 }
3683 else
3684 {
3685 pCNd = rNdPos.GetNode().GetContentNode();
3686 // if cursor in hidden frame, always move it
3687 if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr))
3688 {
3689 SwCursorMoveState aTmpState( CursorMoveState::NONE );
3690 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3691 GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(),
3692 &aTmpState );
3693 }
3694 }
3695 return bOk;
3696 }
3697
IsCursorReadonly() const3698 bool SwCursorShell::IsCursorReadonly() const
3699 {
3700 if ( GetViewOptions()->IsReadonly() ||
3701 GetViewOptions()->IsFormView() /* Formula view */ )
3702 {
3703 SwFrame *pFrame = GetCurrFrame( false );
3704 const SwFlyFrame* pFly;
3705 const SwSection* pSection;
3706
3707 if( pFrame && pFrame->IsInFly() &&
3708 (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
3709 pFly->Lower() &&
3710 !pFly->Lower()->IsNoTextFrame() &&
3711 !GetDrawView()->GetMarkedObjectList().GetMarkCount() )
3712 {
3713 return false;
3714 }
3715 // edit in readonly sections
3716 else if ( pFrame && pFrame->IsInSct() &&
3717 nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) &&
3718 pSection->IsEditInReadonlyFlag() )
3719 {
3720 return false;
3721 }
3722 else if ( !IsMultiSelection() && CursorInsideInputField() )
3723 {
3724 return false;
3725 }
3726
3727 return true;
3728 }
3729 return false;
3730 }
3731
3732 /// is the cursor allowed to enter ReadOnly sections?
SetReadOnlyAvailable(bool bFlag)3733 void SwCursorShell::SetReadOnlyAvailable( bool bFlag )
3734 {
3735 // *never* switch in GlobalDoc
3736 if( (!GetDoc()->GetDocShell() ||
3737 dynamic_cast<const SwGlobalDocShell*>(GetDoc()->GetDocShell()) == nullptr ) &&
3738 bFlag != m_bSetCursorInReadOnly )
3739 {
3740 // If the flag is switched off then all selections need to be
3741 // invalidated. Otherwise we would trust that nothing protected is selected.
3742 if( !bFlag )
3743 {
3744 ClearMark();
3745 }
3746 m_bSetCursorInReadOnly = bFlag;
3747 UpdateCursor();
3748 }
3749 }
3750
HasReadonlySel(bool const isReplace) const3751 bool SwCursorShell::HasReadonlySel(bool const isReplace) const
3752 {
3753 // Treat selections that span over start or end of paragraph of an outline node
3754 // with folded outline content as read-only.
3755 if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
3756 {
3757 SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
3758 if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
3759 return true;
3760 }
3761 bool bRet = false;
3762 // If protected area is to be ignored, then selections are never read-only.
3763 if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() ||
3764 GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) &&
3765 !SwViewOption::IsIgnoreProtectedArea())
3766 {
3767 if ( m_pTableCursor != nullptr )
3768 {
3769 // TODO: handling when a table cell (cells) is selected
3770 bRet = m_pTableCursor->HasReadOnlyBoxSel()
3771 || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace);
3772 }
3773 else
3774 {
3775 for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
3776 {
3777 if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace))
3778 {
3779 bRet = true;
3780 break;
3781 }
3782 }
3783 }
3784 }
3785 return bRet;
3786 }
3787
HasHiddenSections() const3788 bool SwCursorShell::HasHiddenSections() const
3789 {
3790 // Treat selections that span over start or end of paragraph of an outline node
3791 // with folded outline content as read-only.
3792 if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
3793 {
3794 SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
3795 if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
3796 return true;
3797 }
3798 bool bRet = false;
3799
3800 if ( m_pTableCursor != nullptr )
3801 {
3802 bRet = m_pTableCursor->HasHiddenBoxSel()
3803 || m_pTableCursor->HasHiddenSections();
3804 }
3805 else
3806 {
3807 for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
3808 {
3809 if (rCursor.HasHiddenSections())
3810 {
3811 bRet = true;
3812 break;
3813 }
3814 }
3815 }
3816
3817 return bRet;
3818 }
3819
IsSelFullPara() const3820 bool SwCursorShell::IsSelFullPara() const
3821 {
3822 bool bRet = false;
3823
3824 if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
3825 m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() )
3826 {
3827 sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex();
3828 sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex();
3829 if( nStt > nEnd )
3830 std::swap( nStt, nEnd );
3831 const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode();
3832 bRet = pCNd && !nStt && nEnd == pCNd->Len();
3833 }
3834 return bRet;
3835 }
3836
GetTextDirection(const Point * pPt) const3837 SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const
3838 {
3839 SwPosition aPos( *m_pCurrentCursor->GetPoint() );
3840 Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() );
3841 if( pPt )
3842 {
3843 SwCursorMoveState aTmpState( CursorMoveState::NONE );
3844 aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
3845
3846 GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState );
3847 }
3848
3849 return mxDoc->GetTextDirection( aPos, &aPt );
3850 }
3851
IsInVerticalText(const Point * pPt) const3852 bool SwCursorShell::IsInVerticalText( const Point* pPt ) const
3853 {
3854 const SvxFrameDirection nDir = GetTextDirection( pPt );
3855 return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir
3856 || nDir == SvxFrameDirection::Vertical_LR_BT;
3857 }
3858
IsInRightToLeftText() const3859 bool SwCursorShell::IsInRightToLeftText() const
3860 {
3861 const SvxFrameDirection nDir = GetTextDirection();
3862 // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in
3863 // vertical environment
3864 return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir;
3865 }
3866
3867 /// If the current cursor position is inside a hidden range true is returned. If bSelect is
3868 /// true, the hidden range is selected. If bSelect is false, the hidden range is not selected.
IsInHiddenRange(const bool bSelect)3869 bool SwCursorShell::IsInHiddenRange(const bool bSelect)
3870 {
3871 bool bRet = false;
3872 if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() )
3873 {
3874 SwPosition& rPt = *m_pCurrentCursor->GetPoint();
3875 const SwTextNode* pNode = rPt.GetNode().GetTextNode();
3876 if ( pNode )
3877 {
3878 const sal_Int32 nPos = rPt.GetContentIndex();
3879
3880 // check if nPos is in hidden range
3881 sal_Int32 nHiddenStart;
3882 sal_Int32 nHiddenEnd;
3883 SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd );
3884 if ( COMPLETE_STRING != nHiddenStart )
3885 {
3886 if (bSelect)
3887 {
3888 // make selection:
3889 m_pCurrentCursor->SetMark();
3890 m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd);
3891 }
3892 bRet = true;
3893 }
3894 }
3895 }
3896
3897 return bRet;
3898 }
3899
Find_Text(const i18nutil::SearchOptions2 & rSearchOpt,bool bSearchInNotes,SwDocPositions eStart,SwDocPositions eEnd,bool & bCancel,FindRanges eRng,bool bReplace)3900 sal_Int32 SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt,
3901 bool bSearchInNotes,
3902 SwDocPositions eStart, SwDocPositions eEnd,
3903 bool& bCancel,
3904 FindRanges eRng,
3905 bool bReplace )
3906 {
3907 if( m_pTableCursor )
3908 GetCursor();
3909 delete m_pTableCursor;
3910 m_pTableCursor = nullptr;
3911 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3912 sal_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd,
3913 bCancel, eRng, bReplace, GetLayout());
3914 if( nRet || bCancel )
3915 UpdateCursor();
3916 return nRet;
3917 }
3918
FindFormat(const SwTextFormatColl & rFormatColl,SwDocPositions eStart,SwDocPositions eEnd,bool & bCancel,FindRanges eRng,const SwTextFormatColl * pReplFormat)3919 sal_Int32 SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl,
3920 SwDocPositions eStart, SwDocPositions eEnd,
3921 bool& bCancel,
3922 FindRanges eRng,
3923 const SwTextFormatColl* pReplFormat )
3924 {
3925 if( m_pTableCursor )
3926 GetCursor();
3927 delete m_pTableCursor;
3928 m_pTableCursor = nullptr;
3929 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3930 sal_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng,
3931 pReplFormat );
3932 if( nRet )
3933 UpdateCursor();
3934 return nRet;
3935 }
3936
FindAttrs(const SfxItemSet & rSet,bool bNoCollections,SwDocPositions eStart,SwDocPositions eEnd,bool & bCancel,FindRanges eRng,const i18nutil::SearchOptions2 * pSearchOpt,const SfxItemSet * rReplSet)3937 sal_Int32 SwCursorShell::FindAttrs( const SfxItemSet& rSet,
3938 bool bNoCollections,
3939 SwDocPositions eStart, SwDocPositions eEnd,
3940 bool& bCancel,
3941 FindRanges eRng,
3942 const i18nutil::SearchOptions2* pSearchOpt,
3943 const SfxItemSet* rReplSet )
3944 {
3945 if( m_pTableCursor )
3946 GetCursor();
3947 delete m_pTableCursor;
3948 m_pTableCursor = nullptr;
3949 SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
3950 sal_Int32 nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd,
3951 bCancel, eRng, pSearchOpt, rReplSet, GetLayout());
3952 if( nRet )
3953 UpdateCursor();
3954 return nRet;
3955 }
3956
SetSelection(const SwPaM & rCursor)3957 void SwCursorShell::SetSelection( const SwPaM& rCursor )
3958 {
3959 StartAction();
3960 SwCursor* pCursor = GetCursor();
3961 *pCursor->GetPoint() = *rCursor.GetPoint();
3962 if(rCursor.GetNext() != &rCursor)
3963 {
3964 const SwPaM *_pStartCursor = rCursor.GetNext();
3965 do
3966 {
3967 SwPaM* pCurrentCursor = CreateCursor();
3968 *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint();
3969 if(_pStartCursor->HasMark())
3970 {
3971 pCurrentCursor->SetMark();
3972 *pCurrentCursor->GetMark() = *_pStartCursor->GetMark();
3973 }
3974 } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor );
3975 }
3976 // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current
3977 // cursor; therefore set current cursor's mark only after creating all other cursors
3978 if (rCursor.HasMark())
3979 {
3980 pCursor->SetMark();
3981 *pCursor->GetMark() = *rCursor.GetMark();
3982 }
3983 EndAction();
3984 }
3985
lcl_NodeContext(const SwNode & rNode)3986 static const SwStartNode* lcl_NodeContext( const SwNode& rNode )
3987 {
3988 const SwStartNode *pRet = rNode.StartOfSectionNode();
3989 while( pRet->IsSectionNode() || pRet->IsTableNode() ||
3990 pRet->GetStartNodeType() == SwTableBoxStartNode )
3991 {
3992 pRet = pRet->StartOfSectionNode();
3993 }
3994 return pRet;
3995 }
3996
3997 /**
3998 Checks if a position is valid. To be valid the position's node must
3999 be a content node and the content must not be unregistered.
4000
4001 @param aPos the position to check.
4002 */
sw_PosOk(const SwPosition & aPos)4003 static bool sw_PosOk(const SwPosition & aPos)
4004 {
4005 return nullptr != aPos.GetNode().GetContentNode() &&
4006 aPos.GetContentNode();
4007 }
4008
4009 /**
4010 Checks if a PaM is valid. For a PaM to be valid its point must be
4011 valid. Additionally if the PaM has a mark this has to be valid, too.
4012
4013 @param aPam the PaM to check
4014 */
lcl_CursorOk(SwPaM & aPam)4015 static bool lcl_CursorOk(SwPaM & aPam)
4016 {
4017 return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark()
4018 || sw_PosOk(*aPam.GetMark()));
4019 }
4020
ClearUpCursors()4021 void SwCursorShell::ClearUpCursors()
4022 {
4023 // start of the ring
4024 SwPaM * pStartCursor = GetCursor();
4025 // start loop with second entry of the ring
4026 SwPaM * pCursor = pStartCursor->GetNext();
4027 SwPaM * pTmpCursor;
4028 bool bChanged = false;
4029
4030 // For all entries in the ring except the start entry delete the entry if
4031 // it is invalid.
4032 while (pCursor != pStartCursor)
4033 {
4034 pTmpCursor = pCursor->GetNext();
4035 if ( ! lcl_CursorOk(*pCursor))
4036 {
4037 delete pCursor;
4038 bChanged = true;
4039 }
4040 pCursor = pTmpCursor;
4041 }
4042
4043 if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) )
4044 {
4045 pStartCursor->DeleteMark();
4046 bChanged = true;
4047 }
4048 if (pStartCursor->GetPoint()->GetNode().IsTableNode())
4049 {
4050 // tdf#106959: When cursor points to start of a table, the proper content
4051 // node is the first one inside the table, not the previous one
4052 SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode());
4053 if (SwNode* pNode = SwNodes::GoNext(&aIdx))
4054 {
4055 SwPaM aTmpPam(*pNode);
4056 *pStartCursor = aTmpPam;
4057 bChanged = true;
4058 }
4059 }
4060 if( !sw_PosOk( *pStartCursor->GetPoint() ) )
4061 {
4062 SwNodes & aNodes = GetDoc()->GetNodes();
4063 const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->GetNode() );
4064 SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() );
4065 SwNode * pNode = SwNodes::GoPrevious(&aIdx);
4066 if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
4067 {
4068 pNode = SwNodes::GoNext(&aIdx);
4069 if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
4070 {
4071 // If the start entry of the ring is invalid replace it with a
4072 // cursor pointing to the beginning of the first content node in the
4073 // document.
4074 aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode());
4075 pNode = SwNodes::GoNext(&aIdx);
4076 }
4077 }
4078 bool bFound = (pNode != nullptr);
4079
4080 assert(bFound);
4081
4082 if (bFound)
4083 {
4084 SwPaM aTmpPam(*pNode);
4085 *pStartCursor = aTmpPam;
4086 }
4087
4088 bChanged = true;
4089 }
4090
4091 // If at least one of the cursors in the ring have been deleted or replaced,
4092 // remove the table cursor.
4093 if (m_pTableCursor != nullptr && bChanged)
4094 TableCursorToCursor();
4095 }
4096
GetCursorDescr() const4097 OUString SwCursorShell::GetCursorDescr() const
4098 {
4099 OUString aResult;
4100
4101 if (IsMultiSelection())
4102 aResult += SwResId(STR_MULTISEL);
4103 else
4104 aResult = SwDoc::GetPaMDescr(*GetCursor());
4105
4106 return aResult;
4107 }
4108
dumpAsXml(xmlTextWriterPtr pWriter) const4109 void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const
4110 {
4111 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell"));
4112
4113 SwViewShell::dumpAsXml(pWriter);
4114
4115 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor"));
4116 for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer())
4117 rPaM.dumpAsXml(pWriter);
4118 (void)xmlTextWriterEndElement(pWriter);
4119
4120 (void)xmlTextWriterEndElement(pWriter);
4121 }
4122
lcl_FillRecognizerData(std::vector<OUString> & rSmartTagTypes,uno::Sequence<uno::Reference<container::XStringKeyMap>> & rStringKeyMaps,const SwWrongList & rSmartTagList,sal_Int32 nCurrent)4123 static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes,
4124 uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
4125 const SwWrongList& rSmartTagList, sal_Int32 nCurrent )
4126 {
4127 // Insert smart tag information
4128 std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps;
4129
4130 for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i )
4131 {
4132 const sal_Int32 nSTPos = rSmartTagList.Pos( i );
4133 const sal_Int32 nSTLen = rSmartTagList.Len( i );
4134
4135 if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen )
4136 {
4137 const SwWrongArea* pArea = rSmartTagList.GetElement( i );
4138 if ( pArea )
4139 {
4140 rSmartTagTypes.push_back( pArea->maType );
4141 aStringKeyMaps.push_back( pArea->mxPropertyBag );
4142 }
4143 }
4144 }
4145
4146 if ( !rSmartTagTypes.empty() )
4147 {
4148 rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps);
4149 }
4150 }
4151
lcl_FillTextRange(uno::Reference<text::XTextRange> & rRange,SwTextNode & rNode,sal_Int32 nBegin,sal_Int32 nLen)4152 static void lcl_FillTextRange( uno::Reference<text::XTextRange>& rRange,
4153 SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen )
4154 {
4155 // create SwPosition for nStartIndex
4156 SwPosition aStartPos( rNode, nBegin );
4157 // create SwPosition for nEndIndex
4158 SwPosition aEndPos( rNode, nBegin + nLen );
4159
4160 const rtl::Reference<SwXTextRange> xRange =
4161 SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos);
4162
4163 rRange = xRange;
4164 }
4165
GetSmartTagTerm(std::vector<OUString> & rSmartTagTypes,uno::Sequence<uno::Reference<container::XStringKeyMap>> & rStringKeyMaps,uno::Reference<text::XTextRange> & rRange) const4166 void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes,
4167 uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
4168 uno::Reference< text::XTextRange>& rRange ) const
4169 {
4170 if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() )
4171 return;
4172
4173 SwPaM* pCursor = GetCursor();
4174 SwPosition aPos(*pCursor->Start());
4175 SwTextNode *pNode = aPos.GetNode().GetTextNode();
4176 if ( !pNode || pNode->IsInProtectSect() )
4177 return;
4178
4179 const SwWrongList *pSmartTagList = pNode->GetSmartTags();
4180 if ( !pSmartTagList )
4181 return;
4182
4183 sal_Int32 nCurrent = aPos.GetContentIndex();
4184 sal_Int32 nBegin = nCurrent;
4185 sal_Int32 nLen = 1;
4186
4187 if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
4188 return;
4189
4190 const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin );
4191 const SwWrongList* pSubList = pSmartTagList->SubList( nIndex );
4192 if ( pSubList )
4193 {
4194 pSmartTagList = pSubList;
4195 nCurrent = 0;
4196 }
4197
4198 lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent );
4199 lcl_FillTextRange( rRange, *pNode, nBegin, nLen );
4200 }
4201
4202 // see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
GetSmartTagRect(const Point & rPt,SwRect & rSelectRect)4203 void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect )
4204 {
4205 SwPaM* pCursor = GetCursor();
4206 SwPosition aPos( *pCursor->GetPoint() );
4207 Point aPt( rPt );
4208 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
4209 eTmpState.m_bPosMatchesBounds = true; // treat last half of character same as first half
4210 SwSpecialPos aSpecialPos;
4211 eTmpState.m_pSpecialPos = &aSpecialPos;
4212 SwTextNode *pNode;
4213 const SwWrongList *pSmartTagList;
4214
4215 if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) )
4216 return;
4217 pNode = aPos.GetNode().GetTextNode();
4218 if( !pNode )
4219 return;
4220 pSmartTagList = pNode->GetSmartTags();
4221 if( !pSmartTagList )
4222 return;
4223 if( pNode->IsInProtectSect() )
4224 return;
4225
4226 sal_Int32 nBegin = aPos.GetContentIndex();
4227 sal_Int32 nLen = 1;
4228
4229 if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
4230 return;
4231
4232 // get smarttag word
4233 OUString aText( pNode->GetText().copy(nBegin, nLen) );
4234
4235 //save the start and end positions of the line and the starting point
4236 Push();
4237 LeftMargin();
4238 const sal_Int32 nLineStart = GetCursor()->GetPoint()->GetContentIndex();
4239 RightMargin();
4240 const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex();
4241 Pop(PopMode::DeleteCurrent);
4242
4243 // make sure the selection build later from the data below does not
4244 // include "in word" character to the left and right in order to
4245 // preserve those. Therefore count those "in words" in order to
4246 // modify the selection accordingly.
4247 const sal_Unicode* pChar = aText.getStr();
4248 sal_Int32 nLeft = 0;
4249 while (*pChar++ == CH_TXTATR_INWORD)
4250 ++nLeft;
4251 pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
4252 sal_Int32 nRight = 0;
4253 while (pChar && *pChar-- == CH_TXTATR_INWORD)
4254 ++nRight;
4255
4256 aPos.SetContent( nBegin + nLeft );
4257 pCursor = GetCursor();
4258 *pCursor->GetPoint() = aPos;
4259 pCursor->SetMark();
4260 ExtendSelection( true, nLen - nLeft - nRight );
4261 // do not determine the rectangle in the current line
4262 const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
4263 // take one less than the line end - otherwise the next line would
4264 // be calculated
4265 const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd);
4266 Push();
4267 pCursor->DeleteMark();
4268 SwPosition& rPos = *GetCursor()->GetPoint();
4269 rPos.SetContent( nWordStart );
4270 SwRect aStartRect;
4271 SwCursorMoveState aState;
4272 aState.m_bRealWidth = true;
4273 SwContentNode* pContentNode = pCursor->GetPointContentNode();
4274 std::pair<Point, bool> const tmp(rPt, false);
4275 SwContentFrame *pContentFrame = pContentNode->getLayoutFrame(
4276 GetLayout(), pCursor->GetPoint(), &tmp);
4277
4278 pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
4279 rPos.SetContent( nWordEnd - 1 );
4280 SwRect aEndRect;
4281 pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
4282 rSelectRect = aStartRect.Union( aEndRect );
4283 Pop(PopMode::DeleteCurrent);
4284 }
4285
4286 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4287