1010 lines
33 KiB
C++
1010 lines
33 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 <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: */
|