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