/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BlockCursor.hxx" #include #include #include #include #include #include #include #include #include #include "callnk.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; using namespace util; /** * Check if pCurrentCursor points into already existing ranges and delete those. * @param Pointer to SwCursor object */ static void CheckRange( SwCursor* pCurrentCursor ) { auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition* SwPaM *pTmpDel = nullptr, *pTmp = pCurrentCursor->GetNext(); // Search the complete ring while( pTmp != pCurrentCursor ) { auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition* if( *pStt <= *pTmpStt ) { if( *pEnd > *pTmpStt || ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) pTmpDel = pTmp; } else if( *pStt < *pTmpEnd ) pTmpDel = pTmp; // If Point or Mark is within the Cursor range, we need to remove the old // range. Take note that Point does not belong to the range anymore. pTmp = pTmp->GetNext(); delete pTmpDel; // Remove old range pTmpDel = nullptr; } } // SwCursorShell /** * Add a copy of current cursor, append it after current, and collapse current cursor. * @return - Returns a newly created copy of current cursor. */ SwPaM * SwCursorShell::CreateCursor() { // don't create new Cursor with active table Selection assert(!IsTableMode()); // ensure that m_pCurrentCursor is valid; if it's invalid it would be // copied to pNew and then pNew would be deleted in UpdateCursor() below ClearUpCursors(); // New cursor as copy of current one. Add to the ring. // Links point to previously created one, ie forward. SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); // Hide PaM logically, to avoid undoing the inverting from // copied PaM (#i75172#) pNew->swapContent(*m_pCurrentCursor); m_pCurrentCursor->DeleteMark(); UpdateCursor( SwCursorShell::SCROLLWIN ); return pNew; } /** * Delete current Cursor, making the following one the current. * Note, this function does not delete anything if there is no other cursor. * @return - returns true if there was another cursor and we deleted one. */ void SwCursorShell::DestroyCursor() { // don't delete Cursor with active table Selection assert(!IsTableMode()); // Is there a next one? Don't do anything if not. if(!m_pCurrentCursor->IsMultiSelection()) return; SwCallLink aLk( *this ); // watch Cursor-Moves SwCursor* pNextCursor = static_cast(m_pCurrentCursor->GetNext()); delete m_pCurrentCursor; m_pCurrentCursor = dynamic_cast(pNextCursor); UpdateCursor(); } /** * Create and return a new shell cursor. * Simply returns the current shell cursor if there is no selection * (HasSelection()). */ SwCursor & SwCursorShell::CreateNewShellCursor() { if (HasSelection()) { (void) CreateCursor(); // n.b. returns old cursor } return *GetCursor(); } /** * Return the current shell cursor * @return - returns current `SwPaM` shell cursor */ SwCursor & SwCursorShell::GetCurrentShellCursor() { return *GetCursor(); } /** * Return pointer to the current shell cursor * @return - returns pointer to current `SwCursor` shell cursor */ SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const { if( m_pTableCursor ) { if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() ) { //don't re-create 'parked' cursors if( m_pTableCursor->GetPoint()->GetNodeIndex() && m_pTableCursor->GetMark()->GetNodeIndex() ) { const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode(); if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) { pCNd = m_pTableCursor->GetMarkContentNode(); if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) { SwShellTableCursor* pTC = m_pTableCursor; GetLayout()->MakeTableCursors( *pTC ); } } } } if( m_pTableCursor->IsChgd() ) { const_cast(this)->m_pCurrentCursor = dynamic_cast(m_pTableCursor->MakeBoxSels( m_pCurrentCursor )); } } return m_pCurrentCursor; } void SwCursorShell::StartAction() { if( !ActionPend() ) { // save for update of the ribbon bar const SwNode& rNd = m_pCurrentCursor->GetPoint()->GetNode(); m_nCurrentNode = rNd.GetIndex(); m_nCurrentContent = m_pCurrentCursor->GetPoint()->GetContentIndex(); m_nCurrentNdTyp = rNd.GetNodeType(); if( rNd.IsTextNode() ) m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true ); else m_nLeftFramePos = 0; } SwViewShell::StartAction(); // to the SwViewShell } void SwCursorShell::EndAction( const bool bIdleEnd ) { comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); bool bVis = m_bSVCursorVis; // Idle-formatting? if( bIdleEnd && Imp()->HasPaintRegion() ) { m_pCurrentCursor->Hide(); } // Update all invalid numberings before the last action if( 1 == mnStartAction ) GetDoc()->UpdateNumRule(); // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call. // Only the UpdateCursor shows the cursor. bool bSavSVCursorVis = m_bSVCursorVis; m_bSVCursorVis = false; SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first m_bSVCursorVis = bSavSVCursorVis; if( ActionPend() ) { if( bVis ) // display SV-Cursor again m_pVisibleCursor->Show(); return; } sal_uInt16 eFlags = SwCursorShell::CHKRANGE; if ( !bIdleEnd ) eFlags |= SwCursorShell::SCROLLWIN; UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes { SwCallLink aLk( *this ); // Watch cursor moves, aLk.m_nNode = m_nCurrentNode; // possibly call the link aLk.m_nNodeType = m_nCurrentNdTyp; aLk.m_nContent = m_nCurrentContent; aLk.m_nLeftFramePos = m_nLeftFramePos; if( !m_nCursorMove || ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) ) // display Cursor & Selections again ShowCursors( m_bSVCursorVis ); } // call ChgCall if there is still one if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() ) { m_aChgLnk.Call(nullptr); m_bChgCallFlag = false; // reset flag } } void SwCursorShell::SttCursorMove() { #ifdef DBG_UTIL OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." ); #endif ++m_nCursorMove; StartAction(); } void SwCursorShell::EndCursorMove( const bool bIdleEnd ) { #ifdef DBG_UTIL OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." ); #endif EndAction( bIdleEnd ); --m_nCursorMove; #ifdef DBG_UTIL if( !m_nCursorMove ) m_bInCMvVisportChgd = false; #endif } bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode, bool bVisualAllowed ) { if( IsTableMode() ) return bLeft ? GoPrevCell() : GoNextCell(); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed bool bRet = false; // #i27615# Handle cursor in front of label. const SwTextNode* pTextNd = nullptr; if( m_pBlockCursor ) m_pBlockCursor->clearPoints(); // 1. CASE: Cursor is in front of label. A move to the right // will simply reset the bInFrontOfLabel flag: SwShellCursor* pShellCursor = getShellCursor( true ); if ( !bLeft && pShellCursor->IsInFrontOfLabel() ) { SetInFrontOfLabel( false ); bRet = true; } // 2. CASE: Cursor is at beginning of numbered paragraph. A move // to the left will simply set the bInFrontOfLabel flag: else if (bLeft && pShellCursor->GetPoint()->GetNode().IsTextNode() && static_cast( pShellCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout()) )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0) && !pShellCursor->IsInFrontOfLabel() && !pShellCursor->HasMark() && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->GetNode())) && pTextNd->HasVisibleNumberingOrBullet()) { SetInFrontOfLabel( true ); bRet = true; } // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag: else { const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar(); // #i107447# // To avoid loop the reset of flag is no longer // reflected in the return value . const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false ); bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed, bSkipHidden, !IsOverwriteCursor(), GetLayout(), GetViewOptions()->IsFieldName()); if ( !bRet && bLeft && bResetOfInFrontOfLabel ) { // undo reset of flag SetInFrontOfLabel( true ); } } if( bRet ) { UpdateCursor(); } return bRet; } void SwCursorShell::MarkListLevel( const OUString& sListId, const int nListLevel ) { if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel) return; // Writer redraws the "marked" list with the field shading, if there // is no field shading then the marked list would be redrawn for no // visually identifiable reason, so skip the mark if field shadings // are disabled. const bool bVisuallyMarked(GetViewOptions()->IsFieldShadings()); if (bVisuallyMarked) { if ( !m_sMarkedListId.isEmpty() ) mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false ); if ( !sListId.isEmpty() ) mxDoc->MarkListLevel( sListId, nListLevel, true ); } m_sMarkedListId = sListId; m_nMarkedListLevel = nListLevel; } void SwCursorShell::UpdateMarkedListLevel() { SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()); if ( !pTextNd ) return; if (!pTextNd->IsNumbered(GetLayout())) { m_pCurrentCursor->SetInFrontOfLabel_( false ); MarkListLevel( OUString(), 0 ); } else if ( m_pCurrentCursor->IsInFrontOfLabel() ) { if ( pTextNd->IsInList() ) { assert(pTextNd->GetActualListLevel() >= 0 && pTextNd->GetActualListLevel() < MAXLEVEL); MarkListLevel( pTextNd->GetListId(), pTextNd->GetActualListLevel() ); } } else { MarkListLevel( OUString(), 0 ); } } void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage) { #ifdef ACCESSIBLE_LAYOUT if( Imp()->IsAccessible() ) Imp()->FirePageChangeEvent( nOldPage, nNewPage ); #else (void)nOldPage; (void)nNewPage; #endif } void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn) { #ifdef ACCESSIBLE_LAYOUT if( Imp()->IsAccessible() ) Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn); #else (void)nOldColumn; (void)nNewColumn; #endif } void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection) { #ifdef ACCESSIBLE_LAYOUT if( Imp()->IsAccessible() ) Imp()->FireSectionChangeEvent( nOldSection, nNewSection ); #else (void)nOldSection; (void)nNewSection; #endif } bool SwCursorShell::bColumnChange() { SwFrame* pCurrFrame = GetCurrFrame(false); if (pCurrFrame == nullptr) { return false; } SwFrame* pCurrCol=pCurrFrame->FindColFrame(); while(pCurrCol== nullptr && pCurrFrame!=nullptr ) { SwLayoutFrame* pParent = pCurrFrame->GetUpper(); if(pParent!=nullptr) { pCurrCol=static_cast(pParent)->FindColFrame(); pCurrFrame = pParent; } else { break; } } if(m_oldColFrame == pCurrCol) return false; else { m_oldColFrame = pCurrCol; return true; } } bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt ) { CurrShell aCurr( this ); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed bool bTableMode = IsTableMode(); SwShellCursor* pTmpCursor = getShellCursor( true ); bool bRet = pTmpCursor->UpDown( bUp, nCnt ); // #i40019# UpDown should always reset the bInFrontOfLabel flag: bRet |= SetInFrontOfLabel(false); if( m_pBlockCursor ) m_pBlockCursor->clearPoints(); if( bRet ) { m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint if( !ActionPend() ) { CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN; if( !bTableMode ) eUpdateMode = static_cast(eUpdateMode | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE); UpdateCursor( o3tl::narrowing(eUpdateMode) ); } } return bRet; } bool SwCursorShell::LRMargin( bool bLeft, bool bAPI) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed CurrShell aCurr( this ); m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint const bool bTableMode = IsTableMode(); SwShellCursor* pTmpCursor = getShellCursor( true ); if( m_pBlockCursor ) m_pBlockCursor->clearPoints(); const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI); bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI); if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() ) { const SwTextNode * pTextNd = GetCursor_()->GetPointNode().GetTextNode(); assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == pTextNd); if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() ) SetInFrontOfLabel( true ); } else if ( !bLeft ) { bRet = SetInFrontOfLabel( false ) || bRet; } if( bRet ) { UpdateCursor(); } return bRet; } bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const { const SwShellCursor* pTmpCursor = getShellCursor( true ); return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI); } bool SwCursorShell::SttEndDoc( bool bStt ) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor; bool bRet = pTmpCursor->SttEndDoc( bStt ); if( bRet ) { if( bStt ) pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header) if( m_pBlockCursor ) { m_pBlockCursor->clearPoints(); RefreshBlockCursor(); } UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); } return bRet; } const SwTableNode* SwCursorShell::IsCursorInTable() const { if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount()) { // find the table that has the selected boxes return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode(); } return m_pCurrentCursor->GetPointNode().FindTableNode(); } // fun cases to consider: // * outermost table // - into para => SA/ESA // - into prev/next table => continue... // - no prev/next => done // * inner table // - into containing cell => SA/ESA // - into prev/next of containing cell // + into para // + into table nested in prev/next cell // - out of table -> as above // => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table // - parent reached => SA/ESA depending // - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable // - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable // => select-all cannot select a sequence of table with no para at same level; only 1 table // - no parent, no prev/next => TrySelectOuterTable bool SwCursorShell::MoveOutOfTable() { SwPosition const point(*getShellCursor(false)->GetPoint()); SwPosition const mark(*getShellCursor(false)->GetMark()); for (auto const fnMove : {&fnMoveBackward, &fnMoveForward}) { Push(); SwCursor *const pCursor(getShellCursor(false)); pCursor->Normalize(fnMove == &fnMoveBackward); pCursor->DeleteMark(); SwTableNode const*const pTable(pCursor->GetPoint()->GetNode().FindTableNode()); assert(pTable); while (MovePara(GoInContent, *fnMove)) { SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode()); if (!pBox) { Pop(SwCursorShell::PopMode::DeleteStack); return true; // moved to paragraph at top-level of text } if (pBox->GetIndex() < pTable->GetIndex() && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex()) { Pop(SwCursorShell::PopMode::DeleteStack); return true; // pBox contains start position (pTable) } } Pop(SwCursorShell::PopMode::DeleteCurrent); // FIXME: Pop doesn't restore original cursor if nested tables *getShellCursor(false)->GetPoint() = point; getShellCursor(false)->SetMark(); *getShellCursor(false)->GetMark() = mark; } return false; } bool SwCursorShell::TrySelectOuterTable() { assert(m_pTableCursor); SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->GetNode().FindTableNode()); SwNodes const& rNodes(rInnerTable.GetNodes()); SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode()); if (!pOuterTable) { return false; } // manually select boxes of pOuterTable SwNodeIndex firstCell(*pOuterTable, +1); SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode()); SwSelBoxes aNew; pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(), aNew, SwTable::SEARCH_NONE, false); // set table cursor to 1st / last content which may be in inner table SwContentNode *const pStart = rNodes.GoNext(&firstCell); assert(pStart); // must at least find the previous point node lastCell = *lastCell.GetNode().EndOfSectionNode(); SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell); assert(pEnd); // must at least find the previous point node delete m_pTableCursor; m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(), SwPosition(*pEnd, 0), Point()); m_pTableCursor->ActualizeSelection( aNew ); m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes // this will update m_pCurrentCursor based on m_pTableCursor UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); return true; } /// find XText start node static SwStartNode const* FindTextStart(SwPosition const& rPos) { SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode()); while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode())) { pStartNode = pStartNode->StartOfSectionNode(); } return pStartNode; } static SwStartNode const* FindParentText(SwShellCursor const& rCursor) { // find closest section containing both start and end - ignore Sections SwStartNode const* pStartNode(FindTextStart(*rCursor.Start())); SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode()); while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex()) { pStartNode = pStartNode->StartOfSectionNode(); } while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex()) { pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode(); } assert(pStartNode->EndOfSectionNode() == pEndNode); return (pStartNode->IsSectionNode() || pStartNode->IsTableNode()) ? FindTextStart(SwPosition(*pStartNode)) : pStartNode; } bool SwCursorShell::MoveStartText() { SwPosition const old(*m_pCurrentCursor->GetPoint()); SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); assert(pStartNode); SwTableNode const*const pTable(pStartNode->FindTableNode()); m_pCurrentCursor->GetPoint()->Assign(*pStartNode); GetDoc()->GetNodes().GoNext(m_pCurrentCursor->GetPoint()); while (auto* pFoundTable = m_pCurrentCursor->GetPoint()->GetNode().FindTableNode()) { if (pFoundTable == pTable) break; if (pTable && pTable->GetIndex() >= pFoundTable->GetIndex()) break; if (!MoveOutOfTable()) break; } UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); return old != *m_pCurrentCursor->GetPoint(); } // select all inside the current XText, with table or hidden para at start/end void SwCursorShell::ExtendedSelectAll(bool bFootnotes) { // find common ancestor node of both ends of cursor SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); assert(pStartNode); if (IsTableMode()) { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode TableCursorToCursor(); } SwNodes& rNodes = GetDoc()->GetNodes(); m_pCurrentCursor->Normalize(true); SwPosition* pPos = m_pCurrentCursor->GetPoint(); pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast(*pStartNode)); rNodes.GoNext( pPos ); pPos = m_pCurrentCursor->GetMark(); pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast(*pStartNode->EndOfSectionNode())); SwContentNode* pCNd = SwNodes::GoPrevious( pPos ); if (pCNd) pPos->AssignEndIndex(*pCNd); } static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart) { for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i) { SwNode const& rNode(*rStart.GetNodes()[i]); switch (rNode.GetNodeType()) { case SwNodeType::Section: if (rNode.GetSectionNode()->GetSection().IsHidden()) return SwCursorShell::StartsWith::HiddenSection; continue; case SwNodeType::Table: return SwCursorShell::StartsWith::Table; case SwNodeType::Text: if (rNode.GetTextNode()->IsHidden()) { return SwCursorShell::StartsWith::HiddenPara; } return SwCursorShell::StartsWith::None; default: return SwCursorShell::StartsWith::None; } } return SwCursorShell::StartsWith::None; } static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart) { for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i) { SwNode const& rNode(*rStart.GetNodes()[i]); switch (rNode.GetNodeType()) { case SwNodeType::End: if (auto pStartNode = rNode.StartOfSectionNode(); pStartNode->IsTableNode()) { return SwCursorShell::StartsWith::Table; } else if (pStartNode->IsSectionNode()) { if (pStartNode->GetSectionNode()->GetSection().IsHidden()) return SwCursorShell::StartsWith::HiddenSection; } //TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode()); break; case SwNodeType::Text: if (rNode.GetTextNode()->IsHidden()) { return SwCursorShell::StartsWith::HiddenPara; } return SwCursorShell::StartsWith::None; default: return SwCursorShell::StartsWith::None; } } return SwCursorShell::StartsWith::None; } // return the node that is the start of the extended selection (to include table // or section start nodes; looks like extending for end nodes is not required) ::std::optional<::std::pair>> SwCursorShell::ExtendedSelectedAll() const { if (m_pTableCursor) { return {}; } SwNodes& rNodes = GetDoc()->GetNodes(); SwShellCursor const*const pShellCursor = getShellCursor(false); SwStartNode const* pStartNode(FindParentText(*pShellCursor)); SwNodeIndex nNode(*pStartNode); SwContentNode* pStart = rNodes.GoNext(&nNode); if (!pStart) { return {}; } nNode = *pStartNode->EndOfSectionNode(); SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); if (!pEnd) { return {}; } SwPosition aStart(*pStart, 0); SwPosition aEnd(*pEnd, pEnd->Len()); if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End())) { return {}; } auto const ends(::EndsWith(*pStartNode)); if (::StartsWith(*pStartNode) == StartsWith::None && ends == StartsWith::None) { return {}; // "ordinary" selection will work } ::std::vector tablesAtEnd; if (ends == StartsWith::Table) { SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]); while (pLastNode->IsEndNode()) { SwNode *const pNode(pLastNode->StartOfSectionNode()); if (pNode->IsTableNode()) { tablesAtEnd.push_back(pNode->GetTableNode()); pLastNode = rNodes[pNode->GetIndex() - 1]; } else if (pNode->IsSectionNode()) { pLastNode = rNodes[pLastNode->GetIndex() - 1]; } } assert(!tablesAtEnd.empty()); } // tdf#133990 ensure directly containing section is included in SwUndoDelete while (pStartNode->IsSectionNode() && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1 && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex()) { pStartNode = pStartNode->StartOfSectionNode(); } // pStartNode is the node that fully contains the selection - the first // node of the selection is the first node inside pStartNode return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd); } typename SwCursorShell::StartsWith SwCursorShell::StartsWith_() { SwShellCursor const*const pShellCursor = getShellCursor(false); // first, check if this is invalid; ExtendedSelectAll(true) may result in // a) an ordinary selection that is valid // b) a selection that is extended // c) a selection that is invalid and will cause FindParentText to loop SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras()); if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex() && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex()) { return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling! } SwStartNode const*const pStartNode(FindParentText(*pShellCursor)); if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None) { return ret; } if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None) { return ret; } return StartsWith::None; } bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage ) { bool bRet = false; // never jump of section borders at selection if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() ) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed CurrShell aCurr( this ); SwCursorSaveState aSaveState( *m_pCurrentCursor ); Point& rPt = m_pCurrentCursor->GetPtPos(); std::pair tmp(rPt, false); SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()-> getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) && !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos )) { UpdateCursor(); bRet = true; } } return bRet; } bool SwCursorShell::isInHiddenFrame(SwShellCursor* pShellCursor) { SwContentNode *pCNode = pShellCursor->GetPointContentNode(); std::pair tmp(pShellCursor->GetPtPos(), false); SwContentFrame *const pFrame = pCNode ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp) : nullptr; return !pFrame || pFrame->IsHiddenNow(); } // sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell, SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara) { SwContentNode *const pCNode = pShellCursor->GetPointContentNode(); assert(pCNode); // surely can't have moved otherwise? std::pair tmp(pShellCursor->GetPtPos(), false); SwContentFrame const*const pFrame = pCNode->getLayoutFrame( pShell->GetLayout(), pShellCursor->GetPoint(), &tmp); if (!pFrame || !pFrame->IsTextFrame()) { return false; } SwTextFrame const& rTextFrame(static_cast(*pFrame)); TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint())); if (&fnParaStart == &fnPosPara) { return ix == TextFrameIndex(0); } else { assert(&fnParaEnd == &fnPosPara); return ix == TextFrameIndex(rTextFrame.GetText().getLength()); } } bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara ) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed SwShellCursor* pTmpCursor = getShellCursor( true ); bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara ); if( bRet ) { //keep going until we get something visible, i.e. skip //over hidden paragraphs, don't get stuck at the start //which is what SwCursorShell::UpdateCursorPos will reset //the position to if we pass it a position in an //invisible hidden paragraph field while (isInHiddenFrame(pTmpCursor) || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara)) { if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara)) break; } UpdateCursor(); } return bRet; } bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect, SwMoveFnCollection const & fnPosSect) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed SwCursor* pTmpCursor = getShellCursor( true ); bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect ); if( bRet ) UpdateCursor(); return bRet; } // position cursor static SwFrame* lcl_IsInHeaderFooter( SwNode& rNd, Point& rPt ) { SwFrame* pFrame = nullptr; SwContentNode* pCNd = rNd.GetContentNode(); if( pCNd ) { std::pair tmp(rPt, false); SwContentFrame *pContentFrame = pCNd->getLayoutFrame( pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr; while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) pFrame = pFrame->IsFlyFrame() ? static_cast(pFrame)->AnchorFrame() : pFrame->GetUpper(); } return pFrame; } bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const { Point aPt; SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->GetNode(), aPt ); if( pFrame && pbInHeader ) *pbInHeader = pFrame->IsHeaderFrame(); return nullptr != pFrame; } int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock ) { CurrShell aCurr( this ); SwShellCursor* pCursor = getShellCursor( bBlock ); SwPosition aPos( *pCursor->GetPoint() ); Point aPt( rLPt ); Point & rCurrentCursorPt = pCursor->GetPtPos(); SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel : bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); if ( pTextNd && !IsTableMode() && // #i37515# No bInFrontOfLabel during selection !pCursor->HasMark() && pTextNd->HasVisibleNumberingOrBullet() ) { aTmpState.m_bInFrontOfLabel = true; // #i27615# } else { aTmpState.m_bInFrontOfLabel = false; } int bRet = CRSR_POSOLD | ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) ? 0 : CRSR_POSCHG ); const bool bOldInFrontOfLabel = IsInFrontOfLabel(); const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel; pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel ); if( CursorMoveState::RightMargin == aTmpState.m_eState ) m_eMvState = CursorMoveState::RightMargin; // is the new position in header or footer? SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.GetNode(), aPt ); if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() == pCursor->GetPoint()->GetNode().StartOfSectionNode() ) // same table column and not in header/footer -> back return bRet; if( m_pBlockCursor && bBlock ) { m_pBlockCursor->setEndPoint( rLPt ); if( !pCursor->HasMark() ) m_pBlockCursor->setStartPoint( rLPt ); else if( !m_pBlockCursor->getStartPoint() ) m_pBlockCursor->setStartPoint( pCursor->GetMkPos() ); } if( !pCursor->HasMark() ) { // is at the same position and if in header/footer -> in the same if( aPos == *pCursor->GetPoint() && bOldInFrontOfLabel == bNewInFrontOfLabel ) { if( pFrame ) { if( pFrame->getFrameArea().Contains( rCurrentCursorPt )) return bRet; } else if( aPos.GetNode().IsContentNode() ) { // in the same frame? std::pair tmp(m_aCharRect.Pos(), false); SwFrame* pOld = static_cast(aPos.GetNode()).getLayoutFrame( GetLayout(), nullptr, &tmp); tmp.first = aPt; SwFrame* pNew = static_cast(aPos.GetNode()).getLayoutFrame( GetLayout(), nullptr, &tmp); if( pNew == pOld ) return bRet; } } } else { // SSelection over not allowed sections or if in header/footer -> different if( !CheckNodesRange( aPos.GetNode(), pCursor->GetMark()->GetNode(), true ) || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) )) return bRet; // is at same position but not in header/footer if( aPos == *pCursor->GetPoint() ) return bRet; } SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed SwCursorSaveState aSaveState( *pCursor ); *pCursor->GetPoint() = aPos; rCurrentCursorPt = aPt; // #i41424# Only update the marked number levels if necessary // Force update of marked number levels if necessary. if ( bNewInFrontOfLabel || bOldInFrontOfLabel ) m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel ); SetInFrontOfLabel( bNewInFrontOfLabel ); if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) ) { UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE ); bRet &= ~CRSR_POSOLD; } else if( bOnlyText && !m_pCurrentCursor->HasMark() ) { if( FindValidContentNode( bOnlyText ) ) { // position cursor in a valid content if( aPos == *pCursor->GetPoint() ) bRet = CRSR_POSOLD; else { UpdateCursor(); bRet &= ~CRSR_POSOLD; } } else { // there is no valid content -> hide cursor m_pVisibleCursor->Hide(); // always hide visible cursor m_eMvState = CursorMoveState::NONE; // status for Cursor travelling m_bAllProtect = true; if( GetDoc()->GetDocShell() ) { GetDoc()->GetDocShell()->SetReadOnlyUI(); CallChgLnk(); // notify UI } } } return bRet; } void SwCursorShell::TableCursorToCursor() { assert(m_pTableCursor); delete m_pTableCursor; m_pTableCursor = nullptr; } void SwCursorShell::BlockCursorToCursor() { assert(m_pBlockCursor); if( m_pBlockCursor && !HasSelection() ) { SwPaM& rPam = m_pBlockCursor->getShellCursor(); m_pCurrentCursor->SetMark(); *m_pCurrentCursor->GetPoint() = *rPam.GetPoint(); if( rPam.HasMark() ) *m_pCurrentCursor->GetMark() = *rPam.GetMark(); else m_pCurrentCursor->DeleteMark(); } delete m_pBlockCursor; m_pBlockCursor = nullptr; } void SwCursorShell::CursorToBlockCursor() { if( !m_pBlockCursor ) { SwPosition aPos( *m_pCurrentCursor->GetPoint() ); m_pBlockCursor = new SwBlockCursor( *this, aPos ); SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos(); if( m_pCurrentCursor->HasMark() ) { rBlock.SetMark(); *rBlock.GetMark() = *m_pCurrentCursor->GetMark(); rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos(); } } m_pBlockCursor->clearPoints(); RefreshBlockCursor(); } void SwCursorShell::ClearMark() { // is there any GetMark? if( m_pTableCursor ) { std::vector vCursors; for(auto& rCursor : m_pCurrentCursor->GetRingContainer()) if(&rCursor != m_pCurrentCursor) vCursors.push_back(&rCursor); for(auto pCursor : vCursors) delete pCursor; m_pTableCursor->DeleteMark(); m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); delete m_pTableCursor; m_pTableCursor = nullptr; m_pCurrentCursor->SwSelPaintRects::Show(); } else { if( !m_pCurrentCursor->HasMark() ) return; m_pCurrentCursor->DeleteMark(); if( !m_nCursorMove ) m_pCurrentCursor->SwSelPaintRects::Show(); } } void SwCursorShell::NormalizePam(bool bPointFirst) { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed m_pCurrentCursor->Normalize(bPointFirst); } void SwCursorShell::SwapPam() { SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed m_pCurrentCursor->Exchange(); } //TODO: provide documentation /** Search in the selected area for a Selection that covers the given point. It checks if a Selection exists but does not move the current cursor. @param rPt The point to search at. @param bTstHit ??? */ bool SwCursorShell::TestCurrPam( const Point & rPt, bool bTstHit ) { CurrShell aCurr( this ); // check if the SPoint is in a table selection if( m_pTableCursor ) return m_pTableCursor->Contains( rPt ); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed // search position in document SwPosition aPtPos( *m_pCurrentCursor->GetPoint() ); Point aPt( rPt ); SwCursorMoveState aTmpState( CursorMoveState::NONE ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit ) return false; // search in all selections for this position SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor do { if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos) return true; // return without update pCmp = pCmp->GetNext(); } while (m_pCurrentCursor != pCmp); return false; } void SwCursorShell::KillPams() { // Does any exist for deletion? if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() ) return; while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) delete m_pCurrentCursor->GetNext(); m_pCurrentCursor->SetColumnSelection( false ); if( m_pTableCursor ) { // delete the ring of cursors m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); delete m_pTableCursor; m_pTableCursor = nullptr; } else if( m_pBlockCursor ) { // delete the ring of cursors m_pCurrentCursor->DeleteMark(); SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint(); m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos(); rBlock.DeleteMark(); m_pBlockCursor->clearPoints(); } UpdateCursor( SwCursorShell::SCROLLWIN ); } int SwCursorShell::CompareCursorStackMkCurrPt() const { int nRet = 0; const SwPosition *pFirst = nullptr, *pSecond = nullptr; const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor; // cursor on stack is needed if we compare against stack if( pStack ) { pFirst = pStack->GetMark(); pSecond = pCur->GetPoint(); } if( !pFirst || !pSecond ) nRet = INT_MAX; else if( *pFirst < *pSecond ) nRet = -1; else if( *pFirst == *pSecond ) nRet = 0; else nRet = 1; return nRet; } bool SwCursorShell::IsSelOnePara() const { if (m_pCurrentCursor->IsMultiSelection()) { return false; } if (m_pCurrentCursor->GetPoint()->GetNode() == m_pCurrentCursor->GetMark()->GetNode()) { return true; } if (GetLayout()->HasMergedParas()) { SwContentFrame const*const pFrame(GetCurrFrame(false)); auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex()); return FrameContainsNode(*pFrame, n); } return false; } bool SwCursorShell::IsSttPara() const { if (GetLayout()->HasMergedParas()) { SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode()); if (pNode) { SwTextFrame const*const pFrame(static_cast( pNode->getLayoutFrame(GetLayout()))); if (pFrame) { return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) == TextFrameIndex(0); } } } return m_pCurrentCursor->GetPoint()->GetContentIndex() == 0; } bool SwCursorShell::IsEndPara() const { if (GetLayout()->HasMergedParas()) { SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode()); if (pNode) { SwTextFrame const*const pFrame(static_cast( pNode->getLayoutFrame(GetLayout()))); if (pFrame) { return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) == TextFrameIndex(pFrame->GetText().getLength()); } } } return m_pCurrentCursor->GetPoint()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->Len(); } bool SwCursorShell::IsEndOfTable() const { if (IsTableMode() || IsBlockMode() || !IsEndPara()) { return false; } SwTableNode const*const pTableNode( IsCursorInTable() ); if (!pTableNode) { return false; } SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode()); SwNodeIndex const lastNode(*pEndTableNode, -2); SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core", "text node expected"); return (lastNode == m_pCurrentCursor->GetPoint()->GetNode()); } bool SwCursorShell::IsCursorInFootnote() const { SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType(); return aStartNodeType == SwStartNodeType::SwFootnoteStartNode; } Point SwCursorShell::GetCursorPagePos() const { Point aRet(-1, -1); if (SwFrame *pFrame = GetCurrFrame()) { if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame()) { const Point& rDocPos = GetCursorDocPos(); aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft(); } } return aRet; } bool SwCursorShell::IsInFrontOfLabel() const { return m_pCurrentCursor->IsInFrontOfLabel(); } bool SwCursorShell::SetInFrontOfLabel( bool bNew ) { if ( bNew != IsInFrontOfLabel() ) { m_pCurrentCursor->SetInFrontOfLabel_( bNew ); UpdateMarkedListLevel(); return true; } return false; } namespace { void collectUIInformation(const OUString& aPage) { EventDescription aDescription; aDescription.aAction = "GOTO"; aDescription.aParameters = {{"PAGE", aPage}}; aDescription.aID = "writer_edit"; aDescription.aKeyWord = "SwEditWinUIObject"; aDescription.aParent = "MainWindow"; UITestLogger::getInstance().logEvent(aDescription); } } bool SwCursorShell::GotoPage( sal_uInt16 nPage ) { CurrShell aCurr( this ); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed SwCursorSaveState aSaveState( *m_pCurrentCursor ); bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) && !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ); if( bRet ) UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); collectUIInformation(OUString::number(nPage)); return bRet; } void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos) { SwContentFrame* pFrame = GetCurrFrame(); pFrame->GetCharRect( rRect, *pPos ); } void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum, bool bAtCursorPos, const bool bCalcFrame ) { CurrShell aCurr( this ); // page number: first visible page or the one at the cursor const SwContentFrame* pCFrame; const SwPageFrame *pPg = nullptr; if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) || nullptr == (pPg = pCFrame->FindPageFrame()) ) { pPg = Imp()->GetFirstVisPage(GetOut()); while( pPg && pPg->IsEmptyPage() ) pPg = static_cast(pPg->GetNext()); } // pPg has to exist with a default of 1 for the special case "Writerstart" rnPhyNum = pPg? pPg->GetPhyPageNum() : 1; rnVirtNum = pPg? pPg->GetVirtPageNum() : 1; } sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty() { CurrShell aCurr(this); // page number: first visible page or the one at the cursor const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true); const SwPageFrame* pPg = nullptr; if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame())) { pPg = Imp()->GetFirstVisPage(GetOut()); while (pPg && pPg->IsEmptyPage()) pPg = static_cast(pPg->GetNext()); } sal_uInt16 nPageNo = 0; while (pPg) { if (!pPg->IsEmptyPage()) ++nPageNo; pPg = static_cast(pPg->GetPrev()); } return nPageNo; } sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext ) { CurrShell aCurr( this ); // page number: first visible page or the one at the cursor const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut()); if( pPg ) { const SwTwips nPageTop = pPg->getFrameArea().Top(); if( bNext ) { // go to next view layout row: do { pPg = static_cast(pPg->GetNext()); } while( pPg && pPg->getFrameArea().Top() == nPageTop ); while( pPg && pPg->IsEmptyPage() ) pPg = static_cast(pPg->GetNext()); } else { // go to previous view layout row: do { pPg = static_cast(pPg->GetPrev()); } while( pPg && pPg->getFrameArea().Top() == nPageTop ); while( pPg && pPg->IsEmptyPage() ) pPg = static_cast(pPg->GetPrev()); } } // pPg has to exist with a default of 1 for the special case "Writerstart" return pPg ? pPg->GetPhyPageNum() : USHRT_MAX; } sal_uInt16 SwCursorShell::GetPageCnt() { CurrShell aCurr( this ); // return number of pages return GetLayout()->GetPageNum(); } OUString SwCursorShell::getPageRectangles() { CurrShell aCurr(this); SwRootFrame* pLayout = GetLayout(); OUStringBuffer aBuf; for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext()) { aBuf.append(OUString::number(pFrame->getFrameArea().Left()) + ", " + OUString::number(pFrame->getFrameArea().Top()) + ", " + OUString::number(pFrame->getFrameArea().Width()) + ", " + OUString::number(pFrame->getFrameArea().Height()) + "; "); } if (!aBuf.isEmpty()) aBuf.setLength( aBuf.getLength() - 2); // remove the last "; " return aBuf.makeStringAndClear(); } void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const { auto pView = const_cast(GetDrawView()); if (pView->GetTextEditObject()) { // Blinking cursor. EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); rEditView.RegisterOtherShell(pOtherShell); rEditView.ShowCursor(); rEditView.RegisterOtherShell(nullptr); // Text selection, if any. rEditView.DrawSelectionXOR(pOtherShell); // Shape text lock. if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView()) { OString sRect = pOutlinerView->GetOutputArea().toString(); SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect); } } else { // Cursor position. m_pVisibleCursor->SetPosAndShow(pOtherShell); // Cursor visibility. if (GetSfxViewShell() != pOtherShell) { OString aPayload = OString::boolean(m_bSVCursorVis); SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); } // Text selection. m_pCurrentCursor->Show(pOtherShell); // Graphic selection. pView->AdjustMarkHdl(pOtherShell); } } /// go to the next SSelection bool SwCursorShell::GoNextCursor() { if( !m_pCurrentCursor->IsMultiSelection() ) return false; CurrShell aCurr( this ); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed m_pCurrentCursor = m_pCurrentCursor->GetNext(); // #i24086#: show also all others if( !ActionPend() ) { UpdateCursor(); m_pCurrentCursor->Show(nullptr); } return true; } /// go to the previous SSelection bool SwCursorShell::GoPrevCursor() { if( !m_pCurrentCursor->IsMultiSelection() ) return false; CurrShell aCurr( this ); SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed m_pCurrentCursor = m_pCurrentCursor->GetPrev(); // #i24086#: show also all others if( !ActionPend() ) { UpdateCursor(); m_pCurrentCursor->Show(nullptr); } return true; } void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext) { SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); if( !m_pCurrentCursor->IsMultiSelection() ) { if( !m_pCurrentCursor->HasMark() ) SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); return; } if (bNext) GoNextCursor(); else GoPrevCursor(); } void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect) { comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); CurrShell aCurr( this ); // always switch off all cursors when painting SwRect aRect( rRect ); bool bVis = false; // if a cursor is visible then hide the SV cursor if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) ) { bVis = true; m_pVisibleCursor->Hide(); } // re-paint area SwViewShell::Paint(rRenderContext, rRect); if( m_bHasFocus && !m_bBasicHideCursor ) { SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; if( !ActionPend() ) { // so that right/bottom borders will not be cropped pCurrentCursor->Invalidate( VisArea() ); pCurrentCursor->Show(nullptr); } else pCurrentCursor->Invalidate( aRect ); } if (SwPostItMgr* pPostItMgr = GetPostItMgr()) { // No point in showing the cursor for Writer text when there is an // active annotation edit. if (bVis) bVis = !pPostItMgr->HasActiveSidebarWin(); } if( m_bSVCursorVis && bVis ) // also show SV cursor again m_pVisibleCursor->Show(); } void SwCursorShell::VisPortChgd( const SwRect & rRect ) { CurrShell aCurr( this ); bool bVis; // switch off all cursors when scrolling // if a cursor is visible then hide the SV cursor bVis = m_pVisibleCursor->IsVisible(); if( bVis ) m_pVisibleCursor->Hide(); m_bVisPortChgd = true; m_aOldRBPos.setX(VisArea().Right()); m_aOldRBPos.setY(VisArea().Bottom()); // For not having problems with the SV cursor, Update() is called for the // Window in SwViewShell::VisPo... // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact? SwViewShell::VisPortChgd( rRect ); // move area if( m_bSVCursorVis && bVis ) // show SV cursor again m_pVisibleCursor->Show(); if( m_nCursorMove ) m_bInCMvVisportChgd = true; m_bVisPortChgd = false; } /** Set the cursor back into content. This should only be called if the cursor was moved (e.g. when deleting a text frame). The new position is calculated from its current position in the layout. */ void SwCursorShell::UpdateCursorPos() { CurrShell aCurr( this ); ++mnStartAction; SwShellCursor* pShellCursor = getShellCursor( true ); Size aOldSz( GetDocSize() ); if (isInHiddenFrame(pShellCursor) && !ExtendedSelectedAll()) { SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(), &aTmpState ); pShellCursor->DeleteMark(); // kde45196-1.html: try to get to a non-hidden paragraph, there must // be one in the document body while (isInHiddenFrame(pShellCursor)) { if (!pShellCursor->MovePara(GoNextPara, fnParaStart)) { break; } } while (isInHiddenFrame(pShellCursor)) { if (!pShellCursor->MovePara(GoPrevPara, fnParaStart)) { break; } } } auto* pDoc = GetDoc(); if (pDoc) { pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint()); pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint()); } --mnStartAction; if( aOldSz != GetDocSize() ) SizeChgNotify(); } // #i65475# - if Point/Mark in hidden sections, move them out static bool lcl_CheckHiddenSection( SwPosition& rPos ) { bool bOk = true; const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode(); if( pSectNd && pSectNd->GetSection().IsHiddenFlag() ) { const SwNode* pFrameNd = rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() ); bOk = pFrameNd != nullptr; SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames"); rPos.Assign( *(bOk ? pFrameNd : pSectNd) ); } return bOk; } /// Try to set the cursor to the next visible content node. static void lcl_CheckHiddenPara( SwPosition& rPos ) { SwNodeIndex aTmp( rPos.GetNode() ); SwTextNode* pTextNd = aTmp.GetNode().GetTextNode(); while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) ) { SwContentNode* pContent = aTmp.GetNodes().GoNext( &aTmp ); if ( pContent && pContent->IsTextNode() ) pTextNd = pContent->GetTextNode(); else pTextNd = nullptr; } if ( pTextNd ) rPos.Assign( *pTextNd, 0 ); } #if !ENABLE_WASM_STRIP_ACCESSIBILITY namespace { // #i27301# - helper class that notifies the accessibility about invalid text // selections in its destructor class SwNotifyAccAboutInvalidTextSelections { private: SwCursorShell& mrCursorSh; public: explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh ) : mrCursorSh( _rCursorSh ) {} ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE { mrCursorSh.InvalidateAccessibleParaTextSelection(); } }; } #endif void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd ) { CurrShell aCurr( this ); ClearUpCursors(); if (ActionPend()) { if ( eFlags & SwCursorShell::READONLY ) m_bIgnoreReadonly = true; return; // if not then no update } #if !ENABLE_WASM_STRIP_ACCESSIBILITY SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this ); #endif if ( m_bIgnoreReadonly ) { m_bIgnoreReadonly = false; eFlags |= SwCursorShell::READONLY; } if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for CheckRange( m_pCurrentCursor ); // overlapping ranges if( !bIdleEnd ) CheckTableBoxContent(); // If the current cursor is in a table and point/mark in different boxes, // then the table mode is active (also if it is already active: m_pTableCursor) SwPaM* pTstCursor = getShellCursor( true ); if( pTstCursor->HasMark() && !m_pBlockCursor && SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) && ( m_pTableCursor || pTstCursor->GetPointNode().StartOfSectionNode() != pTstCursor->GetMarkNode().StartOfSectionNode() ) && !mbSelectAll) { SwShellCursor* pITmpCursor = getShellCursor( true ); Point aTmpPt( pITmpCursor->GetPtPos() ); Point aTmpMk( pITmpCursor->GetMkPos() ); SwPosition* pPos = pITmpCursor->GetPoint(); // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out lcl_CheckHiddenSection( *pPos ); lcl_CheckHiddenSection( *pITmpCursor->GetMark() ); // Move cursor out of hidden paragraphs if ( !GetViewOptions()->IsShowHiddenChar() ) { lcl_CheckHiddenPara( *pPos ); lcl_CheckHiddenPara( *pITmpCursor->GetMark() ); } std::pair const tmp(aTmpPt, false); SwContentFrame *pTableFrame = pPos->GetNode().GetContentNode()-> getLayoutFrame( GetLayout(), pPos, &tmp); OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" ); // --> Make code robust. The table cursor may point // to a table in a currently inactive header. SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr; if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 ) { // First check if point is in repeated headline: bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame ); // Second check if mark is in repeated headline: if ( !bInRepeatedHeadline ) { std::pair const tmp1(aTmpMk, false); SwContentFrame* pMarkTableFrame = pITmpCursor->GetMarkContentNode()-> getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1); OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" ); if ( pMarkTableFrame ) { SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame(); OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" ); // Make code robust: if ( pMarkTab ) { bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame ); } } } // No table cursor in repeated headlines: if ( bInRepeatedHeadline ) { pTableFrame = nullptr; SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark() ? fnSectionStart : fnSectionEnd; // then only select inside the Box if( m_pTableCursor ) { m_pCurrentCursor->SetMark(); *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark(); m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos(); m_pTableCursor->DeleteMark(); m_pTableCursor->SwSelPaintRects::Hide(); } *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark(); GoCurrSection( *m_pCurrentCursor, fnPosSect ); } } // we really want a table selection if( pTab && pTableFrame ) { if( !m_pTableCursor ) { m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(), *pPos, aTmpPt ); m_pCurrentCursor->DeleteMark(); m_pCurrentCursor->SwSelPaintRects::Hide(); CheckTableBoxContent(); if(!m_pTableCursor) { SAL_WARN("sw.core", "fdo#74854: " "this should not happen, but better lose the selection " "rather than crashing"); return; } } SwCursorMoveState aTmpState( CursorMoveState::NONE ); aTmpState.m_bRealHeight = true; { DisableCallbackAction a(*GetLayout()); if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState)) { Point aCentrPt( m_aCharRect.Center() ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState); bool const bResult = pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint()); OSL_ENSURE( bResult, "GetCharRect failed." ); } } m_pVisibleCursor->Hide(); // always hide visible Cursor // scroll Cursor to visible area if( eFlags & SwCursorShell::SCROLLWIN && (HasSelection() || eFlags & SwCursorShell::READONLY || !IsCursorReadonly()) ) { SwFrame* pBoxFrame = pTableFrame; while( pBoxFrame && !pBoxFrame->IsCellFrame() ) pBoxFrame = pBoxFrame->GetUpper(); if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() ) MakeVisible( pBoxFrame->getFrameArea() ); else MakeVisible( m_aCharRect ); } // let Layout create the Cursors in the Boxes if( m_pTableCursor->IsCursorMovedUpdate() ) GetLayout()->MakeTableCursors( *m_pTableCursor ); if( m_bHasFocus && !m_bBasicHideCursor ) m_pTableCursor->Show(nullptr); // set Cursor-Points to the new Positions m_pTableCursor->GetPtPos().setX(m_aCharRect.Left()); m_pTableCursor->GetPtPos().setY(m_aCharRect.Top()); if( m_bSVCursorVis ) { m_aCursorHeight.setX(0); m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? -m_aCharRect.Width() : m_aCharRect.Height()); m_pVisibleCursor->Show(); // show again } m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint #if !ENABLE_WASM_STRIP_ACCESSIBILITY if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents) Imp()->InvalidateAccessibleCursorPosition( pTableFrame ); #endif return; } } if( m_pTableCursor ) { // delete Ring while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) delete m_pCurrentCursor->GetNext(); m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); delete m_pTableCursor; m_pTableCursor = nullptr; } m_pVisibleCursor->Hide(); // always hide visible Cursor // are we perhaps in a protected / hidden Section ? { SwShellCursor* pShellCursor = getShellCursor( true ); bool bChgState = true; const SwSectionNode* pSectNd = pShellCursor->GetPointNode().FindSectionNode(); if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || ( !IsReadOnlyAvailable() && pSectNd->GetSection().IsProtectFlag() && ( !mxDoc->GetDocShell() || !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) ) { if( !FindValidContentNode( !HasDrawView() || 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) { // everything protected/hidden -> special mode if( m_bAllProtect && !IsReadOnlyAvailable() && pSectNd->GetSection().IsProtectFlag() ) bChgState = false; else { m_eMvState = CursorMoveState::NONE; // state for cursor travelling m_bAllProtect = true; if( GetDoc()->GetDocShell() ) { GetDoc()->GetDocShell()->SetReadOnlyUI(); CallChgLnk(); // notify UI! } return; } } } if( bChgState ) { bool bWasAllProtect = m_bAllProtect; m_bAllProtect = false; if( bWasAllProtect && GetDoc()->GetDocShell() && GetDoc()->GetDocShell()->IsReadOnlyUI() ) { GetDoc()->GetDocShell()->SetReadOnlyUI( false ); CallChgLnk(); // notify UI! } } } UpdateCursorPos(); // The cursor must always point into content; there's some code // that relies on this. (E.g. in SwEditShell::GetScriptType, which always // loops _behind_ the last node in the selection, which always works if you // are in content.) To achieve this, we'll force cursor(s) to point into // content, if UpdateCursorPos() hasn't already done so. for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer()) { // start will move forwards, end will move backwards bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() ); // move point; forward if it's the start, backwards if it's the end if( ! rCmp.GetPoint()->GetNode().IsContentNode() ) rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward, GoInContent ); // move mark (if exists); forward if it's the start, else backwards if( rCmp.HasMark() ) { if( ! rCmp.GetMark()->GetNode().IsContentNode() ) { rCmp.Exchange(); rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward, GoInContent ); rCmp.Exchange(); } } } SwRect aOld( m_aCharRect ); bool bFirst = true; SwContentFrame *pFrame; int nLoopCnt = 100; SwShellCursor* pShellCursor = getShellCursor( true ); do { bool bAgainst; do { bAgainst = false; std::pair const tmp1(pShellCursor->GetPtPos(), false); pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp1); // if the Frame doesn't exist anymore, the complete Layout has to be // created, because there used to be a Frame here! if ( !pFrame ) { // skip, if it is a hidden deleted cell without frame if ( GetLayout()->IsHideRedlines() ) { const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode(); if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete ) return; } do { CalcLayout(); std::pair const tmp(pShellCursor->GetPtPos(), false); pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame( GetLayout(), pShellCursor->GetPoint(), &tmp); } while( !pFrame ); } else if ( Imp()->IsIdleAction() ) // Guarantee everything's properly formatted pFrame->PrepareCursor(); // In protected Fly? but ignore in case of frame selection if( !IsReadOnlyAvailable() && pFrame->IsProtected() && ( !Imp()->GetDrawView() || !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) && (!mxDoc->GetDocShell() || !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) ) { // look for a valid position bool bChgState = true; if( !FindValidContentNode(!HasDrawView() || 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) { // everything is protected / hidden -> special Mode if( m_bAllProtect ) bChgState = false; else { m_eMvState = CursorMoveState::NONE; // state for cursor travelling m_bAllProtect = true; if( GetDoc()->GetDocShell() ) { GetDoc()->GetDocShell()->SetReadOnlyUI(); CallChgLnk(); // notify UI! } return; } } if( bChgState ) { bool bWasAllProtect = m_bAllProtect; m_bAllProtect = false; if( bWasAllProtect && GetDoc()->GetDocShell() && GetDoc()->GetDocShell()->IsReadOnlyUI() ) { GetDoc()->GetDocShell()->SetReadOnlyUI( false ); CallChgLnk(); // notify UI! } m_bAllProtect = false; bAgainst = true; // look for the right Frame again } } } while( bAgainst ); SwCursorMoveState aTmpState( m_eMvState ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); aTmpState.m_bRealHeight = true; aTmpState.m_bRealWidth = IsOverwriteCursor(); aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel(); // #i27615#,#i30453# SwSpecialPos aSpecialPos; aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE; if (pShellCursor->IsInFrontOfLabel()) { aTmpState.m_pSpecialPos = &aSpecialPos; } { DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState)) { Point& rPt = pShellCursor->GetPtPos(); rPt = m_aCharRect.Center(); pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState ); } } UISizeNotify(); // tdf#96256 update view size if( !pShellCursor->HasMark() ) m_aCursorHeight = aTmpState.m_aRealHeight; else { m_aCursorHeight.setX(0); m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? -m_aCharRect.Width() : m_aCharRect.Height()); } if( !bFirst && aOld == m_aCharRect ) break; // if the layout says that we are after the 100th iteration still in // flow then we should always take the current position for granted. // (see bug: 29658) if( !--nLoopCnt ) { OSL_ENSURE( false, "endless loop? CharRect != OldCharRect "); break; } aOld = m_aCharRect; bFirst = false; // update cursor Points to the new Positions pShellCursor->GetPtPos().setX(m_aCharRect.Left()); pShellCursor->GetPtPos().setY(m_aCharRect.Top()); if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down { DisableCallbackAction a(*GetLayout()); pFrame->Calc(GetOut()); m_nUpDownX = pFrame->IsVertical() ? m_aCharRect.Top() - pFrame->getFrameArea().Top() : m_aCharRect.Left() - pFrame->getFrameArea().Left(); } // scroll Cursor to visible area if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN && (HasSelection() || eFlags & SwCursorShell::READONLY || !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) ) { // in case of scrolling this EndAction doesn't show the SV cursor // again, thus save and reset the flag here bool bSav = m_bSVCursorVis; m_bSVCursorVis = false; MakeSelVisible(); m_bSVCursorVis = bSav; } } while( eFlags & SwCursorShell::SCROLLWIN ); assert(pFrame); if( m_pBlockCursor ) RefreshBlockCursor(); // We should not restrict cursor update to the active view when using LOK bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive(); if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor ) { if( m_pTableCursor ) m_pTableCursor->SwSelPaintRects::Show(); else { m_pCurrentCursor->SwSelPaintRects::Show(); if( m_pBlockCursor ) { SwShellCursor* pNxt = m_pCurrentCursor->GetNext(); while( pNxt && pNxt != m_pCurrentCursor ) { pNxt->SwSelPaintRects::Show(); pNxt = pNxt->GetNext(); } } } } m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint #if !ENABLE_WASM_STRIP_ACCESSIBILITY if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents) Imp()->InvalidateAccessibleCursorPosition( pFrame ); #endif // switch from blinking cursor to read-only-text-selection cursor const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings(). GetCursorBlinkTime(); if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) == ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) ) { // non blinking cursor in read only - text selection mode AllSettings aSettings = GetOut()->GetSettings(); StyleSettings aStyleSettings = aSettings.GetStyleSettings(); const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ? Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() : STYLE_CURSOR_NOBLINKTIME; aStyleSettings.SetCursorBlinkTime( nNewBlinkTime ); aSettings.SetStyleSettings( aStyleSettings ); GetOut()->SetSettings( aSettings ); } if( m_bSVCursorVis ) m_pVisibleCursor->Show(); // show again if (comphelper::LibreOfficeKit::isActive()) sendLOKCursorUpdates(); getIDocumentMarkAccess()->NotifyCursorUpdate(*this); } void SwCursorShell::sendLOKCursorUpdates() { SwView* pView = static_cast(GetSfxViewShell()); if (!pView || !pView->GetWrtShellPtr()) return; SwWrtShell* pShell = &pView->GetWrtShell(); SwFrame* pCurrentFrame = GetCurrFrame(); SelectionType eType = pShell->GetSelectionType(); tools::JsonWriter aJsonWriter; if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab()) { const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr); { auto columnsNode = aJsonWriter.startNode("columns"); SwTabCols aTabCols; pShell->GetTabCols(aTabCols); const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left(); aJsonWriter.put("left", aTabCols.GetLeft()); aJsonWriter.put("right", aTabCols.GetRight()); aJsonWriter.put("tableOffset", static_cast(nColumnOffset)); { auto entriesNode = aJsonWriter.startArray("entries"); for (size_t i = 0; i < aTabCols.Count(); ++i) { auto entryNode = aJsonWriter.startStruct(); auto const & rEntry = aTabCols.GetEntry(i); aJsonWriter.put("position", rEntry.nPos); aJsonWriter.put("min", rEntry.nMin); aJsonWriter.put("max", rEntry.nMax); aJsonWriter.put("hidden", rEntry.bHidden); } } } { auto rowsNode = aJsonWriter.startNode("rows"); SwTabCols aTabRows; pShell->GetTabRows(aTabRows); const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top(); aJsonWriter.put("left", aTabRows.GetLeft()); aJsonWriter.put("right", aTabRows.GetRight()); aJsonWriter.put("tableOffset", static_cast(nRowOffset)); { auto entriesNode = aJsonWriter.startArray("entries"); for (size_t i = 0; i < aTabRows.Count(); ++i) { auto entryNode = aJsonWriter.startStruct(); auto const & rEntry = aTabRows.GetEntry(i); aJsonWriter.put("position", rEntry.nPos); aJsonWriter.put("min", rEntry.nMin); aJsonWriter.put("max", rEntry.nMax); aJsonWriter.put("hidden", rEntry.bHidden); } } } } OString pChar = aJsonWriter.finishAndGetAsOString(); GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar); } void SwCursorShell::RefreshBlockCursor() { assert(m_pBlockCursor); SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); Point aPt = rBlock.GetPtPos(); std::pair const tmp(aPt, false); SwContentFrame* pFrame = rBlock.GetPointContentNode()->getLayoutFrame( GetLayout(), rBlock.GetPoint(), &tmp); Point aMk; if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() ) { aPt = *m_pBlockCursor->getStartPoint(); aMk = *m_pBlockCursor->getEndPoint(); } else { aPt = rBlock.GetPtPos(); if( pFrame ) { if( pFrame->IsVertical() ) aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX()); else aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX()); } aMk = rBlock.GetMkPos(); } SwRect aRect( aMk, aPt ); aRect.Justify(); SwSelectionList aSelList( pFrame ); if( !GetLayout()->FillSelection( aSelList, aRect ) ) return; SwCursor* pNxt = static_cast(m_pCurrentCursor->GetNext()); while( pNxt != m_pCurrentCursor ) { delete pNxt; pNxt = static_cast(m_pCurrentCursor->GetNext()); } std::list::iterator pStart = aSelList.getStart(); std::list::iterator pPam = aSelList.getEnd(); OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" ); m_pCurrentCursor->SetMark(); --pPam; // If there is only one text portion inside the rectangle, a simple // selection is created if( pPam == pStart ) { *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); if( (*pPam)->HasMark() ) *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); else m_pCurrentCursor->DeleteMark(); delete *pPam; m_pCurrentCursor->SetColumnSelection( false ); } else { // The order of the SwSelectionList has to be preserved but // the order inside the ring created by CreateCursor() is not like // expected => First create the selections before the last one // downto the first selection. // At least create the cursor for the last selection --pPam; *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections) if( (*pPam)->HasMark() ) *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); else m_pCurrentCursor->DeleteMark(); delete *pPam; m_pCurrentCursor->SetColumnSelection( true ); while( pPam != pStart ) { --pPam; SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end()); m_pCurrentCursor->clear(); m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1 if( (*pPam)->HasMark() ) { m_pCurrentCursor->SetMark(); *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); } else m_pCurrentCursor->DeleteMark(); m_pCurrentCursor->SetColumnSelection( true ); delete *pPam; } { SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() ); m_pCurrentCursor->clear(); m_pCurrentCursor->DeleteMark(); } pPam = aSelList.getEnd(); --pPam; *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection if( (*pPam)->HasMark() ) { m_pCurrentCursor->SetMark(); *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); } else m_pCurrentCursor->DeleteMark(); m_pCurrentCursor->SetColumnSelection( true ); delete *pPam; } } /// create a copy of the cursor and save it in the stack void SwCursorShell::Push() { // fdo#60513: if we have a table cursor, copy that; else copy current. // This seems to work because UpdateCursor() will fix this up on Pop(), // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring. SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor); m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(), pCurrent->GetPtPos(), m_pStackCursor ); if (pCurrent->HasMark()) { m_pStackCursor->SetMark(); *m_pStackCursor->GetMark() = *pCurrent->GetMark(); } } /** delete cursor @param eDelete delete from stack, or delete current and assign the one from stack as the new current cursor. @return if there was one on the stack, otherwise */ bool SwCursorShell::Pop(PopMode const eDelete) { std::optional aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed return Pop(eDelete, aLink); } bool SwCursorShell::Pop(PopMode const eDelete, [[maybe_unused]] std::optional& roLink) { // parameter exists only to be deleted before return assert(roLink); comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } ); // are there any left? if (nullptr == m_pStackCursor) return false; SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor; // the successor becomes the current one if (m_pStackCursor->GetNext() != m_pStackCursor) { pTmp = m_pStackCursor->GetNext(); } if (PopMode::DeleteStack == eDelete) delete m_pStackCursor; m_pStackCursor = pTmp; // assign new one if (PopMode::DeleteCurrent == eDelete) { ::std::optional oSaveState( *m_pCurrentCursor ); // If the visible SSelection was not changed const Point& rPoint = pOldStack->GetPtPos(); if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos()) { // move "Selections Rectangles" m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() ); pOldStack->clear(); } if( pOldStack->HasMark() ) { m_pCurrentCursor->SetMark(); *m_pCurrentCursor->GetMark() = *pOldStack->GetMark(); m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos(); } else // no selection so revoke old one and set to old position m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint(); m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos(); delete pOldStack; if( !m_pCurrentCursor->IsInProtectTable( true ) && !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ) ) { oSaveState.reset(); // prevent UAF UpdateCursor(); // update current cursor if (m_pTableCursor) { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table m_pTableCursor->SetChgd(); } } } return true; } /** Combine two cursors Delete topmost from stack and use its GetMark in the current. */ void SwCursorShell::Combine() { // any others left? if (nullptr == m_pStackCursor) return; SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed // rhbz#689053: IsSelOvr must restore the saved stack position, not the // current one, because current point + stack mark may be invalid PaM SwCursorSaveState aSaveState(*m_pStackCursor); // stack cursor & current cursor in same Section? assert(!m_pStackCursor->HasMark() || CheckNodesRange(m_pStackCursor->GetMark()->GetNode(), m_pCurrentCursor->GetPoint()->GetNode(), true)); *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint(); m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos(); SwShellCursor * pTmp = nullptr; if (m_pStackCursor->GetNext() != m_pStackCursor) { pTmp = m_pStackCursor->GetNext(); } delete m_pCurrentCursor; m_pCurrentCursor = m_pStackCursor; m_pStackCursor->MoveTo(nullptr); // remove from ring m_pStackCursor = pTmp; if( !m_pCurrentCursor->IsInProtectTable( true ) && !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ) ) { UpdateCursor(); // update current cursor } } void SwCursorShell::HideCursors() { if( !m_bHasFocus || m_bBasicHideCursor ) return; // if cursor is visible then hide SV cursor if( m_pVisibleCursor->IsVisible() ) { CurrShell aCurr( this ); m_pVisibleCursor->Hide(); } // revoke inversion of SSelection SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; pCurrentCursor->Hide(); } void SwCursorShell::ShowCursors( bool bCursorVis ) { if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor ) return; CurrShell aCurr( this ); SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; pCurrentCursor->Show(nullptr); if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again m_pVisibleCursor->Show(); } void SwCursorShell::ShowCursor() { if( m_bBasicHideCursor ) return; comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); m_bSVCursorVis = true; m_pCurrentCursor->SetShowTextInputFieldOverlay( true ); m_pCurrentCursor->SetShowContentControlOverlay(true); if (comphelper::LibreOfficeKit::isActive()) { const OString aPayload = OString::boolean(m_bSVCursorVis); GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload); SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); } UpdateCursor(); } void SwCursorShell::HideCursor() { if( m_bBasicHideCursor ) return; m_bSVCursorVis = false; // possibly reverse selected areas!! CurrShell aCurr( this ); m_pCurrentCursor->SetShowTextInputFieldOverlay( false ); m_pCurrentCursor->SetShowContentControlOverlay(false); m_pVisibleCursor->Hide(); if (comphelper::LibreOfficeKit::isActive()) { OString aPayload = OString::boolean(m_bSVCursorVis); GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload); SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); } } void SwCursorShell::ShellLoseFocus() { if( !m_bBasicHideCursor ) HideCursors(); m_bHasFocus = false; } void SwCursorShell::ShellGetFocus() { comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); m_bHasFocus = true; if( !m_bBasicHideCursor && VisArea().Width() ) { UpdateCursor( o3tl::narrowing( SwCursorShell::CHKRANGE ) ); ShowCursors( m_bSVCursorVis ); } } /** Get current frame in which the cursor is positioned. */ SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const { CurrShell aCurr( const_cast(this) ); SwContentFrame *pRet = nullptr; SwContentNode *pNd = m_pCurrentCursor->GetPointContentNode(); if ( pNd ) { if ( bCalcFrame ) { sal_uInt16* pST = const_cast(&mnStartAction); ++(*pST); const Size aOldSz( GetDocSize() ); std::pair const tmp(m_pCurrentCursor->GetPtPos(), true); pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); --(*pST); if( aOldSz != GetDocSize() ) const_cast(this)->SizeChgNotify(); } else { std::pair const tmp(m_pCurrentCursor->GetPtPos(), false); pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); } } return pRet; } //TODO: provide documentation /** forward all attribute/format changes at the current node to the Link @param pOld ??? @param pNew ??? */ void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint) { if(dynamic_cast(&rHint) && m_aGrfArrivedLnk.IsSet()) { m_aGrfArrivedLnk.Call(*this); return; } if (rHint.GetId() != SfxHintId::SwLegacyModify) return; auto pLegacy = static_cast(&rHint); auto nWhich = pLegacy->GetWhich(); if(!nWhich) nWhich = RES_OBJECTDYING; if( m_bCallChgLnk && ( !isFormatMessage(nWhich) || nWhich == RES_FMT_CHG || nWhich == RES_UPDATE_ATTR || nWhich == RES_ATTRSET_CHG )) // messages are not forwarded // #i6681#: RES_UPDATE_ATTR is implicitly unset in // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do // not need to send the expensive RES_FMT_CHG in Insert. CallChgLnk(); if( nWhich == RES_OBJECTDYING ) { EndListeningAll(); } } /** Does the current cursor create a selection? This means checking if GetMark is set and if SPoint and GetMark differ. */ bool SwCursorShell::HasSelection() const { const SwPaM* pCursor = getShellCursor( true ); return IsTableMode() || (pCursor->HasMark() && (*pCursor->GetPoint() != *pCursor->GetMark() || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End()))); } void SwCursorShell::CallChgLnk() { // Do not make any call in StartAction/EndAction but just set the flag. // This will be handled in EndAction. if (ActionPend()) m_bChgCallFlag = true; // remember change else if( m_aChgLnk.IsSet() ) { if( m_bCallChgLnk ) m_aChgLnk.Call(nullptr); m_bChgCallFlag = false; // reset flag } } /// get selected text of a node at current cursor OUString SwCursorShell::GetSelText() const { OUString aText; if (GetLayout()->HasMergedParas()) { SwContentFrame const*const pFrame(GetCurrFrame(false)); if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->GetNodeIndex())) { OUStringBuffer buf; SwPosition const*const pStart(m_pCurrentCursor->Start()); SwPosition const*const pEnd(m_pCurrentCursor->End()); for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i) { SwNode const& rNode(*pStart->GetNodes()[i]); assert(!rNode.IsEndNode()); if (rNode.IsStartNode()) { i = rNode.EndOfSectionIndex(); } else if (rNode.IsTextNode()) { sal_Int32 const nStart(i == pStart->GetNodeIndex() ? pStart->GetContentIndex() : 0); sal_Int32 const nEnd(i == pEnd->GetNodeIndex() ? pEnd->GetContentIndex() : rNode.GetTextNode()->Len()); buf.append(rNode.GetTextNode()->GetExpandText( GetLayout(), nStart, nEnd - nStart, false, false, false, ExpandMode::HideDeletions)); } } aText = buf.makeStringAndClear(); } } else if( m_pCurrentCursor->GetPoint()->GetNodeIndex() == m_pCurrentCursor->GetMark()->GetNodeIndex() ) { SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode(); if( pTextNd ) { const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex(); aText = pTextNd->GetExpandText(GetLayout(), nStt, m_pCurrentCursor->End()->GetContentIndex() - nStt ); } } return aText; } /** get the nth character of the current SSelection in the same paragraph as the start/end. @param bEnd Start counting from the end? From start otherwise. @param nOffset position of the character */ sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset ) { if( IsTableMode() ) // not possible in table mode return 0; const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint() : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); SwTextNode* pTextNd = pPos->GetNode().GetTextNode(); if( !pTextNd ) return 0; SwTextFrame const*const pFrame(static_cast(pTextNd->getLayoutFrame(GetLayout()))); if (!pFrame) { return 0; } const sal_Int32 nPos(sal_Int32(pFrame->MapModelToViewPos(*pPos))); const OUString& rStr(pFrame->GetText()); sal_Unicode cCh = 0; if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength()) cCh = rStr[nPos + nOffset]; return cCh; } /** extend current SSelection by n characters @param bEnd Start counting from the end? From start otherwise. @param nCount Number of characters. */ bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount ) { if( !m_pCurrentCursor->HasMark() || IsTableMode() ) return false; // no selection SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); SwTextNode* pTextNd = pPos->GetNode().GetTextNode(); assert(pTextNd); sal_Int32 nPos = pPos->GetContentIndex(); if( bEnd ) { if ((nPos + nCount) <= pTextNd->GetText().getLength()) nPos = nPos + nCount; else return false; // not possible } else if( nPos >= nCount ) nPos = nPos - nCount; else return false; // not possible anymore SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed pPos->SetContent(nPos) ; UpdateCursor(); return true; } /** Move visible cursor to given position in document. @param rPt The position to move the visible cursor to. @return if SPoint was corrected by the layout. */ bool SwCursorShell::SetVisibleCursor( const Point &rPt ) { CurrShell aCurr( this ); Point aPt( rPt ); SwPosition aPos( *m_pCurrentCursor->GetPoint() ); SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); aTmpState.m_bRealHeight = true; const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ ); SetInFrontOfLabel( false ); // #i27615# // show only in TextNodes SwTextNode* pTextNd = aPos.GetNode().GetTextNode(); if( !pTextNd ) return false; const SwSectionNode* pSectNd = pTextNd->FindSectionNode(); if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() || ( !IsReadOnlyAvailable() && pSectNd->GetSection().IsProtectFlag())) ) return false; std::pair const tmp(aPt, true); SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp); if ( Imp()->IsIdleAction() ) pFrame->PrepareCursor(); SwRect aTmp( m_aCharRect ); pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState ); // #i10137# if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() ) return true; m_pVisibleCursor->Hide(); // always hide visible cursor if( IsScrollMDI( this, m_aCharRect )) { MakeVisible( m_aCharRect ); m_pCurrentCursor->Show(nullptr); } { if( aTmpState.m_bRealHeight ) m_aCursorHeight = aTmpState.m_aRealHeight; else { m_aCursorHeight.setX(0); m_aCursorHeight.setY(m_aCharRect.Height()); } m_pVisibleCursor->SetDragCursor(); m_pVisibleCursor->Show(); // show again } return bRet; } SwVisibleCursor* SwCursorShell::GetVisibleCursor() const { return m_pVisibleCursor; } bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const { Point aPt( rPt ); SwPaM aPam( *m_pCurrentCursor->GetPoint() ); GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt ); // form view return aPam.HasReadonlySel(GetViewOptions()->IsFormView(), false); } /** Get the number of elements in the ring of cursors @param bAll If get only spanned ones (= with selections) (Basic). */ sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const { SwPaM* pTmp = GetCursor()->GetNext(); sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() && *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0; while( pTmp != m_pCurrentCursor ) { if( bAll || ( pTmp->HasMark() && *pTmp->GetPoint() != *pTmp->GetMark())) ++n; pTmp = pTmp->GetNext(); } return n; } bool SwCursorShell::IsStartOfDoc() const { if( m_pCurrentCursor->GetPoint()->GetContentIndex() ) return false; // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd) SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 ); if( !aIdx.GetNode().IsContentNode() ) GetDoc()->GetNodes().GoNext( &aIdx ); return aIdx == m_pCurrentCursor->GetPoint()->GetNode(); } bool SwCursorShell::IsEndOfDoc() const { SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 ); SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); if( !pCNd ) pCNd = SwNodes::GoPrevious( &aIdx ); return aIdx == m_pCurrentCursor->GetPoint()->GetNode() && pCNd && pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex(); } /** Invalidate cursors Delete all created cursors, set table crsr and last crsr to their TextNode (or StartNode?). They will then all re-created at the next ::GetCursor() call. This is needed for Drag&Drop/ Clipboard-paste in tables. */ bool SwCursorShell::ParkTableCursor() { if( !m_pTableCursor ) return false; m_pTableCursor->ParkCursor(); while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) delete m_pCurrentCursor->GetNext(); // *always* move cursor's Point and Mark m_pCurrentCursor->DeleteMark(); *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); return true; } void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing ) { auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition* SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing; // search over the whole ring bool bGoNext; do { if (!pTmp) break; auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition* // If a SPoint or GetMark are in a cursor area then cancel the old area. // During comparison keep in mind that End() is outside the area. if( *pStt <= *pTmpStt ) { if( *pEnd > *pTmpStt || ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) pTmpDel = pTmp; } else if( *pStt < *pTmpEnd ) pTmpDel = pTmp; bGoNext = true; if (pTmpDel) // is the pam in the range -> delete { bool bDelete = true; if( *ppDelRing == pTmpDel ) { if( *ppDelRing == m_pCurrentCursor ) { bDelete = GoNextCursor(); if( bDelete ) { bGoNext = false; pTmp = pTmp->GetNext(); } } else bDelete = false; // never delete the StackCursor } if( bDelete ) { if (pTmp == pTmpDel) pTmp = nullptr; delete pTmpDel; // invalidate old area } else { pTmpDel->GetPoint()->Assign(SwNodeOffset(0)); pTmpDel->DeleteMark(); } pTmpDel = nullptr; } if( bGoNext && pTmp ) pTmp = pTmp->GetNext(); } while( !bGoNext || *ppDelRing != pTmp ); } //TODO: provide documentation /** Remove selections and additional cursors of all shells. The remaining cursor of the shell is parked. @param rIdx ??? */ void SwCursorShell::ParkCursor( const SwNode &rIdx ) { const SwNode *pNode = &rIdx; // create a new PaM SwPaM aNew( *GetCursor()->GetPoint() ); if( pNode->GetStartNode() ) { pNode = pNode->StartOfSectionNode(); if( pNode->IsTableNode() ) { // the given node is in a table, thus park cursor to table node // (outside of the table) aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() ); } else // Also on the start node itself. Then we need to request the start // node always via its end node! (StartOfSelection of StartNode is // the parent) aNew.GetPoint()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() ); } else aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() ); aNew.SetMark(); aNew.GetPoint()->Assign(*pNode->EndOfSectionNode()); // take care of all shells for(SwViewShell& rTmp : GetRingContainer()) { if( auto pSh = dynamic_cast(&rTmp)) { if (pSh->m_pStackCursor) pSh->ParkPams(&aNew, &pSh->m_pStackCursor); pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor ); if( pSh->m_pTableCursor ) { // set table cursor always to 0 and the current one always to // the beginning of the table SwPaM* pTCursor = pSh->GetTableCrs(); SwNode* pTableNd = pTCursor->GetPoint()->GetNode().FindTableNode(); if ( pTableNd ) { pTCursor->GetPoint()->Assign(SwNodeOffset(0)); pTCursor->DeleteMark(); pSh->m_pCurrentCursor->GetPoint()->Assign( *pTableNd ); } } } } } /** Copy constructor Copy cursor position and add it to the ring. All views of a document are in the ring of the shell. */ SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin ) : SwViewShell( rShell, pInitWin ) , sw::BroadcastingModify() , m_pStackCursor( nullptr ) , m_pBlockCursor( nullptr ) , m_pTableCursor( nullptr ) , m_pBoxIdx( nullptr ) , m_pBoxPtr( nullptr ) , m_nUpDownX(0) , m_nLeftFramePos(0) , m_nCurrentNode(0) , m_nCurrentContent(0) , m_nCurrentNdTyp(SwNodeType::NONE) , m_nCursorMove( 0 ) , m_eMvState( CursorMoveState::NONE ) , m_eEnhancedTableSel(SwTable::SEARCH_NONE) , m_nMarkedListLevel( 0 ) , m_oldColFrame(nullptr) { CurrShell aCurr( this ); // only keep the position of the current cursor of the copy shell m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) ); m_pCurrentCursor->GetPointContentNode()->Add( this ); m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = m_bOverwriteCursor = false; m_bSendAccessibleCursorEvents = true; m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; m_bSVCursorVis = true; m_bSetCursorInReadOnly = true; m_pVisibleCursor = new SwVisibleCursor( this ); m_bMacroExecAllowed = rShell.IsMacroExecAllowed(); } /// default constructor SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin, const SwViewOption *pInitOpt ) : SwViewShell( rDoc, pInitWin, pInitOpt ) , sw::BroadcastingModify() , m_pStackCursor( nullptr ) , m_pBlockCursor( nullptr ) , m_pTableCursor( nullptr ) , m_pBoxIdx( nullptr ) , m_pBoxPtr( nullptr ) , m_nUpDownX(0) , m_nLeftFramePos(0) , m_nCurrentNode(0) , m_nCurrentContent(0) , m_nCurrentNdTyp(SwNodeType::NONE) , m_nCursorMove( 0 ) , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint , m_eEnhancedTableSel(SwTable::SEARCH_NONE) , m_nMarkedListLevel( 0 ) , m_oldColFrame(nullptr) { CurrShell aCurr( this ); // create initial cursor and set it to first content position SwNodes& rNds = rDoc.GetNodes(); SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() ); SwContentNode* pCNd = rNds.GoNext( &aNodeIdx ); // go to the first ContentNode m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, pCNd, 0 ) ); // Register shell as dependent at current node. As a result all attribute // changes can be forwarded via the Link. pCNd->Add( this ); m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = m_bOverwriteCursor = false; m_bSendAccessibleCursorEvents = true; m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; m_bSVCursorVis = true; m_bSetCursorInReadOnly = true; m_pVisibleCursor = new SwVisibleCursor( this ); m_bMacroExecAllowed = true; } SwCursorShell::~SwCursorShell() { // if it is not the last view then at least the field should be updated if( !unique() ) CheckTableBoxContent( m_pCurrentCursor->GetPoint() ); else ClearTableBoxContent(); delete m_pVisibleCursor; delete m_pBlockCursor; delete m_pTableCursor; // release cursors while(m_pCurrentCursor->GetNext() != m_pCurrentCursor) delete m_pCurrentCursor->GetNext(); delete m_pCurrentCursor; // free stack if (m_pStackCursor) { while (m_pStackCursor->GetNext() != m_pStackCursor) delete m_pStackCursor->GetNext(); delete m_pStackCursor; } // #i54025# - do not give a HTML parser that might potentially hang as // a client at the cursor shell the chance to hang itself on a TextNode EndListeningAll(); } SwShellCursor* SwCursorShell::getShellCursor( bool bBlock ) { if( m_pTableCursor ) return m_pTableCursor; if( m_pBlockCursor && bBlock ) return &m_pBlockCursor->getShellCursor(); return m_pCurrentCursor; } /** Should WaitPtr be switched on for the clipboard? Wait for TableMode, multiple selections and more than x selected paragraphs. */ bool SwCursorShell::ShouldWait() const { if ( IsTableMode() || GetCursorCnt() > 1 ) return true; if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() ) return true; SwPaM* pPam = GetCursor(); return pPam->Start()->GetNodeIndex() + SwNodeOffset(10) < pPam->End()->GetNodeIndex(); } size_t SwCursorShell::UpdateTableSelBoxes() { if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount())) { GetLayout()->MakeTableCursors( *m_pTableCursor ); } return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0; } /// show the current selected "object" void SwCursorShell::MakeSelVisible() { OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" ); if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() ) { SwRect aTmp( m_aCharRect ); tools::Long nDiff = m_aCharRect.Height() - VisArea().Height(); if( nDiff < m_aCursorHeight.getX() ) aTmp.Top( nDiff + m_aCharRect.Top() ); else { aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() ); aTmp.Height( m_aCursorHeight.getY() ); } if( !aTmp.HasArea() ) { aTmp.AddHeight(1 ); aTmp.AddWidth(1 ); } MakeVisible( aTmp ); } else { if( m_aCharRect.HasArea() ) MakeVisible( m_aCharRect ); else { SwRect aTmp( m_aCharRect ); aTmp.AddHeight(1 ); aTmp.AddWidth(1 ); MakeVisible( aTmp ); } } } /// search a valid content position (not protected/hidden) bool SwCursorShell::FindValidContentNode( bool bOnlyText ) { if( m_pTableCursor ) { assert(!"Did not remove table selection!"); return false; } // #i45129# - everything is allowed in UI-readonly if( !m_bAllProtect && GetDoc()->GetDocShell() && GetDoc()->GetDocShell()->IsReadOnlyUI() ) return true; if( m_pCurrentCursor->HasMark() && !mbSelectAll ) ClearMark(); // first check for frames SwPosition& rNdPos = *m_pCurrentCursor->GetPoint(); SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup SwNodes& rNds = mxDoc->GetNodes(); SwContentNode* pCNd = rNdPos.GetNode().GetContentNode(); const SwContentFrame * pFrame; if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) && !IsReadOnlyAvailable() && pFrame->IsProtected() && nNdIdx < rNds.GetEndOfExtras().GetIndex() ) { // skip protected frame SwPaM aPam( *m_pCurrentCursor->GetPoint() ); aPam.SetMark(); aPam.GetMark()->Assign( rNds.GetEndOfContent() ); aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() ); bool bFirst = false; if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward ))) { aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() ); pCNd = ::GetNode( aPam, bFirst, fnMoveBackward ); } if( !pCNd ) // should *never* happen { rNdPos.Assign(nNdIdx); // back to old node return false; } *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); } else if( bOnlyText && pCNd && pCNd->IsNoTextNode() ) { // set to beginning of document rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() ); mxDoc->GetNodes().GoNext( &rNdPos ); nNdIdx = rNdPos.GetNodeIndex(); } bool bOk = true; // #i9059# cursor may not stand in protected cells // (unless cursor in protected areas is OK.) const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode(); if( !IsReadOnlyAvailable() && pTableNode != nullptr && rNdPos.GetNode().IsProtect() ) { // we're in a table, and we're in a protected area, so we're // probably in a protected cell. // move forward into non-protected area. SwPaM aPam( rNdPos.GetNode(), 0 ); while( aPam.GetPointNode().IsProtect() && aPam.Move( fnMoveForward, GoInContent ) ) ; // nothing to do in the loop; the aPam.Move does the moving! // didn't work? then go backwards! if( aPam.GetPointNode().IsProtect() ) { SwPaM aTmpPaM( rNdPos.GetNode(), 0 ); aPam = aTmpPaM; while( aPam.GetPointNode().IsProtect() && aPam.Move( fnMoveBackward, GoInContent ) ) ; // nothing to do in the loop; the aPam.Move does the moving! } // if we're successful, set the new position if( ! aPam.GetPointNode().IsProtect() ) { *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); } } // in a protected frame const SwSectionNode* pSectNd = rNdPos.GetNode().FindSectionNode(); if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || ( !IsReadOnlyAvailable() && pSectNd->GetSection().IsProtectFlag() )) ) { bOk = false; bool bGoNextSection = true; for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt ) { bool bContinue; do { bContinue = false; for (;;) { if (bGoNextSection) pCNd = rNds.GoNextSection( &rNdPos, true, !IsReadOnlyAvailable() ); else pCNd = SwNodes::GoPrevSection( &rNdPos, true, !IsReadOnlyAvailable() ); if ( pCNd == nullptr) break; // moved inside a table -> check if it is protected if( pCNd->FindTableNode() ) { SwCallLink aTmp( *this ); SwCursorSaveState aSaveState( *m_pCurrentCursor ); aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR if( !m_pCurrentCursor->IsInProtectTable( true ) ) { const SwSectionNode* pSNd = pCNd->FindSectionNode(); if( !pSNd || !pSNd->GetSection().IsHiddenFlag() || (!IsReadOnlyAvailable() && pSNd->GetSection().IsProtectFlag() )) { bOk = true; break; // found non-protected cell } continue; // continue search } } else { bOk = true; break; // found non-protected cell } } if( bOk && rNdPos.GetNodeIndex() < rNds.GetEndOfExtras().GetIndex() ) { // also check for Fly - might be protected as well pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr); if (nullptr == pFrame || ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) || ( bOnlyText && pCNd->IsNoTextNode() ) ) { // continue search bOk = false; bContinue = true; } } } while( bContinue ); if( !bOk ) { if( !nLoopCnt ) bGoNextSection = false; rNdPos.Assign( nNdIdx ); } } } if( bOk ) { pCNd = rNdPos.GetNode().GetContentNode(); const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0; m_pCurrentCursor->GetPoint()->SetContent( nContent ); } else { pCNd = rNdPos.GetNode().GetContentNode(); // if cursor in hidden frame, always move it if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr)) { SwCursorMoveState aTmpState( CursorMoveState::NONE ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(), &aTmpState ); } } return bOk; } bool SwCursorShell::IsCursorReadonly() const { if ( GetViewOptions()->IsReadonly() || GetViewOptions()->IsFormView() /* Formula view */ ) { SwFrame *pFrame = GetCurrFrame( false ); const SwFlyFrame* pFly; const SwSection* pSection; if( pFrame && pFrame->IsInFly() && (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() && pFly->Lower() && !pFly->Lower()->IsNoTextFrame() && !GetDrawView()->GetMarkedObjectList().GetMarkCount() ) { return false; } // edit in readonly sections else if ( pFrame && pFrame->IsInSct() && nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) && pSection->IsEditInReadonlyFlag() ) { return false; } else if ( !IsMultiSelection() && CursorInsideInputField() ) { return false; } return true; } return false; } /// is the cursor allowed to enter ReadOnly sections? void SwCursorShell::SetReadOnlyAvailable( bool bFlag ) { // *never* switch in GlobalDoc if( (!GetDoc()->GetDocShell() || dynamic_cast(GetDoc()->GetDocShell()) == nullptr ) && bFlag != m_bSetCursorInReadOnly ) { // If the flag is switched off then all selections need to be // invalidated. Otherwise we would trust that nothing protected is selected. if( !bFlag ) { ClearMark(); } m_bSetCursorInReadOnly = bFlag; UpdateCursor(); } } bool SwCursorShell::HasReadonlySel(bool const isReplace) const { // Treat selections that span over start or end of paragraph of an outline node // with folded outline content as read-only. if (GetViewOptions()->IsShowOutlineContentVisibilityButton()) { SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell(); if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected()) return true; } bool bRet = false; // If protected area is to be ignored, then selections are never read-only. if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() || GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) && !SwViewOption::IsIgnoreProtectedArea()) { if ( m_pTableCursor != nullptr ) { // TODO: handling when a table cell (cells) is selected bRet = m_pTableCursor->HasReadOnlyBoxSel() || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace); } else { for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) { if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace)) { bRet = true; break; } } } } return bRet; } bool SwCursorShell::HasHiddenSections() const { // Treat selections that span over start or end of paragraph of an outline node // with folded outline content as read-only. if (GetViewOptions()->IsShowOutlineContentVisibilityButton()) { SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell(); if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected()) return true; } bool bRet = false; if ( m_pTableCursor != nullptr ) { bRet = m_pTableCursor->HasHiddenBoxSel() || m_pTableCursor->HasHiddenSections(); } else { for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) { if (rCursor.HasHiddenSections()) { bRet = true; break; } } } return bRet; } bool SwCursorShell::IsSelFullPara() const { bool bRet = false; if( m_pCurrentCursor->GetPoint()->GetNodeIndex() == m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() ) { sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex(); sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex(); if( nStt > nEnd ) std::swap( nStt, nEnd ); const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode(); bRet = pCNd && !nStt && nEnd == pCNd->Len(); } return bRet; } SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const { SwPosition aPos( *m_pCurrentCursor->GetPoint() ); Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() ); if( pPt ) { SwCursorMoveState aTmpState( CursorMoveState::NONE ); aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ); } return mxDoc->GetTextDirection( aPos, &aPt ); } bool SwCursorShell::IsInVerticalText( const Point* pPt ) const { const SvxFrameDirection nDir = GetTextDirection( pPt ); return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir || nDir == SvxFrameDirection::Vertical_LR_BT; } bool SwCursorShell::IsInRightToLeftText() const { const SvxFrameDirection nDir = GetTextDirection(); // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in // vertical environment return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir; } /// If the current cursor position is inside a hidden range true is returned. If bSelect is /// true, the hidden range is selected. If bSelect is false, the hidden range is not selected. bool SwCursorShell::IsInHiddenRange(const bool bSelect) { bool bRet = false; if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() ) { SwPosition& rPt = *m_pCurrentCursor->GetPoint(); const SwTextNode* pNode = rPt.GetNode().GetTextNode(); if ( pNode ) { const sal_Int32 nPos = rPt.GetContentIndex(); // check if nPos is in hidden range sal_Int32 nHiddenStart; sal_Int32 nHiddenEnd; SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd ); if ( COMPLETE_STRING != nHiddenStart ) { if (bSelect) { // make selection: m_pCurrentCursor->SetMark(); m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd); } bRet = true; } } } return bRet; } sal_Int32 SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes, SwDocPositions eStart, SwDocPositions eEnd, bool& bCancel, FindRanges eRng, bool bReplace ) { if( m_pTableCursor ) GetCursor(); delete m_pTableCursor; m_pTableCursor = nullptr; SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed sal_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd, bCancel, eRng, bReplace, GetLayout()); if( nRet || bCancel ) UpdateCursor(); return nRet; } sal_Int32 SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl, SwDocPositions eStart, SwDocPositions eEnd, bool& bCancel, FindRanges eRng, const SwTextFormatColl* pReplFormat ) { if( m_pTableCursor ) GetCursor(); delete m_pTableCursor; m_pTableCursor = nullptr; SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed sal_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng, pReplFormat ); if( nRet ) UpdateCursor(); return nRet; } sal_Int32 SwCursorShell::FindAttrs( const SfxItemSet& rSet, bool bNoCollections, SwDocPositions eStart, SwDocPositions eEnd, bool& bCancel, FindRanges eRng, const i18nutil::SearchOptions2* pSearchOpt, const SfxItemSet* rReplSet ) { if( m_pTableCursor ) GetCursor(); delete m_pTableCursor; m_pTableCursor = nullptr; SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed sal_Int32 nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd, bCancel, eRng, pSearchOpt, rReplSet, GetLayout()); if( nRet ) UpdateCursor(); return nRet; } void SwCursorShell::SetSelection( const SwPaM& rCursor ) { StartAction(); SwCursor* pCursor = GetCursor(); *pCursor->GetPoint() = *rCursor.GetPoint(); if(rCursor.GetNext() != &rCursor) { const SwPaM *_pStartCursor = rCursor.GetNext(); do { SwPaM* pCurrentCursor = CreateCursor(); *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint(); if(_pStartCursor->HasMark()) { pCurrentCursor->SetMark(); *pCurrentCursor->GetMark() = *_pStartCursor->GetMark(); } } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor ); } // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current // cursor; therefore set current cursor's mark only after creating all other cursors if (rCursor.HasMark()) { pCursor->SetMark(); *pCursor->GetMark() = *rCursor.GetMark(); } EndAction(); } static const SwStartNode* lcl_NodeContext( const SwNode& rNode ) { const SwStartNode *pRet = rNode.StartOfSectionNode(); while( pRet->IsSectionNode() || pRet->IsTableNode() || pRet->GetStartNodeType() == SwTableBoxStartNode ) { pRet = pRet->StartOfSectionNode(); } return pRet; } /** Checks if a position is valid. To be valid the position's node must be a content node and the content must not be unregistered. @param aPos the position to check. */ static bool sw_PosOk(const SwPosition & aPos) { return nullptr != aPos.GetNode().GetContentNode() && aPos.GetContentNode(); } /** Checks if a PaM is valid. For a PaM to be valid its point must be valid. Additionally if the PaM has a mark this has to be valid, too. @param aPam the PaM to check */ static bool lcl_CursorOk(SwPaM & aPam) { return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark() || sw_PosOk(*aPam.GetMark())); } void SwCursorShell::ClearUpCursors() { // start of the ring SwPaM * pStartCursor = GetCursor(); // start loop with second entry of the ring SwPaM * pCursor = pStartCursor->GetNext(); SwPaM * pTmpCursor; bool bChanged = false; // For all entries in the ring except the start entry delete the entry if // it is invalid. while (pCursor != pStartCursor) { pTmpCursor = pCursor->GetNext(); if ( ! lcl_CursorOk(*pCursor)) { delete pCursor; bChanged = true; } pCursor = pTmpCursor; } if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) ) { pStartCursor->DeleteMark(); bChanged = true; } if (pStartCursor->GetPoint()->GetNode().IsTableNode()) { // tdf#106959: When cursor points to start of a table, the proper content // node is the first one inside the table, not the previous one SwNodes& aNodes = GetDoc()->GetNodes(); SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode()); if (SwNode* pNode = aNodes.GoNext(&aIdx)) { SwPaM aTmpPam(*pNode); *pStartCursor = aTmpPam; bChanged = true; } } if( !sw_PosOk( *pStartCursor->GetPoint() ) ) { SwNodes & aNodes = GetDoc()->GetNodes(); const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->GetNode() ); SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() ); SwNode * pNode = SwNodes::GoPrevious(&aIdx); if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) { pNode = aNodes.GoNext( &aIdx ); if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) { // If the start entry of the ring is invalid replace it with a // cursor pointing to the beginning of the first content node in the // document. aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode()); pNode = aNodes.GoNext( &aIdx ); } } bool bFound = (pNode != nullptr); assert(bFound); if (bFound) { SwPaM aTmpPam(*pNode); *pStartCursor = aTmpPam; } bChanged = true; } // If at least one of the cursors in the ring have been deleted or replaced, // remove the table cursor. if (m_pTableCursor != nullptr && bChanged) TableCursorToCursor(); } OUString SwCursorShell::GetCursorDescr() const { OUString aResult; if (IsMultiSelection()) aResult += SwResId(STR_MULTISEL); else aResult = SwDoc::GetPaMDescr(*GetCursor()); return aResult; } void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell")); SwViewShell::dumpAsXml(pWriter); (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor")); for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer()) rPaM.dumpAsXml(pWriter); (void)xmlTextWriterEndElement(pWriter); (void)xmlTextWriterEndElement(pWriter); } static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes, uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, const SwWrongList& rSmartTagList, sal_Int32 nCurrent ) { // Insert smart tag information std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps; for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i ) { const sal_Int32 nSTPos = rSmartTagList.Pos( i ); const sal_Int32 nSTLen = rSmartTagList.Len( i ); if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen ) { const SwWrongArea* pArea = rSmartTagList.GetElement( i ); if ( pArea ) { rSmartTagTypes.push_back( pArea->maType ); aStringKeyMaps.push_back( pArea->mxPropertyBag ); } } } if ( !rSmartTagTypes.empty() ) { rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps); } } static void lcl_FillTextRange( uno::Reference& rRange, SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen ) { // create SwPosition for nStartIndex SwPosition aStartPos( rNode, nBegin ); // create SwPosition for nEndIndex SwPosition aEndPos( rNode, nBegin + nLen ); const rtl::Reference xRange = SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos); rRange = xRange; } void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes, uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, uno::Reference< text::XTextRange>& rRange ) const { if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() ) return; SwPaM* pCursor = GetCursor(); SwPosition aPos( *pCursor->GetPoint() ); SwTextNode *pNode = aPos.GetNode().GetTextNode(); if ( !pNode || pNode->IsInProtectSect() ) return; const SwWrongList *pSmartTagList = pNode->GetSmartTags(); if ( !pSmartTagList ) return; sal_Int32 nCurrent = aPos.GetContentIndex(); sal_Int32 nBegin = nCurrent; sal_Int32 nLen = 1; if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin)) return; const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin ); const SwWrongList* pSubList = pSmartTagList->SubList( nIndex ); if ( pSubList ) { pSmartTagList = pSubList; nCurrent = 0; } lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent ); lcl_FillTextRange( rRange, *pNode, nBegin, nLen ); } // see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect ) void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect ) { SwPaM* pCursor = GetCursor(); SwPosition aPos( *pCursor->GetPoint() ); Point aPt( rPt ); SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); SwSpecialPos aSpecialPos; eTmpState.m_pSpecialPos = &aSpecialPos; SwTextNode *pNode; const SwWrongList *pSmartTagList; if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) ) return; pNode = aPos.GetNode().GetTextNode(); if( !pNode ) return; pSmartTagList = pNode->GetSmartTags(); if( !pSmartTagList ) return; if( pNode->IsInProtectSect() ) return; sal_Int32 nBegin = aPos.GetContentIndex(); sal_Int32 nLen = 1; if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin)) return; // get smarttag word OUString aText( pNode->GetText().copy(nBegin, nLen) ); //save the start and end positions of the line and the starting point Push(); LeftMargin(); const sal_Int32 nLineStart = GetCursor()->GetPoint()->GetContentIndex(); RightMargin(); const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex(); Pop(PopMode::DeleteCurrent); // make sure the selection build later from the data below does not // include "in word" character to the left and right in order to // preserve those. Therefore count those "in words" in order to // modify the selection accordingly. const sal_Unicode* pChar = aText.getStr(); sal_Int32 nLeft = 0; while (*pChar++ == CH_TXTATR_INWORD) ++nLeft; pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr; sal_Int32 nRight = 0; while (pChar && *pChar-- == CH_TXTATR_INWORD) ++nRight; aPos.SetContent( nBegin + nLeft ); pCursor = GetCursor(); *pCursor->GetPoint() = aPos; pCursor->SetMark(); ExtendSelection( true, nLen - nLeft - nRight ); // do not determine the rectangle in the current line const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft; // take one less than the line end - otherwise the next line would // be calculated const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd); Push(); pCursor->DeleteMark(); SwPosition& rPos = *GetCursor()->GetPoint(); rPos.SetContent( nWordStart ); SwRect aStartRect; SwCursorMoveState aState; aState.m_bRealWidth = true; SwContentNode* pContentNode = pCursor->GetPointContentNode(); std::pair const tmp(rPt, false); SwContentFrame *pContentFrame = pContentNode->getLayoutFrame( GetLayout(), pCursor->GetPoint(), &tmp); pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); rPos.SetContent( nWordEnd - 1 ); SwRect aEndRect; pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); rSelectRect = aStartRect.Union( aEndRect ); Pop(PopMode::DeleteCurrent); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */