summaryrefslogtreecommitdiffstats
path: root/svx/source/unodraw/unoshtxt.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/unodraw/unoshtxt.cxx')
-rw-r--r--svx/source/unodraw/unoshtxt.cxx1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/svx/source/unodraw/unoshtxt.cxx b/svx/source/unodraw/unoshtxt.cxx
new file mode 100644
index 0000000000..d3837e187b
--- /dev/null
+++ b/svx/source/unodraw/unoshtxt.cxx
@@ -0,0 +1,1009 @@
+/* -*- 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 <unotools/configmgr.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( mpModel )
+ StartListening( *mpModel );
+
+ if( mpObject )
+ 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 );
+ }
+
+ StartListening( *mpModel );
+ 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;
+
+ case SdrHintKind::ModelCleared:
+ dispose();
+ 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 )
+ {
+ EndListening( *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->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 (!utl::ConfigManager::IsFuzzing())
+ {
+ if ( !m_xLinguServiceManager.is() )
+ {
+ 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( 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( "", 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)
+ if( HasView() )
+ {
+ 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: */