/* -*- 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 <sal/config.h>
#include <memory>
#include <vcl/svapp.hxx>
#include <svx/unoshtxt.hxx>
#include <editeng/unoedhlp.hxx>
#include <svl/lstner.hxx>
#include <rtl/ref.hxx>
#include <tools/debug.hxx>
#include <svl/hint.hxx>
#include <svl/style.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdview.hxx>
#include <editeng/outliner.hxx>
#include <editeng/unoforou.hxx>
#include <editeng/unoviwou.hxx>
#include <editeng/outlobj.hxx>
#include <svx/svdotext.hxx>
#include <svx/svdpage.hxx>
#include <editeng/editeng.hxx>
#include <editeng/unotext.hxx>
#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
#include <comphelper/processfactory.hxx>
#include <svx/svdotable.hxx>
#include <cell.hxx>
#include <comphelper/configuration.hxx>
// SvxTextEditSourceImpl
/** @descr
<p>This class essentially provides the text and view forwarders. If
no SdrView is given, this class handles the UNO objects, which are
currently not concerned with view issues. In this case,
GetViewForwarder() always returns NULL and the underlying
EditEngine of the SvxTextForwarder is a background one (i.e. not
the official DrawOutliner, but one created exclusively for this
object, with no relation to a view).
</p>
<p>If a SdrView is given at construction time, the caller is
responsible for destroying this object when the view becomes
invalid (the views cannot notify). If GetViewForwarder(sal_True)
is called, the underlying shape is put into edit mode, the view
forwarder returned encapsulates the OutlinerView and the next call
to GetTextForwarder() yields a forwarder encapsulating the actual
DrawOutliner. Thus, changes on that Outliner are immediately
reflected on the screen. If the object leaves edit mode, the old
behaviour is restored.</p>
*/
class SvxTextEditSourceImpl : public SfxListener, public SfxBroadcaster, public sdr::ObjectUser
{
private:
oslInterlockedCount maRefCount;
SdrObject* mpObject; // TTTT could be reference (?)
SdrText* mpText;
SdrView* mpView;
VclPtr<const OutputDevice> mpWindow;
SdrModel* mpModel; // TTTT probably not needed -> use SdrModel from SdrObject (?)
std::unique_ptr<SdrOutliner> mpOutliner;
std::unique_ptr<SvxOutlinerForwarder> mpTextForwarder;
std::unique_ptr<SvxDrawOutlinerViewForwarder> mpViewForwarder; // if non-NULL, use GetViewModeTextForwarder text forwarder
css::uno::Reference< css::linguistic2::XLinguServiceManager2 > m_xLinguServiceManager;
Point maTextOffset;
bool mbDataValid;
bool mbIsLocked;
bool mbNeedsUpdate;
bool mbOldUndoMode;
bool mbForwarderIsEditMode; // have to reflect that, since ENDEDIT can happen more often
bool mbShapeIsEditMode; // only true, if SdrHintKind::BeginEdit was received
bool mbNotificationsDisabled; // prevent EditEngine/Outliner notifications (e.g. when setting up forwarder)
bool mbNotifyEditOutlinerSet;
SvxUnoTextRangeBaseVec mvTextRanges;
SvxTextForwarder* GetBackgroundTextForwarder();
SvxTextForwarder* GetEditModeTextForwarder();
std::unique_ptr<SvxDrawOutlinerViewForwarder> CreateViewForwarder();
void SetupOutliner();
bool HasView() const { return mpView != nullptr; }
bool IsEditMode() const
{
if (!mbShapeIsEditMode)
return false;
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
return pTextObj && pTextObj->IsTextEditActive();
}
void dispose();
public:
SvxTextEditSourceImpl( SdrObject* pObject, SdrText* pText );
SvxTextEditSourceImpl( SdrObject& rObject, SdrText* pText, SdrView& rView, const OutputDevice& rWindow );
virtual ~SvxTextEditSourceImpl() override;
void acquire();
void release();
virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
SvxTextForwarder* GetTextForwarder();
SvxEditViewForwarder* GetEditViewForwarder( bool );
void UpdateData();
void addRange( SvxUnoTextRangeBase* pNewRange );
void removeRange( SvxUnoTextRangeBase* pOldRange );
const SvxUnoTextRangeBaseVec& getRanges() const { return mvTextRanges;}
void lock();
void unlock();
bool IsValid() const;
Point LogicToPixel( const Point&, const MapMode& rMapMode );
Point PixelToLogic( const Point&, const MapMode& rMapMode );
DECL_LINK( NotifyHdl, EENotify&, void );
virtual void ObjectInDestruction(const SdrObject& rObject) override;
void UpdateOutliner();
};
SvxTextEditSourceImpl::SvxTextEditSourceImpl( SdrObject* pObject, SdrText* pText )
: maRefCount ( 0 ),
mpObject ( pObject ),
mpText ( pText ),
mpView ( nullptr ),
mpWindow ( nullptr ),
mpModel ( pObject ? &pObject->getSdrModelFromSdrObject() : nullptr ), // TTTT should be reference
mbDataValid ( false ),
mbIsLocked ( false ),
mbNeedsUpdate ( false ),
mbOldUndoMode ( false ),
mbForwarderIsEditMode ( false ),
mbShapeIsEditMode ( false ),
mbNotificationsDisabled ( false ),
mbNotifyEditOutlinerSet ( false )
{
DBG_ASSERT( mpObject, "invalid pObject!" );
if( !mpText )
{
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
mpText = pTextObj->getText( 0 );
}
if( mpObject )
{
mpObject->AddListener( *this );
mpObject->AddObjectUser( *this );
}
}
SvxTextEditSourceImpl::SvxTextEditSourceImpl( SdrObject& rObject, SdrText* pText, SdrView& rView, const OutputDevice& rWindow )
: maRefCount ( 0 ),
mpObject ( &rObject ),
mpText ( pText ),
mpView ( &rView ),
mpWindow ( &rWindow ),
mpModel ( &rObject.getSdrModelFromSdrObject() ), // TTTT should be reference
mbDataValid ( false ),
mbIsLocked ( false ),
mbNeedsUpdate ( false ),
mbOldUndoMode ( false ),
mbForwarderIsEditMode ( false ),
mbShapeIsEditMode ( true ),
mbNotificationsDisabled ( false ),
mbNotifyEditOutlinerSet ( false )
{
if( !mpText )
{
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
mpText = pTextObj->getText( 0 );
}
rObject.AddListener( *this );
StartListening( *mpView );
mpObject->AddObjectUser( *this );
// Init edit mode state from shape info (IsTextEditActive())
mbShapeIsEditMode = IsEditMode();
}
SvxTextEditSourceImpl::~SvxTextEditSourceImpl()
{
DBG_ASSERT( !mbIsLocked, "text edit source was not unlocked before dispose!" );
if( mpObject )
mpObject->RemoveObjectUser( *this );
dispose();
}
void SvxTextEditSourceImpl::addRange( SvxUnoTextRangeBase* pNewRange )
{
if( pNewRange )
if( std::find( mvTextRanges.begin(), mvTextRanges.end(), pNewRange ) == mvTextRanges.end() )
mvTextRanges.push_back( pNewRange );
}
void SvxTextEditSourceImpl::removeRange( SvxUnoTextRangeBase* pOldRange )
{
if( pOldRange )
std::erase(mvTextRanges, pOldRange);
}
void SvxTextEditSourceImpl::acquire()
{
osl_atomic_increment( &maRefCount );
}
void SvxTextEditSourceImpl::release()
{
if( ! osl_atomic_decrement( &maRefCount ) )
delete this;
}
void SvxTextEditSourceImpl::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
{
// #i105988 keep reference to this object
rtl::Reference< SvxTextEditSourceImpl > xThis( this );
if (SfxHintId::Dying == rHint.GetId())
{
if (&rBC == mpView)
{
mpView = nullptr;
mpViewForwarder.reset();
}
}
else if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
{
const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
switch( pSdrHint->GetKind() )
{
case SdrHintKind::ObjectChange:
{
mbDataValid = false; // Text has to be get again
if( HasView() )
{
// Update maTextOffset, object has changed
// Cannot call that here, since TakeTextRect() (called from there)
// changes outliner content.
// UpdateOutliner();
// Broadcast object changes, as they might change visible attributes
SvxViewChangedHint aHint;
Broadcast( aHint );
}
break;
}
case SdrHintKind::BeginEdit:
if( mpObject == pSdrHint->GetObject() )
{
// Once SdrHintKind::BeginEdit is broadcast, each EditSource of
// AccessibleCell will handle it here and call below:
// mpView->GetTextEditOutliner()->SetNotifyHdl(), which
// will replace the Notifier for current editable cell. It
// is totally wrong. So add check here to avoid the
// incorrect replacement of notifier.
// Currently it only happens on the editsource of
// AccessibleCell
if (mpObject && mpText)
{
sdr::table::SdrTableObj* pTableObj = dynamic_cast< sdr::table::SdrTableObj* >( mpObject );
if(pTableObj)
{
const sdr::table::CellRef& xCell = pTableObj->getActiveCell();
if (xCell.is())
{
sdr::table::Cell* pCellObj = dynamic_cast< sdr::table::Cell* >( mpText );
if (pCellObj && xCell.get() != pCellObj)
break;
}
}
}
// invalidate old forwarder
if( !mbForwarderIsEditMode )
{
mpTextForwarder.reset();
}
// register as listener - need to broadcast state change messages
if( mpView && mpView->GetTextEditOutliner() )
{
mpView->GetTextEditOutliner()->SetNotifyHdl( LINK(this, SvxTextEditSourceImpl, NotifyHdl) );
mbNotifyEditOutlinerSet = true;
}
// Only now we're really in edit mode
mbShapeIsEditMode = true;
Broadcast( *pSdrHint );
}
break;
case SdrHintKind::EndEdit:
if( mpObject == pSdrHint->GetObject() )
{
Broadcast( *pSdrHint );
// We're no longer in edit mode
mbShapeIsEditMode = false;
// remove as listener - outliner might outlive ourselves
if( mpView && mpView->GetTextEditOutliner() )
{
mpView->GetTextEditOutliner()->SetNotifyHdl( Link<EENotify&,void>() );
mbNotifyEditOutlinerSet = false;
}
// destroy view forwarder, OutlinerView no longer
// valid (no need for UpdateData(), it's been
// synched on SdrEndTextEdit)
mpViewForwarder.reset();
// Invalidate text forwarder, we might
// not be called again before entering edit mode a
// second time! Then, the old outliner might be
// invalid.
if( mbForwarderIsEditMode )
{
mbForwarderIsEditMode = false;
mpTextForwarder.reset();
}
}
break;
default:
break;
}
}
else if (rHint.GetId() == SfxHintId::SvxViewChanged)
{
const SvxViewChangedHint* pViewHint = static_cast<const SvxViewChangedHint*>(&rHint);
Broadcast( *pViewHint );
}
}
/* this is a callback from the attached SdrObject when it is actually deleted */
void SvxTextEditSourceImpl::ObjectInDestruction(const SdrObject&)
{
mpObject = nullptr;
dispose();
Broadcast( SfxHint( SfxHintId::Dying ) );
}
/* unregister at all objects and set all references to 0 */
void SvxTextEditSourceImpl::dispose()
{
mpTextForwarder.reset();
mpViewForwarder.reset();
if( mpOutliner )
{
if( mpModel )
{
mpModel->disposeOutliner( std::move(mpOutliner) );
}
else
{
mpOutliner.reset();
}
}
if( mpModel )
mpModel = nullptr;
if( mpView )
{
// remove as listener - outliner might outlive ourselves
if (mbNotifyEditOutlinerSet && mpView->GetTextEditOutliner())
{
mpView->GetTextEditOutliner()->SetNotifyHdl(Link<EENotify&,void>());
mbNotifyEditOutlinerSet = false;
}
EndListening( *mpView );
mpView = nullptr;
}
if( mpObject )
{
mpObject->RemoveListener( *this );
mpObject->RemoveObjectUser( *this );
mpObject = nullptr;
}
mpWindow = nullptr;
}
void SvxTextEditSourceImpl::SetupOutliner()
{
// only for UAA edit source: setup outliner equivalently as in
// SdrTextObj::Paint(), such that formatting equals screen
// layout
if( !(mpObject && mpOutliner) )
return;
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
{
tools::Rectangle aPaintRect;
tools::Rectangle aBoundRect( pTextObj->GetCurrentBoundRect() );
pTextObj->SetupOutlinerFormatting( *mpOutliner, aPaintRect );
// calc text offset from shape anchor
maTextOffset = aPaintRect.TopLeft() - aBoundRect.TopLeft();
}
}
void SvxTextEditSourceImpl::UpdateOutliner()
{
// only for UAA edit source: update outliner equivalently as in
// SdrTextObj::Paint(), such that formatting equals screen
// layout
if( !(mpObject && mpOutliner) )
return;
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
{
tools::Rectangle aPaintRect;
tools::Rectangle aBoundRect( pTextObj->GetCurrentBoundRect() );
pTextObj->UpdateOutlinerFormatting( *mpOutliner, aPaintRect );
// calc text offset from shape anchor
maTextOffset = aPaintRect.TopLeft() - aBoundRect.TopLeft();
}
}
SvxTextForwarder* SvxTextEditSourceImpl::GetBackgroundTextForwarder()
{
bool bCreated = false;
// prevent EE/Outliner notifications during setup
mbNotificationsDisabled = true;
if (!mpTextForwarder)
{
if( mpOutliner == nullptr )
{
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
OutlinerMode nOutlMode = OutlinerMode::TextObject;
if( pTextObj && pTextObj->IsTextFrame() && pTextObj->GetTextKind() == SdrObjKind::OutlineText )
nOutlMode = OutlinerMode::OutlineObject;
mpOutliner = mpModel->createOutliner( nOutlMode );
// Do the setup after outliner creation, would be useless otherwise
if( HasView() )
{
// Setup outliner _before_ filling it
SetupOutliner();
}
mpOutliner->SetTextObjNoInit( pTextObj );
if( mbIsLocked )
{
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->SetUpdateLayout( false );
mbOldUndoMode = mpOutliner->GetEditEngine().IsUndoEnabled();
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->EnableUndo( false );
}
if (!comphelper::IsFuzzing())
{
if ( !m_xLinguServiceManager.is() )
{
const css::uno::Reference< css::uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
m_xLinguServiceManager.set(css::linguistic2::LinguServiceManager::create(xContext));
}
css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator = m_xLinguServiceManager->getHyphenator();
if( xHyphenator.is() )
mpOutliner->SetHyphenator( xHyphenator );
}
}
mpTextForwarder.reset(new SvxOutlinerForwarder( *mpOutliner, (mpObject->GetObjInventor() == SdrInventor::Default) && (mpObject->GetObjIdentifier() == SdrObjKind::OutlineText) ));
// delay listener subscription and UAA initialization until Outliner is fully setup
bCreated = true;
mbForwarderIsEditMode = false;
mbDataValid = false;
}
if( mpObject && mpText && !mbDataValid && mpObject->IsInserted() && mpObject->getSdrPageFromSdrObject() )
{
mpTextForwarder->flushCache();
std::optional<OutlinerParaObject> pOutlinerParaObject;
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj && pTextObj->getActiveText() == mpText )
pOutlinerParaObject = pTextObj->CreateEditOutlinerParaObject(); // Get the OutlinerParaObject if text edit is active
bool bTextEditActive(false);
if( pOutlinerParaObject )
bTextEditActive = true; // text edit active
else if (mpText->GetOutlinerParaObject())
pOutlinerParaObject = *mpText->GetOutlinerParaObject();
if( pOutlinerParaObject && ( bTextEditActive || !mpObject->IsEmptyPresObj() || mpObject->getSdrPageFromSdrObject()->IsMasterPage() ) )
{
mpOutliner->SetText( *pOutlinerParaObject );
// put text to object and set EmptyPresObj to FALSE
if (mpText && bTextEditActive && mpObject->IsEmptyPresObj() && pTextObj && pTextObj->IsReallyEdited())
{
mpObject->SetEmptyPresObj( false );
static_cast<SdrTextObj*>(mpObject)->NbcSetOutlinerParaObjectForText(std::move(pOutlinerParaObject), mpText);
}
}
else
{
bool bVertical = pOutlinerParaObject && pOutlinerParaObject->IsEffectivelyVertical();
// set objects style sheet on empty outliner
SfxStyleSheetPool* pPool = static_cast<SfxStyleSheetPool*>(mpObject->getSdrModelFromSdrObject().GetStyleSheetPool());
if( pPool )
mpOutliner->SetStyleSheetPool( pPool );
SfxStyleSheet* pStyleSheet = mpObject->getSdrPageFromSdrObject()->GetTextStyleSheetForObject( mpObject );
if( pStyleSheet )
mpOutliner->SetStyleSheet( 0, pStyleSheet );
if( bVertical )
{
mpOutliner->SetVertical( pOutlinerParaObject->GetVertical());
mpOutliner->SetRotation( pOutlinerParaObject->GetRotation());
}
}
// maybe we have to set the border attributes
if (mpOutliner->GetParagraphCount()==1)
{
// if we only have one paragraph we check if it is empty
OUString aStr(mpOutliner->GetText(mpOutliner->GetParagraph(0)));
if (aStr.isEmpty())
{
// its empty, so we have to force the outliner to initialise itself
mpOutliner->SetText( u""_ustr, mpOutliner->GetParagraph( 0 ) );
auto pCell = dynamic_cast<sdr::table::Cell*>(mpText);
if (pCell && pCell->GetStyleSheet())
mpOutliner->SetStyleSheet( 0, pCell->GetStyleSheet());
else if (mpObject->GetStyleSheet())
mpOutliner->SetStyleSheet( 0, mpObject->GetStyleSheet());
}
}
mbDataValid = true;
}
if( bCreated && mpOutliner && HasView() )
{
// register as listener - need to broadcast state change messages
// registration delayed until outliner is completely set up
mpOutliner->SetNotifyHdl( LINK(this, SvxTextEditSourceImpl, NotifyHdl) );
}
// prevent EE/Outliner notifications during setup
mbNotificationsDisabled = false;
return mpTextForwarder.get();
}
SvxTextForwarder* SvxTextEditSourceImpl::GetEditModeTextForwarder()
{
if( !mpTextForwarder && HasView() )
{
SdrOutliner* pEditOutliner = mpView->GetTextEditOutliner();
if( pEditOutliner )
{
mpTextForwarder.reset(new SvxOutlinerForwarder( *pEditOutliner, (mpObject->GetObjInventor() == SdrInventor::Default) && (mpObject->GetObjIdentifier() == SdrObjKind::OutlineText) ));
mbForwarderIsEditMode = true;
}
}
return mpTextForwarder.get();
}
SvxTextForwarder* SvxTextEditSourceImpl::GetTextForwarder()
{
if( mpObject == nullptr )
return nullptr;
if( mpModel == nullptr )
mpModel = &mpObject->getSdrModelFromSdrObject();
// distinguish the cases
// a) connected to view, maybe edit mode is active, can work directly on the EditOutliner
// b) background Outliner, reflect changes into ParaOutlinerObject (this is exactly the old UNO code)
// IASS: testing for HasView() is *not* sufficient - there may be more views of one document
// open and TextEdit is only active in one of them, or - as with IASS - it may even be the view
// of the running SlideShow itself which also will have no active TextEdit and thus no Outliner.
// Thus, to identify the view which indeed does have an outliner (and is in TextEdit mode),
// also check if it has an active Outliner by using GetTextEditOutliner()
if( HasView() && nullptr != mpView->GetTextEditOutliner() )
{
if( IsEditMode() != mbForwarderIsEditMode )
{
// forwarder mismatch - create new
mpTextForwarder.reset();
}
if( IsEditMode() )
return GetEditModeTextForwarder();
else
return GetBackgroundTextForwarder();
}
else
{
// tdf#123470 if the text edit mode of the shape is active, then we
// cannot trust a previously cached TextForwarder state as the text may
// be out of date, so force a refetch in that case, unless locked against
// changes
if (IsEditMode() && mpTextForwarder && !mbIsLocked)
{
assert(!mbForwarderIsEditMode); // because without a view there is no other option except !mbForwarderIsEditMode
bool bTextEditActive = false;
SdrTextObj* pTextObj = DynCastSdrTextObj(mpObject);
// similar to the GetBackgroundTextForwarder check, see if the text edit is active
if (pTextObj && pTextObj->getActiveText() == mpText && pTextObj->CanCreateEditOutlinerParaObject())
bTextEditActive = true; // text edit active
if (bTextEditActive)
mbDataValid = false;
}
return GetBackgroundTextForwarder();
}
}
std::unique_ptr<SvxDrawOutlinerViewForwarder> SvxTextEditSourceImpl::CreateViewForwarder()
{
if( mpView->GetTextEditOutlinerView() && mpObject )
{
// register as listener - need to broadcast state change messages
mpView->GetTextEditOutliner()->SetNotifyHdl( LINK(this, SvxTextEditSourceImpl, NotifyHdl) );
mbNotifyEditOutlinerSet = true;
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
{
tools::Rectangle aBoundRect( pTextObj->GetCurrentBoundRect() );
OutlinerView& rOutlView = *mpView->GetTextEditOutlinerView();
return std::unique_ptr<SvxDrawOutlinerViewForwarder>(new SvxDrawOutlinerViewForwarder( rOutlView, aBoundRect.TopLeft() ));
}
}
return nullptr;
}
SvxEditViewForwarder* SvxTextEditSourceImpl::GetEditViewForwarder( bool bCreate )
{
if( mpObject == nullptr )
return nullptr;
if( mpModel == nullptr )
mpModel = &mpObject->getSdrModelFromSdrObject();
// shall we delete?
if( mpViewForwarder )
{
if( !IsEditMode() )
{
// destroy all forwarders (no need for UpdateData(),
// it's been synched on SdrEndTextEdit)
mpViewForwarder.reset();
}
}
// which to create? Directly in edit mode, create new, or none?
else if( mpView )
{
if( IsEditMode() )
{
// create new view forwarder
mpViewForwarder = CreateViewForwarder();
}
else if( bCreate )
{
// dispose old text forwarder
UpdateData();
mpTextForwarder.reset();
// enter edit mode
mpView->SdrEndTextEdit();
if(mpView->SdrBeginTextEdit(mpObject))
{
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if (pTextObj && pTextObj->IsTextEditActive())
{
// create new view forwarder
mpViewForwarder = CreateViewForwarder();
}
else
{
// failure. Somehow, SdrBeginTextEdit did not set
// our SdrTextObj into edit mode
mpView->SdrEndTextEdit();
}
}
}
}
return mpViewForwarder.get();
}
void SvxTextEditSourceImpl::UpdateData()
{
// if we have a view and in edit mode, we're working with the
// DrawOutliner. Thus, all changes made on the text forwarder are
// reflected on the view and committed to the model on
// SdrEndTextEdit(). Thus, no need for explicit updates here.
if( HasView() && IsEditMode() )
return;
if( mbIsLocked )
{
mbNeedsUpdate = true;
}
else
{
if( mpOutliner && mpObject && mpText )
{
SdrTextObj* pTextObj = DynCastSdrTextObj( mpObject );
if( pTextObj )
{
if( (mpOutliner->GetParagraphCount() == 1 && mpOutliner->GetEditEngine().GetTextLen( 0 ) == 0 )
|| (mpOutliner->GetParagraphCount() == 2 && mpOutliner->GetEditEngine().GetTextLen( 0 ) == 0
&& mpOutliner->GetEditEngine().GetTextLen( 1 ) == 0) )
{
pTextObj->NbcSetOutlinerParaObjectForText( std::nullopt, mpText );
}
else
{
pTextObj->NbcSetOutlinerParaObjectForText( mpOutliner->CreateParaObject(), mpText );
}
}
if( mpObject->IsEmptyPresObj() )
mpObject->SetEmptyPresObj(false);
}
}
}
void SvxTextEditSourceImpl::lock()
{
// if this assert ever fires, we will need to make this a counter instead of a boolean
assert(!mbIsLocked && "cannot nest these loc() calls");
mbIsLocked = true;
if( mpOutliner )
{
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->SetUpdateLayout( false );
mbOldUndoMode = mpOutliner->GetEditEngine().IsUndoEnabled();
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->EnableUndo( false );
}
}
void SvxTextEditSourceImpl::unlock()
{
mbIsLocked = false;
if( mbNeedsUpdate )
{
UpdateData();
mbNeedsUpdate = false;
}
if( mpOutliner )
{
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->SetUpdateLayout( true );
const_cast<EditEngine*>(&(mpOutliner->GetEditEngine()))->EnableUndo( mbOldUndoMode );
}
}
bool SvxTextEditSourceImpl::IsValid() const
{
return mpView && mpWindow;
}
Point SvxTextEditSourceImpl::LogicToPixel( const Point& rPoint, const MapMode& rMapMode )
{
// The responsibilities of ViewForwarder happen to be
// somewhat mixed in this case. On the one hand, we need the
// different interface queries on the SvxEditSource interface,
// since we need both VisAreas. On the other hand, if an
// EditViewForwarder exists, maTextOffset does not remain static,
// but may change with every key press.
if( IsEditMode() )
{
SvxEditViewForwarder* pForwarder = GetEditViewForwarder(false);
if( pForwarder )
return pForwarder->LogicToPixel( rPoint, rMapMode );
}
else if( IsValid() && mpModel )
{
Point aPoint1( rPoint );
aPoint1.AdjustX(maTextOffset.X() );
aPoint1.AdjustY(maTextOffset.Y() );
Point aPoint2( OutputDevice::LogicToLogic( aPoint1, rMapMode,
MapMode(mpModel->GetScaleUnit()) ) );
MapMode aMapMode(mpWindow->GetMapMode());
aMapMode.SetOrigin(Point());
return mpWindow->LogicToPixel( aPoint2, aMapMode );
}
return Point();
}
Point SvxTextEditSourceImpl::PixelToLogic( const Point& rPoint, const MapMode& rMapMode )
{
// The responsibilities of ViewForwarder happen to be
// somewhat mixed in this case. On the one hand, we need the
// different interface queries on the SvxEditSource interface,
// since we need both VisAreas. On the other hand, if an
// EditViewForwarder exists, maTextOffset does not remain static,
// but may change with every key press.
if( IsEditMode() )
{
SvxEditViewForwarder* pForwarder = GetEditViewForwarder(false);
if( pForwarder )
return pForwarder->PixelToLogic( rPoint, rMapMode );
}
else if( IsValid() && mpModel )
{
MapMode aMapMode(mpWindow->GetMapMode());
aMapMode.SetOrigin(Point());
Point aPoint1( mpWindow->PixelToLogic( rPoint, aMapMode ) );
Point aPoint2( OutputDevice::LogicToLogic( aPoint1,
MapMode(mpModel->GetScaleUnit()),
rMapMode ) );
aPoint2.AdjustX( -(maTextOffset.X()) );
aPoint2.AdjustY( -(maTextOffset.Y()) );
return aPoint2;
}
return Point();
}
IMPL_LINK(SvxTextEditSourceImpl, NotifyHdl, EENotify&, rNotify, void)
{
if( !mbNotificationsDisabled )
{
std::unique_ptr< SfxHint > aHint( SvxEditSourceHelper::EENotification2Hint( &rNotify) );
if (aHint)
Broadcast(*aHint);
}
}
SvxTextEditSource::SvxTextEditSource( SdrObject* pObject, SdrText* pText )
{
mpImpl = new SvxTextEditSourceImpl( pObject, pText );
}
SvxTextEditSource::SvxTextEditSource( SdrObject& rObj, SdrText* pText, SdrView& rView, const OutputDevice& rWindow )
{
mpImpl = new SvxTextEditSourceImpl( rObj, pText, rView, rWindow );
}
SvxTextEditSource::SvxTextEditSource( SvxTextEditSourceImpl* pImpl )
{
mpImpl = pImpl;
}
SvxTextEditSource::~SvxTextEditSource()
{
::SolarMutexGuard aGuard;
mpImpl.clear();
}
std::unique_ptr<SvxEditSource> SvxTextEditSource::Clone() const
{
return std::unique_ptr<SvxEditSource>(new SvxTextEditSource( mpImpl.get() ));
}
SvxTextForwarder* SvxTextEditSource::GetTextForwarder()
{
return mpImpl->GetTextForwarder();
}
SvxEditViewForwarder* SvxTextEditSource::GetEditViewForwarder( bool bCreate )
{
return mpImpl->GetEditViewForwarder( bCreate );
}
SvxViewForwarder* SvxTextEditSource::GetViewForwarder()
{
return this;
}
void SvxTextEditSource::UpdateData()
{
mpImpl->UpdateData();
}
SfxBroadcaster& SvxTextEditSource::GetBroadcaster() const
{
return *mpImpl;
}
void SvxTextEditSource::lock()
{
mpImpl->lock();
}
void SvxTextEditSource::unlock()
{
mpImpl->unlock();
}
bool SvxTextEditSource::IsValid() const
{
return mpImpl->IsValid();
}
Point SvxTextEditSource::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const
{
return mpImpl->LogicToPixel( rPoint, rMapMode );
}
Point SvxTextEditSource::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const
{
return mpImpl->PixelToLogic( rPoint, rMapMode );
}
void SvxTextEditSource::addRange( SvxUnoTextRangeBase* pNewRange )
{
mpImpl->addRange( pNewRange );
}
void SvxTextEditSource::removeRange( SvxUnoTextRangeBase* pOldRange )
{
mpImpl->removeRange( pOldRange );
}
const SvxUnoTextRangeBaseVec& SvxTextEditSource::getRanges() const
{
return mpImpl->getRanges();
}
void SvxTextEditSource::UpdateOutliner()
{
mpImpl->UpdateOutliner();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V595 The 'mpObject' pointer was utilized before it was verified against nullptr. Check lines: 521, 529.